Service 和 AIDL 的运用
[TOC]
1. Service
Service
(服务) 是一个一种能够在后台履行长期操作而没有用户界面的运用组件。- 服务可由其他运用组件发动(如
Activity
),若没进行绑定,服务一旦发动将在后台一向运转,即便发动服务的组件(Activity
)已毁掉也不受影响。- 组件也能够绑定到服务,以与之进行交互,乃至是履行进程间通讯 (
IPC
),这时组件毁掉时服务也会中止。
该类中的常用办法如下:
public abstract class Service extends ContextWrapper implements ComponentCallbacks2, ContentCaptureManager.ContentCaptureClient {
// 创立时回调
public void onCreate() {
}
// startService 时回调
public @StartResult int onStartCommand(Intent intent, @StartArgFlags int flags, int startId) {
onStart(intent, startId);
return mStartCompatibility ? START_STICKY_COMPATIBILITY : START_STICKY;
}
// bindService 时的回调
@Nullable
public abstract IBinder onBind(Intent intent);
// unbindService 时的回调
public boolean onUnbind(Intent intent) {
return false;
}
// 当 onUnbind 回来 true 时,从头绑守时的回调
public void onRebind(Intent intent) {
}
// 毁掉中止时的回调
public void onDestroy() {
}
// 内部的中止办法,若想中止并获取成果运用 stopSelfResult(startId)
public final void stopSelf() {
stopSelf(-1);
}
}
这儿 onStartCommand()
的回来值很重要,共有如下四种不同的取值:
-
START_STICKY
-
粘性的
-
onStartCommand()
运用这个回来值履行后,假如service
进程被 kill 掉,保存service
的状况为开端状况,但不保存投递的intent
目标;体系随后会尝试从头创立service
,由于服务状况为开端状况,所以创立服务后一定会调用onStartCommand (Intent, int, int)
办法。假如在此期间没有任何发动指令被传递到service
, 那么参数Intent
将为null
。
-
-
START_NOT_STICKY
-
非粘性的
-
运用这个回来值时 , 假如在履行完
onStartCommand()
后 , 服务被反常kill
掉 ,体系不会主动重启该服务。
-
-
START_REDELIVER_INTENT
-
重传
Intent
-
运用这个回来值时,假如在履行完
onStartCommand()
后,服务被反常 kill 掉,体系会主动重启该服务 , 并将 Intent 的值传入。
-
-
START_STICKY_COMPATIBILITY
-
START_STICKY
的兼容版别 , 但不保证服务被kill
后一定能重启。
-
1.1 Service 的根本生命周期
提到生命周期就不得不提到关于 Service 的两种发动办法:
startService
bindService
1.1.1 startService
此办法发动的 Service 会一向无限运转,只要调用了它的 stopService()
或 stopSelf()
办法时,才会中止运转并毁掉。
生命周期:
- startService:
若 service 没被创立,调用 startService()
后会履行 onCreate()
—-> onStartCommand()
,若 service 已创立,startService()
只会履行 onStartCommand()
。
-
stopService:
stopService()
在 Service 被绑守时无法中止毁掉。
1.1.2 bindService
发动的服务和调用者之间是典型的 client-server 形式,调用者是 client,Service 则是 server 端。
-
Service 只要一个,但绑定到 Service 上面的 client 能够有多个。
-
client 能够经过 IBinder 接口获取 Service 实例,完结 client 端调用 Service 中的办法以完结交互。
-
发动的 Service 的生命周期与其绑定的 client 休戚相关。当 client 毁掉时,会主动与 Service 免除绑定,当然,也能够调用 Context 的 `unbindService()` 办法手动与 Service 免除绑定。当没有任何 client 与 Service 绑守时,Service 就会自行毁掉。
生命周期:
-
bindService:
没啥特别的,便是 bindService 时 Service 若没创立会创立 Service 示例,并在绑定成功后收到
onBind()
回调。 -
unbindService:
unbindService 将组件与 Service 解绑后会收到
onUnbind()
回调,此刻该 Service 若无任何组件与其绑定,则会自行毁掉。
1.2 Service 的发动办法
经过上节介绍,能够了解到发动 Service 有 startService()
和 bindService()
两种办法。
1.2.1 startService
该发动办法,app
杀死、Activity
毁掉没有任何影响,服务都不会毁掉中止运转,所以此办法合适后台一向运转的使命,但无法调用 Service 中的办法进行交互。
比方:播放音乐、下载文件、进程保活…
中止办法:主动 stopService()
1.2.1 bindService
该发动办法依赖于客户端生命周期,当客户端 Activity
毁掉时,即便没有调用 unbindService()
办法,Service
也会毁掉中止运转。所以此办法合适短时运用一起与 Service 发生交互的使命。
比方:经过 Service 跨进程传输数据…
中止办法:Service 无任何绑定即会主动中止
1.2.3 startService + bindService
该发动办法 Service 能够在后台一向运转,一起还能够与 Service 发生交互,调用它的办法。
比方:播放并操控音乐、下载文件并更新进展…
中止办法:需求免除绑定并 stopService()
1.3 Service 和 Thread 的差异
既然提到 Service 能够履行后台使命,那么也能够运用线程呀?那么看看有啥不同吧!
首要看看界说:
-
Thread
线程为程序履行的最小单元,Android 中的 UI 线程便是其间一种。
-
Service
安卓中的四大组件之一,为一种特别的机制,其实是一种轻量级的
IPC
通讯。
其次,需求了解一下 Thread 的局限性:
Thread
的运转是独立于 Activity
的但依赖于进程,也便是说当运用进程被杀死或者线程体运转结束时就会中止运转。
当 Activity
被 finish
后,是无法对其进行管理的。一起 Thread
运转过程中对其进行操控操作也十分麻烦,而且也无法经过它完结 IPC(跨进程)通讯。
那么,就能够了解到 Service 是能够做到这些的。
默认状况下, Service 是运转在当时 app 进程的 UI 主线程中,能够在 AndroidManifest 文件中装备 android:process
指定它所在的进程。
因而,与 Activity 相同,Service 是无法直接在其内部履行耗时使命的,需求开启子线程去履行,不然就会发生 ANR。
1.4 IntentService
在介绍 IntentService 之前先说明一下运用传统的 Service 会有何问题:
- 无法直接处理耗时使命,需求内部开启子线程
-
startService()
发动之后需求手动去中止
那么 IntentService 便是为了解决这些问题而生的,看下该类的首要内容:
@Deprecated
public abstract class IntentService extends Service {
// 该 Handler 在子线程中创立
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
// 处理完使命后主动中止
stopSelf(msg.arg1);
}
}
@Override
public void onCreate() {
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
// startService() 时将信息发送到 ServiceHandler 中
mServiceHandler.sendMessage(msg);
}
/**
* 该办法的回来值为 Service 的标志位:
*
* @see Service.START_STICKY_COMPATIBILITY: START_STICKY 的兼容版别,但不保证服务被kill后一定能重启
*
* @see Service.START_STICKY: 粘性的,假如 service 进程被 kill 掉,保存 service 的状况为开端状况,但不保存传送的 intent 目标。随后体系会尝试从头创立 service,创立后即会从头调用 onStartCommand(Intent,int,int) 办法。假如在此期间没有任何发动指令被传递到 service,那么参数 Intent 将为null
*
* @see Service.START_NOT_STICKY: 非粘性的,在履行完 onStartCommand 后,服务被反常 kill 掉,体系不会主动重启该服务
*
* @see Service.START_REDELIVER_INTENT: 重传 Intent,在履行完 onStartCommand 后,服务被反常 kill 掉,体系会主动重启该服务,并将 Intent 的值传入
*
*/
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
@Override
public void onDestroy() {
mServiceLooper.quit();
}
@Override
@Nullable
public IBinder onBind(Intent intent) {
return null;
}
// startService() -----> ServiceHandler 的 handleMessage() ------> onHandleIntent()
@WorkerThread
protected abstract void onHandleIntent(@Nullable Intent intent);
}
从上述代码中就能够看出它的首要特征:
- 会创立独立的
worker
线程来处理一切的Intent
恳求,并终究履行复写的onHandleIntent()
办法; - 恳求使命处理完结后,
IntentService
会主动中止,无需调用stopSelf()
办法中止Service
;
因而,关于那种需求后台处理某些使命,处理完结即退出是十分合适运用 IntentService 的,如:下载文件。
1.5 Android 8.0 对后台服务的约束
上节中能够看到 IntentService 被标记抛弃了,这是由于 Android 8.0(API 26) 以后体系不允许后台运用创立后台服务,创立后台服务需求运用 JobScheduler
来由体系进行调度使命的履行。那么怎样运用会被认定为后台呢?
假如满意以下恣意条件,运用将被视为处于前台:
- 具有可见 Activity (不论该 Activity 已发动仍是已暂停);
- 具有前台服务;
- 另一个前台运用已关联到该运用(不论是经过绑定到其间一个服务,仍是经过运用其间一个内容供给程序);
- IME;
- 壁纸服务;
- 告诉侦听器;
- 语音或文本服务。
假如以上条件均不满意,运用将被视为处于后台。
因而,Android 8.0 引入了一种全新的办法,即 Context.startForegroundService()
以在前台发动新服务。体系创立服务后应在五秒的时刻内调用该服务的 startForeground()
办法以显现新服务的用户可见告诉。假如未在此刻间约束内未调用 startForeground()
办法,则体系将中止服务并声明此运用为 ANR
。
但在一些特别状况下,仍是能够创立后台服务的:
- 处理对用户可见的使命时,后台运用将被置于一个临时白名单中并持续数分钟。坐落白名单中时,运用能够无约束地发动服务,而且这以后台服务也能够运转。
- 处理一条高优先级 Firebase 云音讯传递 (FCM) 音讯;
- 接纳播送,例如短信/彩信音讯;
- 从告诉履行 PendingIntent。
-
bindService()
办法不受后台约束。
1.5.1 前台服务
运用过程:
-
增加权限
创立一个前台服务,首要需求恳求前台服务权限(Android 9 – API 等级 28 及以上 ),如下:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" ...> <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> <application ...> ... </application> </manifest>
这是一个正常的权限,体系会主动将其授予恳求的运用程序。
-
创立前台服务
创立一个前台服务与创立一个正常服务没太大差异,仅仅需求在 Service 创立之后调用
startForeground()
来发动前台服务,如下:class TestService : Service() { override fun onCreate() { super.onCreate() // Android 8.0 调用 startForefround() 办法发动服务 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { setForegroundService(); } } private fun setForegroundService() { // 创立告诉途径 val CHANNEL_ID = 1 val channelName = getString(R.string.channel_name) // 设置告诉的优先级 val importance = NotificationManager.IMPORTANCE_LOW val channel = NotificationChannel(CHANNEL_ID, channelName, importance) val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.createNotificationChannel(channel) // 设置前台服务告诉的点击事情 val pendingIntent: PendingIntent = Intent(this, ExampleActivity::class.java).let { notificationIntent -> PendingIntent.getActivity(this, 0, notificationIntent, 0) } // 创立告诉 val notification: Notification = NotificationCompat.Builder(this, CHANNEL_ID) .setContentTitle(getText(R.string.notification_title)) .setContentText(getText(R.string.notification_message)) .setSmallIcon(R.drawable.icon) .setContentIntent(pendingIntent) .setTicker(getText(R.string.ticker_text)) .build() // Notification ID cannot be 0. startForeground(NOTIFICATION_ID, notification) } override fun onBind(intent: Intent): IBinder? { return null } }
在这儿有两个点需求留意一下:
-
Android 8.0 以上创立告诉需求先创立告诉途径,再运用途径 id 创立告诉
-
前台服务的告诉优先级有必要为
PRIORITY_LOW
或更高,告诉优先级详细见设置途径的重要性等级
-
-
注册服务
<manifest xmlns:android="http://schemas.android.com/apk/res/android" ...> <application ...> <service android:name="com.hl.myplugin.TestService" android:enabled="true" android:exported="false" /> </application> </manifest>
-
声明前台服务类型
假如在 Android 10(API 等级 29)或更高版别拜访前台服务中的方位信息,则需求声明
<service>
组件的前台服务类型为 location;假如在 Android 11(API 等级 30)或更高版别拜访前台服务中的摄像头或麦克风,则需求声明
<service>
组件的前台服务类型为 camera 或 microphone。<manifest xmlns:android="http://schemas.android.com/apk/res/android" ...> <application ...> <service ... android:foregroundServiceType="location|camera|microphone"/> </application> </manifest>
在运转时,假如前台服务只需求拜访清单中声明的类型的子集,则能够运用以下代码片段中的逻辑来约束服务的拜访:
val notification: Notification = ... // 开启前台服务的重载办法 startForeground(NOTIFICATION_ID, notification, FOREGROUND_SERVICE_TYPE_LOCATION or FOREGROUND_SERVICE_TYPE_CAMERA)
但 Android 11(API 等级 30)为了帮助维护用户隐私,对前台服务何时能够拜访设备的方位、摄像头或麦克风进行了约束。当运用程序在后台运转发动前台服务时,前台服务有以下约束:
-
除非用户已授予运用程序
ACCESS_BACKGROUND_LOCATION
权限,不然 前台服务无法拜访方位。 -
前台服务无法拜访麦克风或摄像头。
假如发动的服务对方位、麦克风和摄像头的拜访受到约束,那么在调试时 Logcat 中会显现以下音讯:
Foreground service started from background can not have location/camera/microphone access: service SERVICE_NAME
约束豁免:
在某些状况下,即便运用程序在后台运转时发动了前台服务 ,它依然能够在运用程序在前台运转时(“运用中”)拜访方位、相机和麦克风信息。在这些状况下,假如服务声明晰一个 前台服务类型 为
location
,而且服务由一个具有ACCESS_BACKGROUND_LOCATION
权限的运用程序发动,那么该服务即便在后台发动但在前台运转时仍可拜访方位信息。以下包括这些状况:
-
该服务由体系组件发动。
-
该服务经过与运用小部件交互发动。
-
该服务经过与告诉交互来发动。
-
该服务作为
PendingIntent
从不同的可见运用程序发送的发动 。 -
该服务由在设备一切者形式下运转的设备战略操控器运用程序发动。
-
该服务由供给
VoiceInteractionService
. -
该服务由具有
START_ACTIVITIES_FROM_BACKGROUND
特权权限的运用程序发动 。
-
-
发动前台服务
// Android 8.0运用 startForegroundService 在前台发动服务 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){ startForegroundService(Intent(this,TestService::class.java) }else{ startService(Intent(this,TestService::class.java)) }
-
移除前台服务
调用
stopForeground(removeNotification: Boolean)
办法即可移除前台服务,参数代表是否删除状况栏告诉。留意:
stopForeground()
并不会使服务中止运转,若想中止服务,仍需调用stopService()
1.5.2 JobIntentService
由于前台服务在告诉栏上会显现该 Service
正在运转,这或许会带来不好的用户体会。假如仍是希望运用服务在后台默默作业、经过运用服务开启子进程等等,那么就能够运用 JobIntentService
。
JobIntentService
用于处理被加入到行列的 job/service 使命。当运转在 Android O 或更高版别时,使命将作为 job
经过 JobScheduler.enqueue()
进行分发;当运转在较老版别的平台上时,使命仍旧会运用 Context.startService()
履行。
运用过程:
-
在 Manifest 中声明 Permission
JobIntentService
处理了亮屏/锁屏,因而需求在AndroidManifest.xml
中增加android.Manifest.permission.WAKE_LOCK
权限,如下:<uses-permission android:name="android.permission.WAKE_LOCK" />
-
在 Manifest 中声明 Service
JobIntentService
本质上也是一个Service
,因而需求在AndroidManifest.xml
声明,以便体系与之交互,如下:<service android:name="SimpleJobIntentService" android:permission="android.permission.BIND_JOB_SERVICE" > ... </service>
-
创立 JobIntentService 的完结类
与 IntentService 的
onHandleIntent()
办法类似,只需重写onHandleWork()
办法处理相应的逻辑即可:class SimpleJobIntentService : JobIntentService() { companion object { private const val JOB_ID = 1 fun enqueueWork(context: Context, work: Intent) { enqueueWork(context, SimpleJobIntentService::class.java, JOB_ID, work) } } override fun onHandleWork(intent: Intent) { // 详细逻辑 } }
-
发动服务
SimpleJobIntentService.enqueueWork(context, new Intent());
能够看出,它运用起来十分简略,由于现已封装了很多的内部逻辑,只需求调用
enqueueWork()
静态办法就能够了。
2. Service 的保活
Android 体系会尽或许长的连续一个运用程序进程,但在内存过低的时分,依然会不行避免需求移除旧的进程。为了决议哪些进程留下,哪些进程被杀死,体系根据在进程中在运转的组件及组件的状况,为每一个进程分配了一个优先级等级。优先级最低的进程首要被杀死。这个进程重要性的层次结构首要有五个等级。
2.1 进程的五个常用等级:
首要分为:
-
前台进程(Foreground process)
-
可见进程(Visible process)
-
服务进程 (Service process)
-
后台进程 (Background process)
-
空进程
了解这些以后,你就能理解为啥不主张在 Activity 中开线程处理耗时使命?
首要原因如下:
- Activity 中开线程做耗时操作,切到桌面会变成后台进程
- 发动 Service 新建线程处理耗时使命,这时会变为服务进程
由于服务进程的优先级比后台进程的优先级高,所以此办法处理耗时使命更好。 一起,运用 Service 将会保证 app 至少有服务进程的优先级。
2.1.1 前台进程
前台进程是用户当时做的事一切必要的进程,假如满意下面各种状况中的一种,一个进程被以为是在前台:
-
进程持有一个正在与用户交互的 Activity。
-
进程持有一个 Service,这个 Service 处于这几种状况:
-
Service 与用户正在交互的 Activity 绑定。
-
Service 是在前台运转的,即它调用了
startForeground()
。 -
Service 正在履行它的生命周期回调函数 ——
onCreate()
、onStart()
或onDestroy()
。
-
-
进程持有一个 BroadcastReceiver,这个 BroadcastReceiver 正在履行它的
onReceive()
办法。
杀死前台进程需求用户交互,由于前台进程的优先级是最高的。
2.1.2 可见进程
假如一个进程不含有任何前台的组件,但仍可被用户在屏幕上所见。当满意如下任一条件时,进程被以为是可见的:
-
进程持有一个 Activity,这个 Activity 不在前台,可是依然被用户可见,即处于
onPause()
状况。 -
进程持有一个 Service,这个 Service 和一个可见的 Activity 绑定。
可见的进程也被以为是很重要的,一般不会被毁掉,除非是为了保证一切前台进程的运转而不得不杀死可见进程的时分。
2.1.3 服务进程
假如一个进程中运转着一个 Service,这个 Service 是经过 startService()
开启的,而且不属于上面两种较高优先级的状况(未进行任何绑定),这个进程便是一个服务进程。
虽然服务进程没有和用户能够看到的东西绑定,可是它们一般在做的事情是用户关心的,比方后台播放音乐,后台下载数据等。所以体系会尽量保持它们的运转,除非体系内存不足以保持前台进程和可见进程的运转需求。
2.1.4 后台进程
假如进程不属于上面三种状况,可是它持有一个用户不行见的activity(Activity的 onStop()
被调用,可是 onDestroy()
没有调用的状况),就以为进程是一个后台进程。
后台进程不直接影响用户体会,体系会为了前台进程、可见进程、服务进程而恣意杀死后台进程。
通常会有许多个后台进程存在,它们会被保存在一个 LRU (least recently used) 列表中,这样就能够保证用户最近运用的 Activity 最终被毁掉,即最早毁掉时刻最远的 Activity。
2.1.5 空进程
假如一个进程不包括任何活跃的运用组件,则以为是空进程。
例如:一个进程傍边现已没有数据在运转,可是内存傍边还为这个运用驻留了一个进程空间。
保存这种进程的仅有理由是为了缓存的需求,为了加快下非有必要发动这个进程中的组件时的发动时刻。体系为了平衡进程缓存和底层内核缓存的资源,经常会杀死空进程。
2.2 Service 保活的常用技巧
-
设置最高优先级
<service android:name="com.dbjtech.acbxt.waiqin.UploadService" android:enabled="true" > <intent-filter android:priority="1000" > <action android:name="xxxx" /> </intent-filter> </service>
如上,Service 关于 intent-filter 能够经过
android:priority = “1000”
这个特点设置最高优先级,1000是最高值,假如数字越小则优先级越低,一起适用于播送。 -
运用前台服务
Service 创立时经过
startForeground()
办法把 Service 提升为前台进程等级,在onDestroy()
里边要记住调用stopForeground()
办法。 -
复写
onStartCommand()
办法,回来START_STICKY
当 Service 因内存不足被 kill,当内存又有的时分,Service 就会被从头创立发动。
留意:可是不能保证任何状况下都被重建,比方进程被干掉了….
-
onDestroy()
办法里发播送重启 ServiceService + Broadcast 办法,便是当 Service 走
onDestory()
的时分,发送一个自界说的播送,当收到播送的时分,从头发动 Service。留意:第三方运用或是在 setting 里-运用-强制中止时,APP 进程就直接被干掉了,
onDestroy()
办法都进不来,所以无法保证会履行 -
监听体系播送判别 Service 状况
经过体系的一些播送,比方:手机重启、界面唤醒、运用状况改变等等监听并捕获,然后判别咱们的 Service 是否还存活决议是否从头发动。
-
Application 加上 Persistent 特点
该特点相当于将该进程设置为常驻内存进程,即体系运用。一般为安装在/system/app下的 app,正常的三方运用安装在 /data/app 下。
3. AIDL 的运用
Android 接口界说言语 (AIDL),利用它界说客户端与服务均认可的编程接口,以便二者运用进程间通讯 (IPC) 进行相互通讯。
跨进程通讯 (IPC) 的办法许多,AIDL 是其间一种。还有
Binder
、文件共享、Messenger
、ContentProvider
和Socket
等进程间通讯的办法。AIDL 是接口界说言语,仅仅一个工具。详细通讯仍是得用Binder 来进行。Binder 是 Android 独有的跨进程通讯办法,只需求一次复制,更快速和安全。官方推荐用
Messenger
来进行跨进程通讯,可是Messenger
是以串行的办法来处理客户端发来的音讯,假如很多的音讯一起发送到服务端,服务端依然只能一个个处理。因而关于很多的并发恳求,这种状况就得用 AIDL 。其实 Messenger 的底层也是 AIDL,只不过体系做了层封装,简化运用。
3.1 Messenger (串行处理)
3.1.1 服务端
- 创立一个 Handler 目标,并完结
hanlemessage
办法,用于接纳来自客户端的音讯,并作处理 - 创立一个 Messenger,封装 Handler
-
messenger.getBinder()
办法获取一个 IBinder 目标,经过onBind
回来给客户端
运用示例如下:
public class MessengerService extends Service {
// 存储客户端发送的 Messenger 目标
ArrayList<Messenger> mClients = new ArrayList<Messenger>();
int mValue = 0;
/**
* 客户端恳求注册 Messenger
*/
static final int MSG_REGISTER_CLIENT = 1;
/**
* 客户端恳求反注册 Messenger
*/
static final int MSG_UNREGISTER_CLIENT = 2;
/**
* 客户端恳求设值,相当于恳求其他指令
*/
static final int MSG_SET_VALUE = 3;
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_REGISTER_CLIENT:
mClients.add(msg.replyTo);
break;
case MSG_UNREGISTER_CLIENT:
mClients.remove(msg.replyTo);
break;
case MSG_SET_VALUE:
mValue = msg.arg1;
for (int i=mClients.size()-1; i>=0; i--) {
try {
// 获得客户端传送的 Messenger,发送音讯回 Messenger 完结双向通讯
mClients.get(i).send(Message.obtain(null, MSG_SET_VALUE, mValue, 0));
} catch (RemoteException e) {
// 客户端有或许在此过程中死了发生反常,需求移除
mClients.remove(i);
}
}
break;
default:
super.handleMessage(msg);
}
}
}
final Messenger mMessenger = new Messenger(new IncomingHandler());
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
}
留意:该Service 在声明时有必要对外开放,即 android:exported="true"
3.1.2 客户端
- 在 Activity 中绑定服务
- 创立 ServiceConnection ,在其
onServiceConnected()
办法中经过参数 IBinder 将 Messenger 实例化 - 运用 Messenger 向服务端发送指令,或需求接纳服务器端的回来信息,则还要创立一个
Messenger(handler)
,并将这个 Messenger 传递给服务端,在handler 中接纳处理服务端的音讯,这就完结了客户端和服务端的双向通讯
运用示例如下:
public class MessengerServiceActivities extends Activity{
// 向服务端发送指令的 Messenger
private Messenger mService = null;
private boolean mIsBound;
private TextView mCallbackText;
private class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MessengerService.MSG_SET_VALUE:
mCallbackText.setText("Received from service: " + msg.arg1);
break;
default:
super.handleMessage(msg);
}
}
}
// 接纳服务端回来音讯的 Messenger
private final Messenger mMessenger = new Messenger(new IncomingHandler());
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// 衔接时获取与服务端交互的 Messenger
mService = new Messenger(service);
mCallbackText.setText("Attached.");
try {
// 将需求接纳服务端回来音讯的 Messenger 发送在音讯体中
Message msg = Message.obtain(null, MessengerService.MSG_REGISTER_CLIENT);
msg.replyTo = mMessenger;
mService.send(msg);
// 向服务端发送设值指令
msg = Message.obtain(null, MessengerService.MSG_SET_VALUE, this.hashCode(), 0);
mService.send(msg);
} catch (RemoteException e) {
// xxx
}
Toast.makeText(this, R.string.remote_service_connected, Toast.LENGTH_SHORT).show();
}
public void onServiceDisconnected(ComponentName className) {
mService = null;
mCallbackText.setText("Disconnected.");
Toast.makeText(this, R.string.remote_service_disconnected,Toast.LENGTH_SHORT).show();
}
};
void doBindService() {
bindService(new Intent(this, MessengerService.class), mConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
mCallbackText.setText("Binding.");
}
void doUnbindService() {
if (mIsBound) {
if (mService != null) {
try {
// 解绑时移除服务端中增加的 Messenger,撤销音讯接纳
Message msg = Message.obtain(null, MessengerService.MSG_UNREGISTER_CLIENT);
msg.replyTo = mMessenger;
mService.send(msg);
} catch (RemoteException e) {
//xxx
}
}
unbindService(mConnection);
mIsBound = false;
mCallbackText.setText("Unbinding.");
}
}
}
3.2 AIDL(并行处理)
过程:
-
创立 .aidl 文件:
界说 AIDL 接口
-
完结接口:
Android SDK 工具会基于 .aidl 文件,运用 Java 编程言语生成承继自
IInterface
接口的接口。生成的接口具有一个承继自 Binder 类名为 Stub 的内部笼统类,并声明 AIDL 接口中的笼统办法。大约结构如下:public interface IInterface{ public IBinder asBinder(); } public interface xxxInterface extends android.os.IInterface{ public static abstract class Stub extends android.os.Binder implements xxxInterface{ @Override public android.os.IBinder asBinder() { return this; } xxxx } // AIDL 中声明的笼统办法 xxxx }
-
向客户端揭露接口:
完结 Service 并重写 onBind(),然后回来 Stub 类的完结.
3.2.1 界说 AIDL 接口
在 src/main
下面创立 aidl 目录,然后新建 IPersonManager.aidl
文件,里边声明办法用于客户端调用,服务端完结。如下:
package com.xfhy.allinone.ipc.aidl;
import com.xfhy.allinone.ipc.aidl.Person;
interface IPersonManager {
List<Person> getPersonList();
//in: 从客户端流向服务端
boolean addPerson(in Person person);
}
这个接口和往常咱们界说接口时差别不是很大,需求留意的是即便 Person 和 PersonManager 在同一个包下面仍是得导包,这是AIDL的规矩。
-
AIDL 支撑的数据类型
在 AIDL 文件中,不是一切数据类型都是能够运用的,支撑的数据类型如下:
-
Java 编程言语中的一切原语类型(如 int、long、char、boolean 等)
-
String 和 CharSequence
-
List:只支撑 ArrayList,里边每个元素都有必要能够被 AIDL 支撑
-
Map:只支撑HashMap,里边的每个元素都有必要被 AIDL 支撑,包括 key 和 value
-
Parcelable:一切完结了Parcelable接口的目标
-
AIDL:一切的AIDL接口自身也能够在 AIDL 文件中运用
-
-
界说传输的目标
在 kotlin 或 Java 这边需求界说好这个需求传输的目标 Person,,或者界说在 aidl 目录下, 但需求经过
sourceSet{}
将此目录界说为 kotlin 或 java 源码目录,这儿以在 kotlin 下为示例:class Person(var name: String? = "") : Parcelable { constructor(parcel: Parcel) : this(parcel.readString()) override fun toString(): String { return "Person(name=$name) hashcode = ${hashCode()}" } override fun writeToParcel(parcel: Parcel, flags: Int) { parcel.writeString(name) } fun readFromParcel(parcel: Parcel) { this.name = parcel.readString() } override fun describeContents(): Int { return 0 } companion object CREATOR : Parcelable.Creator<Person> { override fun createFromParcel(parcel: Parcel): Person { return Person(parcel) } override fun newArray(size: Int): Array<Person?> { return arrayOfNulls(size) } }
然后得在 aidl 的相同目录下也需求声明一下这个 Person 目标,新建一个 Person.aidl:
package com.xfhy.allinone.ipc.aidl; parcelable Person;
留意:当需求传递目标时,则该目标有必要完结 Parcelable 接口而且需求指示数据走向的方向标记
方向标记 意义 in 数据只能由客户端流向服务端,服务端修正数据不会同步回来 out 数据只能由服务端流向客户端,客户端会新创立一个无参目标传递到服务端,服务端修正数据会同步回来 inout 数据可在服务端与客户端之间双向流通,服务端和客户端同步共用一个目标 原语类型(根本类型)默认是 in,inout 开销很大,因而慎用。调用 AIDL 生成接口的为客户端,完结接口方为服务端。
都完结了之后,rebuild 一下,AS 会主动生成
IPersonManager.java
接口文件。
3.2.2 服务端完结接口
界说一个 Service, 然后将其 process 设置成一个新的进程,与主进程区分隔,模仿跨进程拜访,它里边需求完结 .aidl
生成的接口,如下:
class RemoteService : Service() {
private val mPersonList = mutableListOf<Person?>()
private val mBinder: Binder = object : IPersonManager.Stub() {
override fun getPersonList(): MutableList<Person?> = mPersonList
override fun addPerson(person: Person?): Boolean {
return mPersonList.add(person)
}
}
override fun onBind(intent: Intent?): IBinder? {
return mBinder
}
override fun onCreate() {
super.onCreate()
mPersonList.add(Person("Garen"))
mPersonList.add(Person("Darius"))
}
}
完结的 IPersonManager.Stub
是一个 Binder,需求经过 onBind()
回来,客户端需求经过这个 Binder 来跨进程调用 Service 这边的服务。
3.2.3 客户端与服务端进行通讯
客户端这边需求经过 bindService()
来衔接此 Service,然后完结通讯。客户端的 onServiceConnected()
回调会接纳 Service 的 onBind()
办法所回来的 binder 实例。再调用 XxxInterface.Stub.asInterface(service)
就能转换获得 XxxInterface 实例。如下:
class AidlActivity : TitleBarActivity() {
companion object {
const val TAG = "xfhy_aidl"
}
private var remoteServer: IPersonManager? = null
private val serviceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
log(TAG, "onServiceConnected")
//在onServiceConnected调用IPersonManager.Stub.asInterface 获取接口类型的实例
//经过这个实例调用服务端的服务
remoteServer = IPersonManager.Stub.asInterface(service)
}
override fun onServiceDisconnected(name: ComponentName?) {
log(TAG, "onServiceDisconnected")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_aidl)
btnConnect.setOnClickListener {
connectService()
}
btnGetPerson.setOnClickListener {
getPerson()
}
btnAddPerson.setOnClickListener {
addPerson()
}
}
private fun connectService() {
val intent = Intent()
//action 和 package(app的包名)
intent.action = "com.xfhy.aidl.Server.Action"
intent.setPackage("com.xfhy.allinone")
val bindServiceResult = bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
log(TAG, "bindService $bindServiceResult")
//假如targetSdk是30,那么需求处理Android 11中的程序包可见性 详细拜见: https://developer.android.com/about/versions/11/privacy/package-visibility
}
private fun addPerson() {
//客户端调服务端办法时,需求捕获以下几个反常:
//RemoteException 反常:
//DeadObjectException 反常:衔接中止时会抛出反常;
//SecurityException 反常:客户端和服务端中界说的 AIDL 发生冲突时会抛出反常;
try {
val addPersonResult = remoteServer?.addPerson(Person("盖伦"))
log(TAG, "addPerson result = $addPersonResult")
} catch (e: RemoteException) {
e.printStackTrace()
} catch (e: DeadObjectException) {
e.printStackTrace()
} catch (e: SecurityException) {
e.printStackTrace()
}
}
private fun getPerson() {
val personList = remoteServer?.personList
log(TAG, "person 列表 $personList")
}
override fun onDestroy() {
super.onDestroy()
//最终记住unbindService
unbindService(serviceConnection)
}
}
测验时先 getPerson 再 addPerson 最终 getPerson,输出日志:
2020-12-24 12:41:00.170 24785-24785/com.xfhy.allinone D/xfhy_aidl: bindService true
2020-12-24 12:41:00.906 24785-24785/com.xfhy.allinone D/xfhy_aidl: onServiceConnected
2020-12-24 12:41:04.253 24785-24785/com.xfhy.allinone D/xfhy_aidl: person 列表 [Person(name=Garen), Person(name=Darius)]
2020-12-24 12:41:05.952 24785-24785/com.xfhy.allinone D/xfhy_aidl: addPerson result = true
2020-12-24 12:41:09.022 24785-24785/com.xfhy.allinone D/xfhy_aidl: person 列表 [Person(name=Garen), Person(name=Darius), Person(name=盖伦)]
留意:在客户端调用这些长途办法时是同步调用,在主线程调用或许会导致 ANR,应该在子线程去调用。
3.2.4 oneway 关键字(异步)
将 aidl 接口的办法前加上 oneway
关键字则这个办法便是异步调用,不会阻塞调用线程。当客户端调用服务端的办法不需求知道回来成果时,运用异步调用能够提高客户端的履行效率。
3.2.5 线程安全
AIDL 的办法是在服务端的 Binder 线程池中履行的,所以多个客户端一起进行衔接且操作数据时或许存在多个线程一起拜访的景象。这时就需求在服务端 AIDL 办法中处理多线程同步问题。
先看下服务端的 AIDL 办法是在哪个线程中:
override fun addPerson(person: Person?): Boolean {
log(TAG, "服务端 addPerson() 当时线程 : ${Thread.currentThread().name}")
return mPersonList.add(person)
}
//日志输出
服务端 addPerson() 当时线程 : Binder:3961_3
能够看到,确实是在非主线程中履行的,那确实会存在多线程安全问题。这就需求将 mPersonList 的类型修正为 CopyOnWriteArrayList,以保证线程安全:
//服务端
private val mPersonList = CopyOnWriteArrayList<Person?>()
override fun getPersonList(): MutableList<Person?> = mPersonList
//客户端
private fun getPerson() {
val personList = remoteServer?.personList
personList?.let {
log(TAG, "personList ${it::class.java}")
}
}
//输出日志
personList class java.util.ArrayList
另外还有 ConcurrentHashMap 也是同样的道理,这儿就不验证了。
3.2.6 AIDL 监听器(观察者? 双向通讯?)
上面的事例中,只能在客户端每次去调服务端的办法然后获得成果。若想服务端数据有改变就告诉一下客户端,这就需求增加监听器了。
由于这个监听器 Listener 是需求跨进程的,这儿首要就需求为这个 Listener 创立一个 aidl 的回调接口IPersonChangeListener.aidl
interface IPersonChangeListener {
// 这儿由服务端调用此接口,因而服务端其实充任 "Client",数据流通方向标记为 in 更合理
void onPersonDataChanged(in Person person);
}
有了监听器,还需求在 IPersonManager.aidl
中加上注册/反注册监听的办法:
interface IPersonManager {
......
void registerListener(IPersonChangeListener listener);
void unregisterListener(IPersonChangeListener listener);
}
现在咱们在服务端完结这个注册/反注册的办法,这还不简略吗? 搞一个 List<IPersonChangeListener>
来存放 Listener 集合,当数据改变的时分遍历这个集合,告诉一下这些Listener就行。
仔细想想这样真的行吗? 这个 IPersonChangeListener
是需求跨进程的,那么客户端每次传过来的目标是经过序列化与反序列化的,服务端这边接纳到的根本不是客户端传过来的那个目标。 虽然传过来的 Listener 不同,可是用来通讯的 Binder 是同一个,利用这个原理 Android 供给了一个 RemoteCallbackList
的东西,专门用于存放监听接口的集合的。RemoteCallbackList
内部将数据存储于一个 ArrayMap 中,key 便是用来传输的 binder,value 便是监听接口的封装。如下:
//RemoteCallbackList.java 源码有删减
public class RemoteCallbackList<E extends IInterface> {
ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>();
private final class Callback implements IBinder.DeathRecipient {
final E mCallback;
final Object mCookie;
Callback(E callback, Object cookie) {
mCallback = callback;
mCookie = cookie;
}
}
public boolean register(E callback, Object cookie) {
synchronized (mCallbacks) {
IBinder binder = callback.asBinder();
Callback cb = new Callback(callback, cookie);
mCallbacks.put(binder, cb);
return true;
}
}
}
RemoteCallbackList
内部在操作数据的时分现已做了线程同步的操作,所以不需求独自做额外的线程同步操作。现在来完结一下这个注册/反注册办法:
private val mListenerList = RemoteCallbackList<IPersonChangeListener?>()
private val mBinder: Binder = object : IPersonManager.Stub() {
.....
override fun registerListener(listener: IPersonChangeListener?) {
mListenerList.register(listener)
}
override fun unregisterListener(listener: IPersonChangeListener?) {
mListenerList.unregister(listener)
}
}
RemoteCallbackList
增加与删除数据对应着 register()/unregister()
办法,然后咱们模仿一下服务端数据更新的状况,开个线程每隔 5 秒增加一个 Person 数据,然后告诉一下观察者:
//死循环 每隔5秒增加一次person,告诉观察者
private val serviceWorker = Runnable {
while (!Thread.currentThread().isInterrupted) {
Thread.sleep(5000)
val person = Person("name${Random().nextInt(10000)}")
log(AidlActivity.TAG, "服务端 onDataChange() 出产的 person = $person}")
mPersonList.add(person)
onDataChange(person)
}
}
private val mServiceListenerThread = Thread(serviceWorker)
//数据改变->告诉观察者
private fun onDataChange(person: Person?) {
//1. 运用RemoteCallbackList时,有必要首要调用beginBroadcast(), 最终调用finishBroadcast(). 得成对呈现
//这儿拿到的是监听器的数量
val callbackCount = mListenerList.beginBroadcast()
for (i in 0 until callbackCount) {
try {
//这儿try一下避免有反常时无法调用finishBroadcast()
mListenerList.getBroadcastItem(i)?.onPersonDataChanged(person)
} catch (e: RemoteException) {
e.printStackTrace()
}
}
//3. 最终调用finishBroadcast() 必不行少
mListenerList.finishBroadcast()
}
override fun onCreate() {
.....
mServiceListenerThread.start()
}
override fun onDestroy() {
super.onDestroy()
mServiceListenerThread.interrupt()
}
服务端完结好了,客户端就比较好办:
private val mPersonChangeListener = object : IPersonChangeListener.Stub() {
override fun onPersonDataChanged(person: Person?) {
log(TAG, "客户端 onPersonDataChanged() person = $person}")
}
}
private fun registerListener() {
remoteServer?.registerListener(mPersonChangeListener)
}
private fun unregisterListener() {
remoteServer?.asBinder()?.isBinderAlive?.let {
remoteServer?.unregisterListener(mPersonChangeListener)
}
}
由于是需求跨进程通讯的,所以需求承继自 IPersonChangeListener.Stub 然后生成一个监听器目标。最终输出日志如下:
服务端 onDataChange() 出产的 person = Person(name=name9398) hashcode = 130037351}
客户端 onPersonDataChanged() person = Person(name=name9398) hashcode = 217703225}
3.2.7 Binder 逝世告诉
服务端进程或许随时会被杀掉,这时需求在客户端能够被感知到 binder 现已逝世,然后做一些收尾清理作业或者进程从头衔接。有如下 4 种办法能知道服务端是否现已挂掉:
- 调用 binder 的
pingBinder()
检查,回来 false 则说明长途服务失效 - 调用 binder 的
linkToDeath()
注册监听器,当长途服务失效时,就会收到回调 - 绑定 Service 时用到的 ServiceConnection 有个
onServiceDisconnected()
回调在服务端断开时也能收到回调 - 客户端调用长途办法时,抛出
DeadObjectException(RemoteException)
写份代码验证一下,在客户端修正为如下:
private val mDeathRecipient = object : IBinder.DeathRecipient {
override fun binderDied() {
//监听 binder died
log(TAG, "binder died")
//移除逝世告诉
mService?.unlinkToDeath(this, 0)
mService = null
//从头衔接
connectService()
}
}
private val serviceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
this@AidlActivity.mService = service
log(TAG, "onServiceConnected")
//给binder设置一个逝世署理
service?.linkToDeath(mDeathRecipient, 0)
mRemoteServer = IPersonManager.Stub.asInterface(service)
}
override fun onServiceDisconnected(name: ComponentName?) {
log(TAG, "onServiceDisconnected")
}
}
绑定服务之后,将服务端进程杀掉,输出日志如下:
//第一次衔接
bindService true
onServiceConnected, thread = main
//杀掉服务端
binder died, thread = Binder:29391_3
onServiceDisconnected, thread = main
//重连
bindService true
onServiceConnected, thread = main
确实是监听到服务端断开衔接的时刻,然后从头衔接也是 ok 的。
留意:binderDied()
办法是运转在子线程的,onServiceDisconnected()
是运转在主线程的,假如要在这儿更新UI,得留意一下。
3.2.8 权限验证
有没有留意到,目前的 Service 是完全暴露的,任何 app 都能够拜访这个 Service 而且长途调用 Service 的服务,这样不太安全。能够在清单文件中加入自界说权限,然后在 Service 中校验一下客户端有没有这个权限即可。如下:
<permission
android:name="com.xfhy.allinone.ipc.aidl.ACCESS_PERSON_SERVICE"
android:protectionLevel="normal" />
客户端需求在清单文件中声明这个权限:
<uses-permission android:name="com.xfhy.allinone.ipc.aidl.ACCESS_PERSON_SERVICE"/>
服务端 Service 校验权限:
override fun onBind(intent: Intent?): IBinder? {
val check = checkCallingOrSelfPermission("com.xfhy.allinone.ipc.aidl.ACCESS_PERSON_SERVICE")
if (check == PackageManager.PERMISSION_DENIED) {
log(TAG,"没有权限")
return null
}
log(TAG,"有权限")
return mBinder
}