Android Service演义
(本文以Android 5.1為準)
1.概述
在Android平臺上,那種持續性工作一般都是由service來執行的。不少初學者總是搞不清service和線程、進程之間的關系,這當然會影響到他們開展具體的開發工作。
其實,簡單說起來,service和線程、進程是沒什么關系的。我們知道,在Android平臺上已經大幅度地弱化了進程的概念,取而代之的是一個個有意義的邏輯實體,比如activity、service等。Service實體必然要寄身到某個進程里才行,它也可以再啟動幾個線程來幫它干活兒。但是,說到底service只是一個邏輯實體、一個運行期上下文而已。
相比activity這種“操控UI界面的運行期上下文”,service這種上下文一般是沒有界面部分的。當然這里說的只是一般情況,有些特殊的service還是可以創建自己的界面的,比如當一個service需要顯現某種浮動面板時,就必須自己創建、銷毀界面了。
在Android系統內部的AMS里,是利用各種類型的Record節點來管理不同的運行期上下文的。比如以ActivityRecord來管理activity,以ServiceRecord來管理service。可是,線程這種東東可沒有對應的Record節點喔。一些初學者常常會在activity里啟動一個線程,從事某種耗時費力的工作,可是一旦activity被遮擋住,天知道它會在什么時候被系統砍掉,進而導致連應用進程也退出。從AMS的角度來看,它壓根就不知道用戶進程里還搞了個工作線程在干活兒,所以當它要干掉用戶進程時,是不會考慮用戶進程里還有沒有工作沒干完。
但如果是在service里啟動了工作線程,那么AMS一般是不會隨便砍掉service所在的進程的,所以耗時的工作也就可以順利進行了。
可是,我們也常常遇到那種“一次性處理”的工作,難道就不能臨時創建個線程來干活嗎?關于這一點,請大家留意,在Android平臺上應對這種情況的較好做法是創建一個IntentService派生類,而后覆蓋其onHandleIntent()成員函數。IntentService內部會自動為你啟動一個工作線程,并在工作線程里回調onHandleIntent()。當onHandleIntent()返回后,IntentService還會自動執行stopSelf()關閉自己。瞧瞧,多么自動化。
關于service,有兩個動作是誰都繞不開的,那就是startService()和bindService()。我們想知道,它們的內部機制到底是怎樣的?雖然網上已經有不少文章講述過這兩個動作,但是不同人理解技術的視角往往是不一樣的,本文我將以自己的視角來闡述它們。
?
2.Service機制
我們先來看一下Service類的代碼截選:
【frameworks/base/core/java/android/app/Service.java】
Service是個抽象類,它間接繼承于Context,其繼承關系如下圖所示:
看了這張圖,請大家務必理解,Service只是個“上下文”(Context)對象而已,它和進程、線程是沒什么關系的。
在AMS中,負責管理service的ServiceRecord節點本身就是個binder實體。當AMS向應用進程發出語義,要求其創建service對象時,會把ServiceRecord通過binder機制“傳遞”給應用進程。這樣,應用進程的ActivityThread在處理AMS發來的語義時,就可以得到一個合法的binder代理,這個binder代理最終會被記錄在Service對象中,如此一來,Service實體就和系統里的ServiceRecord關聯起來了。我們畫個圖來說明,假如一個應用進程里啟動了兩個不同的Service,那么當service創建成功之后,AMS和用戶進程之間就會形成如下關系示意圖:
當然,如果用戶進程和service再多一點兒也完全沒問題,此時會形成下圖:
我們對圖中的ApplicationThread并不陌生,它記錄在ActivityThread中mAppThread域中。每當系統新fork一個用戶進程后,就會自動執行ActivityThread的attach()動作,里面會調用:
final IActivityManager mgr = ActivityManagerNative.getDefault(); . . . . . .mgr.attachApplication(mAppThread); . . . . . .將ApplicationThread對象遠程“傳遞”給AMS,從而讓AMS得到一個合法的代理端。而當系統要求用戶進程創建service時,就會通過這個合法的代理端向用戶進程傳遞明確的語義。現在我們就從這里開始講解細節吧。
3.先說startService()
我們先來看啟動service的流程。要啟動一個service,我們一般是調用startService()。說穿了只是向AMS發起一個請求,導致AMS執行如下的startService()動作:
【frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java】
其中的mServices域是ActiveServices類型的,其startServiceLocked()函數的代碼截選如下:
【frameworks/base/services/core/java/com/android/server/am/ActiveServices.java】
看到了嗎?必須先通過retrieveServiceLocked()找到(或創建)一個ServiceRecord節點,而后才能執行后續的啟動service的動作。請大家注意,在Android frameworks里有不少這樣的動作,基本上都是先創建xxxRecord節點,而后再向目標用戶進程傳遞語義,創建具體的對象。
retrieveServiceLocked()的代碼截選如下:
private ServiceLookupResult retrieveServiceLocked(Intent service, ? ? ? ? String resolvedType, int callingPid, int callingUid, int userId, ? ? ? ? boolean createIfNeeded, boolean callingFromFg) {ServiceRecord r = null;. . . . . .ServiceMap smap = getServiceMap(userId);final ComponentName comp = service.getComponent();if (comp != null) {r = smap.mServicesByName.get(comp);}if (r == null) {Intent.FilterComparison filter = new Intent.FilterComparison(service);r = smap.mServicesByIntent.get(filter);}if (r == null) {. . . . . .// 從PKMS處查到ServiceInfo. . . . . .ComponentName name = new ComponentName(sInfo.applicationInfo.packageName, sInfo.name);. . . . . .r = smap.mServicesByName.get(name);if (r == null && createIfNeeded) {. . . . . .r = new ServiceRecord(mAm, ss, name, filter, sInfo, callingFromFg, res);. . . . . .smap.mServicesByName.put(name, r);smap.mServicesByIntent.put(filter, r);. . . . . .}. . . . . .}if (r != null) {. . . . . .return new ServiceLookupResult(r, null);}return null; }總之就是希望在AMS內部的相關表格里找到對應的ServiceRecord節點,如果找不到,就創建一個新節點,并插入到相應的表格中。
我手頭參考的是Android5.1的代碼,可以說這張表格大體上就是ServiceMap里的mServicesByName啦。當然,ServiceRecord節點一般還會同時記錄到其他表格里,比如mServicesByIntent表格,但現在我們不妨先只考慮mServicesByName,一切以便于理解為上。
事實上,在早期的Android代碼(比如Android 2.3版的代碼)中,是沒有那個ServiceMap的,那時的mServicesByName表格直接位于ActivityManagerService里,而且對應的域名叫作mServices。后來隨著Android的發展,需要支持多用戶以及其他一些概念,于是就搞出了個ServiceMap,并把原來的這張ServiceRecord表格搬到ServiceMap里了。我們可以畫一張示意圖,來說明Android代碼的變遷:
(Android 2.3版示意圖)
(Android 5.1版示意圖)
但是,不管ServiceRecord表格被放到哪里,其本質都是一致的。而AMS必須保證在實際啟動一個Service之前查到或創建對應的ServiceRecord節點。
在ServiceRecord類中有不少成員變量,其中有一個app域,專門用來記錄Service對應的用戶進程的ProcessRecord。當然,一開始這個app域是為null的,也就是說ServiceRecord節點還沒有和用戶進程關聯起來,待Service真正啟動之后,ServiceRecord的app域就有實際的值了。
現在我們繼續看startServiceLocked()函數,它在找到ServiceRecord節點之后,開始調用startServiceInnerLocked()。我們可以繪制一下相關的調用關系:
請注意上圖中bringUpServiceLocked()里的內容。此時大體上分三種情況:
1)如果ServiceRecord已經和Service寄身的用戶進程關聯起來了,此時ServiceRecord的app域以及app.thread域都是有值的,那么只調用 sendServiceArgsLocked()即可。這一步會讓Service間接走到大家熟悉的onStartCommand()。
2)如果尚未啟動Service寄身的用戶進程,那么需要調用mAm.startProcessLocked()啟動用戶進程。
3)如果Service寄身的用戶進程已經啟動,但尚未和ServiceRecord關聯起來,那么調用realStartServiceLocked(r, app, execInFg);這種情況下,會讓Service先走到onCreate(),而后再走到onStartCommand()。
我們重點看第三種情況,realStartServiceLocked()的調用示意圖如下:
看到了吧,無非是利用scheduleXXX()這樣的函數,來通知用戶進程去做什么事。以后大家看到這種以schedule打頭的函數,可以直接打開ActivityThread.java文件去查找其對應的實現函數。比如scheduleCreateService()的代碼如下:
【frameworks/base/core/java/android/app/ActivityThread.java】
它發出的CREATE_SERVICE消息,會由ActivityThread的內嵌類H處理,H繼承于Handler,而且是在UI主線程里處理消息的。可以看到,處理CREATE_SERVICE消息的函數是handleCreateService():
case CREATE_SERVICE:Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceCreate");handleCreateService((CreateServiceData)msg.obj);Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);break;另外,請大家注意realStartServiceLocked()傳給scheduleCreateService()函數的第一個參數就是ServiceRecord類型的r,而ServiceRecord本身是個Binder實體噢。待“傳到”應用進程時,這個Binder實體對應的Binder代理被稱作token,記在了CreateServiceData對象的token域中。現在CreateServiceData對象又經由msg.obj傳遞到消息處理函數里,并進一步作為參數傳遞給handleCreateService()函數。
handleCreateService()的代碼如下:
private void handleCreateService(CreateServiceData data) {. . . . . .LoadedApk packageInfo = getPackageInfoNoCheck(data.info.applicationInfo, data.compatInfo);Service service = null;. . . . . .java.lang.ClassLoader cl = packageInfo.getClassLoader();service = (Service) cl.loadClass(data.info.name).newInstance();. . . . . .ContextImpl context = ContextImpl.createAppContext(this, packageInfo);context.setOuterContext(service);Application app = packageInfo.makeApplication(false, mInstrumentation);// 注意,ServiceRecord實體對應的代理端,就是此處的data.token。service.attach(context, this, data.info.name, data.token, app,ActivityManagerNative.getDefault());service.onCreate();mServices.put(data.token, service);. . . . . . }簡單地說就是,先利用反射機制創建出Service對象:
service = (Service) cl.loadClass(data.info.name).newInstance();然后再調用該對象的onCreate()成員函數:
service.onCreate();而因為handleCreateService()函數本身是在用戶進程的UI主線程里執行的,所以service的onCreate()函數也就是在UI主線程里執行的。常常會有人告誡新手,不要在onCreate()、onStart()里執行耗時的操作,現在大家知道是為什么了吧,因為在UI主線程里執行耗時的操作不但會引起界面卡頓,嚴重的還會導致ANR報錯。
另外,在執行到上面的service.attach()時,那個和ServiceRecord對應的代理端token也傳進來了:
public final void attach( ? ? ? ? Context context, ? ? ? ? ActivityThread thread, String className, IBinder token, ? ? ? ? Application application, Object activityManager) {attachBaseContext(context);mThread = thread; ? ? ? ? ??mClassName = className;mToken = token; ? ? // 注意這個token噢,它的對端就是AMS里的ServiceRecord!mApplication = application;mActivityManager = (IActivityManager)activityManager;mStartCompatibility = getApplicationInfo().targetSdkVersion< Build.VERSION_CODES.ECLAIR; }可以看到,token記錄到ServiceRecord的mToken域了。
最后,新創建出的Service對象還會記錄進ActivityThread的mServices表格去,于是我們可以在前文示意圖的基礎上,再畫一張新圖:
這就是startService()啟動服務的大體過程。上圖并沒有繪制“調用startService()的用戶進程”,因為當Service啟動后,它基本上就和發起方沒什么關系了。
?
4.再說bindService()
接下來我們來說說bindService(),它可比startService()要麻煩一些。當一個用戶進程bindService()時,它需要先準備好一個實現了ServiceConnection接口的對象。ServiceConnection的定義如下:
public interface ServiceConnection {public void onServiceConnected(ComponentName name, IBinder service);public void onServiceDisconnected(ComponentName name); }在Android平臺上,每當用戶調用bindService(),Android都將之視作是要建立一個新的“邏輯連接”。而當連接建立起來時,系統會回調ServiceConnection接口的onServiceConnected()。另一方面,那個onServiceDisconnected()函數卻不是在unbindService()時發生的。一般來說,當目標service所在的進程意外掛掉或者被殺掉時,系統才會回調onServiceDisconnected(),而且,此時并不會銷毀之前的邏輯連接,也就是說,那個“邏輯連接”仍然處于激活狀態,一旦service后續再次運行,系統會再次回調onServiceConnected()。
在Android平臺上,要和其他進程建立邏輯連接往往都需要利用binder機制。那么,發起bindService()的用戶進程又是在哪里創建邏輯連接需要的binder實體呢?我們可以看看bindServiceCommon()函數的代碼截選:
【frameworks/base/core/java/android/app/ContextImpl.java】
請大家注意mPackageInfo.getServiceDispatcher()那一句,它返回的就是binder實體。此處的mPackageInfo是用戶進程里和apk對應的LoadedApk對象。getServiceDispatcher()的代碼如下:
【frameworks/base/core/java/android/app/LoadedApk.java】
也就是說,先嘗試在LoadedApk的mServices表中查詢ServiceDispatcher對象,如果查不到,就重新創建一個。ServiceDispatcher對象會記錄下從用戶處傳來的ServiceConnection引用。而且,ServiceDispatcher對象內部還含有一個binder實體,現在我們可以通過調用sd.getIServiceConnection()一句,返回這個內部的binder實體。
現在我們畫一張示意圖,來說明發起bindService()動作的用戶進程中的樣子:
LoadedApk中的mServices是常見的表中表形式,定義如下:
private final ArrayMap<Context, ArrayMap<ServiceConnection,?LoadedApk.ServiceDispatcher>> mServices這里比較有趣的是,第一層表的key類型為Context,大家不妨想一想,如果我們的應用里有兩個activity都去綁定同一個Service會怎么樣?很明顯,在mServices表里就會有兩個不同的子表,也會創建出兩個不同的ServiceDispatcher對象,而且即便我們在調用bindService()時傳入同一個ServiceConnection對象,依舊會有兩個ServiceDispatcher對象。示意圖如下:
說完了發起bindService()動作的用戶進程一側,接下來我們再來看AMS一側的代碼。對應的bindService()代碼如下:
【frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java】
主要工作集中在mServices.bindServiceLocked()一句。請注意bindService()的倒數第三個參數,它對應的就是上圖中ServiceDispatcher的mIServiceConnection域記錄的binder實體。至于bindService()的第二個參數IBinder token,其實記錄的是發起綁定動作的Activity的token,當然,如果發起者是其他非Activity型的對象,那么這個參數應該是null。
上面的代碼最終走到mServices.bindServiceLocked()一句。我們摘錄一下bindServiceLocked()的代碼:
【frameworks/base/services/core/java/com/android/server/am/ActiveServices.java】
盡管這個函數里有不少技術細節,而且涉及到多個映射表,但它的總體意思大概是這樣的。每當用戶調用bindService()時,Android都將之看作是要建立一個新的“邏輯連接”,而每個邏輯連接都對應一個ConnectionRecord節點,所以最終的表現肯定是向ServiceRecord內部的某張映射表里添加一個新的ConnectionRecord節點。當然,在實際運作時,這個節點還會記錄進其他幾個映射表里(比如系統總映射表),但這不影響我們理解問題。
那么為什么系統里會有那么多映射表呢?這大概是為了在復雜的網狀聯系中快速便捷地找到相關的節點。我們知道,一個用戶進程可以綁定多個Service,而一個Service也可以被多個用戶進程綁定,這就是網狀結構。示意圖如下:
前文我們已經說過,綁定時使用的ServiceConnection對象在發起端其實對應了一個ServiceDispatcher對象,現在,ServiceDispatcher的mIServiceConnection傳遞給bindServiceLocked(),也就是那個IServiceConnection connection參數。如果系統要建立“邏輯連接”,那么就需要把IServiceConnection代理端記錄到ConnectionRecord節點里。
ConnectionRecord c = new ConnectionRecord(b, activity, connection, flags,?clientLabel, clientIntent);另外,請大家注意bindServiceLocked()里那個重要的AppBindRecord節點。
AppBindRecord b = s.retrieveAppBindingLocked(service, callerApp); ?// 注意這個b!相傳對于一個Service而言,有多少應用和它建立了綁定關系,就會有多少個AppBindRecord節點,要不怎么叫App-Bind呢?當然,一個應用里可以有多個地方發起綁定動作,所以AppBindRecord里需要用一個ArraySet<ConnectionRecord>記錄下每個綁定動作對應的邏輯連接節點。
有了這些認識后,我們可以畫一張“發起端用戶進程”和“系統進程”之間的示意圖:
在ConnectionRecord被記錄進合適的表后,要開始和目標service建立連接了。我們可以看到,bindServiceLocked()會嘗試呼叫起目標service。
看到這句if語句,大家應該知道調用bindService()時為什么常常要加上BIND_AUTO_CREATE了吧:
bindService(intent, conn, Context.BIND_AUTO_CREATE);?
假設目標Service之前已經啟動過,那么現在的綁定流程會走到if (s.app != null && b.intent.received)分支里,于是調用到c.conn.connected()。
? ? ? ? if (s.app != null && b.intent.received) {. . . . . .c.conn.connected(s.name, b.intent.binder); ?// 注意這個!. . . . . .if (b.intent.apps.size() == 1 && b.intent.doRebind) {requestServiceBindingLocked(s, b.intent, callerFg, true);}} else if (!b.intent.requested) {requestServiceBindingLocked(s, b.intent, callerFg, false); ?}但是,如果Service之前并未啟動,而且這次是我們首次綁定service,那么不就到不了c.conn.connected()了嗎?此時應該會走到else if 分支,調用到requestServiceBindingLocked(),該函數主要是向目標service發起綁定的請求,但是現在連service寄身的進程可能都還沒有啟動,request又有什么意義呢?所以,此處調用requestServiceBindingLocked()也許不會有什么重大意義。這并不是說requestServiceBindingLocked()不重要,而是說它真正起作用的地方也許不在這里。為了說明問題,我們得看一下剛剛調用的bringUpServiceLocked()函數:
private final String bringUpServiceLocked(ServiceRecord r, ? ? ? ? int intentFlags, boolean execInFg, boolean whileRestarting) {. . . . . .ProcessRecord app;. . . . . .if (app == null) {if ((app = mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,"service", r.name, false, isolated, false)) == null) {. . . . . .bringDownServiceLocked(r);return msg;}. . . . . .}// 注意此處的mPendingServices!if (!mPendingServices.contains(r)) {mPendingServices.add(r);}. . . . . . }bringUpServiceLocked()通過調用mAm.startProcessLocked()啟動目標service寄身的進程,而后會把ServiceRecord節點記入mPendingServices數組列表中。
待后續service寄身的進程成功啟動后,會輾轉調用到attachApplicationLocked(),該函數的代碼截選如下:
boolean attachApplicationLocked(ProcessRecord proc, String processName) throws RemoteException {. . . . . .if (mPendingServices.size() > 0) {ServiceRecord sr = null;. . . . . .for (int i=0; i<mPendingServices.size(); i++) {sr = mPendingServices.get(i);. . . . . .mPendingServices.remove(i);i--;proc.addPackage(sr.appInfo.packageName, sr.appInfo.versionCode, mAm.mProcessStats); realStartServiceLocked(sr, proc, sr.createdFromFg);. . . . . .}. . . . . . }也就是說,當目標service寄身的進程啟動后,會從mPendingServices數組列表中把ServiceRecord節點刪除,并進一步調用realStartServiceLocked():
private final void realStartServiceLocked(ServiceRecord r, ? ? ? ? ProcessRecord app, boolean execInFg) throws RemoteException {. . . . . .r.app = app;. . . . . .app.services.add(r);. . . . . .app.thread.scheduleCreateService(r, r.serviceInfo,mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),app.repProcState);. . . . . .requestServiceBindingsLocked(r, execInFg);. . . . . .sendServiceArgsLocked(r, execInFg, true);. . . . . . }此處調用的app.thread.scheduleCreateService()會間接導致目標service走到大家熟悉的onCreate()。而后還會調用requestServiceBindingsLocked()。這里大概才是requestServiceBindingLocked()真正起作用的地方。
requestServiceBindingsLocked()的代碼如下:
private final void requestServiceBindingsLocked(ServiceRecord r, boolean execInFg) {for (int i=r.bindings.size()-1; i>=0; i--) {IntentBindRecord ibr = r.bindings.valueAt(i);if (!requestServiceBindingLocked(r, ibr, execInFg, false)) {break;}} } private final boolean requestServiceBindingLocked(ServiceRecord r, ? ? ? ? IntentBindRecord i, boolean execInFg, boolean rebind) {. . . . . .r.app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind,r.app.repProcState);if (!rebind) {i.requested = true;}i.hasBound = true;i.doRebind = false;. . . . . . }終于看到調用scheduleBindService()了。
到了“Service所屬的進程”里,scheduleBindService()執行的代碼如下:
public final void scheduleBindService(IBinder token, Intent intent, ? ? ? ? boolean rebind, int processState) {updateProcessState(processState, false);BindServiceData s = new BindServiceData();s.token = token;?? ??? ?// 對應AMS里的ServiceRecords.intent = intent;s.rebind = rebind;......sendMessage(H.BIND_SERVICE, s); }?
所發出的BIND_SERVICE消息,會導致service寄身的進程走到handleBindService():
【frameworks/base/core/java/android/app/ActivityThread.java】
回調了目標service的onBind(),而后向AMS發布自己,即調用publishService()。
publishService()的第一個參數指代AMS里的ServiceRecord節點,而最后一個參數是目標Service的onBind()函數返回的服務對象。因此publish函數其實起的是銜接的作用。
在AMS一側,publish動作最終會走到publishServiceLocked():
【frameworks/base/services/core/java/com/android/server/am/ActiveServices.java】
這里又牽扯到ServiceRecord的connections映射表,在介紹bindServiceLocked()函數時,我們其實已經看到過幾句相關的代碼了,現在再列舉一下:
? ? ? ? IBinder binder = connection.asBinder();ArrayList<ConnectionRecord> clist = s.connections.get(binder);. . . . . .clist.add(c);也就是說,我們在綁定服務時創建的那個ConnectionRecord節點,會同時將該節點記錄進ServiceRecord的connections映射表,而映射表的key值就是邏輯上指向發起端的IServiceConnection代理。
為什么要有這個映射表呢?很簡單,就是為了快速地找出所有使用相同IServiceConnection.Stub對象,完成綁定動作的ConnectionRecord節點。請大家設想,我們完全可以在一個Activity里,使用同一個ServiceConnection對象發起多次綁定同一個service的動作,發起綁定時使用的intent也許會不同,但最終有可能綁定到同一個service,此時,上面代碼中s.connections.get(binder)得到的ArrayList就會含有多個元素啦。
好,介紹完connections映射表,我們繼續看publishServiceLocked()里的那句c.conn.connected()。ConnectionRecord節點的conn成員也是IServiceConnection類型的代理端,所以這一句調用最終是遠程調用到發起端的ServiceDispatcher里的mIServiceConnection對象,該對象是InnerConnection類型的。
InnerConnection的connected()函數如下:
【frameworks/base/core/java/android/app/LoadedApk.java】
而ServiceDispatcher的connected()函數如下:
public void connected(ComponentName name, IBinder service) {if (mActivityThread != null) {mActivityThread.post(new RunConnection(name, service, 0));} else {doConnected(name, service);} } private final class RunConnection implements Runnable {. . . . . .public void run() {if (mCommand == 0) {doConnected(mName, mService);} else if (mCommand == 1) {doDeath(mName, mService);}}. . . . . . }?
【frameworks/base/core/java/android/app/LoadedApk.java】
public void doConnected(ComponentName name, IBinder service) {. . . . . .info = new ConnectionInfo();info.binder = service;info.deathMonitor = new DeathMonitor(name, service);try {service.linkToDeath(info.deathMonitor, 0);mActiveConnections.put(name, info);} catch (RemoteException e) {. . . . . .}. . . . . .// If there is a new service, it is now connected.if (service != null) {mConnection.onServiceConnected(name, service); ? // 終于看到onServiceConnected了!} }
至此,我們終于看到大家熟悉的onServiceConnected()回調啦!而傳來的service參數,就是我們希望綁定的那個service提供的binder代理。現在我們可以說已經打通了bindService()動作涉及的三方關系:發起方、AMS、目標Service。我們不妨再畫一張圖看看:
有關Service的基本機制和啟動流程,我們就先說這么多吧。以后我們再補充其他方面的內容。
原文地址:?https://my.oschina.net/youranhongcha/blog/710046
總結
以上是生活随笔為你收集整理的Android Service演义的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 品茗论道说广播(Broadcast内部机
- 下一篇: 深入讲解Android Property