日志分类:Android游戏

Android推送方式比较

时间:2011年09月08日作者:么吉查看次数:766 views评论次数:0

推送方式的基础知识
  当我们开发需要和服务器交互的应用程序时,基本上都需要获取服务器端的数据,比如《地震及时通》就需要及时获取服务器上最新的地震信息。要获取服务器上不定时更新的信息一般来说有两种方法,第一种是客户端使用Pull(拉)的方式,隔一段时间就去服务器上获取信息,看是否有更新的信息出现。第二种就是服务器使用Push(推送)的方式,当服务器端有新信息了,则把最新的信息Push到客户端上。
  虽然Pull和Push两种方式都能实现获取服务器端更新信息的功能,但是明显来说Push is better than pull。因为Pull方式更费客户端的网络流量,更主要的是费电量。

  在开发Android和iPhone应用程序时,我们往往需要从服务器不定的向手机客户端即时推送各种通知消息,iPhone上已经有了比较简单的和完美的推送通知解决方案,可是Android平台上实现起来却相对比较麻烦,最近利用几天的时间对Android的推送通知服务进行初步的研究。

  在Android手机平台上,Google提供了C2DM(Cloudto Device Messaging)服务,起初我就是准备采用这个服务来实现自己手机上的推送功能。

  Android Cloud to Device Messaging (C2DM)是一个用来帮助开发者从服务器向Android应用程序发送数据的服务。该服务提供了一个简单的、轻量级的机制,允许服务器可以通知移动应用程序直接与服务器进行通信,以便于从服务器获取应用程序更新和用户数据。C2DM服务负责处理诸如消息排队等事务并向运行于目标设备上的应用程序分发这些消息。

C2DM操作过程图:

但是经过一番研究发现,这个服务存在很大的问题:

1)C2DM内置于Android的2.2系统上,无法兼容老的1.6到2.1系统;

2)C2DM需要依赖于Google官方提供的C2DM服务器,由于国内的网络环境,这个服务经常不可用,如果想要很好的使用,我们的App Server必须也在国外,这个恐怕不是每个开发者都能够实现的;

  有了上述两个使用上的制约,导致我最终放弃了这个方案,不过我想利用另外一篇文章来详细的介绍C2DM的框架以及客户端和App Server的相应设置方法,可以作为学习与参考之用。

  即然C2DM无法满足我们的要求,那么我们就需要自己来实现Android手机客户端与App Server之间的通信协议,保证在App Server想向指定的Android设备发送消息时,Android设备能够及时的收到。下面我来介绍几种常见的方案:

1)轮询(Pull):应用程序应当阶段性的与服务器进行连接并查询是否有新的消息到达,你必须自己实现与服务器之间的通信,例如消息排队等。而且你还要考虑轮询的频率,如果太慢可能导致某些消息的延迟,如果太快,则会大量消耗网络带宽和电池。

2)SMS(Push):在Android平台上,你可以通过拦截SMS消息并且解析消息内容来了解服务器的意图。这是一个不错的想法,我就见过采用这个方案的应用程序。这个方案的好处是,可以实现完全的实时操作。但是问题是这个方案的成本相对比较高,你很难找到免费的短消息发送网关,关于这个方案的实现。

3)持久连接(Push):这个方案可以解决由轮询带来的性能问题,但是还是会消耗手机的电池。Apple的推送服务之所以工作的很好,是因为每一台手机仅仅保持一个与服务器之间的连接,事实上C2DM也是这么工作的。不过这个方案也存在不足,就是我们很难在手机上实现一个可靠的服务。Android操作系统允许在低内存情况下杀死系统服务,所以你的通知服务很可能被操作系统Kill掉了。

  前两个方案存在明显的不足,第三个方案也有不足,不过我们可以通过良好的设计来弥补,以便于让该方案可以有效的工作。毕竟,我们要知道GMail,GTalk以及GoogleVoice都可以实现实时更新的。

采用MQTT协议实现Android推送
  MQTT是一个轻量级的消息发布/订阅协议,它是实现基于手机客户端的消息推送服务器的理想解决方案。

wmqtt.jar 是IBM提供的MQTT协议的实现。你可以从如下站点下载它。你可以将该jar包加入你自己的Android应用程序中。

Really Small Message Broker (RSMB) ,他是一个简单的MQTT代理,同样由IBM提供。缺省打开1883端口,应用程序当中,它负责接收来自服务器的消息并将其转发给指定的移动设备。

采用XMPP协议实现Android推送
  这是我在项目中采用的方案。事实上Google官方的C2DM服务器底层也是采用XMPP协议进行的封装。
  XMPP(可扩展通讯和表示协议)是基于可扩展标记语言(XML)的协议,它用于即时消息(IM)以及在线探测。这个协议可能最终允许因特网用户向因特网上的其他任何人发送即时消息。

  androidpn是一个基于XMPP协议的java开源Android push notification实现。它包含了完整的客户端和服务器端。经过源代码研究我发现,该服务器端基本是在另外一个开源工程openfire基础上修改实现的,不过比较郁闷的是androidpn的文档是由韩语写的,所以整个研究过程基本都是读源码。

  androidpn客户端需要用到一个基于java的开源XMPP协议包asmack,这个包同样也是基于openfire下的另外一个开源项目smack,不过我们不需要自己编译,可以直接把androidpn客户端里面的asmack.jar拿来使用。客户端利用asmack中提供的XMPPConnection类与服务器建立持久连接,并通过该连接进行用户注册和登录认证,同样也是通过这条连接,接收服务器发送的通知。

  androidpn服务器端也是java语言实现的,基于openfire开源工程,不过它的Web部分采用的是spring框架,这一点与openfire是不同的。Androidpn服务器包含两个部分,一个是侦听在5222端口上的XMPP服务,负责与客户端的XMPPConnection类进行通信,作用是用户注册和身份认证,并发送推送通知消息。另外一部分是Web服务器,采用一个轻量级的HTTP服务器,负责接收用户的Web请求。服务器架构如下:

  最上层包含四个组成部分,分别是SessionManager,Auth Manager,PresenceManager以及Notification Manager。SessionManager负责管理客户端与服务器之间的会话,Auth Manager负责客户端用户认证管理,Presence Manager负责管理客户端用户的登录状态,NotificationManager负责实现服务器向客户端推送消息功能。

  这个解决方案的最大优势就是简单,我们不需要象C2DM那样依赖操作系统版本,也不会担心某一天Google服务器不可用。利用XMPP协议我们还可以进一步的对协议进行扩展,实现更为完善的功能。

  采用这个方案,我们目前只能发送文字消息,不过对于推送来说一般足够了,因为我们不能指望通过推送得到所有的数据,一般情况下,利用推送只是告诉手机端服务器发生了某些改变,当客户端收到通知以后,应该主动到服务器获取最新的数据,这样才是推送服务的完整实现。

