标签: java

无框架完整搭建安卓app及其服务端

技术背景:

我的一个项目做的的是图片处理,用 python 实现图片处理的核心功能后,想部署到安卓app中,但是对于一个对安卓和服务器都一知半解的小白来说要现学的东西太多了。

而实际上,我们的项目要求并不算高,以我现有的知识也是能实现相应功能的,所以我将在本文记录下一次没用到任何服务器框架的服务器搭建经历。

%title插图%num%title插图%num

 

需要的技术:

  <java>,<socket>,<android>

确切的说只要你会java,就能实现你想要的所有功能了。因为android是基于java的,其使用的代码和原生java一模一样,只是在android上把前后台完全分割开了。

而对于socket也很容易使用,就算没有了解过计算机网络,在看过我这篇博客后你也能有一定的了解。

要实现的功能:

1,android界面及后台

2,安卓和服务器建立连接,并进行连接有效性检查

3,基于字节流的图片收发

4,java调用python用预先训练好的fgsm模型处理图片,并将结果发给客户端

开始实现:

 一:安卓app

首先我们建个安卓工程,看看结构是什么样的:

%title插图%num%title插图%num

容易看出,这里有两个大目录分别是”app”,”login”,这两的大目录的子目录的结构是一样的,都有三个子目录(“manifests”,”java”,”res”)。

没错,这两个大目录就是我写的两个界面(顾名思义,登录界面和登录后的界面),这样是不是就觉得恍然大悟了,怪不得平时app都是一个界面一个界面的,

这点和pc还是有点不同的。

 

manifests下写的是xml文件,也就是常用的标签配置文件,用来定义界面的外观。

java中就是你写的java代码,也就是安卓后台代码,一般是给前端界面添加监听,以及网络通信和处理代码。

res就是资源文件夹了,用来放置app需要的资源,比如图标,图片,视频,音乐等。

 

Java深拷贝和浅拷贝

定义

浅拷贝

  • 基本数据类型 拷贝数值
  • 引用类型 拷贝对象引用

深拷贝

  • 基本数据类型 拷贝数值
  • 引用类型 拷贝引用所对应对象的所有数值

浅拷贝实现方式

1. 拷贝构造方法

拷贝构造方法指的是该类的构造方法参数为该类的对象。使用拷贝构造方法可以很好地完成浅拷贝,直接通过一个现有的对象创建出与该对象属性相同的新的对象。

实现如下(PS:已略去部分代码):

 

%title插图%num

浅拷贝-拷贝构造

运行结果:

Student{age=18, parent=Parent{age=40}}
Student{age=18, parent=Parent{age=42}}
Student{age=20, parent=Parent{age=42}}

2. 重写clone方法

Object类是类结构的根类,其中有一个方法为protected Object clone() throws CloneNotSupportedException,这个方法就是进行的浅拷贝。有了这个浅拷贝模板,我们可以通过调用clone()方法来实现对象的浅拷贝。

使用clone()方法时需要注意以下几点:

  1. Object中的clone()方法是被保护的,需要重写才可以调用;
  2. 重写clone()方法的类必须实现Cloneable接口,否则会抛出异常CloneNotSupportedException。

实现如下(PS:已略去部分代码):

%title插图%num

浅拷贝-重写clone

运行结果:

Student{age=18, parent=Parent{age=40}}
Student{age=18, parent=Parent{age=42}}
Student{age=20, parent=Parent{age=42}}

深拷贝实现方式

1. 重写clone方法

与通过重写clone方法实现浅拷贝的基本思路一样,只需要为对象的每一层的每一个对象都实现Cloneable接口并重写clone方法,*后在*顶层的类的重写的clone方法中调用所有的clone方法即可实现深拷贝。
简单的说就是:每一层的每个对象都进行浅拷贝=深拷贝

实现如下(PS:已略去部分代码):

 

%title插图%num

深拷贝-重写clone

运行结果:

Student{age=18, parent=Parent{age=40}}

Student{age=18, parent=Parent{age=42}}
Student{age=20, parent=Parent{age=40}}

2. 对象序列化

虽然层次调用clone方法可以实现深拷贝,但是显然代码量实在太大。特别对于属性数量比较多、层次比较深的类而言,每个类都要重写clone方法太过繁琐。

将对象序列化为字节序列后,默认会将该对象的整个对象图进行序列化,再通过反序列即可完美地实现深拷贝。

实现如下(PS:已略去部分代码):

 

%title插图%num

深拷贝-序列化

运行结果:

Student{age=18, parent=Parent{age=40}}
Student{age=18, parent=Parent{age=42}}
Student{age=20, parent=Parent{age=40}}

java实现 FTP实现上传、下载

上传

/**
* FTP上传单个文件测试
*/
public static void uploadFtpFile(String hostname,String username,
String password,String uploadFilePath,String fileName,String ftpWorkPath)
throws RuntimeException{
FTPClient ftpClient = new FTPClient();
FileInputStream fis = null;

try {
ftpClient.connect(hostname);
ftpClient.login(username, password);

File srcFile = new File(uploadFilePath+fileName);
fis = new FileInputStream(srcFile);
//设置上传目录
ftpClient.changeWorkingDirectory(“/”+ftpWorkPath);
ftpClient.setBufferSize(1024);
ftpClient.setControlEncoding(“GBK”);

//设置为被动模式
ftpClient.enterLocalPassiveMode();

//设置文件类型(二进制)
ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
ftpClient.storeFile(fileName, fis);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(“FTP客户端出错!”, e);
} finally {
IOUtils.closeQuietly(fis);
try {
ftpClient.disconnect();
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(“关闭FTP连接发生异常!”, e);
}
logger.info(“已上传至FTP服务器路径!”);
}
}
下载

/**
* 获取FTPClient对象
*
* @param ftpHost
* FTP主机服务器
* @param ftpPassword
* FTP 登录密码
* @param ftpUserName
* FTP登录用户名
* @param ftpPort
* FTP端口 默认为21
* @return
*/
public static FTPClient getFTPClient(String ftpHost, String ftpUserName,
String ftpPassword, int ftpPort) throws RuntimeException{
FTPClient ftpClient = new FTPClient();
try {
ftpClient = new FTPClient();
ftpClient.connect(ftpHost, ftpPort);// 连接FTP服务器
ftpClient.login(ftpUserName, ftpPassword);// 登陆FTP服务器
if (!FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) {
logger.info(“未连接到FTP,用户名或密码错误。”);
ftpClient.disconnect();
} else {
logger.info(“FTP连接成功。”);
}
} catch (SocketException e) {
e.printStackTrace();
logger.info(“FTP的IP地址可能错误,请正确配置。”);
throw new RuntimeException(“FTP的IP地址可能错误,请正确配置!”, e);
} catch (IOException e) {
e.printStackTrace();
logger.info(“FTP的端口错误,请正确配置。”);
throw new RuntimeException(“FTP的端口错误,请正确配置!”, e);
}
return ftpClient;
}

