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的源码分析,改进以及扩展。
共勉之。谢谢。

0 0
原创粉丝点击