service常見的有2種方式,本地service以及remote service。 這2種的生命周期,同activity的通信方式等,都不相同。 關於這2種service如何使用,這裡不做介紹,只是介紹一些被遺漏的地方 1.遠程Service(AIDL方式) package com.joyfulm ...
service常見的有2種方式,本地service以及remote service。
這2種的生命周期,同activity的通信方式等,都不相同。
關於這2種service如何使用,這裡不做介紹,只是介紹一些被遺漏的地方
1.遠程Service(AIDL方式)
package com.joyfulmath.samples.basecontrol; import android.app.Activity; import android.content.ComponentName; import android.content.Intent; import android.content.ServiceConnection; import android.os.IBinder; import android.os.RemoteException; import com.joyfulmath.samples.R; import com.joyfulmath.samples.TraceLog; import org.androidannotations.annotations.Click; import org.androidannotations.annotations.EActivity; /** * Created by Administrator on 2016/10/11 0011. * service connect activity samples */ @EActivity(R.layout.activity_connect_service) public class ServiceConActivity extends Activity { private ISamplesAidlInterface binder; private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { TraceLog.i(); binder = ISamplesAidlInterface.Stub.asInterface(service); if(binder!=null) { try { binder.registerCallBack(mCallBack); } catch (RemoteException e) { e.printStackTrace(); } } } @Override public void onServiceDisconnected(ComponentName name) { TraceLog.i(); binder = null; } }; private ICallBack.Stub mCallBack = new ICallBack.Stub() { @Override public void onServiceStateChanged(int s) throws RemoteException { TraceLog.i(String.valueOf(s)); } }; public void bindSamplesService() { TraceLog.i(); Intent intent = new Intent(this,ServiceSamples.class); // intent.setAction("com.joyfulmath.service.samples"); bindService(intent,connection,BIND_AUTO_CREATE); } public void unBindSamplesService() { TraceLog.i(); if(binder!=null) { try { binder.unRegisterCallBack(mCallBack); } catch (RemoteException e) { e.printStackTrace(); } } unbindService(connection); } @Click(R.id.btn_connect) void connectClick() { TraceLog.i(); bindSamplesService(); } @Click(R.id.btn_unconnect) void unConnectClick() { TraceLog.i(); unBindSamplesService(); } @Click(R.id.btn_do) void doAction() { if(binder!=null) { try { int r = binder.doBackground("action"); TraceLog.i(String.valueOf(r)); } catch (RemoteException e) { e.printStackTrace(); } } } @Override protected void onDestroy() { super.onDestroy(); TraceLog.i(); unBindSamplesService(); } }ServiceConActivity
package com.joyfulmath.samples.basecontrol; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.support.annotation.Nullable; import com.joyfulmath.samples.TraceLog; /** * Created by Administrator on 2016/10/11 0011. */ public class ServiceSamples extends Service { private SamplesBinder samplesBinder = null; private RemoteCallbackList<ICallBack> mCallbacks = new RemoteCallbackList<>(); @Nullable @Override public IBinder onBind(Intent intent) { TraceLog.i(); return samplesBinder; } @Override public int onStartCommand(Intent intent, int flags, int startId) { TraceLog.i(); return super.onStartCommand(intent, flags, startId); } @Override public boolean onUnbind(Intent intent) { TraceLog.i(); return super.onUnbind(intent); } @Override public void onDestroy() { super.onDestroy(); TraceLog.i(); mCallbacks.kill(); } @Override public void onCreate() { super.onCreate(); samplesBinder = new SamplesBinder(); TraceLog.i(); } public class SamplesBinder extends ISamplesAidlInterface.Stub{ @Override public int doBackground(String action) throws RemoteException { TraceLog.i(); return -1; } @Override public void findPerson(PersonCall p) throws RemoteException { notifyFindPerson(); } @Override public void registerCallBack(ICallBack cb) throws RemoteException { mCallbacks.register(cb); } @Override public void unRegisterCallBack(ICallBack cb) throws RemoteException { mCallbacks.unregister(cb); } } private void notifyFindPerson() throws RemoteException { try{ synchronized (this){ int n = mCallbacks.beginBroadcast(); for(int i=0;i<n;i++){ mCallbacks.getBroadcastItem(i).onServiceStateChanged(0x11); } mCallbacks.finishBroadcast(); } }catch (RemoteException e) { TraceLog.i(e.getMessage()); } } }ServiceSamples
這是簡單的service & activity交互的代碼。
在看關鍵的AIDL代碼:
// ISamplesAidlInterface.aidl package com.joyfulmath.samples.basecontrol; import com.joyfulmath.samples.basecontrol.PersonCall; import com.joyfulmath.samples.basecontrol.ICallBack; // Declare any non-default types here with import statements interface ISamplesAidlInterface { int doBackground(in String action); void findPerson(in PersonCall p); void registerCallBack(ICallBack cb); void unRegisterCallBack(ICallBack cb); }
// PersonCall.aidl package com.joyfulmath.samples.basecontrol; parcelable PersonCall;
// ICallBack.aidl package com.joyfulmath.samples.basecontrol; // Declare any non-default types here with import statements interface ICallBack { void onServiceStateChanged(int s); }
這裡有3個問題,我們從頭往下看,就能明白。
1)為什麼在其他APK調用該service的時候,aidl的文件包必須一致
2)為什麼要自定義PersonCall.aidl
3) ICallBack是什麼玩意。
4)多個APK連接同一個service,該service會產生多個實例嗎。怎麼保證不衝突呢?
其實1) & 2)的問題是一樣的,都是基於java的classloader原理。
同一個類,必須在同一個包內,並且由同一個classloader載入,才能表示他們是同一個類。
所以AIDL在拷貝的時候,必須保證是同一個包名(AIDL在打包的時候會生成java文件。)
並且自定義的參數class,必須有AIDL定義,才能讓其他APK可以理解該類。當然為了傳輸,需要繼承自pracacle
3)關於service回調的工作,是由RemoteCallbackList 專門用來回調通知client端。
首先在client端定義的listener,遠端是沒有實體對象的,所以在作為參數傳入到遠端的時候,會複製一份,並且與binder綁定。
下麵來看看真正的乾貨,第4個問題:
我們分成幾個小問題來解答。
I,如果service和activity不在同一個app,那麼activity可以通過startservice or bindservice的方式啟動該service嗎?如果不行,怎麼啟動該service。
經測試,可以通過bindservice的方式啟動。
II,如果2個client同時對同一個service做bind操作,會有什麼結果?
binderservice都會返回成功操作,並且前一個client,沒有收到disconnect的通知。
此時service的操作,會返回對後面一個client傳遞的參數的操作,也就是只有一份service實例,會同時binder2個client,but只會處理後面一個client的行為。
所以此時,service應該阻止由其他client端輸入的請求,並且可以提供介面給到client,由他決定是否關閉這個binder。
下麵是bindservice的flag參數說明:
常量名 | 值 | 含義 |
---|---|---|
BIND_ABOVE_CLIENT | 8 | 如果當綁定服務期間遇到OOM需要殺死進程,客戶進程會先於服務進程被殺死。 |
BIND_ADJUST_WITH_ACTIVITY | 128 | 允許客戶進程提升被綁定服務進程的優先順序 |
BIND_ALLOW_OOM_MANAGEMENT | 16 | 如果綁定服務期間遇到OOM需要殺死進程,被綁定的服務進程會被OOM列入獵殺對象中。 |
BIND_AUTO_CREATE | 1 | 若綁定服務時服務未啟動,則會自動啟動服務。 註意,這種情況下服務的onStartCommand 仍然未被調用(它只會在顯式調用startService 時才會被調用)。 |
BIND_DEBUG_UNBIND | 2 | 使用此標誌綁定服務之後的unBindService 方法會無效。 這種方法會引起記憶體泄露,只能在調試時使用。 |
BIND_IMPORTANT | 64 | 被綁定的服務進程優先順序會被提到FOREGROUND級別 |
BIND_NOT_FOREGROUND | 4 | 被綁定的服務進程優先順序不允許被提到FOREGROUND級別 |
BIND_WAIVE_PRIORITY | 32 | 被綁定的服務進程不會被OOM列入獵殺對象中。 |
可以看到,他們是可以組合使用的。
如果在第三方APP 使用service
第一步:在java同級目錄下,創建aidl文件夾
第二步:把AIDL文件copy該目錄下,註意保持包名一致。
第三步:把自定義的class,copy到java目錄下,包名一致。
第四步:啟動service需要用顯示的定義(android5.0開始):
public void bindSamplesService() { TraceLog.i(); // ComponentName name = new ComponentName("com.joyfulmath.samples","ServiceSamples"); Intent intent = new Intent(); // intent.setComponent(name); intent.setAction("com.joyfulmath.service.samples"); intent.setPackage("com.joyfulmath.samples"); intent.putExtra("cookie","third"); bindService(intent,connection,BIND_AUTO_CREATE|BIND_ADJUST_WITH_ACTIVITY); }
android 5.1上,親測,該方式可行,使用componentName不行,需進一步研究。
2.startservice
startservice可以跨進程調用,也就是調用其他app的service。
public void bindSamplesService() { TraceLog.i(); Intent intent = new Intent(); intent.setAction("com.joyfulmath.service.samples"); intent.setPackage("com.joyfulmath.samples"); intent.putExtra("cookie","third"); // bindService(intent,connection,BIND_AUTO_CREATE|BIND_ADJUST_WITH_ACTIVITY); startService(intent); }
關於startservice,你所不知道的內容如下:
public abstract ComponentName startService(Intent service);
該方法還會返回一個ComponentName ,這個name就是表示package+name,因為classname會重覆。
* @return If the service is being started or is already running, the * {@link ComponentName} of the actual service that was started is * returned; else if the service does not exist null is returned.
註釋說的很清楚。
* <p>This function will throw {@link SecurityException} if you do not * have permission to start the given service.
沒有許可權,就會報安全異常。
跨進程啟動service的流程:
如果考慮到進程,那麼我們就應該暫時撇開四大組件的概念。
從操作系統,進程線程的本質來考慮問題。
Activity是生存在一個ActivityThread。它就是一個app(一般對應一個進程)的主線程。
那麼service在哪裡,也在主線程中。可以通過tracelong來認證這個結論。
所以說,service雖然是有獨立生命周期的一大組件,但是它預設還是在主線程中。所以也會ANR。
既然要跨進程,必然也需要binder機制,可能我們看不到而已。
大致流程如下:
從主進程調用到AMS進程(SystemServer進程),創建新的進程。這個過程需要用到binder通信。
從新進程回調AMS,獲取新進程的一些信息。關鍵是這些信息是從源進程傳遞過來+manifest註冊的。
從AMS回到新進程,直到新進程啟動(同時包括service啟動)
這三步都是跨進程啟動service的過程,都需要binder機制來通信。
具體詳細流程,後續會繼續分析。
3.process lifecycle
關於service對應的lifecycle已經在activity那篇里說明瞭。
4.binder機制
關於這塊之前以及有相關博文,接下來打算再詳細分析下。binder機制是android最重要的基石。
server 會在通過servermanger註冊它,然後提供遠程調用的句柄,通過binder機制
client獲取servermanger不需要通過binder,應為servermanger是預設的句柄為0,可以直接獲取到。
所以說,servermanager是在等待client端發送請求,然後它去尋找以及註冊的server,得到它的遠程對象,進行通信。