/**
* 从FTP服务器下载文件
* @param ftpHost FTP IP地址
* @param ftpUserName FTP 用户名
* @param ftpPassword FTP用户名密码
* @param ftpPort FTP端口
* @param ftpPath FTP服务器中文件所在路径 格式: ftptest/aa
* @param localPath 下载到本地的位置 格式:H:/download
* @param fileName 文件名称
*/
public static boolean downloadFtpFile(String ftpHost, String ftpUserName,
String ftpPassword, int ftpPort, String ftpPath, String localPath,
String fileName) throws RuntimeException{
boolean flag = false;
FTPClient ftpClient = null;

try {
ftpClient = getFTPClient(ftpHost, ftpUserName, ftpPassword, ftpPort);
ftpClient.setControlEncoding(“UTF-8”); // 中文支持
ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
ftpClient.enterLocalPassiveMode();
ftpClient.changeWorkingDirectory(ftpPath);

File localFile = new File(localPath + File.separatorChar + fileName);
OutputStream os = new FileOutputStream(localFile);
ftpClient.retrieveFile(fileName, os);
os.close();
ftpClient.logout();
flag = true;
} catch (FileNotFoundException e) {
logger.error(“没有找到” + ftpPath + “文件”);
e.printStackTrace();
throw new RuntimeException(“没有找到” + ftpPath + “文件:”, e);
} catch (SocketException e) {
logger.error(“连接FTP失败.”);
e.printStackTrace();
throw new RuntimeException(“连接FTP失败:”, e);
} catch (IOException e) {
e.printStackTrace();
logger.error(“文件读取错误。”);
e.printStackTrace();
throw new RuntimeException(“文件读取错误:”, e);
}
return flag;
}
测试代码

public static void main(String[] args) {
//下载
// FavFTPUtil.downloadFtpFile(“192.168.2.133″,”adks”,”adconcepts2017″,
// “fileConllect/test”,”E:\\xcc”, “53840afe-0682-4960-84ef-3f3b972a0f12.zip”);

FavFTPUtil.uploadFtpFile(“192.168.2.133″,”test”,”123″,
“E:\\360\\pic\\”, “a.txt”,
“fengWu/”);

}

java 个人存储服务器搭建_我的世界个人服务器搭建

环境:CentOS 6.0+   openjdk-1.8

安装jdk:

yum install java-1.8.0-openjdk.x86_64

安装宝塔面板:

yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && bash install.sh

*步:安装宝塔面板

复制命令,到服务器控制台输入,具体登录参考ssh登录的方法

等待安装

两分钟后安装成功

复制一些重要信息

Bt-Panel: http://134.175.120.169:8888/32831e0d

username: kq4q5xpp     #用户名

password: e1fe0472      #密码

下载好上面百度网盘两个文件  上传jar包(服务端)到云服务器  具体:到home目录下新建文件夹  *好是英文名

上传好后  Java -jar运行   不过先安装jdk  检查安装是否成功  输入java  我们不需要开发  所以不用配置java环境

OK  安装成功java

进入刚刚jar包目录

Java -jar  这里出错了  需要修改里面的一些东西

eula=false这里改成eula=true

重新运行  *次很慢

低配置服务器用这条命令运行,因为运行内存太小  这里限制512MB  可以修改

java -Xms256m -Xmx512m -jar /home/Minecraft/minecraft_server.1.12.jar nogui

好了  运行好了  开始客户端的使用   服务端版本1.12  和客户端一定要是一致  客户端也要有java环境

我们换一个工具连接服务器

现在开启服务器端口  25565

好了  现在可以加入游戏了  具体游戏的的配置信息  参考这里

#Minecraft server properties 无视他就行,只是说明这个是神马东西而已

#Mon Nov 21 19:18:32 PST 2013 建立时间(星期几/月/日/时/分/秒/时区/年份)

level-name=world 世界的名字,例子:level-name=liu464765169,“=”号后的名字必须要在服务端目录有对应存档否则将会自动生成随机地图,导入存档时记得修改,多世界的配置需要依赖插件,可在站内搜索进行配置。

allow-nether=true 是否允许地狱,true(是) false(不是)

view-distance=10 观望距离 (注意,类似ptweaks等任何防卡顿插件都会替换本选项的参数,修改这里无效请检查插件配置)

11.spawn-monsters=true 是否刷怪物,true(是) false(不是)

13.difficulty=1 难度,随便啦

15.gamemode=0 模式,0(生存) 1(创造)

17.spawn-animals=true 刷生物,true(是) false(不是)

18.(注意 spawn-monsters difficulty gamemode spawn-animals四项会被任何带权限管理的多世界插件替换,部分防卡顿以及限制动物插件也会起到同样效果,如mv多世界插件。)

20.max-players=20 *多同时在线人数

22.online-mode=true 官网正版认证,true(是) false(不是)<-盗版必选

24.server-ip= 服务器IP

25.enable-command-block=true 是否使用命令方块…true(是) false(否)(强烈建议正规的服务器将此项设为false,因为这实在是太危险了,只要有一个命令方块就是全民OP的……)

27.pvp=true 玩家VS玩家,true(可以) false(不可以)

29.level-seed= 地图种子,随机不要添

31.server-port=25565 服务器端口,默认25565

33.allow-flight=false 飞行许可,一般都不允许拉,你不想看到玩家在里面乱飞

35.white-list=false 白名单,true(使用) false(不使用)

37.motd=A Minecraft Server 服务器备注(此项可以被motd美化插件替换,这里的motd仅支持英文单色,强烈建议使用ppmotd、motdcolor的美化插件美化,可以去本站搜索下载哦。)

注意   一定要保持服务一直在运行 一旦在我的电脑关掉控制窗口 进程就结束

iOS compare字符串的比较相关的使用略微整理

从前,有一个程序员,他得到了一盏神灯。灯神答应实现他一个愿望。然后他向神灯许愿,希望在有生之年能写一个好项目。后来….后来….. 他得到了永生。

 

关于字符串,这里我总结一些方法案例:

