• Android组件内核之Service内核原理(三)


    阿里P7Android高级架构进阶视频免费学习请点击:https://space.bilibili.com/474380680
    本篇文章将先从以下三个内容来介绍Service内核原理:

    • [startService与bindService的区别]
    • [多线程IntentService的工作原理 & 源码分析]
    • [前台服务以及通知]

    一 startService与bindService的区别

    Android执行Service有两种方法,一种是startService,一种是bindService。下面让我们一起来聊一聊这两种执行Service方法的区别。

     
    19956127-6278abea2d05deed.png
     

    1、生命周期上的区别

    执行startService时,Service会经历onCreate->onStartCommand。当执行stopService时,直接调用onDestroy方法。调用者如果没有stopService,Service会一直在后台运行,下次调用者再起来仍然可以stopService。
    
    执行bindService时,Service会经历onCreate->onBind。这个时候调用者和Service绑定在一起。调用者调用unbindService方法或者调用者Context不存在了(如Activity被finish了),Service就会调用onUnbind->onDestroy。这里所谓的绑定在一起就是说两者共存亡了。
    
    多次调用startService,该Service只能被创建一次,即该Service的onCreate方法只会被调用一次。但是每次调用startService,onStartCommand方法都会被调用。Service的onStart方法在API 5时被废弃,替代它的是onStartCommand方法。
    
    第一次执行bindService时,onCreate和onBind方法会被调用,但是多次执行bindService时,onCreate和onBind方法并不会被多次调用,即并不会多次创建服务和绑定服务。
    

    2、调用者如何获取绑定后的Service的方法

    onBind回调方法将返回给客户端一个IBinder接口实例,IBinder允许客户端回调服务的方法,比如得到Service运行的状态或其他操作。我们需要IBinder对象返回具体的Service对象才能操作,所以说具体的Service对象必须首先实现Binder对象。
    

    3、既使用startService又使用bindService的情况

    如果一个Service又被启动又被绑定,则该Service会一直在后台运行。首先不管如何调用,onCreate始终只会调用一次。对应startService调用多少次,Service的onStart方法便会调用多少次。Service的终止,需要unbindService和stopService同时调用才行。不管startService与bindService的调用顺序,如果先调用unbindService,此时服务不会自动终止,再调用stopService之后,服务才会终止;如果先调用stopService,此时服务也不会终止,而再调用unbindService或者之前调用bindService的Context不存在了(如Activity被finish的时候)之后,服务才会自动停止。
    
    那么,什么情况下既使用startService,又使用bindService呢?
    
    如果你只是想要启动一个后台服务长期进行某项任务,那么使用startService便可以了。如果你还想要与正在运行的Service取得联系,那么有两种方法:一种是使用broadcast,另一种是使用bindService。前者的缺点是如果交流较为频繁,容易造成性能上的问题,而后者则没有这些问题。因此,这种情况就需要startService和bindService一起使用了。
    
    另外,如果你的服务只是公开一个远程接口,供连接上的客户端(Android的Service是C/S架构)远程调用执行方法,这个时候你可以不让服务一开始就运行,而只是bindService,这样在第一次bindService的时候才会创建服务的实例运行它,这会节约很多系统资源,特别是如果你的服务是远程服务,那么效果会越明显(当然在Servcie创建的是偶会花去一定时间,这点需要注意)。    
    

    4、本地服务与远程服务

    本地服务依附在主进程上,在一定程度上节约了资源。本地服务因为是在同一进程,因此不需要IPC,也不需要AIDL。相应bindService会方便很多。缺点是主进程被kill后,服务变会终止。
    
    远程服务是独立的进程,对应进程名格式为所在包名加上你指定的android:process字符串。由于是独立的进程,因此在Activity所在进程被kill的是偶,该服务依然在运行。缺点是该服务是独立的进程,会占用一定资源,并且使用AIDL进行IPC稍微麻烦一点。
    
    对于startService来说,不管是本地服务还是远程服务,我们需要做的工作都一样简单。
    

    5、代码实例
    startService启动服务

    public class LocalService1 extends Service {
        /**
        * onBind 是 Service 的虚方法,因此我们不得不实现它。
        * 返回 null,表示客服端不能建立到此服务的连接。
        */
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    
        @Override
        public void onCreate() {
            super.onCreate();
        }
    
        @Override
        public void onStartCommand(Intent intent, int startId, int flags) {
            super.onStartCommand(intent, startId, flags);
        }
    
        @Override
        public void onDestroy() {
            super.onDestroy();
        }
    }
    

    bindService绑定服务

    public class LocalService extends Service {
    /**
    * 在 Local Service 中我们直接继承 Binder 而不是 IBinder,因为 Binder 实现了 IBinder 接口,这样我们可以** 少做很多工作。
    */
    public class SimpleBinder extends Binder{
    /**
    * 获取 Service 实例
    * @return
    */
    public LocalService getService(){
    return LocalService.this;
    }
    
    public int add(int a, int b){
    return a + b;
    }
    }
    
    public SimpleBinder sBinder;
    
    @Override
    public void onCreate() {
    super.onCreate();
    // 创建 SimpleBinder
    sBinder = new SimpleBinder();
    }
    
    @Override
    public IBinder onBind(Intent intent) {
    // 返回 SimpleBinder 对象
    return sBinder;
    }
    }
    

    上面的代码关键之处,在于 onBind(Intent) 这个方法 返回了一个实现了 IBinder 接口的对象,这个对象将用于绑定Service 的 Activity 与 Local Service 通信。
    下面是 Activity 中的代码:

    public class Main extends Activity {
        private final static String TAG = "SERVICE_TEST";
        private ServiceConnection sc;
        private boolean isBind;
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
            sc = new ServiceConnection() {
                @Override
                public void onServiceDisconnected(ComponentName name) {
                }
    
                @Override
                public void onServiceConnected(ComponentName name, IBinder service) {
                    LocalService.SimpleBinder sBinder = (LocalService.SimpleBinder)service;
                    Log.v(TAG, "3 + 5 = " + sBinder.add(3, 5));
                    Log.v(TAG, sBinder.getService().toString());
                }
            };
    
            findViewById(R.id.btnBind).setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    bindService(new Intent(Main.this, LocalService.class), sc, Context.BIND_AUTO_CREATE);
                    isBind = true;
                }
            });
    
            findViewById(R.id.btnUnbind).setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    if(isBind){
                        unbindService(sc);
                        isBind = false;
                    }
                }
            });
        }
    }
    

    6、在AndroidManifest.xml里Service元素常见选项

    android:name  -------------  服务类名
    android:label  --------------  服务的名字,如果此项不设置,那么默认显示的服务名则为类名
    android:icon  --------------  服务的图标
    android:permission  -------  申明此服务的权限,这意味着只有提供了该权限的应用才能控制或连接此服务
    android:process  ----------  表示该服务是否运行在另外一个进程,如果设置了此项,那么将会在包名后面加上这段字符串表示另一进程的名字
    android:enabled  ----------  表示是否能被系统实例化,为true表示可以,为false表示不可以,默认为true
    android:exported  ---------  表示该服务是否能够被其他应用程序所控制或连接,不设置默认此项为 false

    二 多线程IntentService的工作原理 & 源码分析

    2.1.1流程示意图

    • IntentService的工作原理 & 源码工作流程如下:
     
    944365-fa5bfe6dffa531ce.png
     

    2.1.2 特别注意

    若启动IntentService 多次,那么 每个耗时操作 则 以队列的方式IntentServiceonHandleIntent回调方法中依次执行,执行完自动结束

    接下来,我们将通过 源码分析 解决以下问题:

    • IntentService 如何单独开启1个新的工作线程
    • IntentService 如何通过onStartCommand() 将Intent 传递给服务 & 依次插入到工作队列中

    2.2. 源码分析

    问题1:IntentService如何单独开启1个新的工作线程

    主要分析内容 = IntentService源码中的 onCreate()方法

    @Override
    public void onCreate() {
        super.onCreate();
    
        // 1. 通过实例化andlerThread新建线程 & 启动;故 使用IntentService时,不需额外新建线程
        // HandlerThread继承自Thread,内部封装了 Looper
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();
    
        // 2. 获得工作线程的 Looper & 维护自己的工作队列
        mServiceLooper = thread.getLooper();
    
        // 3. 新建mServiceHandler & 绑定上述获得Looper
        // 新建的Handler 属于工作线程 ->>分析1
        mServiceHandler = new ServiceHandler(mServiceLooper); 
    }
    
       /** 
         * 分析1:ServiceHandler源码分析
         **/ 
         private final class ServiceHandler extends Handler {
    
             // 构造函数
             public ServiceHandler(Looper looper) {
             super(looper);
           }
    
            // IntentService的handleMessage()把接收的消息交给onHandleIntent()处理
            @Override
             public void handleMessage(Message msg) {
    
              // onHandleIntent 方法在工作线程中执行
              // onHandleIntent() = 抽象方法,使用时需重写 ->>分析2
              onHandleIntent((Intent)msg.obj);
              // 执行完调用 stopSelf() 结束服务
              stopSelf(msg.arg1);
    
        }
    }
    
       /** 
         * 分析2: onHandleIntent()源码分析
         * onHandleIntent() = 抽象方法,使用时需重写
         **/ 
          @WorkerThread
          protected abstract void onHandleIntent(Intent intent);
    
    

    问题2:IntentService 如何通过onStartCommand() 将Intent 传递给服务 & 依次插入到工作队列中

    
    /** 
      * onStartCommand()源码分析
      * onHandleIntent() = 抽象方法,使用时需重写
      **/ 
      public int onStartCommand(Intent intent, int flags, int startId) {
    
        // 调用onStart()->>分析1
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }
    
    /** 
      * 分析1:onStart(intent, startId)
      **/ 
      public void onStart(Intent intent, int startId) {
    
        // 1. 获得ServiceHandler消息的引用
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
    
        // 2. 把 Intent参数 包装到 message 的 obj 发送消息中,
        //这里的Intent  = 启动服务时startService(Intent) 里传入的 Intent
        msg.obj = intent;
    
        // 3. 发送消息,即 添加到消息队列里
        mServiceHandler.sendMessage(msg);
    }
    
    

    至此,关于IntentService的源码分析讲解完毕。


    2.3. 源码总结

    从上面源码可看出:IntentService本质 = Handler + HandlerThread

    1. 通过HandlerThread 单独开启1个工作线程:IntentService
    2. 创建1个内部 HandlerServiceHandler
    3. 绑定 ServiceHandlerIntentService
    4. 通过 onStartCommand() 传递服务intentServiceHandler 、依次插入Intent到工作队列中 & 逐个发送给 onHandleIntent()
    5. 通过onHandleIntent() 依次处理所有Intent对象所对应的任务

    因此我们通过复写onHandleIntent() & 在里面 根据Intent的不同进行不同线程操作即可

    三、前台服务与通知

    3.1 什么是前台服务

    前台服务是那些被认为用户知道(用户认可所认可)且在系统内存不足的时候不允许系统杀死的服务。前台服务必须给状态栏提供一个通知,它被放到正在运行(Ongoing)标题之下——这就意味着通知只有在这个服务被终止或从前台主动移除通知后才能被解除。

    3.2 通知

    Notification支持文字内容显示、震动、三色灯、铃声等多种提示形式,在默认情况下,Notification仅显示消息标题、消息内容、送达时间这3项内容。

    1. 标准样式

     
    19956127-cca84662d7d28309.png
     

    2. 扩展样式

     
    19956127-facfc6f8d24944fb.png
     

    3. 自定义样式

     
    19956127-fc7504005c19c0f0.png
     

    使用notification

    Notification:通知信息类,它里面对应了通知栏的各个属性。
    NotificationManager : 状态栏通知的管理类,负责发通知、清除通知等操作。

    构建通知的步骤:

    1. 获取状态通知栏管理类实例

    NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);  
    
    

    2. 实例化通知栏构造器NotificationCompat.Builder

    NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context);  
    
    

    3. 对Builder进行配置

    // 设置通知的基本信息:icon、标题、内容
    mBuilder .setSmallIcon(R.drawable.notification_icon)
    mBuilder .setContentTitle("My notification")
    mBuilder .setContentText("Hello World!");
    
    

    4. 设置通知栏PendingIntent(点击动作事件等都包含在这里)

    // 设置通知的点击行为:这里启动一个 Activity
    Intent intent = new Intent(this, ResultActivity.class);
    PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    mBuilder .setContentIntent(pendingIntent);
    
    

    5. 发送通知请求

    mNotificationManager.notify(notifyId, mBuilder.build());  
    
    

    更新通知
    要想更新通知,需要利用NotificationManager.notify()
    的id参数,该id在应用内需要唯一。要想更新特定id的通知,只需要创建新的Notification,并发出与之前所用 id 相同的 Notification。如果之前的通知仍然可见,则系统会根据新的 Notification 对象的内容更新该通知。相反,如果之前的通知已被清除,系统则会创建一个新通知。

    删除通知
    删除通知可以有多种方式:
    1.通过NotificationCompat.Builder设置setAutoCancel(true),这样当用户点击通知后,通知自动删除。
    2.通过NotificationManager.cancel(id)方法,删除指定 id 的通知
    3.通过 NotificationManager.cancelAll()方法,删除该应用的所有通知

    阿里P7Android高级架构进阶视频免费学习请点击:https://space.bilibili.com/474380680

    参考:https://my.oschina.net/tingzi/blog/376545
    https://www.jianshu.com/p/8a3c44a9173a
    https://www.jianshu.com/p/b59d2e51ddc5

  • 相关阅读:
    php记录代码执行时间
    java中针对同一变量的不同函数的互斥操作
    Linux下mysql新建账号及权限设置
    Linux下重启apache
    Mysql数据导入
    ubuntu安装phpcurl与phptidy扩展
    Linux服务器间文件传输
    Flash本地传递大数据,图片数据,localconnection 超出大小,超出限制 bitmapdata 拂晓风起
    [Java][JavaScript]字符串数组与字符串之间的互转(join/split)(转) 拂晓风起
    java poi读取excel公式,返回计算值(转) 拂晓风起
  • 原文地址:https://www.cnblogs.com/Android-Alvin/p/11953127.html
Copyright © 2020-2023  润新知