实时推送-androidpn 客户端代码分析

首先是环境搭建,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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值