Pro Android学习笔记(一零一):BroadcastReceiver(5):长时间处理通知小例子(上)

文章转载只能用于非商业性质,且不能带有虚拟货币、积分、注册等附加条件。转载须注明出处以及作者@恺风Wei。

IntentService的问题

IntentService确实很不错,因为是service,可以确保进程运行,同时,不在主线程中执行,避免ANR消息。但是IntentService在广播接收器中使用会有一些问题。当一个广播接收器被触发时,特别是通过告警管理器进行触发,如果设备休眠,alarm管理器会通过一个wake lock(唤醒锁)通知电源管理器开启设备(只需能够运行代码,不需要UI呈现)。wake lock会在broadcast receiver返回时释放。然而IntentService的触发没有wake lock,设备可能在代码运行前就sleep了。

因此,我们需要在在线程开启前获取wake lock,并在处理结束时释放。为了在主线程和在worker线程中分别对同一wake lock的获取和释放,采用static的方式。在具体开始小例子之前,我们先看看wake lock。

长时间处理通知的小例子

利用IntentService实现长时间的通知处理,要求从收到通知开始,到通过IntentService,在线程完成通知的处理的整个过程中要持有wake lock,确保有资源执行任务,并在通知处理完后释放资源。小例子将提供一个通用的解决方法,较之以往的小例子,本例稍微复杂一点,但是了解小例子的设计,小例子不过就是个小例子。

对于一个通用的小例子,在context中如何持有和释放wake lock,需要考虑的是在上一次通知没有处理完,又收到新的通知。context中可能有多个接收器,触发不同的IntentService。这些情况都需要得到很好的处理。下图是整个小例子的框架流程。

Pro Android学习笔记(一零一):BroadcastReceiver(5):长时间处理通知小例子(上)_第1张图片

在小例子中,开灯表示持有wake lock,关灯表示释放wake lock。房间表示线程处理,可能是在工作线程A,也可能在工作线程B。有个计数器,用于记录在线程中处理或者等待处理的的通知的总数。通知在onHandleIntent()中进行处理,处理完时,要离开房间,这时要检查一下是否所有的通知已全部处理(计数器为零),如果没有通知就要关灯。

此外通过stopService()可以强制终止service,强制清空了某个工作线程所要处理的全部通知,又或者由于onHandleIntent()的处理过程中出现异常退出,没能运行leaveRoom()。这种情况,计数器无法正确反映正在处理或等待处理的通知数。因此我们通过在onCreate()的registerClient()和在onDestroy()的unregisterClient(),记录当前运行的IntentService数目(房间数),如果发现房间数为零,也说明已经没有通知要处理。(要注意,stopService()会调用onDestroy(),当并不能终止后台运行的线程,后台线程将会继续执行,代码中可以通过设置状态检查等方式来终止后台线程)

wake lock的代码实现

使用wakelock需要WAKE_LOCK的权限,我们将上面的过程形象地模拟为:开启一个IntentService,如同客人注册,开一间房,当IntentService结束时,客人注销,退房。收到一个通知,表示一个人进入某一间房,处理完一个通知,表示某个人离开某间房。我们将统计在房间中的总人数,如果发现没有人,将关灯。由于某种情况,人离开房间可能没有被观察到,所以仅通过总人数判断不够。当发现所有的房间都退掉时,表示房间都没有人,要光灯,并复位房间的总人数为零。

下面通过LightedGreenRoom这个类来管理client人数、房间计数器、开灯和光灯的操作,相关处理代码如下:

public class LightedGreenRoom {
    private static String tag="LightedGreenRoom";     
    private int count;  //记录当前有多少通知在工作线程中处理或等待处理     
    private Context ctx = null;     
    PowerManager.WakeLock wl = null;     
    private int clientCount = 0;  //记录工作线程(客人)数目
    
    public LightedGreenRoom(Context inCtx){
        ctx = inCtx;
        wl = this.createWakeLock(inCtx); //基于context创建wake lock
    } 
    
