Android 中 Service 内核原理

Android Service核心机制解析

Service 内核原理

一、startService 与 bindService 的区别

Android 执行 Service 有两种方法,一种是 startService,一种是 bindService。下面看看这两种执行 Service 方法的区别。
在这里插入图片描述

1.1 生命周期上的区别

执行startService时,Service会经历 onCreate -> onStartCommand。当执行stopService 时,直接调用 onDestroy 方法。调用者如果没有 stopService,Service会一直在后台运行,下次调用者再起来仍然可以stopService。如果多次调用startService,该Service只能被创建一次,即该Service的onCreate方法只会被调用一次。但是每次调用startService,onStartCommand方法都会被调用。Service的onStart方法在API5时被废弃,替代它的是onStartCommand方法。

执行bindService时,Service会经历 onCreate -> onBind 。这个时候调用者和Service 绑定在一起。调用者调用unbindService方法或者调用者Context不存在了(如 Activity 被 finish 了),Service就会调用onUnbind->onDestroy。这里所谓的绑定在一起就是说两者共存亡了。第一次执行bindService时,onCreate和onBind方法会被调用,但是多次执行bindService时,onCreate和onBind方法并不会被多次调用,即并不会多次创建服务和绑定服务。

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

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

1.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 便可以了。
  • 如果你还想要与正在运行的客户端取得联系,那么有两种方法:一种是使用broadcast,另一种是使用bindService。前者的缺点是如果交流较为频繁,容易造成性能上的问题,而后者则没有这些问题。因此,这种情况就需要startService和bindService一起使用了(比如:音乐播放器既要后台持续播放音乐,又要绑定客户端与客户交互)。
  • 如果你的服务只是公开一个远程接口,供连接上的客户端(Android的Service是C/S架构)远程调用执行方法,这个时候你可以不让服务一开始就运行,而只是bindService,这样在第一次bindService的时候才会创建服务的实例运行它,这会节约很多系统资源,特别是如果你的服务是远程服务,那么效果会越明显(当然在Servcie创建的是偶会花去一定时间,这点需要注意)。

1.4 本地服务与远程服务

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

对于startService来说,不管是本地服务还是远程服务,我们需要做的工作都一样简单。

1.5 代码实例

startService启动服务

public class LocalService extends Service {

    /**
     * onBind 是 Service 的虚方法,因此我们不得不实现它。
     * 返回null,表示客服端不能建立到此服务的连接。
     */
    @Override
    public IBinder onBind(Intent intent){
        return null;
    }

    @Override
    public void onCreate(){
        super.onCreate();
    }

    /**
     *     // 服务被杀死后,系统会尝试重新创建服务
     *     // 但是不会传递上次的 intent(intent 为 null)
     *     return START_STICKY;(默认)
     * <p>
     *     // 服务被杀死后,不会自动重启
     *     return START_NOT_STICKY;
     * <p>
     *     // 服务被杀死后,会自动重启并重新传递上次的 intent
     *     return START_REDELIVER_INTENT;
     * */
    @Override
    public int onStartCommand(Intent intent,int startId,int flags){
        return 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 Service 实例 即 LocalService 本身
         */
        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 View.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 View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (isBind) {
                    unbindService(sc);
                    isBind = false;
                }
            }
        });
    }
}

对应main.xml:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <Button
        android:id="@+id/btnBind"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btnUnbind"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="btnUnbind"
        app:layout_constraintLeft_toRightOf="@id/btnBind"
        android:layout_marginStart="10dp"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

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

需要记得在清单文件中注册服务

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

示例:

<service android:name=".LocalService" />

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

2.1 工作原理

2.1.1 流程示意图
IntentService 的工作原理 & 源码工作流程如下:
在这里插入图片描述

2.1.2 特别注意
若启动 IntentService 多次,那么每个耗时操作则以队列的方式在 IntentService 的 onHandleIntent 回调方法中依次执行,执行完自动结束。

2.2 源码分析

通过源码分析解决以下问题:

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

问题1:IntentService如何单独开启1个新的工作线程
主要分析内容,IntentService 源码中的 onCreate() 方法

@Override
public void onCreate() {
    super.onCreate();
    // 1. 通过实例化 HandlerThread 新建线程 & 启动;故使用 IntentService 时,不需要额外新建线程
    // HandlerThread 继承自 Thread,内部封装了 Looper
    HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
    thread.start();
    // 2. 获取工作线程的 Looper & 维护自己的工作队列
    mServiceLooper = thread.getLooper();
    // 3. 新建 mServiceHandler & 绑定到上述获得到的 Looper
    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(Intentintent);

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

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

2.3 源码总结

从上面源码可看出:

  • IntentService本质=Handler+HandlerThread:
  • 通过HandlerThread单独开启1个工作线程:IntentService
  • 创建1个内部Handler:ServiceHandler
  • 绑定ServiceHandler与IntentService
  • 通过onStartCommand()传递服务intent到ServiceHandler、依次插入Intent到工作队列中&逐个发送给onHandleIntent()
  • 通过onHandleIntent()依次处理所有Intent对象所对应的任务

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

三、前台服务与通知

3.1 什么是前台服务

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

3.2 通知

Notification支持文字内容显示、震动、三色灯、铃声等多种提示形式,在默认情况下,Notification仅显示消息标题、消息内容、送达时间这3项内容。
(1)标准样式
在这里插入图片描述
(2)扩展样式
在这里插入图片描述
(3)自定义样式
在这里插入图片描述
使用notification

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

构建通知的步骤:
① 获取状态通知栏管理类实例

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

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

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

③ 对Builder进行配置

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

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

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

⑤ 发送通知请求

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

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

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

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值