Service
什么是Service
在后台長期運行的沒有界面的組件。其他組件可以啟動Service讓他在后台運行,或者綁定Service與它進行交互,甚至實現進程間通信(IPC)。例如,可以讓服務在后台處理網絡交互,播放音樂,文件I/O,或者與ContentProvider交互。
創建一個Service
- 新建一個類,繼承Service,重寫相關方法,如onBind,onUnBind,onCreate,onDestorey。
- 在AndroidManifest.xml中配置Service和相關權限
<manifest ... > ... <application ... > <service android:name=".MyService" /> ... </application> </manifest>
開啟服務:
Intent service = new Intent(this,Service.class); startService(service);
停止服務:
Intent service = new Intent(this,Service.class); stopService(service);
綁定服務:
private boolean mIsBound = false; private ServiceConnection mConnection = new ServiceConnection() { // 服務連接成功回調 @Override public void onServiceConnected(ComponentName name, IBinder service) { MyService.MyBinder binder = (MyService.MyBinder) service; } // 服務失去連接回調 @Override public void onServiceDisconnected(ComponentName name) { } }; @Event(value = R.id.btn_bind_service) private void onBindServiceClick(View view) { bindService(getServiceIntent(), mConnection, Context.BIND_AUTO_CREATE);// 綁定時如果沒有創建Service則自動創建Service。 mIsBound = true; }
解綁服務:
@Event(value = R.id.btn_unbind_service) private void onUnbindServiceClick(View view) { if (!mIsBound) { ToastUtil.show("未綁定服務"); return; } try { unbindService(mConnection);//注意:ServiceConnection要傳綁定時的ServiceConnection對象,否則會報錯。 } catch (Exception e) { ToastUtil.show("解除綁定服務失敗"); e.printStackTrace(); } mIsBound = false; }
Service的生命周期
public class MyService extends Service { // 服務創建 @Override public void onCreate() { super.onCreate(); } // 每次startService都會調用;通過bindService方式啟動服務,該方法不會被調用 @Override public int onStartCommand(Intent intent, int flags, int startId) { return super.onStartCommand(intent, flags, startId); } // 服務銷毀 @Override public void onDestroy() { super.onDestroy(); } // bindService時調用,返回一個IBinder對象,用於與Service交互,IBinder就是Service的代理 @Nullable @Override public IBinder onBind(Intent intent) { return null; } // unbindService時調用 @Override public boolean onUnbind(Intent intent) { return super.onUnbind(intent); } }
startService:onCreate——>onStartCommand
stopService:onDestory
注意:服務只會被創建一次,如果服務已經創建,並且沒有銷毀,多次調用startService方法,只會執行onStartCommand方法。
bindService:onCreate——>onBind
unbindService:onUnbind——>onDestory
注意:
- 如果多次bindService,onBind方法只會在第一次綁定時被調用;同樣,多次startService,onCreate方法也只會在第一次創建時被調用;
- 服務只能被解綁一次,服務需要先綁定才能解除綁定,多次解綁會報錯。
- 通過bindService方式啟動的Service,在調用unbindService時就會自動銷毀。
- 服務只會停止一次,多次調用stopService()的方法無效,但不報錯。
- 每次調用startService()開啟服務都會執行onStartCommand()方法。
如何調用Service里的方法
由於系統框架在創建服務的時候會創建與之對應的上下文,直接new出來的服務對象是沒有上下文的,所以直接new服務對象調用其方法會報異常。
與Service之間交互都是通過其代理人(IBinder)來間接調用Service里的方法的。
這樣設計主要出於安全考慮,有限的暴露出一些方法,而不是直接返回服務對象,因為服務對象里可能有一些成員變量和方法不允許外界直接訪問,需要保護起來。
一般IBinder(代理人)也應該設計成私有的,因為是IBinder中的一些數據也需要保護起來,只需要暴露出一些指定的方法,那么外界如何引用IBinder對象呢?通過接口引用代理人,在接口定義供外界調用的方法,讓IBinder類實現該接口。
bindService與startService
bindService與startService的區別:
- 綁定服務:可以間接調用服務里面的方法;如果綁定的Activity被銷毀了,服務也會跟着銷毀。
- 開啟服務:不可以調用服務里面的方法;如果開啟服務的Activity銷毀,服務還可以長期的在后台運行。
既要保證服務長期在后台運行,又想去調用服務里面的方法。
步驟:
1. startService(),保證服務在后台長期的運行;
2. bindService(),獲取中間人(IBinder對象),間接的調用服務里面的方法;
這時,解綁服務並不會導致服務銷毀,服務可長期在后台運行。
注意:如果服務已經被綁定,直接調用stopService()是停不掉的,必須先解除綁定服務再調stopService(),服務才會被銷毀。
示例代碼
@ContentView(value = R.layout.activity_main) public class MainActivity extends AppCompatActivity { private boolean mIsBound = false; private ServiceConnection mConnection = new ServiceConnection() { // 服務連接成功回調 @Override public void onServiceConnected(ComponentName name, IBinder service) { LogUtil.d(name + " onServiceConnected"); PayService.PayBinder binder = (PayService.PayBinder) service; binder.pay(100); } // 服務失去連接回調 @Override public void onServiceDisconnected(ComponentName name) { LogUtil.d(name + " onServiceDisconnected"); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); x.view().inject(this); } @Override protected void onDestroy() { super.onDestroy(); //當Activity被銷毀時解綁服務,因為如果已經綁定服務不顯式解綁會報異常。 onUnbindServiceClick(null); } private Intent getServiceIntent() { return new Intent(this, PayService.class); } // 啟動服務 @Event(value = R.id.btn_start_service) private void onStartServiceClick(View view) { startService(getServiceIntent()); } // 綁定服務 @Event(value = R.id.btn_bind_service) private void onBindServiceClick(View view) { bindService(getServiceIntent(), mConnection, Context.BIND_AUTO_CREATE);// 綁定時如果沒有創建服務則自動創建Service。 mIsBound = true; } // 解綁服務 @Event(value = R.id.btn_unbind_service) private void onUnbindServiceClick(View view) { if (!mIsBound) { ToastUtil.show("未綁定服務"); return; } try { unbindService(mConnection);//注意:ServiceConnection要傳綁定時的ServiceConnection對象,否則會報錯。 } catch (Exception e) { e.printStackTrace(); } mIsBound = false; } // 停止服務 @Event(value = R.id.btn_stop_service) private void onStopServiceClick(View view) { stopService(getServiceIntent()); } }
參考文檔: https://developer.android.com/reference/android/app/Service.html
https://developer.android.com/guide/components/services.html
使用AIDL實現進程間通信
AIDL(Android Interface Definition Language)用於進程間通信接口的定義,是一種進程間通訊的規范 。
Service端:
1.New一個aidl文件在src目錄下
2.在aidl文件中定義Service中對外開放的接口
// IPayService.aidl package linchaolong.android.aidldemo.service; // Declare any non-default types here with import statements /** * AIDL Demo * * Created by linchaolong on 2016/4/22. */ interface IPayService { void pay(int price); void startTimer(); void stopTimer(); /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. * * 翻譯: * * 展示一些可以在AIDL中用作參數和返回值的基本類型。 * */ void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString); }
aidl語言中沒有權限修飾符,因為進程間通信接口權限肯定是public的。
3.aidl編寫完成后,make一下工程,在build目錄下就會生成該aidl文件對應的java文件,比如我這是IPayService.java。
4.在IPayService中有一個Stub靜態類,繼承了Binder和實現了IPayService接口,定義一個Binder類繼承IPayService.Stub並實現相關接口。
@Override public IBinder onBind(Intent intent) { if (mBinder == null) { mBinder = new PayBinder(); } return mBinder; // 其他應用綁定服務時返回binder對象 } // Binder public class PayBinder extends IPayService.Stub { public void pay(int price) { PayService.this.pay(price); } public void startTimer() { PayService.this.startTimer(); } public void stopTimer() throws RemoteException { PayService.this.stopTimer(); } @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException { // Does nothing } }
5.在AndroidManifest.xml配置Service
<service android:name=".service.PayService" android:enabled="true" android:exported="true"> <intent-filter> <action android:name="linchaolong.android.aidldemo.service.PayService" /> </intent-filter> </service>
到這里Service端就完成了,其他應用需要調用該Service只需要把aidl文件拷貝到自己工程的src目錄下(make一下),並綁定服務即可得到IBinder對象,通過IBinder對象可以實現與Service的交互。
調用示例:
在onServiceConnected回調里,調用YourServiceInterface.Stub.asInterface(service)把IBinder對象轉換為YourServiceInterface類型。
private IPayService iPayService; private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { // 獲取遠程接口實例 iPayService = IPayService.Stub.asInterface(service); } @Override public void onServiceDisconnected(ComponentName name) { Log.e(TAG, "Service has unexpectedly disconnected"); iPayService = null; } };
綁定遠程服務並調用IPC方法
/** * 判斷是否已經綁定遠程服務 * * @return */ private boolean isBinded() { return mIsBound && iPayService != null; } private Intent getServiceIntent() { return new Intent("linchaolong.android.aidldemo.service.PayService"); } // 綁定遠程服務 @Event(value = R.id.btn_bind_remote_service) private void bindRemoteService(View view) { if (isBinded()) { showToast("已綁定遠程服務"); return; } bindService(getServiceIntent(), mConnection, Context.BIND_AUTO_CREATE); mIsBound = true; } // 調用遠程服務方法 @Event(value = R.id.btn_call_service_pay) private void callServicePay(View view) { if (!isBinded()) { showToast("未綁定遠程服務"); return; } try { // 通過IBinder對象調用遠程服務中方法 iPayService.pay(100); } catch (RemoteException e) { e.printStackTrace(); } }
Demo地址:https://coding.net/u/linchaolong/p/AIDLDemo/git
參考文檔: http://developer.android.com/guide/components/aidl.html