   //有一个通知要处理:模拟为进入房间,房间内人数++ 
    public synchronized int enter(){
        count ++;
        Log.d(tag,"A new visitor , count = " + count);
        return count;
    }
    //有一个通知处理结束:模拟为离开房间,房间内人数--
    public synchronized int leave(){
        if(count == 0){
            Log.d(tag,"No one in room.");
            return count;
        }
       
        count --;
        Log.d(tag,"one leave, count = " + count);
        if(count == 0){
            turnOffLights();
        }
        return count;       
    }
   // 获取当前要处理的通知数:模拟为有多少人在房间? 
    synchronized public int getCount(){
        return count;
    }
    //开灯
    private void turnOnLights(){
        Log.d(tag,"Turning on lights. Count = " + count);
        this.wl.acquire();//wake lock操作(3):请求持有wake lock
    }
    //关灯
    private void turnOffLights(){
        if(this.wl.isHeld()){
            Log.d(tag,"Turning off ligths. Count = " + count);
            this.wl.release(); //wake lock操作(4):释放wake lock
        }
    }
    //对于wake lock的操作:(1)获得电源管理器参考;(2)获得wake lock对象,PARTIAL_WAKE_LOCK表示无需屏幕亮,维持最小的运行需求
    private PowerManager.WakeLock createWakeLock(Context inCtx){
        PowerManager pm = (PowerManager) inCtx.getSystemService(Context.POWER_SERVICE);
        PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, tag);

        return wl;
    }
    //IntentService的开启:模拟为客人注册,开间房 
    private int registerClient(){
        Utils.logThreadSignature(tag);
        this.clientCount ++;
        Log.d(tag,"register a new Client ,client count = " + clientCount);
        return clientCount ;
    }
    //IntentService的结束:模拟为客人注销,房间退 
    private int unregisterClient(){
        Utils.logThreadSignature(tag);
        if(clientCount == 0){
            Log.d(tag,"no client registed!");
            return 0;
        }
        clientCount --;
        Log.d(tag,"unregister a client , client count = " + clientCount);
        if(clientCount == 0){
            emptyTheRoom();
        }
        return clientCount;
    }
    //清空房间
    synchronized public void emptyTheRoom(){
        Log.d(tag,"Empty the Room ");
        count = 0;
        this.turnOffLights();
    }
   
}

在IntentService中的应用

从流程框架图中,我们观察到LightedGreenRoom需要在接收器、IntentService的的主线程已经工作线程中调用。接收器开启IntentService,这是通过intent进行数据传递,是没有办法传递对象的参考。因此,我们需要一个全部的LightedGreenRoom对象,要么采用static的方式,要么作为一个application的变量。作为通用范例,能更好地在不同app中使用,我们采用了static方式。为此,对LightedGreenRoom进行了补充:

public class LightedGreenRoom {
    ... ...
    private static LightedGreenRoom s_self = null;  //维持一个全局的LightedGreenRoom对象
    //第一次收到通知,生成全局对象,并开灯(在具体处理的前期准备也要开灯,只要在处理,都要开灯)
    public static void setup(Context inCtx){
        if(s_self == null){
            Log.d(tag,"Create Lighted Green Room.");
            s_self = new LightedGreenRoom(inCtx);
            s_self.turnOnLights();
        }
    }
   
    public static boolean isSetUp(){
        return (s_self == null ? false: true);
    }
    //收到通知,模拟有人进入房间
    public static int s_enter(){
        assertSetup();
        return s_self.enter();
    }
    //通知处理完:模拟有人离开房间
    public static int s_leave(){
        assertSetup();
        return s_self.leave();
    }
    //清空房间
    public static void ds_emptyTheRoom(){
        assertSetup();
        s_self.emptyTheRoom();
    }
    //开启IntentService,模拟开房
    public static void s_registerClient(){
        assertSetup();
        s_self.registerClient();
    }
    //结束IntentService,模拟退房
    public static void s_unregisterClient(){
        assertSetup();
        s_self.unregisterClient();
    }
    //检查是否已经正确初始化
    private static void assertSetup(){
        if(!LightedGreenRoom.isSetUp()){
            Log.w(LightedGreenRoom.tag,"You need to call setup first.");
            throw new RuntimeException("You need to setup GreenRoom first");
        }
    }
   
}

 

小例子代码在:Pro Android学习:Broadcast小例子

相关链接:我的Android开发相关文章

你可能感兴趣的:(Pro Android学习笔记(一零一):BroadcastReceiver(5):长时间处理通知小例子(上))