一、compare:

(判断两对象值的大小,按字母顺序进行比较)

NSString *string = @”9.1.1″;

[string compare:@”9.1.1″];返回的类型是NSComparisonResult 的NSOrderedSame(= 等于)

[string compare:@”9.2.1″];返回的是NSOrderedAscending (后面一个字符串大于前面一个 升序)

[string compare:@”9.0.1″];返回的是NSOrderedDescending (后面一个字符串小于前面一个 降序)

 

在系统内部的样式

typedef NS_ENUM(NSInteger, NSComparisonResult)

{
NSOrderedAscending = -1L,

NSOrderedSame,

NSOrderedDescending

};

一般情况下我们可以使用这个来进行版本号的对比:

例子:

NSString *num1 = @”5.2.0″;
NSString *num2 = @”5.3.0″;
if ([num1 compare:num2 options:NSNumericSearch] == NSOrderedDescending){
ULog(@”%@ is bigger”,num1);
}else{
ULog(@”%@ is bigger”,num2);
}

解释:

NSOrderedDescending是降序,如果numb1>numb2用这个函数相比较那么就等于降序。

字符串的比对结果NSComparisonResult(在NSObjCRunTime.h中定义)是按照字符的ASCII值进行比对的

NSString * str1 = @”abc”;

NSString * str2 = @”abd”;

NSString * str3 = @”ABC”;

NSString * str4 = @”abc”;

NSString * str5 = @”123″;

那么,

[str1 compare:str2] == NSOrderedAscending;//(升序)

[str2 compare:str1] == NSOrderedDescending;//(降序)

[str1 compare:str3] == NSOrderedDescending;//(降序)

[str1 compare:str4] == NSOrderedSame;//(同序)

[str1 compare:str5] == NSOrderedDescending;//(降序)

(compare的比对含义:左比右 左侧的数值小于(等于或者大于)右侧的值,即compare的目标值(右边的值)相比于参数值(左边的值)在字母表中的排序位置更靠后(相同或者靠前))

