AndroidPN客户端源码简要分析
来源:互联网 发布:手机淘宝爱逛街不见了 编辑:程序博客网 时间:2024/05/17 23:18
AndroidPN
https://github.com/dannytiehui/androidpn
郭霖 Android消息推送教学视频
http://www.imooc.com/learn/223
http://www.imooc.com/learn/358
Asmack
https://github.com/Flowdalic/asmack(官网提示asmack废弃换成新的smack)
客户端如何与服务器交互?
开启Service
开启NotificationService服务,后台与服务器交互
ServiceManager serviceManager = new ServiceManager(this); serviceManager.setNotificationIcon(R.drawable.notification);serviceManager.startService();
NotificationService onCreate方法
... xmppManager = new XmppManager(this);taskSubmitter.submit(new Runnable() {//TaskSubmitter开启SingleThreadExcutor线程池 public void run() { NotificationService.this.start(); }});
NotificationService start方法
private void start() { Log.d(LOGTAG, "start()..."); registerNotificationReceiver();//注册通知广播 registerConnectivityReceiver();//注册网络广播 xmppManager.connect();//xmppmanger开启连接 }
添加执行Task
XmppManager conncet等方法
public void connect() { Log.d(LOGTAG, "connect()..."); submitLoginTask(); } private void submitConnectTask() { Log.d(LOGTAG, "submitConnectTask()..."); addTask(new ConnectTask()); } private void submitRegisterTask() { Log.d(LOGTAG, "submitRegisterTask()..."); submitConnectTask(); addTask(new RegisterTask()); } private void submitLoginTask() { Log.d(LOGTAG, "submitLoginTask()..."); submitRegisterTask(); addTask(new LoginTask()); }
Task添加顺序ConnectTask->RegisterTask->LoginTask,执行顺序也是如此
addTask方法
private void addTask(Runnable runnable) { Log.d(LOGTAG, "addTask(runnable)..."); taskTracker.increase();//Task追踪器(计数Task,add task则increase; submit task则decrease) synchronized (taskList) { if (taskList.isEmpty() && !running) { running = true; futureTask = taskSubmitter.submit(runnable);//提交到singleThread线程池中执行 if (futureTask == null) { taskTracker.decrease(); } } else { taskList.add(runnable);//如果队列不空且有运行的task则将新的task加入到队列中 } } Log.d(LOGTAG, "addTask(runnable)... done");}
与之比较runTask方法
public void runTask() { Log.d(LOGTAG, "runTask()..."); synchronized (taskList) { running = false;//将runing标识为false futureTask = null; if (!taskList.isEmpty()) { Runnable runnable = (Runnable) taskList.get(0);//获取task taskList.remove(0);//将该task移除task队列 running = true;//将running标识为true futureTask = taskSubmitter.submit(runnable);//提交到singleThread线程池中执行 if (futureTask == null) { taskTracker.decrease(); } } } taskTracker.decrease(); Log.d(LOGTAG, "runTask()...done");}
addTask添加Task,如果当前tasks列表中无Task且无运行的Task,则执行该Task,如果tasks列表有Task或者有运行的Task,则将Task加入到tasks等待执行
runTask执行Task,tasks列表中有Task则执行tasks.get(0)的Task,且移除tasks列表中该Task
Task之ConnectTask
ConnectTask进行客户端和服务器进行连接
如果未连接且未认证,则进行socket连接以及socket流初始化,packetwriter以及packetreader初始化和启动
如果已连接和认证,则登录(包括匿名登录)
public void run() { Log.i(LOGTAG, "ConnectTask.run()..."); if (!xmppManager.isConnected()) { // Create the configuration for this new connection // 配置连接ConnectionConfiguration 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();//连接到服务器 // Enhance[2015/10/19] xmppManager.runTask();//执行task Log.i(LOGTAG, "XMPP connected successfully"); // packet provider ProviderManager.getInstance().addIQProvider("notification", "androidpn:iq:notification", new NotificationIQProvider());//添加NotificationIQProvider,继承IQProvider,用来解析IQ数据包为NotificationIQ } catch (XMPPException e) { Log.e(LOGTAG, "XMPP connection failed", e); // Enhance[2015/10/19] xmppManager.dropTask(2); xmppManager.runTask(); xmppManager.startReconnectionThread(); } // xmppManager.runTask(); } else { Log.i(LOGTAG, "XMPP connected already"); xmppManager.runTask(); } }}
XmppConnection connect方法
public void connect() throws XMPPException { // Establishes the connection, readers and writers connectUsingConfiguration(config);//进行登录 // Automatically makes the login if the user was previously connected successfully // to the server and the connection was terminated abruptly if (connected && wasAuthenticated) {//连接且认证成功 // Make the login if (isAnonymous()) {//匿名登录 // Make the anonymous login loginAnonymously(); } else { login(config.getUsername(), config.getPassword(), config.getResource()); } notifyReconnection(); } }
假设第一次登录,则调用connectUsingConfiguration方法
private void connectUsingConfiguration(ConnectionConfiguration config) throws XMPPException { ... if (config.getSocketFactory() == null) {//socket创建 this.socket = new Socket(host, port); } else { this.socket = config.getSocketFactory().createSocket(host, port); } ... initConnection(); ... }
显而易见,socket已创建,那何处初始化socket输入输出流?
initConnection方法
private void initConnection() throws XMPPException { ... // Set the reader and writer instance variables initReaderAndWriter();//初始化socket输入输出流 try { if (isFirstInitialization) { //LSEVEN packetWriter = new PacketWriter(this); packetReader = new PacketReader(this); // If debugging is enabled, we should start the thread that will listen for // all packets and then log them. if (config.isDebuggerEnabled()) { addPacketListener(debugger.getReaderListener(), null); if (debugger.getWriterListener() != null) { addPacketSendingListener(debugger.getWriterListener(), null); } } } else { packetWriter.init();//PacketWriter初始化 packetReader.init();//PacketReader初始化 } // Start the packet writer. This will open a XMPP stream to the server packetWriter.startup()//启动packet writer线程 // Start the packet reader. The startup() method will block until we // get an opening stream packet back from server. packetReader.startup();//启动pacekt reader线程 // Make note of the fact that we're now connected. connected = true; if (isFirstInitialization) { // Notify listeners that a new connection has been established for (ConnectionCreationListener listener : getConnectionCreationListeners()) { listener.connectionCreated(this); } } } ... }
initReaderAndWriter方法
获取到writer和reader流
private void initReaderAndWriter() throws XMPPException { try { if (compressionHandler == null) { //socket.getInputStream输入流获取 reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8")); //socket.getOutputStream输出流获取 writer = new BufferedWriter( new OutputStreamWriter(socket.getOutputStream(), "UTF-8")); ... }
Task之RegisterTask
ConnectionTask中调用runTask,开始执行RegisterTask
Register顾名思义注册,将注册信息Registration(xmpp协议格式)发送给服务器,packetListener得到服务器返回的packet数据包进行处理
... Registration registration = new Registration();//Registration继承IQ PacketFilter packetFilter = new AndFilter(new PacketIDFilter( registration.getPacketID()), new PacketTypeFilter( IQ.class)); PacketListener packetListener = new PacketListener() { public void processPacket(Packet packet) { //服务器返回信息packet .... } }; connection.addPacketListener(packetListener, packetFilter);//添加packetListener监听 registration.setType(IQ.Type.SET); registration.addAttribute("username", newUsername); registration.addAttribute("password", newPassword); connection.sendPacket(registration);//发送registration给服务器 xmppManager.runTask(); } else { Log.i(LOGTAG, "Account registered already"); xmppManager.runTask(); }
connection.sendPacket()发送数据给服务器,sendPacket内代码
packetWriter.sendPacket(packet)
调用了packetWriter.sendPacket方法
查看Smack之PacketWriter
Task之LoginTask
LoginTask
...xmppManager.getConnection().login( xmppManager.getUsername(), xmppManager.getPassword(), XMPP_RESOURCE_NAME);//登录Log.d(LOGTAG, "Loggedn in successfully");// connection listenerif (xmppManager.getConnectionListener() != null) { xmppManager.getConnection().addConnectionListener( xmppManager.getConnectionListener());}// packet filterPacketFilter packetFilter = new PacketTypeFilter( NotificationIQ.class);// packet listenerPacketListener packetListener = xmppManager .getNotificationPacketListener();connection.addPacketListener(packetListener, packetFilter);//添加接收NotificationIQ类型的监听器...
在xmppConnection内判断登录认证是否使用了SASL
无使用SASL则调用new NonSASLAuthentication(this).authenticate(username, password, resource);
有使用SASL有密码则调用 saslAuthentication.authenticate(username, password, resource);
有使用SASL无密码则调用 saslAuthentication .authenticate(username, resource, config.getCallbackHandler());
NonSASLAuthentication的authenticate方法
public String authenticate(String username, String password, String resource) throws XMPPException { // If we send an authentication packet in "get" mode with just the username, // the server will return the list of authentication protocols it supports. //发送包含username且带有get类型的验证数据包,服务器将返回它所支持的认证协议列表 Authentication discoveryAuth = new Authentication(); discoveryAuth.setType(IQ.Type.GET); discoveryAuth.setUsername(username); PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(discoveryAuth.getPacketID())); // Send the packet connection.sendPacket(discoveryAuth); // Wait up to a certain number of seconds for a response from the server. IQ response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); if (response == null) { throw new XMPPException("No response from the server."); } // If the server replied with an error, throw an exception. else if (response.getType() == IQ.Type.ERROR) { throw new XMPPException(response.getError()); } // Otherwise, no error so continue processing. Authentication authTypes = (Authentication) response; collector.cancel(); // Now, create the authentication packet we'll send to the server. //创建认证数据包给服务器 Authentication auth = new Authentication(); auth.setUsername(username); // Figure out if we should use digest or plain text authentication. if (authTypes.getDigest() != null) { auth.setDigest(connection.getConnectionID(), password); } else if (authTypes.getPassword() != null) { auth.setPassword(password); } else { throw new XMPPException("Server does not support compatible authentication mechanism."); } auth.setResource(resource); collector = connection.createPacketCollector(new PacketIDFilter(auth.getPacketID())); // Send the packet. connection.sendPacket(auth);...
Smack之PacketWriter
sendPacket方法
将发送的packet数据包入queue队列,后唤醒queue.wait等待线程
public void sendPacket(Packet packet) { if (!done) { // Invoke interceptors for the new packet that is about to be sent. Interceptors // may modify the content of the packet. connection.firePacketInterceptors(packet); try { queue.put(packet);//将数据存入队列 } catch (InterruptedException ie) { ie.printStackTrace(); return; } synchronized (queue) { queue.notifyAll();//唤醒队列 } // Process packet writer listeners. Note that we're using the sending // thread so it's expected that listeners are fast. connection.firePacketSendingListeners(packet); } }
nextPacket方法
queue.poll进行取值,如果queue队列为空则queue.wait阻塞,等待队列有数据进行notify唤醒
private Packet nextPacket() { Packet packet = null; // Wait until there's a packet or we're done. while (!done && (packet = queue.poll()) == null) { try { synchronized (queue) { queue.wait(); } } catch (InterruptedException ie) { // Do nothing } } return packet;}
writePackets方法
调用nextPacket()获取packet数据包,wirte写出packet到服务端
private void writePackets(Thread thisThread) { try { // Open the stream. openStream(); // Write out packets from the queue. while (!done && (writerThread == thisThread)) { Packet packet = nextPacket();//queue中取出数据 if (packet != null) { writer.write(packet.toXML());//写出 if (queue.isEmpty()) { writer.flush(); } } } // Flush out the rest of the queue. If the queue is extremely large, it's possible // we won't have time to entirely flush it before the socket is forced closed // by the shutdown process. try { while (!queue.isEmpty()) { Packet packet = queue.remove(); writer.write(packet.toXML()); } writer.flush(); } catch (Exception e) { e.printStackTrace(); } // Delete the queue contents (hopefully nothing is left). queue.clear(); // Close the stream. try { writer.write("</stream:stream>"); writer.flush(); } catch (Exception e) { // Do nothing } ... }
openStream方法
开启会话
void openStream() throws IOException { StringBuilder stream = new StringBuilder(); stream.append("<stream:stream"); stream.append(" to=\"").append(connection.getServiceName()).append("\""); stream.append(" xmlns=\"jabber:client\""); stream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\""); stream.append(" version=\"1.0\">"); writer.write(stream.toString());//写出流,该writer即是在xmppconnection内初始化的Writer writer.flush(); }
输出xml格式
<stream:stream to="" xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" versuib="1.0"><iq id="packetid" to="" from="" type="set"><query xmlns=\"jabber:iq:register\"><instructions>instructions</instructions><username>username</username><password>password</password></query></iq></stream:stream>
在XmppConnection的connect方法内对packetWriter\ packetReader进行init初始化,以及startup启动 writeThread\readerThread;当客户端调用xmppConnection.sendPacket发送数据包,调用packetwriter的sendPacket将数据存入到queue队列中且唤醒等待的queue队列的writeThread;处于阻塞状态的writeThread 被唤醒后,则调用writePacket执行获取queque队列中数据,写出数据到服务器
Smack之PacketReader
在XmppConnection.connect内对packetReader进行init初始化,startup启动 readerThread
protected void init() { done = false; connectionID = null; readerThread = new Thread() { public void run() { parsePackets(this);//对服务器返回的数据进行读取 } }; ... resetParser(); }
private void resetParser() { try { parser = XmlPullParserFactory.newInstance().newPullParser(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); parser.setInput(connection.reader);//获取服务器写入流 } catch (XmlPullParserException xppe) { xppe.printStackTrace(); } }
Smack之IQ
IQ数据包,用于设置或获取服务端信息,包含验证、角色操作和创建账户。每个IQ数据包都有个指定的类型来表明当前即将被执行的行为类型“get””set” “result” 或者”error”
Type-safe enumeration to represent the type of the IQ packet. The types are:
IQ.Type.get – the IQ is a request for information or requirements.
IQ.Type.set – the IQ provides required data, sets new values, or replaces existing values.
IQ.Type.result – the IQ is a response to a successful get or set request.
IQ.Type.error – an error has occurred regarding processing or delivery of a previously-sent get or set.
来自:http://web.mit.edu/ghudson/dev/openfire/documentation/docs/javadoc/org/xmpp/packet/IQ.Type.html
toxml方法
将数据拼接成的符合XMPP协议的数据 ,getchildElement获取子元素的xml数据
<iq id="packetid" to="" from="" type="">getchildElement()</iq>
Smack之RegisterIQ
RegisterIQ继承IQ
public String getChildElementXML() { StringBuilder buf = new StringBuilder(); buf.append("<query xmlns=\"jabber:iq:register\">"); if (instructions != null && !remove) { buf.append("<instructions>").append(instructions).append("</instructions>"); } if (attributes != null && attributes.size() > 0 && !remove) { for (String name : attributes.keySet()) { String value = attributes.get(name); buf.append("<").append(name).append(">"); buf.append(value); buf.append("</").append(name).append(">"); } } else if(remove){ buf.append("</remove>"); } // Add packet extensions, if any are defined. buf.append(getExtensionsXML()); buf.append("</query>"); return buf.toString(); }
发送给服务器设置的xml数据
<iq id="packetid" to="" from="" type="set"><query xmlns=\"jabber:iq:register\"><instructions>instructions</instructions><username>username</username><password>password</password></query></iq>
Smack之IQProvider
IQProvider用来解析IQ数据包的,每个需要使用到的IQProvider必须在ProviderManager里注册,每个继承IQProvider接口的必须有public的空参数的构造函数
processIQ
解析IQ子元素并且创建IQ实例(xmpp格式的IQ数据包解析)。每一个IQ必须有一个的子元素
NotificationIQProvider继承IQProvider
将服务器返回的数据转为NotificationIQ
注册NotifcationIQProvider
ProviderManager.getInstance().addIQProvider("notification", "androidpn:iq:notification", new NotificationIQProvider());
PacketParseUtils.parseIQ方法
调用指定的IQProvider的parseIQ方法
public static IQ parseIQ(XmlPullParser parser, Connection connection) throws Exception {...//根据elementName和namespace得到相对应的IQProviderObject provider = ProviderManager.getInstance().getIQProvider(elementName, namespace); if (provider != null) { if (provider instanceof IQProvider) { //执行相应的IQProvdier进行数据包解析 iqPacket = ((IQProvider)provider).parseIQ(parser); } else if (provider instanceof Class) { iqPacket = (IQ)PacketParserUtils.parseWithIntrospection(elementName, (Class<?>)provider, parser); } }...}
PacketParserUtils.parseIQ(parser, connection)被调用于PacketReader.parseReader内
...else if (parser.getName().equals("iq")) { IQ iq; try { iq = PacketParserUtils.parseIQ(parser, connection); } catch (Exception e) { String content = PacketParserUtils.parseContentDepth(parser, parserDepth); UnparsablePacket message = new UnparsablePacket(content, e); if (callback != null) { callback.handleUnparsablePacket(message); } continue; } processPacket(iq); }...
Smack之PacketListener
监听服务器返回的数据包
客户端获取服务器返回的数据流后,PacketReader的readerThread获取到当前的流信息进行处理得到Packet数据包,接着调用监听PacketListener.processPacket
PacketListener packetListener = new PacketListener() { public void processPacket(Packet packet) { .... }};connection.addPacketListener(packetListener, packetFilter);//添加监听过滤器
Connection.notifyListener处调用了PacketListener 的processPacket,接着PacketReader 的ListenerNotification调用了notifyListener,processPacket处调用了ListenerNotification
private void processPacket(Packet packet) { if (packet == null) { return; } // Loop through all collectors and notify the appropriate ones. for (PacketCollector collector: connection.getPacketCollectors()) { collector.processPacket(packet); } // Deliver the incoming packet to listeners. listenerExecutor.submit(new ListenerNotification(packet));}
将ListenerNotification放入到监听的线程池中
parsePackets调用processPacket
packet = PacketParserUtils.parseMessage(parser);//服务器返回的xml数据processPacket(packet);//接收到服务器返回的数据交由packetlistener监听或者PacketCollector监听处理
服务器返回的数据读取
private void resetParser() { try { parser = XmlPullParserFactory.newInstance().newPullParser(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); parser.setInput(connection.reader);//获取读入流设置到该parser里 } catch (XmlPullParserException xppe) { xppe.printStackTrace(); } }
Smack之PacketCollector
NonSASLAuthentication.authenticate处示例
PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(discoveryAuth.getPacketID()));// Send the packet(将Authentication发送到服务器GET的方式,Type类型看XMPP协议规范)connection.sendPacket(discoveryAuth);IQ response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());//获取服务器返回的数据
PacketCollector同PacketListener一样,获取服务器返回的数据,但是不同于PacketListener,PacketListener是开启线程,而PacketCollector是阻塞的
public Packet nextResult(long timeout) { try { return resultQueue.poll(timeout, TimeUnit.MILLISECONDS);//取出下个packet } catch (InterruptedException e) { throw new RuntimeException(e); } } protected void processPacket(Packet packet) { if (packet == null) { return; } if (packetFilter == null || packetFilter.accept(packet)) { while (!resultQueue.offer(packet)) {//存入packet // Since we know the queue is full, this poll should never actually block. resultQueue.poll(); } } }
何处存入packet呢
同packetlistener一样,PacketReader的processPackets处
Smack之PacketFilter
过滤器,对packet数据进行过滤,配合PacketCollector以及PacketListener使用
public void notifyListener(Packet packet) { if (packetFilter == null || packetFilter.accept(packet)) { packetListener.processPacket(packet); }}public boolean accept(Packet packet) { return packetType.isInstance(packet);}
大致流程如上所述,可观看郭霖推送视频有关于AndroidPN的源码分析,改进以及扩展。
共勉之。谢谢。
- AndroidPN客户端源码简要分析
- AndroidPN服务器源码简要分析
- Androidpn 消息推送安卓客户端源码分析
- AndroidPN源码分析
- AndroidPN服务器与客户端代码分析
- Androidpn学习笔记-客户端代码分析
- AndroidPN服务器与客户端代码分析
- 实时推送-androidpn 客户端代码分析
- androidpn+tomcat推送消息源码流程分析
- Redis源码简要分析
- uboot源码简要分析
- Redis源码简要分析
- Redis源码简要分析
- Redis源码简要分析
- wifidog 源码简要分析
- uboot源码简要分析
- Retrofit源码简要分析
- Redis源码简要分析
- 第23讲 项目1:被3或者5整除的数
- centos升级gcc到4.8.1(支持c++11)步骤
- 堆栈窗体QStackedWidget
- 数据结构之(1)数组
- 黑马程序员——反射——Class、Constructor、Field、Method及简单框架原理
- AndroidPN客户端源码简要分析
- 深入浅出C/C++中的正则表达式库
- monitor工作方式
- 互联网大脑计划,走有中国特色的大脑计划
- Android SpinnerAdapter 的使用
- C++11 基础之字符数组,字符串,字符指针通过sizeof、strlen求值
- Android Fragment 入门介绍
- 关于UIScrollview不能响应touch事件的解决办法
- Build QT project in ROS