总结
  现在使用XMPP协议进行推送的方式慢慢多了,主要是原因是比较简单,我后面的博文将会写一些相关androidpn服务器的内容。本人的其中一个软件<足球即时比分>,计划进行升级,从之前的拉(Pull)方式改为 推(Push)方式,我相信这样将会减小服务器的压力,并且比分更新将会更新加及时。

返回 : Android开发博文汇总

Android双击事件(模拟ListView双击事件)

时间:2011年07月06日作者:么吉查看次数:243 views评论次数:0

  双击事件在Windows中经常使用,大家都已经用得非常习惯了.但是在Android中默认是没有双击事件的(这个我觉得很奇怪).在我的足球即时比分应用中就用到了双击事件,我上网查过也在一些QQ群中问过一些Android开发人员,他们说这个只能自己来处理了,Android是没有提供双击事件的.

以下是我足球即时比分中的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public class SenseSoccerScoreActivity extends Activity{
	// 双击事件记录最近一次点击的ID
	private String lastClickId;
 
	// 双击事件记录最近一次点击的时间
	private long lastClickTime;
 
	@Override
	public void onCreate(Bundle savedInstanceState) {
		Log.d("score", "SenseSoccerScoreActivity create ...");
		super.onCreate(savedInstanceState);
		ConfigUtil.setConfig(this, KEY_UPDATE_TIME, 0L);
		// 更新本应用的Locale
		ScoreUtil.updateAppLocale(this);
 
 		LeagueUtil.init(this);
 
		this.requestWindowFeature(Window.FEATURE_NO_TITLE);
		setContentView(R.layout.main);
		setTheme(android.R.style.Theme_NoTitleBar);
		initMatchViews() ;
	}
 
	/**
	 * 初始化赛事列表的数据
	 */
	private void initMatchViews() {
               // ------------------ playing listView --------------------
		MatchAdapter playingAdapter = mm.getPlayingAdapter();
		playingAdapter.sortPlayingBy(MatchDateComparator.getInstance());
		playingListView.setAdapter(playingAdapter);
		playingListView.setOnItemLongClickListener(todayListLongClickListener);
 
                // 添加点击事件,双击的判断由mItemClickListenter完成 
		playingListView.setOnItemClickListener(mItemClickListenter); 
		playingListView.setOnTouchListener(listViewOnTouchListener);
		playingListView.setFastScrollEnabled(true);
        }
 
	/**
	 * 双击事件(赛事明细,事件)
	 */
	private OnItemClickListener mItemClickListenter = new OnItemClickListener() {
		public void onItemClick(AdapterView<?> parent, View v, int pos,long id) {
			MatchVO mv = (MatchVO) ((ListView)parent).getAdapter().getItem(pos);
 
			// 如果是双击,1秒内连续点击判断为双击
			if(mv.getId().equals(lastClickId) 
                             && (Math.abs(lastClickTime-System.currentTimeMillis()) < 1000)){
				lastClickId = null;
				lastClickTime = 0;
				Intent intent = new Intent();
				intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
				intent.putExtra("matchId", mv.getId());
				intent.putExtra("matchKey", mv.getMatchKey());
				intent.setClass(SenseSoccerScoreActivity.this, 
                                      MatchEventActivity.class);
				startActivity(intent);
			}else{
				lastClickId = mv.getId();
				lastClickTime = System.currentTimeMillis();
			}
		}
	};
}

返回 : Android开发博文汇总

Android返回键处理(事件)

时间:2011年07月06日作者:么吉查看次数:2,262 views评论次数:0

  Android返回键我们经常都要进行处理,我的两个应用分别有以下两个不同的处理方式
方式一:按返回键显示退出提示框( 自定义提示框架可以参考[ Android使用自定义AlertDialog(退出提示框) ] )
方式二:按返回键不退出应用,返回主页面(即与按Home键操作一样)

方式一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class WelcomeActivity extends Activity {
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		// 如果是返回键,直接返回到桌面
		if(keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_HOME){
                   showExitGameAlert();
		}
 
		return super.onKeyDown(keyCode, event);
	}
 
	private void showExitGameAlert() {
		final AlertDialog dlg = new AlertDialog.Builder(this).create();
		dlg.show();
		Window window = dlg.getWindow();
		window.setContentView(R.layout.shrew_exit_dialog);
		ImageButton ok = (ImageButton) window.findViewById(R.id.btn_ok);
		ok.setOnClickListener(new View.OnClickListener() {
			public void onClick(View v) {
				exitApp();
			}
		});
 
		ImageButton cancel = (ImageButton) window.findViewById(R.id.btn_cancel);
		cancel.setOnClickListener(new View.OnClickListener() {
			public void onClick(View v) {
				dlg.cancel();
			}
		});
	}
}

