日志分类: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有未接来电后处理(判断未接来电)

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

  在Android的手机状态中没有未接来电的监听器,所以如果想当手机有未接来电后进行处理,这时候就需要自己对手机的状态判断是未接来电后再进行处理.

实现思路 :
1,继承PhoneStateListener后,当手机的状态改变后将会触发onCallStateChanged.手机的状态分为CALL_STATE_RINGING(响铃中),CALL_STATE_IDLE(空闲),CALL_STATE_OFFHOOK(忙音).
2,记录上一次的手机状态,如果的手机现在的空闲,上次的状态响铃中的话,就可以判断是未接来电.

不足:
1,我现在的处理不能判断出是用户是否主动不接电话.

实现步骤:
1,编写CallListener,处理手机状态变更监听,当状态改变时进行处理。如果想知道如何在Android发送短信可以看我另一博文[ Android中发送短信(sms) ]

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
package rbase.app.smshelpmate.call.listener;
 
import java.text.MessageFormat;
 
import rbase.app.smshelpmate.Config;
import rbase.app.smshelpmate.R;
import rbase.app.smshelpmate.call.enums.CallStateEnum;
import rbase.app.smshelpmate.forward.ForwardManager;
import rbase.app.smshelpmate.forward.enums.ForwardType;
import rbase.app.smshelpmate.forward.vo.ForwardParam;
import android.content.Context;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.util.Log;
 
/**
 * @author www.r-base.net
 */
public class CallListener extends PhoneStateListener {
	private static final String TAG = "sms";
	private static int lastetState = TelephonyManager.CALL_STATE_IDLE; // 最后的状态
	private Context context;
 
	public CallListener(Context context) {
		super();
		this.context = context;
	}
 
	public void onCallStateChanged(int state, String incomingNumber) {
		Log.v(TAG, "CallListener call state changed : " + incomingNumber);
		String m = null;
 
		// 如果当前状态为空闲,上次状态为响铃中的话,则破觚为认为是未接来电
		if(lastetState ==  TelephonyManager.CALL_STATE_RINGING 
		        && state == TelephonyManager.CALL_STATE_IDLE){
			sendSmgWhenMissedCall(incomingNumber);
		}
 
		// 最后的时候改变当前值
		lastetState = state;
	}
 
	private void sendSmgWhenMissedCall(String incomingNumber) {
	     // ... 进行未接来电处理(发短信,发email等等通知)
	}
}

2,编写CallReceiver,注册来电广播接收器。

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
package rbase.app.smshelpmate.call.service;
 
import rbase.app.smshelpmate.Const;
import rbase.app.smshelpmate.call.listener.CallListener;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.util.Log;
 
/**
 * @author www.r-base.net
 */
public class CallReceiver extends BroadcastReceiver{
	public void onReceive(Context context, Intent intent) {
		Log.i("sms", "CallReceiver Start...");
		TelephonyManager telephony = (TelephonyManager) context
				.getSystemService(Context.TELEPHONY_SERVICE);
		CallListener customPhoneListener = new CallListener(context);
 
		telephony.listen(customPhoneListener,
				PhoneStateListener.LISTEN_CALL_STATE);
 
		Bundle bundle = intent.getExtras();
		String phoneNr = bundle.getString("incoming_number");
		Log.i("sms", "CallReceiver Phone Number : " + phoneNr);
	}
}

3,在AndroidManifest.xml中的application节点下添加如下代码.进行注册电话状态改变广播接收.

1
2
3
4
5
6
7
8
9
<manifest ...>
  <application ...>
    <receiver android:name=".call.service.CallReceiver">
	    <intent-filter android:priority="100">
		    <action android:name="android.intent.action.PHONE_STATE" />
	    </intent-filter>
    </receiver>
  </application>
</manifest>

4,在AndroidManifest.xml中添加读取手机状态的权限.

1
<uses-permission android:name="android.permission.READ_PHONE_STATE" />

总结:
  通过以上的步骤,当手机有未接来电时 sendSmgWhenMissedCall 该方法就会触发,就可以进行相应的处理.本人的其中一个应用(RBase短信助手) 就是用以上的代码实现了当手机有未接来电后可以发短信给指定的手机进行通知. 想了解的朋友可以到这里下载 : http://www.goapk.com/pkg/rbase.app.smshelpmate