(出现问题:

原因:是由于字符串他的比对是逐位从左到右的比对的,9是比1大的。所以出现上面的问题。

同样:

%title插图%num

它的比对成功也是由于@”90.0.1″的第二位0在ASCII表中的位置在.之后。是错误的比对误造成正确的@”90.0.1″大于@”9.0.1″的样子。

同样,对于字符串长度的不一致的

%title插图%num

添加option值(改为:)

NSComparisonResult result = [string2 compare:string options:NSNumericSearch];

能够使字符串的比对添加选项option,针对数字进行比较。解决上面数据的根据字符从左至右依次比对出现的问题:

%title插图%num

扩展:

在c语言中(摘自:https://blog.csdn.net/ctrl_qun/article/details/66970652)

#include <stdio.h>

#include <string.h>

int main(void)

{
char str_1[] = “abc”;

char str_2[] = “abc”;

char str_3[] = “ABC”;

if (strcmp(str_1, str_2) == 0)

printf(“str_1 is equal to str_2. \n”);

else

printf(“str_1 is not equal to str_2. \n”);

if (strcmp(str_1, str_3) == 0)

printf(“str_1 is equal to str_3.\n”);

else

printf(“str_1 is not equal to str_3.\n”);

return 0;

}

 

上例的打印输出如下所示:

%title插图%num
strcmp()函数有两个参数,即要比较的两个字符串。strcmp()函数对两个字符串进行大小写敏感的(case-sensitiVe)和字典式的(lexicographic)比较,并返回下列值之一:
—————————————————-
返  回  值         意  义
—————————————————-
<0               *个字符串小于第二个字符串
0               两个字符串相等    ·
>0               *个字符串大于第二个字符串
—————————————————-

在上例中,当比较str_1(即“abc”)和str_2(即“abc”)时,strcmp()函数的返回值为0。然而,当比较str_1(即”abc”)和str_3(即”ABC”)时,strcmp()函数返回一个大于0的值,因为按ASCII顺序字符串“ABC”小于“abc”。

strcmp()函数有许多变体,它们的基本功能是相同的,都是比较两个字符串,但其它地方稍有差别。下表列出了C语言提供的与strcmp()函数类似的一些函数:
—————————————————————–
函  数  名                   作  用
—————————————————————–
strcmp()         对两个字符串进行大小写敏感的比较
strcmpi()        对两个字符串进行大小写不敏感的比较
stricmp()        同strcmpi()
strncmp()        对两个字符串的一部分进行大小写敏感的比较
strnicmp()       对两个字符串的一部分进行大小写不敏感的比较
—————————————————————–
在前面的例子中,如果用strcmpi()函数代替strcmp()函数,则程序将认为字符串“ABC”等于“abc”。

 

二、[ compare:(NSString *) options:(NSStringCompareOptions)];
传入 NSStringCompareOptions 枚举的值

enum{
NSCaseInsensitiveSearch = 1,//不区分大小写比较

NSLiteralSearch = 2,//区分大小写比较

NSBackwardsSearch = 4,//从字符串末尾开始搜索

NSAnchoredSearch = 8,//搜索限制范围的字符串

NSNumbericSearch = 64//按照字符串里的数字为依据,算出顺序。例如 Foo2.txt < Foo7.txt < Foo25.txt

//以下定义高于 mac os 10.5 或者高于 iphone 2.0 可用

NSDiacriticInsensitiveSearch = 128,//忽略 “-” 符号的比较

NSWidthInsensitiveSearch = 256,//忽略字符串的长度,比较出结果

NSForcedOrderingSearch = 512//忽略不区分大小写比较的选项,并强制返回 NSOrderedAscending 或者 NSOrderedDescending

//以下定义高于 iphone 3.2 可用

NSRegularExpressionSearch = 1024//只能应用于 rangeOfString:…, stringByReplacingOccurrencesOfString:…和 replaceOccurrencesOfString:… 方法。使用通用兼容的比较方法,如果设置此项,可以去掉 NSCaseInsensitiveSearch 和 NSAnchoredSearch

}

 

 

(可以多个条件一起约束

例如:

BOOL result = [astring01 compare:astring02 options:NSCaseInsensitiveSearch | NSNumericSearch] == NSOrderedSame;    //NSCaseInsensitiveSearch:不区分大小写比较 NSLiteralSearch:进行完全比较,区分大小写 NSNumericSearch:比较字符串的字符个数,而不是字符值)

三、[ compare:(NSString *) options:(NSStringCompareOptions) range:(NSRange)];
range:(NSRange)

比较字符串的范围

结构变量:

typedef struct _NSRange {
NSUInteger location;  //需要比较的字串起始位置(以0为起始)

NSUInteger length;  //需要比较的字串长度

} NSRange;

 

NSRange的使用:

NSRange range1 = {2,4};

NSRange range4 = NSMakeRange(2, 5);

//截取字符串

NSString *homebrew = @”cushy yigexinshuju caicaikan”;

 

NSLog(@”%@”, [homebrew substringWithRange:range1]);

NSLog(@”%@”, [homebrew substringWithRange:range4]);

//查找字符串

NSRange range2 = [homebrew rangeOfString: @”cai”];

NSLog(@”range:%@”, NSStringFromRange(range2));

NSRange range3 = [homebrew rangeOfString:@”xinshu”];

NSLog(@”range:%@”, NSStringFromRange(range3));

//反向查找字符串的位置

NSRange range5 = [homebrew rangeOfString:@”ju”];

NSLog(@”%@”, NSStringFromRange(range5));

NSRange range6 = [homebrew rangeOfString:@”ju” options: NSBackwardsSearch];

NSLog(@”%@”, NSStringFromRange(range6));

 

四、改变字符串的大小写
NSString *string1 = @”A String”;

NSString *string2 = @”String”;

NSLog(@”string1:%@”,[string1 uppercaseString]);//大写

NSLog(@”string2:%@”,[string2 lowercaseString]);//小写

NSLog(@”string2:%@”,[string2 capitalizedString]);//首字母大小

Java中 Tomcat 是干什么的?

Tomcat是web容器。它的作用稍后给你解释。
你在做web项目时,多数需要http协议,也就是基于请求和响应,比如你在百度输入一行内容搜索,
那么百度服务器如何处理这个请求呢,他需要创建servlet来处理,servlet其实就是java程序,只是在服务器端的java程序,
servlet通过配置文件拦截你的请求,并进行相应处理,然后展示给你相应界面,那么servlet如何创建? 这时候tomcat用到了,
它就是帮助你创建servlet的东西,所以也称web容器,没有它,没法运行web项目。相对应的web容器有很多,
比如JBOSS,WEBLOGIC等。
Tomcat是一个应用服务器。他可以运行你按照J2EE中的Servlet规范编写好的Java程序。
简单的说它是一个Web网站的运行容器,把你写好的网站放进去就可以运行。
Tomcat是应用(java)服务器,它只是一个servlet容器,是Apache的扩展,处理动态网页部分。
Eclipse+tomcat=网络应用,如JSP类应用

快速学习android开发

因为项目需要,8月中旬开始决定做安卓的程序,所以马上就开始学习安卓方面的开发知识,把*近的学习实践经历和大家分享分享。不要一开始就下载一大堆资料,视频,然后就不知道做什么了,要给自己定个目标,我要做什么?我怎么达到目标?

我不懂java,但是懂C#和C++,所以我没主张去单独学习java语言,如果你是个**初的新手,没啥语言基础,那你必须先看看java语言,不要很详细看,因为学习Android中,你也是在学习java。

1. 明确目标

没有目标的学习,会感觉到后面没什么成果,在1年前,我也打算学习android开发的,但是目的就是学习,到网上去下载很多学习的视频,然后把开发环境搭建起来,能把Helloworld运行起来,能打些log,Activity之间也能互相切换了,但是后面也就不了了之了,因为不知道学了要干什么。依葫芦画瓢的做了几个例子,因为里面的问题都是已经解决的,所以也没能深入的系统学习。

这次因为产品的需要,要做Android版本,要做的东西一开始就已经设计好了,见摇摇2选1安卓版本,刚开始也不知道里面有些什么技术难度,但是要做的目标已经明确了,而且也没有现成的,碰到问题就查资料,慢慢地解决,这样有的放矢,学习的效果非常好。既有现成的技术可以使用,又有些技术,需要查比较多的资料,这样记忆就比较深刻,所掌握的知识也比较系统。

接下来的一系列文章,我会把在开发摇摇2选1中遇到的问题,给大家详细讲讲,程序虽然小,但是五脏俱全,做Demo和做产品的要求完全不是一个级别,如果Android大牛感觉知识讲的比较浅,那可以绕道,毕竟我是从一个完全的新手开始的。

 

2. 了解安卓开发中比较困难的地方

学习一个新平台,就要知道此平台开发要面临的困难有哪些,不要做到*后,这些问题没有考虑,那就比较糟糕了。在网上搜索了下,安卓开发困难总结如何:

1)安卓系统版本比较多,各版本之间的兼容性是个问题,此为系统碎片。

2)安卓设备千变万化,设备难以统一,每个产品都成为独立,分散的Android碎片。

3)分辨率五花八门。一个产品,可能需要多个界面排版,人工消耗比较大。

%title插图%num

看到这张图,有没有头疼的感觉?

总结成一句话:Android的碎片化真是要来开发者的命。

 

3. 搭建开发环境

“巧妇难为无米之炊”,开发环境肯定是*件要做的事情,这类的文章已经很多了,我也不多说了,多说也就比较无聊了。感谢吴秦,也是博客园里的一员,他写的很详细了,见这里。

 

4. 查看网友总结的一些经验。

不是什么都查看,开发中遇到什么问题,就去查看什么问题,这样你查到的知识,马上就能深入的实践,这样知识就巩固了。

1)首先当然要看Android的开发文档,里面其实大部分的知识都有了,还有就是SDK自带的Samples。

2)博客园里搜索“Android开发”,会出来一大把,很多网友都是很系统的讲解了。

3)eoe android社区,里面有很多网友上传了现成的demo代码,里面很多都是模仿现在流行的产品的界面开发,很是不错。

 

5. 掌握调试方法

个人一直认为,调试技巧是开发中*重要的技能,如果调试技能比较差,不知道如何查找问题,那不会是个好的程序员,其技能也不会高到哪里去。

 Android做下来,感觉调试这块做的很不错了,这要感谢Eclipse IDE做的比较不错,但是Android的界面排版部分,真的不敢恭维,Eclipse时间用长了,占用内存真是大。有哪位高手能否告诉我下,你们界面排版是用什么的?

Android里,如果程序出了问题,有些是一下跟踪不到的,这时就要用到Log类了,Log类使用很简单,就不多说了。
 自己一个很有感触的经验,如果你真的打算做Android开发,那就买一个设备,这样能大大提高开发效率,模拟器有时用起来真是麻烦,而且开发的设备不要很好的,只要设备分辨率是主流的,传感器的支持*好全点就够了,国产的手机里,华为的还不错,或者到淘宝上去买个二手的,投资不大。真的买了投资,可能也会增加你学习Android的决心。

 

