BeiHaiZuoPeng 2014-04-30
就最近的形势来看,似乎是不差的。变革即意味着机遇,确实如此。我觉得我似乎在朝着自己的计划慢慢靠近。我想很快就会有确切的答案吧。我静静地等着。
最近两天一直在研究Android 信息推送的事情。学习了AndroidPN这个框架,虽然最后不会用这个,项目还是要自己搭,但终究有值得借鉴的地方,特此把学习的内容总结在这里。
AndroidPN(Android Push Notification)是韩国人思密达共享的一个简单的框架,基于XMPP协议,实现了信息推送服务。
一:安装测试运行
1,下载客户端和服务器端程序。http://sourceforge.net/projects/androidpn/files/ 程序10年11月上传后就没有再更改过,可能已经被原分享者放弃了思密达。
2,打开客户端程序下的raw/androidpn.properties文件,配置客户端信息,将xmppHost配置成10.0.2.2,xmppPort=5222,5222 是服务器的xmpp服务监听端口。
3,运行androidpn-server-0.5.0\bin\run.bat启动服务器,从浏览器访问http://127.0.0.1:7070/
在模拟器中运行客户端。
4,从Web端向客户端发消息。
效果如下:
友情提示:
1,如果出现运行run.bat一闪而过,无法访问http://127.0.0.1:7070/的情况,请配置好Java环境变量。
2,客户端运行后后提示“Application unfortunately Stopped”,有可能是引用的asmack.jar包的问题,重新引用,然后Clean,重新Build Project。祝大家好运。
二:源代码学习
在项目中我是写客户端的,所以我主要学习了客户端的源码。现总结如下:
1,程序入口DemoAppActivity开启服务:
// Start the service ServiceManager serviceManager = new ServiceManager(this); serviceManager.setNotificationIcon(R.drawable.notification);//设置消息的图标 serviceManager.startService();
在实例化ServiceManage的过程中,做了以下工作,加载 res/raw/androidpn.properties 配置文件中的参数信息,并将其保存在SharedPreferences中。然后调用startService()开启服务。
public void startService() { Thread serviceThread = new Thread(new Runnable() { @Override public void run() { Intent intent = NotificationService.getIntent(); context.startService(intent); } }); serviceThread.start(); }
startService()方法开启了一个子线程去启动真正的信息推送服务NotificationService。对于Service,作者思密达在OnCreate()的时候做了很多工作,OnCreate()方法在服务被创建时调用,且只会被调用一次。因此多次的start()并不会产生多个实例。在OnCreate()方法中,作者获取了一个很重要的参数deviceId设备ID。可是后面并没有使用它。
telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); // Get deviceId deviceId = telephonyManager.getDeviceId();
获得设备ID后,
xmppManager = new XmppManager(this); taskSubmitter.submit(new Runnable() { public void run() { NotificationService.this.start(); } });
实例化XmappManager,并且调用taskSubmitter,在taskSubmitter方法里实例化NotificationService类,并且调用其start()方法。实例化NotificationService类时会做以下工作:
public NotificationService() { notificationReceiver = new NotificationReceiver(); connectivityReceiver = new ConnectivityReceiver(this); phoneStateListener = new PhoneStateChangeListener(this); executorService = Executors.newSingleThreadExecutor(); taskSubmitter = new TaskSubmitter(this); taskTracker = new TaskTracker(this); }
实例化notificationReceiver,NotificationReceiver继承了BroadcastReceiver类。BroadcastReceiver (广播接收者)用于接收广播 Intent 。ConnectivityReceiver同样继承了BroadcastReceiver类。PhoneStateChangeListener继承了PhoneStateListener,用于监听手机状态变化。Executors.newSingleThreadExecutor()返回一个线程池(这个线程池只有一个线程),这个线程池可以在线程死后(或发生异常时)重新启动一个线程来替代原来的线程继续执行下去。
NotificationService.start()方法
private void start() { Log.d(LOGTAG, "start()..."); registerNotificationReceiver(); registerConnectivityReceiver(); // Intent intent = getIntent(); // startService(intent); xmppManager.connect(); }
registerNotificationReceiver()方法注册了notificationReceiver来接收广播,registerConnectivityReceiver()方法注册了connectivityReceiver来监听网络连接状况。然后调用xmappManager的connect方法。
继续跟踪xmappManager的connect方法又执行了哪些工作
public void connect() { Log.d(LOGTAG, "connect()..."); submitLoginTask(); }
调用了submitLoginTask()方法,顾名思义,提交登录任务。
private void submitLoginTask() { Log.d(LOGTAG, "submitLoginTask()..."); submitRegisterTask(); addTask(new LoginTask()); }
提交登录任务中,又提交了一个注册任务,同时将新建的登录任务添加到任务集合中并交由 TaskTracker 来对添加的任务进行监视。继续根据在登录任务中执行的工作:
(1)如果连接没有经过身份验证,根据username和password执行登录操作,然后为连接添加各种监听机制。执行刚刚添加的任务runTask()(2)如果连接通过身份验证,直接执行任务runTask();
private LoginTask() { this.xmppManager = XmppManager.this; } public void run() { Log.i(LOGTAG, "LoginTask.run()..."); if (!xmppManager.isAuthenticated()) { Log.d(LOGTAG, "username=" + username); Log.d(LOGTAG, "password=" + password); try { xmppManager.getConnection().login( xmppManager.getUsername(), xmppManager.getPassword(), XMPP_RESOURCE_NAME); Log.d(LOGTAG, "Loggedn in successfully"); // connection listener if (xmppManager.getConnectionListener() != null) { xmppManager.getConnection().addConnectionListener( xmppManager.getConnectionListener()); } // packet filter PacketFilter packetFilter = new PacketTypeFilter( NotificationIQ.class); // packet listener PacketListener packetListener = xmppManager .getNotificationPacketListener(); connection.addPacketListener(packetListener, packetFilter); xmppManager.runTask(); } catch (XMPPException e) { Log.e(LOGTAG, "LoginTask.run()... xmpp error"); Log.e(LOGTAG, "Failed to login to xmpp server. Caused by: " + e.getMessage()); String INVALID_CREDENTIALS_ERROR_CODE = "401"; String errorMessage = e.getMessage(); if (errorMessage != null && errorMessage .contains(INVALID_CREDENTIALS_ERROR_CODE)) { xmppManager.reregisterAccount(); return; } xmppManager.startReconnectionThread(); } catch (Exception e) { Log.e(LOGTAG, "LoginTask.run()... other error"); Log.e(LOGTAG, "Failed to login to xmpp server. Caused by: " + e.getMessage()); xmppManager.startReconnectionThread(); } } else { Log.i(LOGTAG, "Logged in already"); xmppManager.runTask(); } } }
具体的监听和接下来的操作会在下篇进行分析。