方式二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class SenseSoccerScoreActivity extends Activity {
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		// 如果是返回键,直接返回到桌面
                // 经过测试,如果是乐Phone返回桌面会报错
		if(keyCode == KeyEvent.KEYCODE_BACK){
			// 创建退出系统提示框
			if(notSupportKeyCodeBack()){
	                   new AlertDialog.Builder(this)
	                    .setMessage(this.getText(R.string.sure_exit_app).toString())
	                    .setPositiveButton(R.string.text_ok, new DialogInterface.OnClickListener() {
	            	   public void onClick(DialogInterface dialog, int which) {
	            		exitApp(); // 退出应用处理
	            	   }
	            })
	            .setNegativeButton(R.string.text_cancel, new DialogInterface.OnClickListener() {
	            	public void onClick(DialogInterface dialog, int which) {
	            	}
	            }).create().show();
			} else {
				// 返回桌面,经测试,有一些手机不支持,查看 notSupportKeyCodeBack 方法
				Intent i= new Intent(Intent.ACTION_MAIN);
				i.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); 
				i.addCategory(Intent.CATEGORY_HOME);
				startActivity(i);
				return false;
			}
		}
		return super.onKeyDown(keyCode, event);
	}
        // 经过测试,如果是乐Phone返回桌面会报错
	private boolean notSupportKeyCodeBack(){
		if("3GW100".equals(Build.MODEL)  
                         || "3GW101".equals(Build.MODEL) 
                         || "3GC101".equals(Build.MODEL)) {
			return true;
		}
		return false;
	}
}

返回 : Android开发博文汇总

Android压缩文件(压缩目录)

时间:2011年07月02日作者:ronald查看次数:385 views评论次数:0

  在Android中我们很多时候需要进行压缩与解压缩,就如本人的[ 足球即时比分 ]应用中也用到过.需要将一些信息进行收集再进行压缩,最后将压缩文件上传到服务器中(如何上传将文件上传到服务器中可以看我另一篇博文 :[ Android上传文件到服务器 ]).

  以下我的使用到的工具类的代码.需要注意的是,进行压缩与解压缩都不支持中文名,如果需要支持中文名的话,一般是使用 Ant中的ZipInputStream与ZipOutStream,由于手机上使用ant的jar包的话,会令应用或游戏的大小变大很多,所以尽量小引入其它第三方的jar包的.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
package rbase.app.nowscore.util;
 
import java.io.InputStream;
 
/**
 * Android Zip压缩解压缩
 * @author ronald (www.r-base.net)
 */
public final class ZipUtil {
  private ZipUtil(){
  }
 
  /**
   * 取得压缩包中的 文件列表(文件夹,文件自选)
   * @param zipFileString		压缩包名字
   * @param bContainFolder	是否包括 文件夹
   * @param bContainFile		是否包括 文件
   * @return
   * @throws Exception
   */
  public static java.util.List<java.io.File> getFileList(String zipFileString, boolean bContainFolder, 
          boolean bContainFile)throws Exception {
    java.util.List<java.io.File> fileList = new java.util.ArrayList<java.io.File>();
    java.util.zip.ZipInputStream inZip = 
                     new java.util.zip.ZipInputStream(new java.io.FileInputStream(zipFileString));
    java.util.zip.ZipEntry zipEntry;
    String szName = "";		
    while ((zipEntry = inZip.getNextEntry()) != null) {
	szName = zipEntry.getName();
	if (zipEntry.isDirectory()) {
	  // get the folder name of the widget
	  szName = szName.substring(0, szName.length() - 1);
	  java.io.File folder = new java.io.File(szName);
	  if (bContainFolder) {
	    fileList.add(folder);
	  }
        } else {
	  java.io.File file = new java.io.File(szName);
	  if (bContainFile) {
	    fileList.add(file);
	  }
	}
    }//end of while		
    inZip.close();
    return fileList;
  }
 
  /**
   * 返回压缩包中的文件InputStream
   * 
   * @param zipFilePath		压缩文件的名字
   * @param fileString	解压文件的名字
   * @return InputStream
   * @throws Exception
   */
public static java.io.InputStream upZip(String zipFilePath, String fileString)throws Exception {
	java.util.zip.ZipFile zipFile = new java.util.zip.ZipFile(zipFilePath);
	java.util.zip.ZipEntry zipEntry = zipFile.getEntry(fileString);
 
	return zipFile.getInputStream(zipEntry);
}
 
/**
 * 解压一个压缩文档 到指定位置
 * @param zipFileString	压缩包的名字
 * @param outPathString	指定的路径
 * @throws Exception
 */
public static void unZipFolder(InputStream input, String outPathString)throws Exception {
	java.util.zip.ZipInputStream inZip = new java.util.zip.ZipInputStream(input);
	java.util.zip.ZipEntry zipEntry = null;
	String szName = "";
 
	while ((zipEntry = inZip.getNextEntry()) != null) {
		szName = zipEntry.getName();
 
		if (zipEntry.isDirectory()) {
		  // get the folder name of the widget
		  szName = szName.substring(0, szName.length() - 1);
		  java.io.File folder = new java.io.File(outPathString + java.io.File.separator + szName);
		  folder.mkdirs();
		} else {
		  java.io.File file = new java.io.File(outPathString + java.io.File.separator + szName);
		  file.createNewFile();
		  // get the output stream of the file
		  java.io.FileOutputStream out = new java.io.FileOutputStream(file);
		  int len;
		  byte[] buffer = new byte[1024];
		  // read (len) bytes into buffer
		  while ((len = inZip.read(buffer)) != -1) {
			// write (len) byte from buffer at the position 0
			out.write(buffer, 0, len);
			out.flush();
		  }
		  out.close();
		}
	}//end of while
		inZip.close();
	}
 
	/**
	 * 解压一个压缩文档 到指定位置
	 * @param zipFileString	压缩包的名字
	 * @param outPathString	指定的路径
	 * @throws Exception
	 */
	public static void unZipFolder(String zipFileString, String outPathString)throws Exception {
		unZipFolder(new java.io.FileInputStream(zipFileString),outPathString);
	}//end of func
 
 
	/**
	 * 压缩文件,文件夹
	 * 
	 * @param srcFilePath	要压缩的文件/文件夹名字
	 * @param zipFilePath	指定压缩的目的和名字
	 * @throws Exception
	 */
	public static void zipFolder(String srcFilePath, String zipFilePath)throws Exception {
	  //创建Zip包
	  java.util.zip.ZipOutputStream outZip = 
              new java.util.zip.ZipOutputStream(new java.io.FileOutputStream(zipFilePath));
 
	  //打开要输出的文件
	  java.io.File file = new java.io.File(srcFilePath);
 
	  //压缩
	  zipFiles(file.getParent()+java.io.File.separator, file.getName(), outZip);
 
	  //完成,关闭
	  outZip.finish();
          outZip.close();
 
        }//end of func
 