6. 程序框架

经过10多年的经历,认识到程序的框架对开发产品真的是起到举足轻重的作用。一个好的产品,如果有个好的程序框架,那真是事半功倍,可能开始所做的工作会比较多,很难看到令人激动的成果,但是相信我,*对值得。一开始,我也是比较注重程序界面开发,很快就开发出界面来,而且着实比较激动,因为有东西出来了,但是后面的事情,真是令人发狂,想从头再来,太迟了,离计划的时间比较近了,时间不允许,只有硬着头皮往上打补丁,硬着交付*个版本,第二个版本或者后面的版本,迟早是要重来的。所以在后面带领团队开发的时候,在开发前,都是要讨论程序框架,留出很大部分时间进行程序框架开发,及在开发中不断的改进。

刚接触Android,不知道用什么程序框架,所以查了很多资料,一开始看到一个程序框架,感觉很不错,所以一开始就使用了下面这个程序框架。

%title插图%num

开始这个框架用的蛮爽,简单介绍一下,主UI线程如果要做一个比较长时间的任务,创建一个任务,发送到任务队列中去,后台的Service不断的从任务队列中去取任务,然后交给线程处理,线程处理完后,通过消息(Message)发送到Handler里处理,然后Handler的handleMessage函数里回调到UI主线程中去。

此框架的缺点是比较麻烦。

1)首先这里有个Observer模式,每个Activity都要实现一个接口,然后注册到后台服务中去,这样后台服务才能把处理的结果回调到主线程中。

2) 每个事件都要组成一个任务,发送到任务队列。

3)后台线程先要取任务,然后处理,然后通过消息切换到主线程,然后又回到到相应的Activity中,中间有个3次判断当前是什么任务。

4)不能并非,任务只能一个接一个,虽然这个一般不会造成瓶颈。

因为摇摇2选1应用比较简单,所以后来感觉只要使用线程和Handler,通过消息就能处理,然后就查资料,发现已经有比较多的文章描述了这种方法,只是没提出这是一个框架,但是对摇摇2选1,我感觉已经足够了。下面就是主代码,分布在每个Activity中:

private void TaskThread()

{

Thread taskThread = new Thread()

{

@Override public void run()

{

//做耗时的任务

Message msg = Message.obtain();

msg.what = 1;

uiCallback.sendMessage(msg);

}

};

taskThread .start();

}

private Handler uiCallback = new Handler()

{

@Override public void handleMessage(Message msg)

{

if(msg.what == 1)

{

//在主线程中处理结果

}

}

};

 够用就好,至少我现在用着很爽。

 

7. 程序国际化

互联网让地球成为一个村,所以一开始也需要了解一下程序是如何做国际化版本的。不要一开始字符串满天飞,整个程序中都存在hard code,这样做,迟早是要吃亏的。

 

8.产品升级机制

一个方便的产品升级机制,对产品的成长很重要,android是一个开放平台,在这点上比iOS做的好的多,产品升级很方便。

 

此文章的目的就是对想学Android开发的人一个指引,因为我也就是这么走过来的,加上自己在软件开发上的一些经验,让新手少走弯路。有计划,有目标的学习,这是*好的方法。

EasyMock 简单使用

参考案例:(本位使用markdown编写)
https://www.ibm.com/developerworks/cn/opensource/os-cn-easymock/
https://www.yiibai.com/easymock/
git.oschina实例:https://gitee.com/lhhTestTool/LhhEasyMock


# LhhEasyMock
# EasyMock   
EasyMock便于无缝地创建模拟对象。它使用Java反射,以创造为给定接口的模拟对象。模拟对象是什么,只不过是代理的实际实现。
考虑如:股票服务的情况下,它返回一个股票价格的详细信息。在开发过程中,实际的库存服务不能被用于获得实时数据。
因此,我们需要一个虚拟的股票实施服务。简易模拟可以很容易理解顾名思义这样


# EasyMock的好处
* 不用手写 -  没有必要通过自己编写的模拟对象。
* 重构安全 - 重构接口方法的名称或重新排序的参数不会破坏测试代码在运行时创建。
* 返回值支持 - 支持返回值。
* 异常支持 - 支持例外/异常。
* 命令检查支持 - 支持检查命令方法调用。
* 注释支持 - 支持使用注解创建。

# EasyMock架包

        <dependency>
            <groupId>org.easymock</groupId>
            <artifactId>easymock</artifactId>
            <version>RELEASE</version>
        </dependency>




# 阶段介绍

## 数据准备阶段
```
        //创建一个要测试的组合对象
        Portfolio portfolio = new Portfolio();

        //创建将要添加到组合中的数据(股票)列表。
        List<Stock> stocks = new ArrayList<Stock>();
        Stock googleStock = new Stock("1","Google", 10);
        Stock microsoftStock = new Stock("2","Microsoft",100);

        stocks.add(googleStock);
        stocks.add(microsoftStock);

```

## Record 阶段
```

        // Mock的Record 阶段  -》 在这个阶段,Record 状态,用户可以设定 Mock 对象的预期行为和输出,这些对象行为被录制下来,保存在 Mock 对象中
                //创建提供数据(股票)服务的模拟对象
        StockService stockService = EasyMock.createMock(StockService.class);

        //模拟数据(股票)服务的行为,以返回各种方法的价值。设定return的返回值
        EasyMock.expect(stockService.getPrice(googleStock)).andReturn(50.00);
        EasyMock.expect(stockService.getPrice(microsoftStock)).andReturn(1000.00);

```

## Replay  阶段 
   `replay()`,将 Mock 对象的状态置为 `Replay 状态`。
```
        //Mock的Replay  阶段  -》 在这个阶段,在使用 Mock 对象进行实际的测试前,我们需要将 Mock 对象的状态切换为 Replay。在 Replay 状态,Mock 对象能够根据设定对特定的方法调用作出预期的响应。
        EasyMock.replay(stockService);
        //在业务处理组合中增加数据项
        portfolio.setStocks(stocks);
        //在业务处理组合中增加数据服务
        portfolio.setStockService(stockService);

```

## 测试调用阶段

```
        //实际处理逻辑
        double marketValue = portfolio.getMarketValue();

        //验证数据处理 10*50.00 + 100* 1000.00 = 500.00 + 100000.00 = 100500
        System.out.println("Market value of the portfolio: "+ marketValue);

```


