实时推送-androidpn 客户端代码分析
来源:互联网 发布:时间胶囊软件 编辑:程序博客网 时间:2024/06/05 20:11
首先是环境搭建,http://www.devdiv.com/thread-101586-1-1.html,这边文章讲的很详细了。 要注意的是: 由于adt的升级,我们需要把工程的lib目录手动改成libs,然后build-path.
Client这边包含有消息的收发,解析以及持久连接的发起,重连等功能呢,十分强大,我们开发时完全不用管底层的连接,也不用担心断线,可以专注于业务部分的开发。
同时,代码结构也很简单。去除android的Service和BroadCast类以及一些工具类和常量类不谈:
1.NotificationIQ,NotificationIQProvider,NotificationPacketListener三个类负责对收到的Notification格式的消息进行解析和处理,
2.XmppManager是主控制器,NotificationService通过这个类,在后台维护androidpn连接。
3.PersistentConnectionListener,PhoneStateChangeListener,ReconnectionThread.java三个类则负责监听手机的状态并进行断线重连。
我们自定义消息时需要定义3个类:在***IQ中定义消息的实体,在***IQProvider中将消息转化为***IQ实体,在***PacketListener中对实体进行处理,具体的实现可参考NotificationIQ,NotificationIQProvider,NotificationPacketListener三个类。
程序执行流程:
DemoAppActivity->ServiceManager.startService()->NotificationService.onCreate()->NotificationService.start()->xmppManager.connect()->xmppManager.submit…Task()->xmppManager.addTask()->任务执行->NotificationReceiver发出通知。
下面结合代码分析
DemoAppActivity: 程序入口,在onCreate方法中调用
ServiceManager.startService(). ServiceManager serviceManager = new ServiceManager(this); serviceManager.setNotificationIcon(R.drawable.notification); serviceManager.startService();
程序来到ServiceManager,首先在构造方法中加载了raw文件夹下的一些参数放在首选项中,主要是xmpp的ip与port.同时把当前context,既DemoAppActivity的包名与类名也放在首选项中,供后边回调。
props = loadProperties(); apiKey = props.getProperty("apiKey", ""); xmppHost = props.getProperty("xmppHost", "127.0.0.1"); xmppPort = props.getProperty("xmppPort", "5222"); sharedPrefs = context.getSharedPreferences( Constants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); Editor editor = sharedPrefs.edit(); editor.putString(Constants.API_KEY, apiKey); editor.putString(Constants.VERSION, version); editor.putString(Constants.XMPP_HOST, xmppHost); editor.putInt(Constants.XMPP_PORT, Integer.parseInt(xmppPort)); editor.putString(Constants.CALLBACK_ACTIVITY_PACKAGE_NAME, callbackActivityPackageName); editor.putString(Constants.CALLBACK_ACTIVITY_CLASS_NAME, callbackActivityClassName); editor.commit();
然后是执行startService方法,其中启动了一个线程去启动NotificationService
public void startService() { Thread serviceThread = new Thread(new Runnable() { @Override public void run() { Intent intent = NotificationService.getIntent(); context.startService(intent); } }); serviceThread.start(); }
程序来到NotificationService,这个类是负责执行xmppManager中task的后台服务类。首先在构造方法中初始化了一系列的成员变量。
notificationReceiver = new NotificationReceiver(); //监听通知的广播接收者 connectivityReceiver = new ConnectivityReceiver(this); //监听系统连接状态的广播接收者 phoneStateListener = new PhoneStateChangeListener(this); //系统状态监听器 executorService = Executors.newSingleThreadExecutor(); //线程池 taskSubmitter = new TaskSubmitter(this); taskTracker = new TaskTracker(this);
其中notificationReceiver、connectivityReceiver、phoneStateListener主要负责系统断开连接或断网重新连接,executorService 是jdk提供的单例的线程池,负责线程的执行。 TaskSubmitter是自定义的成员类,用来通过executorService 来管理系统线程的执行,submit方法用来执行线程。 TaskTracker主要是记录线程数。
public Future submit(Runnable task) { Future result = null; if (!notificationService.getExecutorService().isTerminated() && !notificationService.getExecutorService().isShutdown() && task != null) { result = notificationService.getExecutorService().submit(task); //执行线程 } return result; }
接下来执行NotificationService生命周期方法onCreate, 在onCreate方法中首先获取设备id,计在着选项中,然后初始化XmppManager, 通过taskSubmiiter开一个线程去执行自己的start方法。
xmppManager = new XmppManager(this); taskSubmitter.submit(new Runnable() { public void run() { NotificationService.this.start(); } });
再来看NotificationService.start()方法, 主要做了两件事: 1.注册广播接收者,这里通过代码方式注册,并在服务停止时注销掉,让广播接收者与服务的生命周期一致。 2.调用xmppManager .connect(),连接服务器。
private void start() { Log.d(LOGTAG, "start()..."); registerNotificationReceiver(); registerConnectivityReceiver(); // Intent intent = getIntent(); // startService(intent); xmppManager.connect(); }
最后来看XmppManager这个类,这是项目最核心的一个类,负责与服务器端的交互。首选是在构造方法中做一些初始化操作,之前放在首选项中的参数,就是在这里获取并使用的。
context = notificationService; taskSubmitter = notificationService.getTaskSubmitter(); taskTracker = notificationService.getTaskTracker(); sharedPrefs = notificationService.getSharedPreferences(); xmppHost = sharedPrefs.getString(Constants.XMPP_HOST, "localhost"); xmppPort = sharedPrefs.getInt(Constants.XMPP_PORT, 5222); username = sharedPrefs.getString(Constants.XMPP_USERNAME, ""); password = sharedPrefs.getString(Constants.XMPP_PASSWORD, ""); connectionListener = new PersistentConnectionListener(this); notificationPacketListener = new NotificationPacketListener(this); handler = new Handler(); taskList = new ArrayList<Runnable>(); reconnection = new ReconnectionThread(this);
再看connect方法。调用了submitLoginTask
public void connect() {
Log.d(LOGTAG, “connect()…”);
submitLoginTask();
} ,往下看,可以看到一个调用链
submitLoginTask-> submitRegisterTask->submitConnectTask,然后没个方法里面又执行了addTask方法。
其实就是把在几个成员类Task,放到taskList中,然后再看这几个task在什么时候执行呢。
来看addTask方法。
private void addTask(Runnable runnable) { Log.d(LOGTAG, "addTask(runnable)..."); taskTracker.increase(); synchronized (taskList) { if (taskList.isEmpty() && !running) { running = true; futureTask = taskSubmitter.submit(runnable); if (futureTask == null) { taskTracker.decrease(); } } else { taskList.add(runnable); } } Log.d(LOGTAG, "addTask(runnable)... done"); }
在addTask方法中有一个判断,当taskList为空时,就调用taskSubmitter.submit()方法来执行线程。所以在submitConnectTask方法中执行了ConnectTask这个线程。
接下来看ConnectTask
public void run() { Log.i(LOGTAG, "ConnectTask.run()..."); if (!xmppManager.isConnected()) { // Create the configuration for this new connection ConnectionConfiguration connConfig = new ConnectionConfiguration( xmppHost, xmppPort); // connConfig.setSecurityMode(SecurityMode.disabled); connConfig.setSecurityMode(SecurityMode.required); connConfig.setSASLAuthenticationEnabled(false); connConfig.setCompressionEnabled(false); XMPPConnection connection = new XMPPConnection(connConfig); xmppManager.setConnection(connection); try { // Connect to the server connection.connect(); Log.i(LOGTAG, "XMPP connected successfully"); // packet provider ProviderManager.getInstance().addIQProvider("notification", "androidpn:iq:notification", new NotificationIQProvider()); } catch (XMPPException e) { Log.e(LOGTAG, "XMPP connection failed", e); } xmppManager.runTask(); } else { Log.i(LOGTAG, "XMPP connected already"); xmppManager.runTask(); } }
可以看到在方法中
其实就是调用smack的api去建立联接了。在run方法最后调用了runTask方法,原来taskList里的其它任务是在这里被执行的。
public void runTask() { Log.d(LOGTAG, "runTask()..."); synchronized (taskList) { running = false; futureTask = null; if (!taskList.isEmpty()) { Runnable runnable = (Runnable) taskList.get(0); taskList.remove(0); running = true; futureTask = taskSubmitter.submit(runnable); if (futureTask == null) { taskTracker.decrease(); } } } taskTracker.decrease(); Log.d(LOGTAG, "runTask()...done"); }
runTask每次会执行list中的第一次元素也就是最选添加进去的,也就是RegisterTask,程序执行到RegisterTask,同样是调用smackapi去注册一个用户,这里每次就是用一个随机数去注册
public void run() { Log.i(LOGTAG, "RegisterTask.run()..."); if (!xmppManager.isRegistered()) { final String newUsername = newRandomUUID(); final String newPassword = newRandomUUID(); Registration registration = new Registration(); PacketFilter packetFilter = new AndFilter(new PacketIDFilter( registration.getPacketID()), new PacketTypeFilter( IQ.class)); PacketListener packetListener = new PacketListener() { public void processPacket(Packet packet) { Log.d("RegisterTask.PacketListener", "processPacket()....."); Log.d("RegisterTask.PacketListener", "packet=" + packet.toXML()); if (packet instanceof IQ) { IQ response = (IQ) packet; if (response.getType() == IQ.Type.ERROR) { if (!response.getError().toString().contains( "409")) { Log.e(LOGTAG, "Unknown error while registering XMPP account! " + response.getError() .getCondition()); } } else if (response.getType() == IQ.Type.RESULT) { xmppManager.setUsername(newUsername); xmppManager.setPassword(newPassword); Log.d(LOGTAG, "username=" + newUsername); Log.d(LOGTAG, "password=" + newPassword); Editor editor = sharedPrefs.edit(); editor.putString(Constants.XMPP_USERNAME, newUsername); editor.putString(Constants.XMPP_PASSWORD, newPassword); editor.commit(); Log .i(LOGTAG, "Account registered successfully"); xmppManager.runTask(); } } } }; connection.addPacketListener(packetListener, packetFilter); registration.setType(IQ.Type.SET); // registration.setTo(xmppHost); // Map<String, String> attributes = new HashMap<String, String>(); // attributes.put("username", rUsername); // attributes.put("password", rPassword); // registration.setAttributes(attributes); registration.addAttribute("username", newUsername); registration.addAttribute("password", newPassword); connection.sendPacket(registration); } else { Log.i(LOGTAG, "Account registered already"); xmppManager.runTask(); } }
这里里有一个packetListener,可以监听到服务端的返回值,当注册成功后会用户名与密码会存在首选项中,下次再进程序就不会再次注册。
之后会执行到LoginTask,用随机的用户密码去登陆。
private class LoginTask implements Runnable { final XmppManager xmppManager; 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(); } } }
那么程序是在哪里监听服务求的数据的呢,回过头看看ConnectTask,在执行connection.connect()后有一段代码
ProviderManager.getInstance().addIQProvider("notification", "androidpn:iq:notification", new NotificationIQProvider());
看看NotificationIQProvider发现在其实就是对服务端发送来xml内容的解析,,那么这段代码就可以理解成添加解析器。
接下来就要看 LoginTask中标红的一段代码,这里也是添加一个监听器,xmppManager.getNotificationPacketListener()
其实就是NotificationPacketListener。看看processPacket, 这个方法是监听器的回调方法。
@Override public void processPacket(Packet packet) { Log.d(LOGTAG, "NotificationPacketListener.processPacket()..."); Log.d(LOGTAG, "packet.toXML()=" + packet.toXML()); if (packet instanceof NotificationIQ) { NotificationIQ notification = (NotificationIQ) packet; if (notification.getChildElementXML().contains( "androidpn:iq:notification")) { String notificationId = notification.getId(); String notificationApiKey = notification.getApiKey(); String notificationTitle = notification.getTitle(); String notificationMessage = notification.getMessage(); // String notificationTicker = notification.getTicker(); String notificationUri = notification.getUri(); Intent intent = new Intent(Constants.ACTION_SHOW_NOTIFICATION); intent.putExtra(Constants.NOTIFICATION_ID, notificationId); intent.putExtra(Constants.NOTIFICATION_API_KEY, notificationApiKey); intent .putExtra(Constants.NOTIFICATION_TITLE, notificationTitle); intent.putExtra(Constants.NOTIFICATION_MESSAGE, notificationMessage); intent.putExtra(Constants.NOTIFICATION_URI, notificationUri); // intent.setData(Uri.parse((new StringBuilder( // "notif://notification.androidpn.org/")).append( // notificationApiKey).append("/").append( // System.currentTimeMillis()).toString())); xmppManager.getContext().sendBroadcast(intent); } } }
方法很简单,就是把消息包转换成我们自定义的NotificationIQ, 解析工作在上边的NotificationIQProvider中完成,这里就是把数据封装到intent中,然后发一个广播,到NotificationReceiver中去处理,
然后NotificationReceiver.onReceive()方法中发出一个系统通知。
把整个流程分析之后,想要把这个功能移值到自己项目中,应该很简单了。
- 实时推送-androidpn 客户端代码分析
- AndroidPN服务器与客户端代码分析
- Androidpn学习笔记-客户端代码分析
- AndroidPN服务器与客户端代码分析
- java 代码模拟androidpn消息推送客户端(测试)
- Androidpn 消息推送安卓客户端源码分析
- AndroidPN客户端源码简要分析
- android下服务器推送实现 androidpn分析
- androidpn+tomcat推送消息源码流程分析
- androidpn推送
- androidpn推送
- Android Push Notification(Android客户端信息推送) (androidpn-server和androidpn-client)
- Android 推送之Androidpn项目分析(一)
- Android 推送之Androidpn项目分析(一)
- 百度实时推送代码 curl
- Androidpn分析
- Androidpn分析
- Androidpn分析
- 关于子线程和Handler 的用法
- Android开发UI之添加Action按钮
- iOS之图片处理
- performSelector用法
- 系统升级到iOS9,真机运行报“was compiled with optimization - stepping may behave oddly...”,闪退
- 实时推送-androidpn 客户端代码分析
- muduo库阅读(25)——Net部分:服务器端的套接字类
- jquery插件
- html5 hash
- html 中javascript 页面加载完成后执行函数
- Super和This 简单理解
- 数据库的一些知识点
- Nginx服务器安全配置详解
- 【C++基础之二】常量指针和指针常量