	/**
	 * 压缩文件
	 * @param folderPath
	 * @param filePath
	 * @param zipOut
	 * @throws Exception
	 */
	private static void zipFiles(String folderPath, String filePath, 
                     java.util.zip.ZipOutputStream zipOut)throws Exception{
	  if(zipOut == null){
	    return;
	  }
 
	  java.io.File file = new java.io.File(folderPath+filePath);
 
	  //判断是不是文件
	  if (file.isFile()) {
	    java.util.zip.ZipEntry zipEntry =  new java.util.zip.ZipEntry(filePath);
	    java.io.FileInputStream inputStream = new java.io.FileInputStream(file);
	    zipOut.putNextEntry(zipEntry);
 
	    int len;
	    byte[] buffer = new byte[4096];
 
	    while((len=inputStream.read(buffer)) != -1) {
	 	zipOut.write(buffer, 0, len);
	    }
 
	     zipOut.closeEntry();
	  } else {
	   //文件夹的方式,获取文件夹下的子文件
	   String fileList[] = file.list();
 
	   //如果没有子文件, 则添加进去即可
	   if (fileList.length <= 0) {
	  	java.util.zip.ZipEntry zipEntry =  
                       new java.util.zip.ZipEntry(filePath+java.io.File.separator);
		zipOut.putNextEntry(zipEntry);
		zipOut.closeEntry();				
	   }
 
	   //如果有子文件, 遍历子文件
	   for (int i = 0; i < fileList.length; i++) {
		zipFiles(folderPath, filePath+java.io.File.separator+fileList[i], zipOut);
	   }//end of for
 
         }//end of if
 
     }//end of func
}

文件下载 : ZipUtil.java.zip

返回 : Android开发博文汇总

Android用Paint计算文字宽度

时间:2011年07月01日作者:么吉查看次数:246 views评论次数:0

  在Android的UI开发中,如果使用xml的方式来配置layout的话,我们可以很方便地实现不同分辨率的兼容性问题(使用dip的单位来实现).但是,如果在游戏游戏的时候,不能用xml来配置layout,这时候我们去兼容不同分辨率时就会麻烦很多.一些坐标计算也可以将dip的单位改成像素.我在开发打地鼠的游戏时,发现文字不好计算出宽度,经过一段时间的上网查找,终于找到了解决方案.

1
2
3
4
5
6
7
// 计算出该TextView中文字的长度(像素)
public static float getTextViewLength(TextView textView,String text){
  TextPaint paint = textView.getPaint();
  // 得到使用该paint写上text的时候,像素为多少
  float textLength = paint.measureText(text);
  return textLength;
}

  通过上面的代码我们得到文字的像素后,我们可以通过一些算法得计算得到该文字的 dip 单位的值.

关于dip(dp)与px的转换方法可以看我另一篇博文.
Android中dip(dp)与px之间单位转换

返回 : Android开发博文汇总

Android根据imsi判断是否是中国用户(判断所有地)

时间:2011年06月23日作者:ronald查看次数:172 views评论次数:0

  以下的方法可以 Android 用户判断是否在中国.先上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
 * 获取SIM卡的IMSI码 SIM卡唯一标识:IMSI 国际移动用户识别码
 * (IMSI:International Mobile Subscriber Identification Number)是区别移动用户的标志,
 * 储存在SIM卡中,可用于区别移动用户的有效信息。
 * IMSI由MCC、MNC、MSIN组成,其中MCC为移动国家号码,由3位数字组成,
 * 唯一地识别移动客户所属的国家,我国为460;MNC为网络id,由2位数字组成,
 * 用于识别移动客户所归属的移动网络,中国移动为00,中国联通为01,
 * 中国电信为03;MSIN为移动客户识别码,采用等长11位数字构成。
 * 唯一地识别国内GSM移动通信网中移动客户。
 * 所以要区分是移动还是联通,只需取得SIM卡中的MNC字段即可
 */
public static TelecomProvider isInChina(Context context) {
	String imsi = getIMSI(context);
	if (imsi != null) {
                // 因为移动网络编号46000下的IMSI已经用完,
                // 所以虚拟了一个46002编号,134/159号段使用了此编号 
		if (imsi.startsWith("46000") || imsi.startsWith("46002")) {
			// 中国移动
			return TelecomProvider.CHINA;
		}
		if (imsi.startsWith("46001")) {
			// 中国联通
			return TelecomProvider.CHINA;
		}
		if (imsi.startsWith("46003")) {
			// 中国电信
			return TelecomProvider.CHINA;
		}
 
		// 香港
		if (imsi.startsWith("454")) {
			return TelecomProvider.HONGKONG;
		}
 
		return TelecomProvider.OTHERS;
	}
 
	return TelecomProvider.UNKNOW;
}
 
public static String getIMSI(Context context){
	TelephonyManager ts = (TelephonyManager) 
                      context.getSystemService(Context.TELEPHONY_SERVICE);
	return ts.getSubscriberId();
}

文件 : TelecomProvider.java ,定义提供商的所在地

1
2
3
public enum TelecomProvider {
	CHINA, HONGKONG, OTHERS, UNKNOW;
}

判断是否在中国,我们可以通过IMSI的信息得到用户的服务商的信息,这样就能判断. 如果没有插入SIM卡的情况下,是不能判断所在地的.

Android读取网络配置(服务器配置)

时间:2011年06月23日作者:ronald查看次数:239 views评论次数:0

  有很多时候,我们的应用和游戏需要从通过网络读取配置文件,文件一般是properties或xml. 我推荐使用properties的方式进行配置,读取文件很简单,不需要像xml那样,需要分析xml.

那什么情况下我们需要从服务器中读取配置文件呢?
1 ,根据网络的配置情况显示广告类型.因为当Android应用或游戏发布后,我们不能再修改应用中的配置,这时候我们只能从网络读取配置文件.
2 ,应用或游戏的版本信息.判断是否有新版本.如果有新版本,提示下载新版本.
3 ,应用事游戏的最新通知,新闻等等.

以上只是一般的情况,那当然还有很多的情况需要读取网络配置文件的.