## EasyMock 验证阶段(非必须)
```
        //Mock的验证阶段 (验证调用过程正常完成)
        EasyMock.verify(stockService);
```


# EasyMock 方法汇总

## EasyMock
    构建和初始化  


1.EasyMock静态方法createMock 
> 使用 EasyMock 动态构建 Mock 对象

2.EasyMock静态方法createControl
> 能创建一个接口 IMocksControl 的对象,该对象能创建并管理多个 Mock 对象。
如果需要在测试中使用多个 Mock 对象,我们推荐您使用这一机制,
因为它在多个 Mock 对象的管理上提供了相对便捷的方法。

> 如果您要模拟的是一个具体类而非接口,那么您需要下载扩展包 `EasyMock Class Extension 2.2.2`。
在对具体类进行模拟时,您只要用 `org.easymock.classextension.EasyMock` 类中的静态方法代替 `org.easymock.EasyMock` 类中的静态方法即可。

3,replay(mockResultSet) or replay();
> 将 Mock 对象切换到 Replay 状态

两种使用方式:  
eg
    1.如果构建对象`IMocksControl`则调用它的`.replay()`;   
    2.如果构建对象为具体的我们业务测试对象,则调用`EasyMock.replay(xxx);`(本例调用这个)

4.verify(mockResultSet) or verify();
> 对 Mock 对象的行为进行验证

两种使用方式:  
eg
    1.如果构建对象`IMocksControl`则调用它的`.verify()`;   
    2.如果构建对象为具体的我们业务测试对象,则调用`EasyMock.verify(xxx);`(本例调用这个)

5.reset 
> 为了避免生成过多的 Mock 对象,EasyMock 允许对原有 Mock 对象进行重用。要对 Mock 对象重新初始化,我们可以采用 reset 方法

两种使用方式:  
eg
    1.如果构建对象`IMocksControl`则调用它的`.reset ()`;   
    2.如果构建对象为具体的我们业务测试对象,则调用`EasyMock.reset (xxx);`(本例调用这个)





# IExpectationSetters
    对 Mock 对象行为的添加和设置是通过接口 IExpectationSetters 来实现的   
    两种类型的输出:(1)产生返回值;(2)抛出异常。  

1.IExpectationSetters<T> andReturn(T value);
> 设定返回值

2.void andStubReturn(T value);
> 有时,某个方法的调用总是返回一个相同的值,为了避免每次调用都为 Mock 对象的行为进行一次设定

3.IExpectationSetters<T> andThrow(Throwable throwable);
> 设定预期抛出异常

4.void andStubThrow(Throwable throwable);
> 设定抛出默认异常的函数 与 `andStubReturn` 类似

5.IExpectationSetters<T> times(int count);
> 该方法可以 Mock 对象方法的调用次数进行确切的设定  

eg:  
    `andReturn("My return value").times(3);`

6. times(int minTimes, int maxTimes)
> 该方法*少被调用 minTimes 次,*多被调用 maxTimes 次

7.atLeastOnce()
> 该方法至少被调用一次。

8. anyTimes()
> 该方法可以被调用任意次。

eg:
    `expect(mockResult.close()).times(3, 5);`

HD地址批量生成 java

HD 钱包全称为是分层确定性(Hierarchical Deterministic)钱包的缩写 HD Wallets。

%title插图%num

首次创建 HD 钱包或者备份钱包时,会产生一个助记词,助记词是一连串的英⽂单词,这一串单词序列就可以创建种子,种子又可以创建所有的私钥。单词顺序也是钱包的备份,可以恢复钱包。而种⼦对应的就是所确定性钱包的随机数。

HD 钱包的优点在于只需要主公钥,就可以生成出任意数量的子公钥。也就是说,无需私钥介入(主私钥和子私钥),就能基于主公钥生成新(公钥)地址,而这些地址其实都能被主私钥所控制。

直接撸代码:

<dependency>
<groupId>org.bitcoinj</groupId>
<artifactId>bitcoinj-tools</artifactId>
<version>0.14.7</version>
</dependency>
<dependency>
<groupId>org.bitcoinj</groupId>
<artifactId>bitcoinj-core</artifactId>
<version>0.14.7</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.59</version>
</dependency>
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;

import org.apache.commons.configuration2.Configuration;
import org.apache.commons.configuration2.builder.fluent.Configurations;
import org.bitcoinj.core.Base58;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.crypto.ChildNumber;
import org.bitcoinj.crypto.DeterministicHierarchy;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.crypto.HDKeyDerivation;
import org.bitcoinj.crypto.HDUtils;
import org.bitcoinj.params.MainNetParams;
import org.bitcoinj.params.TestNet3Params;
import org.bitcoinj.wallet.DeterministicKeyChain;
import org.bitcoinj.wallet.DeterministicSeed;
import org.bitcoinj.wallet.UnreadableWalletException;
import org.bouncycastle.crypto.digests.RIPEMD160Digest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bscoin.coldwallet.cointype.common.ConfigUtil;
import com.bscoin.coldwallet.cointype.common.HDWallet;
import com.bscoin.coldwallet.cointype.common.HashUtils;
import com.bscoin.coldwallet.cointype.common.SecretOperation;