相关博文:
Android中发送短信(sms)
Android文章汇总

Android中发送短信(sms)

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

  分享一下本人中其中一个应用(手机短信转发)的代码,主要是实现短信的发送功能。
  如果想做一些短信相关的应用,以下的代码可能对你有帮助。
相关的软件可以访问我发布在goapk里的页面 : http://www.goapk.com/pkg/rbase.app.smshelpmate

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
package rbase.app.smshelpmate.sms.util;
 
import java.util.ArrayList;
 
import rbase.android.core.util.StringUtils;
import rbase.android.core.util.UIUtil;
import rbase.app.smshelpmate.R;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.telephony.SmsManager;
import android.util.Log;
 
/**
 * SMS 的工具类,主要是发送短信的辅助
 * 
 * @author r-base.net
 */
public class SmsUtil {
 
	public static void sendSMS(Context ctx, String phoneNumber, String message) {
		sendSMS(ctx, phoneNumber, message, null);
	}
 
	/**
	 * 发送sms
	 * 
	 * @param ctx Android 的 Context
	 * @param phoneNumber 发送的手机号码
	 * @param message 发送短信的内容
	 * @param toastText 发送后显示的toast内容
	 */
	public static void sendSMS(Context ctx, String phoneNumber, String message,String toastText) {
		Log.d("sms", "sending sms");
 
		// 检查手机号和转发内容,主要是检查手机号码与发送内容的合法性
		if(!checkMessage(ctx, phoneNumber, message)){
			return;
		}
 
		// 得到SMS的管理类
		SmsManager smsMgr = SmsManager.getDefault();
 
		// 如果大于70个字符需要分割成多条短信发送
		if (message.length() > 70) {
			ArrayList<String> msgs = smsMgr.divideMessage(message);
			for (String msg : msgs) {
				smsMgr.sendTextMessage(phoneNumber, null, msg, null, null);
			}
		} else {
			smsMgr.sendTextMessage(phoneNumber, null, message, null, null);
		}
 
		if(toastText == null){
			UIUtil.showMessage(ctx, "forward sms to "+ phoneNumber);
		}else{
			UIUtil.showMessage(ctx, toastText);
		}
	}
 
        // 检查合法性
	private static boolean checkMessage(Context ctx,String phoneNumber,String message){
		if(StringUtils.isEmptyTrim(phoneNumber)){
			UIUtil.showMessage(ctx, R.string.error_forward_phone_is_empty);
			Log.d("sms", "Forward Failure! Forward phone is empty!");
			return false;
		}
 
		if(StringUtils.isEmptyTrim(message)){
			UIUtil.showMessage(ctx, R.string.error_forward_message_is_empty);
			return false;
		}
 
		return true;
	}
}

注:需要添加以下的权限。

	<uses-permission android:name="android.permission.RECEIVE_SMS"></uses-permission>
	<uses-permission android:name="android.permission.SEND_SMS"></uses-permission>

TabHost中在不同Tab中显示不同的Menu(菜单)

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

  使用TabHost后,我们很多时候都有在不同的Tab中显示不同的菜单.
以下的是我足球即时比分中的一些截图(Tab为关注时显示的菜单).

以下是实现以上功能的主要代码片段(该代码是本人的应用足球即时比分的代码片段):

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
public class SenseSoccerScoreActivity extends Activity{
	public static final int MENU_SETUP_ID = 1;
	public static final int MENU_ABOUT_ID = 2;
	public static final int MENU_LEAVE_ID = 3;
	public static final int MENU_CHECK_UPDATE_ID = 5;
	public static final int MENU_EMPTY_FOCUS_ID = 4;
	public static final int MENU_HISTORY_MATCH = 6;
	public static final int MENU_RETURN = 7;
	public static final int MENU_FILTER_SETTING = 8;
	public static final int MENU_FUTURE_MATCH = 9;
	public static final int MENU_BACK_ID = 10;
	public static final int MENU_REFRESH_ID = 11;
	public static final int MENU_CLEAN_CACHE = 12;
 