当然,如果我们需要从网络读取配置文件的,需要服务器(能下载文件的服务)的支持.

  以下是我的[ 足球即时比分] 应用中的一些代码.本人简单地封装了一些基数,提高重用性,方便其它应用或游戏再次使用.本代码是每天下载只会下载一次.每天生成一个文件,如果文件存在则不会从网络读取配置文件了.

以下是文件 AbstractNetConfigManager.java,是网络配置管理类的基类,提供主要的逻辑.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package rbase.android.core.netconfig;
 
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Date;
import java.util.Properties;
 
import rbase.android.core.util.DateTimeUtil;
import rbase.android.core.util.IOUtil;
import android.content.Context;
import android.util.Log;
 
/**
 * 抽象的网络配置管理类,用于当配置文件是一个URL的时候
 * www.r-base.net by ronald
 */
public abstract class AbstractNetConfigManager<T> {
	protected boolean hasInit = false;
	protected T info;
 
	/**
	 * 得到配置属性,当没有加载时,去网络读取文件进行加载.
	 * 
	 * @param context
	 * @return
	 */
	public T getNetConfigInfo(Context context) {
		if (hasInit && info != null) {
			return info;
		}
 
		// 进行初始化
		init(context);
		if(info == null){
			return getDefaultNetConfig();
		}
		this.hasInit = true;
		return info;
	}
 
	/**
	 * 得到配置属性,当没有加载时,读取本地的默认配置
	 * 
	 * @param context
	 * @return
	 */
	public T getNetConfigInfoNotDownload(Context context) {
		if(!hasInit && info == null){
			File configFile = getFileName(context);
			if(configFile.exists()){
				info = readByFile(context, configFile);
			}
			if(info == null){
				info = getDefaultNetConfig();
			}
		}
		if(info == null){
			return getDefaultNetConfig();
		}
 
		return info;
	}
 
	/**
	 * 生成配置文件的目录(文件名)
	 * 
	 * @param context
	 * @return
	 */
	private static File getFileName(Context context) {
		String fmtDate = DateTimeUtil.formatDate(new Date());
	        return new File(context.getFilesDir(), fmtDate + "_net-config.properties");
	}
 
	/**
	 * 从网络下载配置文件
	 * 
	 * @param context
	 */
	private void downloadPropertiesFromNet(Context context) {
		Log.i("www.r-base.net", "down net config from net,url:" + getNetConfigUrl());
		FileOutputStream out = null;
		InputStream input = null;
		try {
			out = new FileOutputStream(getFileName(context));
			input = new URL(getNetConfigUrl()).openStream();
			IOUtil.copy(input, out);
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			IOUtil.close(out);
			IOUtil.close(input);
		}
	}
 
	/**
	 * 初始化
	 * 
	 * @param context
	 */
	public void init(Context context) {
		// 判断是否需要从网络中下载xml文件
		if (isNeedToUpdateFromNet(context)) {
			downloadPropertiesFromNet(context);
		}
 
		// 得到生成的文件目录
		File file = getFileName(context);
		// 如果文件不存在,可能是下载文件问题,网络问题
		if(!file.exists()){
			return;
		}
 
		// 读取刚刚下载的文件
		readByFile(context,file);
	}
 
	private T readByFile(Context context,File file){
		FileInputStream input = null;
		try {
			input = new FileInputStream(file);
			Properties p = new Properties();
			p.load(input);
			info = readNetConfigByProp(context,p);
			hasInit = true;
		} catch (IOException e) {
			e.printStackTrace();
			if(file.exists() && file.canWrite()){
				file.delete();
			}
			return info;
		} finally{
			IOUtil.close(input);
		}
		return null;
	}
 
	/**
	 * 是否需要从网络下载配置文件
	 */
	private static boolean isNeedToUpdateFromNet(Context context) {
		// 得到手机中的文件目录
		File file = getFileName(context);
		boolean needUpdateFromNet = !file.exists();
		Log.i("www.r-base.net", "need update site info from net : " + 
                         needUpdateFromNet+", file path : "+file.getAbsolutePath());
		return needUpdateFromNet;
	}
 
	public abstract String getNetConfigUrl();
 
	public abstract T getDefaultNetConfig();
 
	public abstract T readNetConfigByProp(Context context, Properties p);
}

以下是使用网络配置的具体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
 * 网络配置类
 * www.r-base.net by ronald
 */
public class NetConfigInfo{
  public String configValue = "1"; // 默认值为1
}
 
 
/**
 * 网络配置的实现类(具体类)
 * www.r-base.net by ronald
 */
public class NetConfigManager extends AbstractNetConfigManager<NetConfigInfo>{
	private static final String NET_CONFIG_URL = "http://XX/net-config.properties";
	private static final NetConfigManager instance = new NetConfigManager();
 
	private NetConfigManager() {
	}
 
	public static final NetConfigManager getInstance() {
		return instance;
	}
 
        // 返回默认的配置类,当网络不能正常访问时会返回网络配置类
	public NetConfigInfo getDefaultNetConfig() {
		return new NetConfigInfo();
	}
 
        // 网络配置文件的URL
	public String getNetConfigUrl() {
		return NET_CONFIG_URL;
	}
 
        // 最重要的方法,读取文件的实现方法.
	public NetConfigInfo readNetConfigByProp(Context context, Properties p) {
		NetConfigInfo info = new NetConfigInfo();
		info.configValue = p.getProperty("configValue", "1");
 
		return info;
	}
}

相关资源 :
Android上传文件到服务器 : http://www.r-base.net/archives/360

返回 : Android开发博文汇总

Android中使用Ant打包(混淆打包)

时间:2011年06月11日作者:ronald查看次数:589 views评论次数:0

有时候为了保护代码不被非法查看或使用时,我们都会对应用或游戏的代码进行混淆打包,就是将类名和方法名换成abcde等没有意义和很难阅读的名字. 像如下图片中的效果,但是有些是不能混淆的.因为配置文件中使用了.

将应用中的所有类变成a,b,c,d,e等等的类,方法名也全部变成a,b,c,d,e的,让反编译出来的全部很难阅读.

需要实现这些效果一般要用于以下的几个

