編輯:關於Android編程
在我的上一篇文章Android Service淺析(上)介紹了服務的基本概念及啟動服務的相關內容,由於文章篇幅的原因,將在本文繼續梳理Service相關的其它知識。
在上一篇介紹了啟動服務startSerivce()的方式,雖然啟動後的服務不再依賴於啟動它的組件(如Activity),甚至在該活動銷毀的情況下,服務繼續保持長時間的運行,直到你通過stopService()或者stopSelf()主動終止它,但是實際應用中有時候希望與服務保持更加密切的關系,比如在上一篇中利用廣播來實現活動和服務之間消息傳遞;除了這種間接方式,服務還提供一個更加直接的方式,就是綁定服務bindService(),關於綁定服務的定義如下:
當應用組件通過調用 bindService() 綁定到服務時,服務即處於“綁定”狀態。綁定服務提供了一個客戶端-服務器接口IBinder,允許組件與服務進行交互、發送請求、獲取結果,甚至是利用進程間通信 (IPC) 跨進程執行這些操作。綁定服務通常只在為其他應用組件服務時處於活動狀態,不會無限期在後台運行。*
通俗的講就是通過bindService()的方式,其他組件(包括跨進程組件)可以指揮Service去干活。
那麼它是怎麼實現的呢?
你必須實現 onBind() 回調方法。該方法返回的 IBinder 對象定義了客戶端用來與服務進行交互的編程接口。
客戶端通過調用 bindService(Intent intent,ServiceConnnection conn,int flags) 綁定到服務時,它必須提供 ServiceConnection 的實現,後者會監控與服務的連接。當客戶端與服務之間的連接時,會調用 ServiceConnection 上的 onServiceConnected(),該方法參數會接受一個IBinder類型的對象,就是服務端onBind()方法返回的。
IBinder對象相當於Service組件的內部鉤子,該鉤子關聯到綁定的Service組件,當其它組件綁定到該Service組件時,Service將會把IBinder對象返回給其它組件,其它組件通過該IBinder對象即可與Service組件進行實時通訊。
代碼示例如下:
MyService:
package com.example.myapplication; import android.app.Service; import android.content.Intent; import android.os.Binder; import android.os.IBinder; import android.util.Log; public class MyService extends Service { public static final String TAG = "MyService"; //onBind()方法所返回的對象 private IBinder mBinder = new MyBinder(); /* * 實現IBinder接口,通常是Binder接口,它是IBinder的實現類,可以先把這看作規定,至於為什麼,需要進一步探索。 * 另外這個類定義在MyService內部,也就是所謂的成員內部類,這是因為內部類對象天然持有外部類的this引用, * 所以可以利用這個特性,訪問MyService的公共方法,甚至是返回外部類對象,內部類在開發中有很多的使用場景。 */ class MyBinder extends Binder{ public MyService getService(){ return MyService.this; } } //在MyService中定義一個公共的方法,這裡為一個下載任務 public void startDownload(){ new Thread(new Runnable() { @Override public void run() { //添加兩行打印語句 Log.d(TAG,"The thread id is " + Thread.currentThread().getId()); Log.d(TAG,"Start download..."); } }).start(); } @Override public void onCreate() { super.onCreate(); Log.d(TAG,"onCreate() executed"); } //必須實現的方法,綁定該Service時,回調該方法 @Override public IBinder onBind(Intent intent) { Log.d(TAG,"onBind() executed"); //返回一個IBinder接口類型的實例 return mBinder; } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d(TAG,"onStartCommand() executed"); return super.onStartCommand(intent,flags,startId); } @Override public void onDestroy() { super.onDestroy(); Log.d(TAG,"onDestroy() executed"); } }
activity_main.xml:
MainActivity:
package com.example.myapplication; import android.app.Activity; import android.content.ComponentName; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.util.Log; import android.view.View; import android.widget.Button; public class MainActivity extends Activity { private static final String TAG = "MainActivity"; private Button mBindService; private Button mUnbindService; private MyService mService; private MyService.MyBinder mBinder; //該標志為用來判斷是否解除綁定 private boolean mBound; private ServiceConnection mConnection = new ServiceConnection() { //當該活動與服務連接成功時回調該方法,並且接收服務onBind()方法返回的IBinder類型的實例。 @Override public void onServiceConnected(ComponentName name, IBinder service) { //通過IBinder實例向下轉型,獲取到MyBinder實例,它屬於MyService內部類對象,所以具備了操作MyService公共方法和成員變量的功能。 mBinder = (MyService.MyBinder) service; //返回MyService對象的實例 mService = mBinder.getService(); //調用MyService對象的公共方法 mService.startDownload(); mBound = true; } //該方法在系統異常導致綁定斷開時才會執行,而主動通過unBind()並不會調用 @Override public void onServiceDisconnected(ComponentName name) { mBound = false; } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(TAG,"The thread id is " + Thread.currentThread().getId()); mBindService = (Button) findViewById(R.id.bind_service); mUnbindService = (Button) findViewById(R.id.unbind_service); mBindService.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, MyService.class); /* * 第二個參數為一個ServiceConnection接口類型的實例; * 第三個參數為常量,它表示綁定時,在Service還未創建時,是否自動創建,一般都是用Context.BIND_AUTO_CREATE:自動創建。 */ bindService(intent, mConnection, BIND_AUTO_CREATE); } }); mUnbindService.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //在該活動未與服務綁定情況下,執行unbindService()會出現異常。 if (mBound) { unbindService(mConnection); mBound = false; } } }); } @Override protected void onDestroy() { if (mBound) { unbindService(mConnection); mBound = false; } super.onDestroy(); } }
運行程序,點擊Bind Service按鈕,Logcat打印日志如下:
你可以試著再次點擊Bind Service按鈕,你會發現Logcat中的內容並沒有變化,這與startService()不同,它會多次執行onStartCommand()方法,但多次調用bindService()不會進行重復綁定。
再次點擊Unbind Service按鈕或者關閉程序,Logcat打印日志如下:
即使把MainActivity中onDestroy()調用unbindService()的代碼注銷掉,如下所示:
@Override protected void onDestroy() { /* if (mBound) { unbindService(mConnection); mBound = false; }*/ super.onDestroy(); }
重新運行程序,點擊Bind Service按鈕,直接通過back退出應用,Logcat的打印日志如下:
也就是說,不管是否主動解除綁定,當服務所綁定的組件被銷毀時,該服務也自動終止。
不過一種情況除外,即既執行了startService(),又執行了bindService()(不論先後),此時要終止服務,必須要既執行onStop()又執行onUnbind()(不論先後),否則,服務不會終止。
代碼修改:在activity_main中添加兩個按鈕分別用來啟動、停止服務,代碼示例如下:
在MainActivity中分別給新增按鈕添加注冊事件,分別用來啟動、停止服務,代碼示例如下:
package com.example.myapplication; import android.app.Activity; import android.content.ComponentName; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.util.Log; import android.view.View; import android.widget.Button; public class MainActivity extends Activity { private static final String TAG = "MainActivity"; private Button mStartService; private Button mStoptService; private Button mBindService; private Button mUnbindService; private MyService mService; private MyService.MyBinder mBinder; //該標志為用來判斷是否解除綁定 private boolean mBound; private ServiceConnection mConnection = new ServiceConnection() { //當該活動與服務連接成功時回調該方法,並且接收服務onBind()方法返回的IBinder類型的實例。 @Override public void onServiceConnected(ComponentName name, IBinder service) { //通過IBinder實例向下轉型,獲取到MyBinder實例,它屬於MyService內部類對象,所以具備了操作MyService公共方法和成員變量的功能。 mBinder = (MyService.MyBinder) service; //返回MyService對象的應用 mService = mBinder.getService(); mService.startDownload(); mBound = true; } //該方法會在系統異常導致綁定斷開時才會執行,而主動通過unBind()並不會調用 @Override public void onServiceDisconnected(ComponentName name) { mBound = false; } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(TAG, "The thread id is " + Thread.currentThread().getId()); mStartService = (Button) findViewById(R.id.start_service); mStoptService = (Button) findViewById(R.id.stop_service); mBindService = (Button) findViewById(R.id.bind_service); mUnbindService = (Button) findViewById(R.id.unbind_service); mStartService.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, MyService.class); startService(intent); } }); mStoptService.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, MyService.class); stopService(intent); } }); mBindService.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, MyService.class); /* * 第二個參數為一個ServiceConnection接口類型的實例; * 第三個參數為常量,它表示綁定時,在Service還未創建時,是否自動創建,一般都是用Context.BIND_AUTO_CREATE:自動創建。 */ bindService(intent, mConnection, BIND_AUTO_CREATE); } }); mUnbindService.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //在該活動未與服務綁定情況下,執行unbindService()會出現異常。 if (mBound) { unbindService(mConnection); mBound = false; } } }); } @Override protected void onDestroy() { /* if (mBound) { unbindService(mConnection); mBound = false; }*/ super.onDestroy(); } }
根據上面的結論來隨機操作,可以驗證該結論。
那麼為什麼會有這樣的現場呢?
可以想象到這樣的應用場景:用戶通過音樂播放器播放一個音樂,此時應用會在後台啟動一個服務(startService),然後用戶由於要看微信而退出應用,由於音樂播放是一個服務,所以它並不會停止,過一會你聽到不喜歡的歌,你需要進入應用進行切歌,此時會通過bindService()來綁定到後台的服務,並進行播放控制,然後又退出應用(執行unBindService),那麼現在音樂是否需要停止播放呢?實際並不會,所以這個特性很好解決了這種應用場景。
所以啟動服務和綁定服務根據應用場景來設計的功能,它們不是對立的,根據功能的需要決定使用哪種方式來創建服務,或者兩者方式都會使用到,這是服務就必須要提供兩者的實現。
您通常應該在客戶端生命周期的匹配引入 (bring-up) 和退出 (tear-down) 時刻期間配對綁定和取消綁定。 例如:
如果您只需要在 Activity 可見時與服務交互,則應在 onStart() 期間綁定,在 onStop() 期間取消綁定。 如果您希望 Activity 在後台停止運行狀態下仍可接收響應,則可在 onCreate() 期間綁定,在 onDestroy() 期間取消綁定。請注意,這意味著您的 Activity 在其整個運行過程中(甚至包括後台運行期間)都需要使用服務,因此如果服務位於其他進程內,那麼當您提高該進程的權重時,系統終止該進程的可能性會增加通常情況下,切勿在 Activity 的 onResume() 和 onPause() 期間綁定和取消綁定,因為每一次生命周期轉換都會發生這些回調,您應該使發生在這些轉換期間的處理保持在最低水平。此外,如果您的應用內的多個 Activity 綁定到同一服務,並且其中兩個 Activity 之間發生了轉換,則如果當前 Activity 在下一次綁定(恢復期間)之前取消綁定(暫停期間),系統可能會銷毀服務並重建服務。 (Activity文檔中介紹了這種有關 Activity 如何協調其生命周期的 Activity 轉換。)
如果您的服務已啟動並接受綁定,則當系統調用您的 onUnbind() 方法時,如果您想在客戶端下一次綁定到服務時接收 onRebind() 調用(而不是接收 onBind() 調用),則可選擇返回 true。onRebind() 返回空值,但客戶端仍在其 onServiceConnected() 回調中接收 IBinder。下文圖 1 說明了這種生命周期的邏輯。
圖 1. 允許綁定的已啟動服務的生命周期。
你可以在MyService中添加onUnbind()和onRebind()方法,讓onUnbind()返回true,代碼如下:
@Override public boolean onUnbind(Intent intent) { Log.d(TAG,"onUnbind() executed"); return true; } @Override public void onRebind(Intent intent) { Log.d(TAG,"onRebind() executed"); super.onRebind(intent); }
啟動程序,分別點擊Start MyService、Bind MyService、Unbind MyService、Bind MyService按鈕,Logcat打印日志如下:
最後點擊Bind MyService按鈕並沒有回調onBind(),而是onReBind(),但是也成功返回IBinder給客戶端了。至於這有什麼意義,暫時還不了解。
如果您的服務僅供本地應用使用,不需要跨進程工作,則可以實現自有 Binder 類,讓您的客戶端通過該類直接訪問服務中的公共方法。之所以要求服務和客戶端必須在同一應用內,是為了便於客戶端轉換返回的對象和正確調用其 API。服務和客戶端還必須在同一進程內,因為此方法不執行任何跨進程編組。
那麼怎麼實現跨進程服務的通信呢,綁定本地服務的方式能不能適用到遠程服務,答案是否定的。下面介紹綁定服務實現不同進程間通信的方式。
在綁定服務的定義中,提到Service可以實現跨進程的通信,所謂進程通信(IPC),狹義的來說是向其它進程傳遞一個數據,比如Intent可以實現;廣義的來說就是獲取其它進程中的對象,並可以調用對象中的方法,而一般IPC都是指廣義上的概念,這屬於個人的理解,不然Intent和廣播也屬於IPC的一種。上文中綁定方式只是實現了同一個應用相同進程下其他組件與本地服務的通信,並不屬於IPC,要實現跨進程服務通信,必須使用AIDL(Android Interface Definition Language)翻譯為中文就是Android接口定義語句,它可以讓服務與不同進程下的組件進行通信,實現多個應用程序共享同一個Service的功能。
我們要是實現不同的進程間的通信,不一定需要建立兩個應用,同一個應用也可以有多個進程,設置非常簡單,在AndroidManifest.xml中將組件android:process屬性指定為一個不同於應用包名的字符串。而將一個普通的Service轉換成遠程Service其實非常簡單,只需要在注冊Service的時候將它的android:process屬性指定成:remote就可以了,代碼如下所示:
為了證實MyService現在確實已經運行在另外一個進程當中了,我們分別在MainActivity的onCreate()方法和MyService的onCreate()方法裡加入一行日志,打印出各自所在的進程id,如下所示:
Log.d("TAG", "process id is " + Process.myPid());
再次重新運行程序,然後點擊一下Bind Service按鈕,打印結果如下圖所示:
可以看到,不僅僅是進程id不同了,就連應用程序包名也不一樣了,MyService中打印的那條日志,包名後面還跟上了:remote標識。
下面我們使用Android提供AIDL機制來實現進程的通信:
使用AIDL的步驟:
1. 創建.aidl文件。
在Android Studio中光標放到對應項目中任何一處,右擊-【New】-【AIDL】-【AIDL FILE】,如下圖:
打開“AIDL FILE”需要輸入Interface Name,輸入“MyAidlService”,如下圖:
打開AIDL文件,用Java語法編寫一個接口,代碼如下:
package com.example.myapplication; interface MyAidlService { int add(int a,int b); }
定義了一個MyAidlService接口,該名稱名稱由系統生成,與文件名一致,在裡面聲明了一個add方法。
編寫完成後,在Android Studio中需要主動編譯一下文件,而Eclipse保存後會自動編譯,可以點擊工具欄中“Sync Projects with Gradle Files”,如下圖:
(不同AS版本,圖標可能一樣),編譯完成以後,SDK會自動生成一個MyAidlService.java文件,地址在項目下build/generated/source/debug/package_name(你的應用包名)下,如下圖:
2、實現接口
在生成的接口文件MyAidlService.java中,有一個抽象的內部類Stub(存根),它不僅實現了IBinder接口,而實現了外部類MyAidlService接口,所以它也繼承了外部類的方法聲明。
通過匿名類的方式,繼承MyAidlService.Stub類並實現接口中方法,並且返回一個IBinder實例。
代碼如下:
private IBinder mBinder = new MyAidlService.Stub() { @Override public int add(int a, int b) throws RemoteException { return a + b; } };
3、把接口暴露給客戶端
通過onBind()向客戶端返回IBinder實例,回調ServiceConnction類中onServiceConnected(),該方法接收該實例。另外,在生成的MyAidlService.java文件中,MyAidlService.Stub會提供一個靜態方法MyAidlService asInterface(IBinder obj),它可以接收IBinder實例並返回一個MyAidlService類型的實例,通過該方法把IBinder實例轉型為MyAidlService類型,這樣就可以通過該實例來調用服務端的方法,代碼如下:
MyService
@Override public IBinder onBind(Intent intent) { return mBinder; }
MainActivity
package com.example.myapplication; import android.app.Activity; import android.content.ComponentName; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.os.Process; import android.os.RemoteException; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.Toast; import static com.example.myapplication.R.id.bind_service; import static com.example.myapplication.R.id.invoke_remote_service; import static com.example.myapplication.R.id.start_service; import static com.example.myapplication.R.id.stop_service; import static com.example.myapplication.R.id.unbind_service; public class MainActivity extends Activity implements View.OnClickListener { private Button mStartService; private Button mStoptService; private Button mBindService; private Button mUnbindService; //返回的AIDL接口類型的對象 private MyAidlService mService; //該標志為用來判斷是否解除綁定 private boolean mBound; private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { //通過MyAidlService.Stub靜態方法asInterface()把IBinder類型轉換為MyAidlService類型 mService = MyAidlService.Stub.asInterface(service); mBound = true; } @Override public void onServiceDisconnected(ComponentName name) { mService = null; mBound = false; } }; private Button mInvokeRS; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d("TAG", "process id is " + Process.myPid()); initView(); } private void initView() { mStartService = (Button) findViewById(R.id.start_service); mStoptService = (Button) findViewById(R.id.stop_service); mBindService = (Button) findViewById(R.id.bind_service); mUnbindService = (Button) findViewById(R.id.unbind_service); mInvokeRS = (Button) findViewById(invoke_remote_service); mStartService.setOnClickListener(this); mStoptService.setOnClickListener(this); mBindService.setOnClickListener(this); mUnbindService.setOnClickListener(this); mInvokeRS.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case start_service: Intent startIntent = new Intent(MainActivity.this, MyService.class); startService(startIntent); break; case stop_service: Intent stopIntent = new Intent(MainActivity.this, MyService.class); stopService(stopIntent); break; case bind_service: Intent bindIntent = new Intent(MainActivity.this, MyService.class); bindService(bindIntent, mConnection, BIND_AUTO_CREATE); break; case unbind_service: if (mBound) { unbindService(mConnection); mBound = false; } break; case invoke_remote_service: if(mService!=null){ try { //調用AIDL接口中聲明的方法 int mSum = mService.add(1, 2); Toast.makeText(this, "a + b =" + mSum, Toast.LENGTH_SHORT).show(); } catch (RemoteException e) { e.printStackTrace(); } }else{ Toast.makeText(this, "請先綁定遠程服務", Toast.LENGTH_SHORT).show(); } break; default: break; } } @Override protected void onDestroy() { if (mBound) { unbindService(mConnection); mBound = false; } super.onDestroy(); } }
activity_main.xml
運行程序,分別點擊“Bind MyService”和“調用遠程服務方法”,結果如下圖:
關於不同應用中ADIL實現方式與同一應用下不同進程的操作步驟及部分內容有所差異,AIDL更多信息暫時不做描述。
通常來說,在進行Android項目開發的時候可以通過MediaRecorder和AudioRecord這兩個工具來實現錄音的功能,MediaRecorder直接把麥克風的
高斯模糊是什麼?高斯模糊(英語:Gaussian Blur),也叫高斯平滑,是在Adobe Photoshop、GIMP以及Paint.NET等圖像處理軟件中廣泛使用的處
一、EasyTouch插件介紹本文總結時,目前網上可以很方便的下載到EasyTouch4.3版本(額……你懂什麼叫很方便的),由於某些版本和版
郁悶了半天,今天發現一點擊手機 menu 鍵應用就崩潰了,記得之前都是好好的,調試了半天代碼還是搞不定,於是網上google了一番,發現僅國外有一兩篇文章有提到類