        // ... 其它代码.
	/**
	 * 当点击menu按钮时,添加菜单
	 * 之前一般是覆盖 onCreateOptionsMenu 方法的,现在要覆盖 onPrepareOptionsMenu
	 */
    public boolean onPrepareOptionsMenu(Menu menu) {
		// *** 这里是实现的主要代码,先要清空菜单,然后再重新添加菜单
    	menu.clear(); // 清空menu
    	super.onPrepareOptionsMenu(menu);
 
		TabHost th = (TabHost) findViewById(R.id.tabhost);
 
		// 如果是关注赛事列表,需要添加清空菜单
		if(th.getCurrentTab() == 3){
			menu.add(0, MENU_SETUP_ID, 1, R.string.settingMenu)
                                .setIcon(android.R.drawable.ic_menu_preferences);
			menu.add(0, MENU_EMPTY_FOCUS_ID, 2, R.string.clearFocusMenu)
                                .setIcon(android.R.drawable.ic_menu_delete);
			menu.add(0, MENU_CHECK_UPDATE_ID, 3, R.string.text_check_update)
                                .setIcon(android.R.drawable.ic_menu_search);
			menu.add(0, MENU_ABOUT_ID, 4, R.string.aboutMenu)
                                .setIcon(android.R.drawable.ic_menu_help);
			menu.add(0, MENU_HISTORY_MATCH, 5, R.string.fullTimeMatch)
                                .setIcon(android.R.drawable.ic_menu_recent_history);
			menu.add(0, MENU_LEAVE_ID, 6, R.string.leaveMenu)
                                .setIcon(android.R.drawable.ic_menu_close_clear_cancel);
			return true;
		} else {
			menu.add(0, MENU_SETUP_ID, 1, R.string.settingMenu)
                               .setIcon(android.R.drawable.ic_menu_preferences);
			menu.add(0, MENU_CHECK_UPDATE_ID, 3, R.string.text_check_update)
                               .setIcon(android.R.drawable.ic_menu_search);
			menu.add(0, MENU_ABOUT_ID, 4, R.string.aboutMenu)
                               .setIcon(android.R.drawable.ic_menu_help);
			menu.add(0, MENU_HISTORY_MATCH, 5, R.string.fullTimeMatch)
                               .setIcon(android.R.drawable.ic_menu_recent_history);
			menu.add(0, MENU_LEAVE_ID, 7, R.string.leaveMenu)
                               .setIcon(android.R.drawable.ic_menu_close_clear_cancel);
			return true;
		}
    }
 
	/**
	 * 当点击相应的菜单后,执行响应的事件
	 */
	public boolean onOptionsItemSelected(MenuItem item){
		Intent intent = null;
        switch (item.getItemId()){
        	// 设置
        case MENU_SETUP_ID:
        	intent = new Intent();  
        	intent.setClass(this, SettingActivity.class);
            this.startActivity(intent);  
            break;
 
            // 清空关注赛事列表
        case MENU_EMPTY_FOCUS_ID:
        	MatchManager.getInstance(this).clearFocus();
        	updateFocusMatchUI();
            break;
 
            // 检查更新
        case MENU_CHECK_UPDATE_ID:
            ProgressDialog checkUpdateDialog = buildCheckUpdateView();
            checkUpdateDialog.show();
            break;
 
            // 关于
        case MENU_ABOUT_ID:
            AlertDialog dialog = buildAboutView();
            dialog.show();
            break;
 
        // ... 实现其它的,代码就省略了...      		
 
        return super.onOptionsItemSelected(item);
	}
}

  实现以上效果的思路是:覆盖 onPrepareOptionsMenu 而不是 onCreateOptionsMenu 方法的. 因为onCreateOptionsMenu的方法只会执行一次,就是第一次点击menu键时才会执行.而onPrepareOptionsMenu每一次点击menu键都会执行.这样,我们就可以先清空menu的内容,再重新添加menu.这就是实现在不同Tab中显示不同菜单(Menu)的原理.

1
2
3
4
5
6
7
8
   public boolean onPrepareOptionsMenu(Menu menu) {
        // *** 这里是实现的主要代码,先要清空菜单,然后再重新添加菜单
    	menu.clear(); // 清空menu
    	super.onPrepareOptionsMenu(menu);
 
        // 添加所需要的菜单...
        menu.add(....);
  }

相关博文:
[ Android中TabHost切换不同的Activity ]
[ Android中TabHost的使用 ]

返回 : 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开发博文汇总