上图中的目录与文件说明 :
proguard4.5.1 : 该目录是proguard4.5.1版本的所需文件,就是使用它来实现混淆效果的
build.properties : 该文件定义使用ant打包打包的配置,如生成的apk包的文件名,jdk目录,android-sdk目录等
build.xml : Ant的配置文件,一般不需要修改,直接执行 “_buildAPK(打包)” 任务来打包,apk包默认放在android-pub目录下
keystore/rbase.keystore : keystore签名文件,如果要上传到google market的话,该文件是必须.因为我们打出来的包都已经进行签名的.
p.properties : 配置keystore的密码(password=xxxx),keystore签名时要使用的
proguard.cfg : 混淆的配置文件,定义了哪些类或方法要进行混淆.因为有一些类是不能混淆的,AndroidManifest.xml需要定义的都不能进行混淆.

——————————————————–
以下是一些重要的文件内容,便于大家直接查看

build.properties文件内容

## 打包后的文件名,该文件将放在项目的根目录下的android-pub目录下
app.name=RB-HitShrew-0.9.9-beta
## 进行操作的包名
application.package=rbase.app.hitshrew
 
sdk.folder=${eclipse.home}/android-sdk-windows/platforms/android-4/
eclipse.home=e\:/android/eclipse-3.6.0
android.tools=${sdk.folder}tools
android_version=1.6
apk.sdk.home=${eclipse.home}/android-sdk-windows-4/
apk.tools=${apk.sdk.home}tools/
bin.dir=${jde.home}bin
app.source.path=d\:/test/
jdk.home=${eclipse.home}/jdk1.6.0_20
output.dir=android-pub
proguard-home=proguard4.5.1/lib
## 指定keystore的文件路径
keystore.file=keystore/rbase.keystore
##  指定制作keystore时的应用名字
keystore.name=myapp.keystore

以下是 proguard.cfg 文件中的内容

-optimizationpasses 5
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-dontpreverify
-verbose
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
 
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class com.android.vending.licensing.ILicensingService
 
-keepclasseswithmembernames class * {
    native <methods>;
}
 
-keepclasseswithmembernames class * {
    public <init>(android.content.Context, android.util.AttributeSet);
}
 
-keepclasseswithmembernames class * {
    public <init>(android.content.Context, android.util.AttributeSet, int);
}
 
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}
 
-keep class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator *;
}

以下是p.properties的文件内容

## keystore的密码,制作keystore文件时输入的密码
password=xxxxxx

以下是build.xml中的内容,是使用Ant打包中最重要的文件