public class HDWalletPK {

private static Logger LOG = LoggerFactory.getLogger(HDWalletPK.class);

static NetworkParameters params;

static{
try {
Configuration config = ConfigUtil.getInstance();
params = config.getBoolean(“bitcoin.testnet”) ? TestNet3Params.get() : MainNetParams.get();
LOG.info(“=== [BTC] bitcoin client networkID:{} ===”,params.getId());
} catch (Exception e) {
LOG.info(“=== [BTC] com.bscoin.coldwallet.cointype.btc.HDWalletPK:{} ===”,e.getMessage(),e);
}
}

/**
* @throws IOException
* @throws FileNotFoundException
* @Title: createHDWalletByPATH
* @param @param word 助记词
* @param @param passphrase 密码
* @param @param childNum 生成的hd钱包数量
* @param @param params
* @param @return 参数
* @return List<HDWallet> 返回类型
* @throws
*/
public static List<HDWallet> createHDWalletByPATH(String word, String passphrase, int[] childNum) throws FileNotFoundException, IOException {
List<HDWallet> wallet = new ArrayList<HDWallet>();
try {
DeterministicSeed deterministicSeed = new DeterministicSeed(word, null, passphrase, 0L);
DeterministicKeyChain deterministicKeyChain = DeterministicKeyChain.builder().seed(deterministicSeed).build();
DeterministicKey main = deterministicKeyChain.getKeyByPath(HDUtils.parsePath(“44H/0H”), true);
DeterministicHierarchy tree = new DeterministicHierarchy(main);
DeterministicKey rootKey = tree.getRootKey();
LOG.info(“### [BTC] childs privKey , pubKey , address start ###”);
for (int i = childNum[0], len = childNum[1]; i < len; i++) {
DeterministicKey deriveChildKey = HDKeyDerivation.deriveChildKey(rootKey, new ChildNumber(i));
wallet.add(new HDWallet(deriveChildKey.getPathAsString(),
deriveChildKey.getPrivateKeyAsWiF(params),
Base58.encode(deriveChildKey.getPubKey()),
ECKey.fromPrivate(deriveChildKey.getPrivKey()).toAddress(params).toBase58()));
}

LOG.info(“### [BTC] childs privKey , pubKey , address end ###”);
} catch (UnreadableWalletException e) {
e.printStackTrace();
}
return wallet;
}

/**
* @Title: generateMnemonic
* @param @param passphrase
* @param @param params
* @param @return
* @param @throws IOException 参数
* @return String 返回类型
* @throws
*/
public static String generateMnemonic(String passphrase) throws IOException {
StringBuilder words = new StringBuilder();
SecureRandom secureRandom = new SecureRandom();
long creationTimeSeconds = System.currentTimeMillis() / 1000;
DeterministicSeed ds = new DeterministicSeed(secureRandom, 128, passphrase, creationTimeSeconds);

for (String str : ds.getMnemonicCode()) {
words.append(str).append(” “);
}
return words.toString().trim();
}

/**
* @Title: generateAddress 根据公钥生成地址
* @param @param publicKey
* @param @return 参数
* @return String 返回类型
* @throws
*/
public static String generateAddress(String publicKey) {
//1. 计算公钥的 SHA-256 哈希值
byte[] sha256Bytes = HashUtils.sha256(Base58.decode(publicKey));
//2. 取上一步结果,计算 RIPEMD-160 哈希值
RIPEMD160Digest digest = new RIPEMD160Digest();
digest.update(sha256Bytes, 0, sha256Bytes.length);
byte[] ripemd160Bytes = new byte[digest.getDigestSize()];
digest.doFinal(ripemd160Bytes, 0);
//3. 取上一步结果,前面加入地址版本号(主网版本号“0x00”)
byte[] networkID = new BigInteger(“00”, 16).toByteArray();
byte[] extendedRipemd160Bytes = HashUtils.add(networkID, ripemd160Bytes);
//4. 取上一步结果,计算 SHA-256 哈希值
byte[] oneceSha256Bytes = HashUtils.sha256(extendedRipemd160Bytes);
//5. 取上一步结果,再计算一下 SHA-256 哈希值
byte[] twiceSha256Bytes = HashUtils.sha256(oneceSha256Bytes);
//6. 取上一步结果的前4个字节(8位十六进制)
byte[] checksum = new byte[4];
System.arraycopy(twiceSha256Bytes, 0, checksum, 0, 4);
//7. 把这4个字节加在第5步的结果后面,作为校验
byte[] binaryAddressBytes = HashUtils.add(extendedRipemd160Bytes, checksum);
//8. 把结果用 Base58 编码算法进行一次编码
return Base58.encode(binaryAddressBytes);
}

/**
* 验证地址是否合法
* @param address
* @return
*/
public static boolean verifyAddress(String address) {
if (address.length() < 26 || address.length() > 35) {
return false;
}
byte[] decoded = HashUtils.decodeBase58To25Bytes(address);
if (null == decoded) {
return false;
}
// 验证校验码
byte[] hash1 = HashUtils.sha256(Arrays.copyOfRange(decoded, 0, 21));
byte[] hash2 = HashUtils.sha256(hash1);

return Arrays.equals(Arrays.copyOfRange(hash2, 0, 4), Arrays.copyOfRange(decoded, 21, 25));
}

public static void main(String[] args) throws IOException {
String s = generateMnemonic(“xx”);//生成助记次
int[] a = {1,10};//根据助记词生成childID={1-10}的钱包地址
List<HDWallet> walls = createHDWalletByPATH(s, “123457”,a);
for (HDWallet hdWallet : walls) {
System.out.println(hdWallet.getPubKey());
System.out.println(hdWallet.getPrivKey());
System.out.println(hdWallet.getAddress());
System.out.println(“———————-“);
}
}
}
HashUtil:
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.stream.Stream;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.ArrayUtils;

/**
* @ClassName: HashUtils
* @author DHing

*
*/
public class HashUtils {

/**
* 加密字符集合
*/
private static final String ALPHABET = “123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz”;

/**
* 使用 sha256 算法加密
* @param input
* @return
*/
public static String sha256Hex(String input) {
return DigestUtils.sha256Hex(input);
}

/**
* 使用 sha256 hash 算法加密,返回一个 64 位的字符串 hash
* @param input
* @return
*/
public static String sha256Hex(byte[] input) {
return DigestUtils.sha256Hex(input);
}

public static byte[] sha256(String input) {
return DigestUtils.sha256(input);
}

public static byte[] sha256(byte[] input) {
return DigestUtils.sha256(input);
}

/**
* 两个byte[]数组相加
*
* @param data1
* @param data2
* @return
*/
public static byte[] add(byte[] data1, byte[] data2) {

byte[] result = new byte[data1.length + data2.length];
System.arraycopy(data1, 0, result, 0, data1.length);
System.arraycopy(data2, 0, result, data1.length, data2.length);

return result;
}

/**
* 将多个字节数组合并成一个字节数组
*
* @param bytes
* @return
*/
public static byte[] merge(byte[]… bytes) {
Stream<Byte> stream = Stream.of();
for (byte[] b : bytes) {
stream = Stream.concat(stream, Arrays.stream(ArrayUtils.toObject(b)));
}
return ArrayUtils.toPrimitive(stream.toArray(Byte[]::new));
}

/**
* long 类型转 byte[]
*
* @param val
* @return
*/
public static byte[] toBytes(long val) {
return ByteBuffer.allocate(Long.BYTES).putLong(val).array();
}

/**
* 使用 Base58 把地址解码成 25 字节
* @param input
* @return
*/
public static byte[] decodeBase58To25Bytes(String input) {

BigInteger num = BigInteger.ZERO;
for (char t : input.toCharArray()) {
int p = ALPHABET.indexOf(t);
if (p == -1) {
return null;
}
num = num.multiply(BigInteger.valueOf(58)).add(BigInteger.valueOf(p));
}

byte[] result = new byte[25];
byte[] numBytes = num.toByteArray();
System.arraycopy(numBytes, 0, result, result.length – numBytes.length, numBytes.length);
return result;
}
}
package com.bscoin.coldwallet.cointype.common;

import java.io.Serializable;

import org.bitcoinj.core.NetworkParameters;

public class HDWallet implements Serializable{

private static final long serialVersionUID = 1L;

public HDWallet(){}

public HDWallet(String path, String privKey, String pubKey, String address) {
super();
this.path = path;
this.privKey = privKey;
this.pubKey = pubKey;
this.address = address;
}

public HDWallet(String privKey, String pubKey, String address) {
super();
this.privKey = privKey;
this.pubKey = pubKey;
this.address = address;
}

private String word; //助记词

private String path;//路径-标识位
private String passphrase;

private String privKey; //私钥
private String pubKey; //公钥

private String address;//地址

public String getWord() {
return word;
}
public void setWord(String word) {
this.word = word;
}
public String getPrivKey() {
return privKey;
}
public void setPrivKey(String privKey) {
this.privKey = privKey;
}
public String getPubKey() {
return pubKey;
}
public void setPubKey(String pubKey) {
this.pubKey = pubKey;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}

public String getPassphrase() {
return passphrase;
}
public void setPassphrase(String passphrase) {
this.passphrase = passphrase;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
}

配置Groovy开发运行环境

      虽然一直在做C和C++,但只要涉及到能用Java或者.net开发的部分,还是很愿意用这两者进行开发的。虽然相比于C/C++,Java或.net的灵活性小了一些,但后者开发的效率真的是相当高,而且写起来也不会像C/C++那样让人郁闷。

      这两年C#的发展可谓是精彩,Microsoft真不愧是*牛X的拿来主义公司,C#结合了多种语言的优点,写代码相当流畅,特别是C#描述中间件和API时可选的表现方法更多,对于类库使用者的要求更低,真的很不错。

      当然,相比Phyton、Ruby或者Javascript这类脚本,任何强类型语言都显得不那么灵活,就好比PHP能提供比C语言更多的语言特性,包括闭包这样强大的功能。C#能以一种强类型语言做到相当多的动态语言特性,真是很了不起。对应Java,这两年发展的真的很慢,*新版本的JDK 7,也不过是增加了.net framework 2.0中几个常用功能,和.net framework 4相比依然相差很远。Java上有Spring等如此强大的API,语言本身却简单的近乎粗陋,真是不好说。

      有人说Java就应该是这样的,就应该用getter/setter而不用=运算符,就应该不停的throws,就应该用一大票的interface,就应该不能自定义值类型……但我总觉得,开发人员喜欢什么样的设计模式都不能成为语言本身不提供该功能的理由。

      否则,Groovy为什么会出现呢?

      不吐槽了,既然Java上诞生了Groovy,这门语法甜的发腻的语言就肯定会让很多人喜欢它!虽然Groovy目前还存在效率方面的问题,它甚至比Javascript更慢,但Groovy毕竟是依赖于Java的,毕竟是和Java无缝挂接的,毕竟具备Java, Javascript, Phython, Ruby等等语言的优点,所以我估计谁也不能阻止Groovy的前进了!

 1、配置Groovy开发环境

      由于Groovy是基于JDK的,所以安装Groovy之前肯定要安装JDK了,从http://www.oracle.com/technetwork/java/javase/downloads/index.html可以下载到*新的JDK,写这篇文章时,JDK已经更新到JDK 7u2了,下载安装包安装即可。

      Groovy的安装挺简单的,基本上和JDK的安装差不多,从http://groovy.codehaus.org/Download下载Groovy的GDK环境的安装包Windows-Installer(应该是1.8版本了),直接安装就可以了;如果下载的是zip版本,则按照如下步骤设置即可:

将zip包解压到随便那个目录中,如d:\devtools\groovy1.8.4
设置环境变量GROOVY_HOME,值为放置解压文件的目录,如d:\devtools\groovy1.8.4
增加环境变量path,指向%GROOVY_HOME%\bin
接下来就可以在Groovy控制台中编写脚本了,在Windows控制台(cmd)下输入groovyconsole命令进入groovy控制台,在里面输入代码 println “HelloWorld”回车,按组合键CTRL+R就可以看到运行结果了。
当然,这是Groovy自带的*基本的开发工具,很不实用,对于Groovy,NetBeans,IntelliJ IDEA等开发环境都提供了完善的支持,eclipse也提供了对应的插件。这里主要说一下eclipse中安装插件的办法。

到http://www.eclipse.org/downloads/上下载*新版本的eclipse,目前是3.7.1版本,下载后解压到任意目录即可运行;
打开eclipse,选择菜单项“Help->Install New Software”,再打开的对话框中按下Add按钮,在其中输入网址 http://dist.springsource.org/release/GRECLIPSE/e3.7/后确定;

%title插图%num
图1

%title插图%num
图2

                    等上一会儿,直到eclipse从网上找到找到要安装的包,此时把要安装的包前面的复选框选中(全选即可),点击Finish按钮即可开始安装

%title插图%num
图3

            其实我试了几次在线安装,都是安装到一半的时候由于神奇的未知网络原因,安装中断,所以*终我使用的是离线安装的办法:
从http://dist.springsource.org/release/GRECLIPSE/e3.7/archive-2.6.0.xx-20111212-0900-e37-RELEASE.zip下载离线安装包,注意,直接下载速度不会超过1kbps,请各位同学自行寻找访问伟大祖国以外神奇网站的具体方法,我使用GoAgent,可以在10Mbps的带宽上达到200多bps的下载;
注意下载的这个安装包不能使用eclipse传统的插件安装方式(内含Groovy编译器),无论覆盖方式、Link文件方式还是dropins目录方式都玩不转,还得使用eclipse的”Help->nstall New Software“方式安装;
当打开上述图2的对话框后,不要填写网址,按下Achieve按钮,在出现的对话框中填写下载文件的路径和文件名

%title插图%num

图4

            点击OK,继续安装即可
插件安装后重启eclipse,在新建项目中应该包含了Groovy Project和Groovy Class,这就表示Groovy插件安装成功,可以利用eclipse开发Groovy应用程序了!

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速