前言
IPC 系列文章:
建議按順序閱讀。
Android IPC 之Service 還可以這么理解
Android IPC 之Binder基礎
Android IPC 之Binder應用
Android IPC 之AIDL應用(上)
Android IPC 之AIDL應用(下)
Android IPC 之Messenger 原理及應用
Android IPC 之服務端回調
Android IPC 之獲取服務(IBinder)
Android Binder 原理換個姿勢就頓悟了(圖文版)
Android四大組件:Activity、Service、BroadcastReceiver、ContentProvider。它們的作用分別是:
Activity--->配合View展示界面
Service--->長時間在后臺運行不與用戶直接交互
BroadcastReceiver--->接收廣播
ContentProvider--->提供數據給其他模塊使用
本篇文章著重分析Service,通過它,你將了解到:
1、Service 開啟與停止
2、Service 執行耗時操作
3、Service 與Thread、Manager關系
4、Service 進程間通信初相識
1、Service 開啟與停止
先定義一個Service類,名為MyService,繼承自Service。
public class MyService extends Service {
public MyService() {
super();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
//必須重寫該方法,該方法為抽象方法
//綁定開啟Service會調用該方法
return null;
}
@Override
public void onCreate() {
//Service初次創建會調用該方法,我們可以做一些初始化操作, 與onDestroy()相對
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//每次顯示啟動Service都會調用該方法
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
//Service銷毀時調用該方法,在該方法里我們可以做釋放資源的操作,與onCreate()相對
super.onDestroy();
}
}
這是一個最簡單的Service Demo。
接著想要使用該Service,還需要在AndroidManifest.xml里注冊:
<service android:name=".service.MyService">
</service>
Service定義好了,怎么使用呢?開啟Service有兩種方式:
1、顯示開啟------> startService(Intent intent)
2、綁定開啟------> bindService(Intent intent)
這倆都是Context里的方法
顯示開啟Service
構造Intent,傳入startService(Intent intent)里。
private void startService() {
Intent intent = new Intent(this, MyService.class);
startService(intent);
}
通過此種方式,Service調用方法如下:
需要注意的點是:
當再次開啟一個已經存在的Service的時候,onStartCommand(xx)依然會被調用。
顯示關閉Service
顯示開啟Service后,Service就已經啟動了。
若要關閉Service,通過如下方法:
private void stopService() {
Intent intent = new Intent(this, MyService.class);
stopService(intent);
}
或者在Service做完了事自己結束:
stopSelf();
綁定開啟Service
通過上面的例子,可以看出顯示開啟Service后,調用者就和Service沒有關聯了。比如調用者是個Activity,Service的作用是不斷地計數。在顯示開啟Service的場景下,會存在兩個問題:
1、Activity無法直接(間接通過廣播等方法)拿到Service計數結果,也就是說沒法拿到Service引用。
2、當Activity退出的時候,若不是主動停止Service,那么Service將不會被關閉。不太恰當的比喻是:"管生不管養"
而綁定開啟Service正好可以解決上面的問題。
為了實現綁定開啟Service,在上面Demo的基礎上稍微做修改。
定義MyBinder繼承自Binder:
public class MyBinder extends Binder {
//持有Service引用
private Service service;
public MyBinder(Service service) {
this.service = service;
}
//返回Service引用
public Service getService() {
return service;
}
}
在顯示開啟Service過程中,我們重寫了onBind(xx),直接返回的是null,該場景下該方法并沒有調用。而當綁定開啟Service時,需要返回IBiner的引用給綁定者使用。
#MyService.java
public IBinder onBind(Intent intent) {
return new MyBinder(this);
}
返回的是MyBinder對象的引用,該對象持有了MyService引用。
綁定者在哪里接收IBinder的引用呢?
在Activity里定義ServiceConnection匿名內部類:
ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//service即是從onBind(xx)方法返回的(綁定者和Service同一進程)
MyBinder myBinder = (MyBinder)service;
//獲取Service的引用
MyService myService = (MyService)myBinder.getService();
}
@Override
public void onServiceDisconnected(ComponentName name) {
//Service被銷毀時調用(內存不足等,正常解綁不會走這)
}
};
有了ServiceConnection 引用,接著就需要和Service建立聯系,建立聯系的過程即是綁定開啟Service的過程。
private void bindService() {
Intent intent = new Intent(this, MyService.class);
bindService(intent, serviceConnection, BIND_AUTO_CREATE);
}
從上面可以看出,綁定開啟的流程:
1、將Service和ServiceConnection建立聯系(綁定開啟)
2、在Service的onBind(xx)方法里返回IBinder引用,該引用持有Service引用
3、綁定成功后會調用ServiceConnection的onServiceConnected(xx)返回IBinder引用
4、通過IBinder引用就能拿到Service引用,進而操作Service
以上回答了上面的第一個問題:綁定者(Activity)無法拿到Service引用。
來看看綁定開啟Service調用方法流程:
解綁Service
既然綁定時傳入了ServiceConnection引用,可以猜測解綁時也需要傳入ServiceConnection引用,不然無法確定解綁哪個Service。
private void unBindService() {
unbindService(serviceConnection);
}
手動調用該方法即可解綁Service。
當Activity綁定開啟Service后,若是Activity銷毀了,那么相應的Service也會被銷毀掉。這就解答了第二個問題。
值得注意的是:
若是顯示開啟了Service,則無法用解綁方法關閉Service。
若是綁定開啟了Service,則無法用顯示關閉Service方法。
2、Service 執行耗時操作
在Service的onCreate(xx)方法里循環計數:
#MyService.java
@Override
public void onCreate() {
super.onCreate();
while(true) {
count++;
try {
Thread.sleep(1000);
} catch (Exception e) {
}
}
}
服務開啟后,過一會就會提示ANR錯誤,說明onCreate(xx)是在主線程執行的。
來看看onCreate(xx)調用棧。
#ActivityThread.java
private class ApplicationThread extends IApplicationThread.Stub {
...
//IPC通信調用該方法,表明要創建服務
public final void scheduleCreateService(IBinder token,
ServiceInfo info, CompatibilityInfo compatInfo, int processState) {
updateProcessState(processState, false);
CreateServiceData s = new CreateServiceData();
s.token = token;
s.info = info;
s.compatInfo = compatInfo;
//發送Message
sendMessage(H.CREATE_SERVICE, s);
}
...
}
//中間調用省略
private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
...
Message msg = Message.obtain();
msg.what = what;
msg.obj = obj;
msg.arg1 = arg1;
msg.arg2 = arg2;
if (async) {
msg.setAsynchronous(true);
}
//mH 為在ActivityThread 里構造的Handler,也就是說在主線程里構造的Handler。
mH.sendMessage(msg);
}
再看看接收Message的地方:
#ActivityThread.java
public void handleMessage(Message msg) {
switch (msg.what) {
...
case CREATE_SERVICE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceCreate: " + String.valueOf(msg.obj)));
//創建Service
handleCreateService((CreateServiceData) msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case BIND_SERVICE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceBind");
//綁定Service
handleBindService((BindServiceData) msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case UNBIND_SERVICE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceUnbind");
handleUnbindService((BindServiceData) msg.obj);
//解綁Service
schedulePurgeIdler();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
...
}
}
以上分支最終會調用到MyService重寫的onCreate(xx)、onBind(xx)、onUnbind(xx)里,這些方法都是在主線程里被調用的。
既然Service各個方法是在主線程里執行,那么想要實現計數功能得子開啟線程來完成此事。
#MyService.java
@Override
public void onCreate() {
super.onCreate();
new Thread(new Runnable() {
@Override
public void run() {
while(true) {
Log.d("time:", count + "");
count++;
try {
Thread.sleep(1000);
} catch (Exception e) {
}
}
}
}).start();
}
public int getCount() {
return count;
}
Service一直在計數,計數結果怎么通知給調用者呢,此處假設調用者是Activity。
依然是兩種方式:
1、如果是顯示開啟的Service,則Service可選擇廣播將數據發送給Activity
2、如果是綁定開啟的Service,Activity拿到IBinder引用,進而拿到Service引用,最終可以調用getCount()獲得計數值,并更新UI。
當然如果覺得每次開啟子線程很麻煩,可以選擇Android 提供的IntentService,該類里封裝了HandlerThread,并提供回調方法用以執行耗時任務。
3、Service 與Thread、Manager關系
Service 與 Thread聯系
既然Service無法直接執行耗時操作,那么需要Service干嘛呢,還不如直接開啟子線程執行任務呢?
前面說了:Service是長時間在后臺運行。
實際上說的是Service的生命周期,也就是說Service對象一直存在,當我們需要使用Service的時候,通過Intent或者IBinder找到它,進而使用它提供的功能。
同樣實現計數功能:
- 如果直接在Activity里開啟Thread計數,當Activity退出的時候,要把Thread關閉了。再次開啟Activity的時候,已經找不到Thread引用了,無法繼續上次的累計計數。再者,就算不考慮內存泄漏,Activity退出時候不關閉Thread,再次開啟Activity的時候,依然找不到Thread引用。
- 另外如果想將計數功能抽出來,供多個Activity使用,直接使用Thread也無法實現多Activity共用計數功能。
上面問題的本質就是需要維護一個對Thread對象的引用。而Thread僅僅是個工具而已,沒必要維護全局的引用(那是線程池要做的工作)。
Service 與 Manager
既然維護Thread全局引用方法不太推薦,那么實現一個單例的Manager(管理類)來持有Thread,進而使用Thread執行耗時任務,而外界通過調用這個Manager來獲取數據,如下:
public class CountManager {
private static volatile CountManager instance;
private CountManager(){
startCount();
}
private int count = 0;
public static CountManager getInstance() {
if (instance == null) {
synchronized (CountManager.class) {
if (instance == null) {
instance = new CountManager();
}
}
}
return instance;
}
private void startCount() {
new Thread(new Runnable() {
@Override
public void run() {
while(true) {
Log.d("time:", count + "");
count++;
try {
Thread.sleep(1000);
} catch (Exception e) {
}
}
}
}).start();
}
public int getCount() {
return count;
}
}
事實上不少的項目都是采用Activity + Manager方式來實現頁面展示 + 數據獲取。
Activity展示UI,后臺通過Manager獲取數據,如從數據庫獲取或者從從網絡獲取等,最后將數據反饋給Activity用以刷新UI。
到此你可能疑惑了,都有了Manager了,Service還有使用的必要嗎?
答案是肯定的。
Service作為Android 四大組件之一,是廣泛使用于Android 系統里的。
1、Service可以調整優先級,盡可能避免在資源緊張的時候被銷毀
2、借助Service + Binder,實現Android 進程間通信
4、Service 進程間通信初相識
之前的例子分析的都是調用者和被調用處于同一進程,試想一下,如果它們不在同一進程還能互相調用嗎?如進程A里的Activity需要使用進程B里的CountManager,顯然無法直接調用。
而對于Service來說,借助于Binder可以實現此功能。
進程A的Activity展示UI需要數據,這些數據從進程B獲取。進程B開啟Service生產數據,進程A通過綁定進程B的Service,從而獲得IBinder引用,最終調用Service的方法獲取數據。關鍵點就是中間的連接橋梁--Binder。
可以看出,Service的重點應該是在進程間通信。
接下來的文章,將重點分析IPC 過程涉及的Binder、AIDL等知識。
本文基于Android 10.0
您若喜歡,請點贊、關注,您的鼓勵是我前進的動力
持續更新中,和我一起步步為營學習Android
1、Android各種Context的前世今生
2、Android DecorView 必知必會
3、Window/WindowManager 不可不知之事
4、View Measure/Layout/Draw 真明白了
5、Android事件分發全套服務
6、Android invalidate/postInvalidate/requestLayout 徹底厘清
7、Android Window 如何確定大小/onMeasure()多次執行原因
8、Android事件驅動Handler-Message-Looper解析
9、Android 鍵盤一招搞定
10、Android 各種坐標徹底明了
11、Android Activity/Window/View 的background
12、Android Activity創建到View的顯示過
13、Android IPC 系列
14、Android 存儲系列
15、Java 并發系列不再疑惑
16、Java 線程池系列