<?xml version="1.0" encoding="UTF-8"?>
<project name="hit-shrew" default="debug" basedir="." >
	<property file="build.properties" />
	<property file="p.properties" />
 
	<!-- The intermediates directory -->
	<!-- Eclipse uses "bin" for its own output, so we do the same. -->
	<property name="outdir" value="target" />
 
	<!-- ************************************************************************************* -->
	<!-- No user servicable parts below. -->
 
	<property name="android-framework" value="${sdk.folder}/framework.aidl" />
 
	<!-- Input directories -->
	<property name="resource-dir" value="res" />
	<property name="asset-dir" value="assets" />
	<property name="srcdir" value="src" />
	<condition property="srcdir-ospath" value="${basedir}\${srcdir}" else="${basedir}/${srcdir}">
		<os family="windows" />
	</condition>
 
	<property name="external-libs" value="lib" />
	<condition property="external-libs-ospath" 
              value="${basedir}\${external-libs}" 
              else="${basedir}/${external-libs}">
		<os family="windows" />
	</condition>
 
	<!-- Output directories -->
	<property name="outdir-classes" value="${outdir}/classes" />
	<condition property="outdir-classes-ospath" value="${basedir}\${outdir-classes}" 
                else="${basedir}/${outdir-classes}">
		<os family="windows" />
	</condition>
 
	<condition property="zipalign-package-ospath" 
             value="${basedir}\${output.dir}\${app.name}.apk" else="${basedir}/${output.dir}">
		<os family="windows" />
	</condition>
	<condition property="zipalign-package-ospath-test" 
             value="${basedir}\${output.dir}\${app.name}-test.apk" else="${basedir}/${output.dir}">
		<os family="windows" />
	</condition>
 
	<!-- Create R.java in the source directory -->
	<property name="outdir-r" value="gen" />
 
	<!-- Intermediate files -->
	<property name="dex-file" value="classes.dex" />
	<property name="intermediate-dex" value="${outdir}/${dex-file}" />
	<condition property="intermediate-dex-ospath" 
              value="${basedir}\${intermediate-dex}" 
              else="${basedir}/${intermediate-dex}">
		<os family="windows" />
	</condition>
 
	<!-- The final package file to generate -->
	<property name="resources-package" value="${outdir}/${ant.project.name}.apk" />
	<condition property="resources-package-ospath" 
              value="${basedir}\${resources-package}" 
              else="${basedir}/${resources-package}">
		<os family="windows" />
	</condition>
 
	<property name="out-debug-package" value="${outdir}/${ant.project.name}-debug.apk" />
	<condition property="out-debug-package-ospath" 
              value="${basedir}\${out-debug-package}" 
              else="${basedir}/${out-debug-package}">
		<os family="windows" />
	</condition>
 
	<property name="out-unsigned-package" value="${outdir}/${ant.project.name}-unsigned.apk" />
	<property name="out-signed-package" value="${outdir}/${ant.project.name}-signed.apk" />
	<condition property="out-unsigned-package-ospath" 
              value="${basedir}\${out-unsigned-package}" 
              else="${basedir}/${out-unsigned-package}">
		<os family="windows" />
	</condition>
	<condition property="out-signed-package-ospath" 
               value="${basedir}\${out-signed-package}" 
               else="${basedir}/${out-signed-package}">
		<os family="windows" />
	</condition>
 
	<!-- Tools -->
	<condition property="aapt" value="${android.tools}/aapt.exe" else="${android.tools}/aapt">
		<os family="windows" />
	</condition>
	<condition property="zipalign" value="${apk.tools}/zipalign.exe" else="${apk.tools}/zipalign">
		<os family="windows" />
	</condition>
	<condition property="jarsigner" value="${jdk.home}/bin/jarsigner.exe" 
                else="${jdk.home}/bin/jarsigner">
		<os family="windows" />
	</condition>
	<condition property="aidl" value="${android.tools}/aidl.exe" else="${android.tools}/aidl">
		<os family="windows" />
	</condition>
	<condition property="adb" value="${apk.tools}/adb.exe" else="${apk.tools}/adb">
		<os family="windows" />
	</condition>
	<condition property="dx" value="${android.tools}/dx.bat" else="${android.tools}/dx">
		<os family="windows" />
	</condition>
	<condition property="apk-builder" value="${apk.tools}/apkbuilder.bat" 
                else="${apk.tools}/apkbuilder">
		<os family="windows" />
	</condition>
 
	<property name="android-jar" value="${sdk.folder}/android.jar" />
 
	<target name="_buildAPK(打包)" description="标准的android打包方法" 
              depends="compile,dex, package-res,release,jarsigner,zipalign">
	</target>
	<target name="_buildAPK-no-asserts(打包)" description="不打包asserts目录" 
             depends="compile,dex, package-res-no-assets,release,jarsigner,zipalign">
	</target>
	<target name="_buildAPK-no-asserts-test(打包)" 
             depends="compile,dex, package-res-no-assets,release,jarsigner,zipalign-test">
	</target>
 
	<target name="_buildAPK(模糊打包)" 
           depends="compile-optimize,dex, package-res,release,jarsigner,zipalign">
	</target>
 
	<!-- Create the output directories if they don't exist yet. -->
	<target name="dirs" depends="init">
		<echo>Creating output directories if needed...</echo>
		<delete dir="${outdir-classes}" />
		<mkdir dir="${outdir}" />
		<mkdir dir="${outdir-classes}" />
	</target>
 
	<!-- Generate the R.java file for this project's resources. -->
	<target name="resource-src" depends="dirs">
		<echo>Generating R.java / Manifest.java from the resources...</echo>
		<exec executable="${aapt}" failonerror="true">
			<arg value="package" />
			<arg value="-m" />
			<arg value="-J" />
			<arg value="${outdir-r}" />
			<arg value="-M" />
			<arg value="AndroidManifest.xml" />
			<arg value="-S" />
			<arg value="${resource-dir}" />
			<arg value="-I" />
			<arg value="${android-jar}" />
		</exec>
	</target>
 
	<!-- Generate java classes from .aidl files. -->
	<target name="aidl" depends="dirs">
		<echo>Compiling aidl files into Java classes...</echo>
		<apply executable="${aidl}" failonerror="true">
			<arg value="-p${android-framework}" />
			<arg value="-I${srcdir}" />
			<fileset dir="${srcdir}">
				<include name="**/*.aidl" />
			</fileset>
		</apply>
	</target>
 
	<!-- Compile this project's .java files into .class files. -->
	<target name="compile" depends="dirs, resource-src, aidl">
		<javac encoding="utf-8" target="1.6" debug="true" extdirs="" srcdir="gen" 
                      destdir="${outdir-classes}" bootclasspath="${android-jar}">
			<classpath>
				<fileset dir="${external-libs}" includes="*.jar" />
			</classpath>
		</javac>
		<javac encoding="utf-8" target="1.6" debug="true" extdirs="" srcdir="src" 
                      destdir="${outdir-classes}" bootclasspath="${android-jar}">
			<classpath>
				<fileset dir="${external-libs}" includes="*.jar" />
			</classpath>
		</javac>
	</target>
 
	<!-- Compile this project's .java files into .class files. -->
	<target name="compile-optimize" depends="compile">
		<antcall target="optimize" />
	</target>
 
	<!-- Convert this project's .class files into .dex files. -->
	<target name="dex">
		<echo>Converting compiled files and external libraries into ${outdir}/${dex-file}...</echo>
		<apply executable="${dx}" failonerror="true" parallel="true">
			<arg value="--dex" />
			<arg value="--output=${intermediate-dex-ospath}" />
			<arg path="${outdir-classes-ospath}" />
			<fileset dir="${external-libs}" includes="*.jar" />
		</apply>
	</target>
 
	<!-- Put the project's resources into the output package file. -->
	<target name="package-res-and-assets">
		<echo>Packaging resources and assets...</echo>
		<exec executable="${aapt}" failonerror="true">
			<arg value="package" />
			<arg value="-f" />
			<arg value="-M" />
			<arg value="AndroidManifest.xml" />
			<arg value="-S" />
			<arg value="${resource-dir}" />
			<arg value="-A" />
			<arg value="${asset-dir}" />
			<arg value="-I" />
			<arg value="${android-jar}" />
			<arg value="-F" />
			<arg value="${resources-package}" />
		</exec>
	</target>
 
	<!-- Same as package-res-and-assets, but without "-A ${asset-dir}" -->
	<target name="package-res-no-assets">
		<echo>Packaging resources...</echo>
		<exec executable="${aapt}" failonerror="true">
			<arg value="package" />
			<arg value="-f" />
			<arg value="-M" />
			<arg value="AndroidManifest.xml" />
			<arg value="-S" />
			<arg value="${resource-dir}" />
			<!-- No assets directory -->
			<arg value="-I" />
			<arg value="${android-jar}" />
			<arg value="-F" />
			<arg value="${resources-package}" />
		</exec>
	</target>
 
	<!-- Invoke the proper target depending on whether or not
	         an assets directory is present. -->
	<!-- TODO: find a nicer way to include the "-A ${asset-dir}" argument
	         only when the assets dir exists. -->
	<target name="package-res">
		<available file="${asset-dir}" type="dir" property="res-target" value="and-assets" />
		<property name="res-target" value="no-assets" />
		<antcall target="package-res-${res-target}" />
	</target>
 
	<!-- Package the application and sign it with a debug key.
			 This is the default target when building. It is used for debug. -->
	<target name="debug" depends="dex, package-res">
		<echo>Packaging ${out-debug-package}, and signing it with a debug key...</echo>
		<exec executable="${apk-builder}" failonerror="true">
			<arg value="${out-debug-package-ospath}" />
			<arg value="-z" />
			<arg value="${resources-package-ospath}" />
			<arg value="-f" />
			<arg value="${intermediate-dex-ospath}" />
			<arg value="-rf" />
			<arg value="${srcdir-ospath}" />
			<arg value="-rj" />
			<arg value="${external-libs-ospath}" />
		</exec>
	</target>
 
	<!-- Package the application without signing it.
	    	 This allows for the application to be signed later with an official publishing key. -->
	<target name="release">
		<echo>Packaging ${out-unsigned-package} for release...</echo>
		<exec executable="${apk-builder}" failonerror="true">
			<arg value="${out-unsigned-package-ospath}" />
			<arg value="-u" />
			<arg value="-z" />
			<arg value="${resources-package-ospath}" />
			<arg value="-f" />
			<arg value="${intermediate-dex-ospath}" />
			<arg value="-rf" />
			<arg value="${srcdir-ospath}" />
			<arg value="-rj" />
			<arg value="${external-libs-ospath}" />
		</exec>
		<echo>It will need to be signed with jarsigner before being published.</echo>
	</target>
 
	<!-- Install the package on the default emulator -->
	<target name="install" depends="debug">
		<echo>Installing ${out-debug-package} onto default emulator...</echo>
		<exec executable="${adb}" failonerror="true">
			<arg value="install" />
			<arg value="${out-debug-package}" />
		</exec>
	</target>
 
	<target name="reinstall" depends="debug">
		<echo>Installing ${out-debug-package} onto default emulator...</echo>
		<exec executable="${adb}" failonerror="true">
			<arg value="install" />
			<arg value="-r" />
			<arg value="${out-debug-package}" />
		</exec>
	</target>
 
	<!-- Uinstall the package from the default emulator -->
	<target name="uninstall">
		<echo>Uninstalling ${application.package} from the default emulator...</echo>
		<exec executable="${adb}" failonerror="true">
			<arg value="uninstall" />
			<arg value="${application.package}" />
		</exec>
	</target>
 
	<!--初始化目录-->
	<target name="init" depends="Copy_Ressource">
		<echo message="Init output directory.....">
		</echo>
		<mkdir dir="${output.dir}" />
	</target>
	<!--拷贝资源-->
	<target name="Copy_Ressource">
		<echo message="Copy app resource. ">
		</echo>
		<copy todir="${asset-dir}" overwrite="true" failonerror="false">
			<fileset dir="${app.source.path}">
				<include name="*.*" />
			</fileset>
		</copy>
 
	</target>
 
	<!--进行签名-->
	<target name="jarsigner">
		<exec executable="${jarsigner}" failonerror="true">
			<arg value="-verbose" />
			<arg value="-storepass" />
			<arg value="${password}" />
			<arg value="-keystore" />
			<arg value="${keystore.file}" />
			<arg value="-signedjar" />
			<arg value="${out-signed-package-ospath}" />
			<arg value="${out-unsigned-package-ospath}" />
			<arg value="${keystore.name}" />
		</exec>
	</target>
	<!--进行优化-->
	<target name="zipalign">
		<exec executable="${zipalign}" failonerror="true">
			<arg value="-v" />
			<arg value="-f" />
			<arg value="4" />
			<arg value="${out-signed-package-ospath}" />
			<arg value="${zipalign-package-ospath}" />
		</exec>
	</target>
	<!--进行优化-->
	<target name="zipalign-test">
		<exec executable="${zipalign}" failonerror="true">
			<arg value="-v" />
			<arg value="-f" />
			<arg value="4" />
			<arg value="${out-signed-package-ospath}" />
			<arg value="${zipalign-package-ospath-test}" />
		</exec>
	</target>
 
	<!--直接上传到手机中去-->
	<target name="adb" depends="zipalign">
			<exec executable="${adb}" failonerror="true">
				<arg value="install" />
				<arg value="-r" />
				<arg value="${zipalign-package-ospath}" />
			</exec>
		</target>
 
	<!--Execute proguard class flies-->
	<target name="optimize">
		<jar basedir="${outdir-classes}" destfile="temp.jar"/>
		<java jar="${proguard-home}/proguard.jar" fork="true" failonerror="true">
			<jvmarg value="-Dmaximum.inlined.code.length=32"/>
			<arg value="-injars temp.jar"/>
			<arg value="-outjars optimized.jar"/>
			<arg value="-libraryjars ${android-jar}"/>
 
			<arg value="-libraryjars ${external-libs}/"/>
			<arg value="-dontpreverify"/>
			<arg value="-dontoptimize"/>
			<arg value="-dontusemixedcaseclassnames"/>
			<arg value="-repackageclasses ''"/>
			<arg value="-allowaccessmodification"/>
			<!--<arg value="-keep public class ${exclude-activity}"/>-->
			<!--<arg value="-keep public class ${exclude-provider}"/>-->
 
			<arg value="@proguard.cfg"/>
			<arg value="-optimizationpasses 7"/>
			<arg value="-verbose"/>
			<arg value="-dontskipnonpubliclibraryclasses"/>
			<arg value="-dontskipnonpubliclibraryclassmembers"/>
		</java>
		<delete file="temp.jar"/>
		<delete dir="${outdir-classes}"/>
		<mkdir dir="${outdir-classes}"/>
		<unzip src="optimized.jar" dest="${outdir-classes}"/>
		<delete file="optimized.jar"/>
	</target>
</project>

—————————— 相关资源 ———————————–

相关资源下载与使用方法说明 :
以上的所有目录与文件都可以在这里下载. 我已经将它打成压缩包了.
Android使用Ant打包(混淆).zip

1 : 不进行签名使用:
一般只需要修改build.properties就以使用.

2 : 进行签名使用方法 :
1, 修改build.properties就能使用.
2, 覆盖rbase.keystore,当然也可以修改,修改build.properties中的keystore.file与keystore.name的配置
3, 修改p.properties,改成生成keystore时定义的密码
(以上资源的rbase.keystore是我的测试keystore,如果大家不想制作keystore也直接使用)

其它相关博文:
如何生成keystore,可以查看我另一篇博文 制作keystore用于android打包

返回 : Android开发博文汇总