实时推送-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()方法中发出一个系统通知。

把整个流程分析之后,想要把这个功能移值到自己项目中,应该很简单了。

1 0