什么是Service
- Service是一個應用組件, 它用來在后臺完成一個時間跨度比較大的工作且沒有關聯(lián)任何界面
- 一個Service可以完成下面這些工作:
- 訪問網(wǎng)絡
- 播放音樂
- 文件IO操作
- 大數(shù)據(jù)量的數(shù)據(jù)庫操作……
-
服務的特點:
- Service在后臺運行,不用與用戶進行交互
- 即使應用退出, 服務也不會停止
- 在默認情況下晨横,Service運行在應用程序進程的主線程(UI線程)中粘捎,如果需要在Service中處理一些網(wǎng)絡連接等耗時的操作赶么,那么應該將這些任務放在分線程中處理,避免阻塞用戶界面
Service的分類
Local Service(本地服務):Service對象與Serive的啟動者在同個進程中運行, 兩者的通信是進程內通信秉撇,通俗點就是啟動同一應用的Service就是本地服務空盼。
Remote Service(遠程服務):Service對象與Service的啟動者不在同一個進程中運行, 這時存在一個進程間通信的問題, Android專門為此設計了AIDL來實現(xiàn)進程間通信饼记。通俗點就是啟動其他應用的Service就是遠程服務。
Service和Thread區(qū)別
-
Service:
- 用來在后臺完成一個時間跨度比較大的工作的應用組件
- Service的生命周期方法運行在主線程, 如果Service想做持續(xù)時間比較長的工作, 需要啟動一個分線程(Thread)
- 應用退出: Service不會停止
- 應用再次進入: 可以與正在運行的Service進行通信
-
Thread:
- 用來開啟一個分線程的類, 做一個長時間的工作
- Thread對象的run()在分線程執(zhí)行
- 應用退出: Thread不會停止,
- 應用再次進入: 不能再控制前面啟動的Thread對象
定義Service
-
定義一個類繼承于Service類
public class MyService extends Service { @Nullable @Override public IBinder onBind(Intent intent) { return null; } ...... }
-
在AndroidManifest.xml中配置Service
<service android:name=".MyService"> <intent-filter> <action android:name="guo.ping.activitydemo.MyService"/> </intent-filter> </service>
啟動荧嵌、停止Service
-
一般啟動與停止
context.startService(Intent intent) context.stopService(Intent intent)
-
綁定啟動與解綁
context.bindService(Intent intent, ServiceConnection connection) context.unbindService(ServiceConnection connection)
-
一般啟動停止Service
public void startMyService(View view){ Intent intent = new Intent(this, MyService.class); startService(intent); } public void stopMyService(View view){ Intent intent = new Intent(this, MyService.class); stopService(intent); }
-
綁定啟動Service與解綁:
public void bindMyService(View view) { Intent intent = new Intent(this, MyService.class); if (mServiceConnection == null) { mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.i(TAG, "onServiceConnected()" + service.hashCode()); } @Override public void onServiceDisconnected(ComponentName name) { Log.i(TAG, "onServiceDisconnected()"); } }; bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE); } else { Log.i(TAG, "已經(jīng)綁定..."); } } public void unbindMyService(View view) { if (mServiceConnection != null) { Intent intent = new Intent(this, MyService.class); unbindService(mServiceConnection); mServiceConnection = null; } else { Log.i(TAG, "已經(jīng)解綁..."); } }
MyService類重寫onBind()和onUnbind()方法:
@Nullable @Override public IBinder onBind(Intent intent) { Log.i(TAG, "MyService onBind()"); Binder binder = new Binder(); Log.i(TAG, "binder.hashCode() = " + binder.hashCode()); return binder; } @Override public boolean onUnbind(Intent intent) { Log.i(TAG, "MyService onUnbind()"); return super.onUnbind(intent); }
測試結果如下呛踊,可以看到在MyService的onBind()方法中返回的Binder對象就是在onServiceConnected()回調方法中的IBinder類型的形參service。
07-29 10:52:43.002 2794-2794/guo.ping.activitydemo I/MyService: MyService() 07-29 10:52:43.002 2794-2794/guo.ping.activitydemo I/MyService: MyService onCreate() 07-29 10:52:43.002 2794-2794/guo.ping.activitydemo I/MyService: MyService onBind() 07-29 10:52:43.002 2794-2794/guo.ping.activitydemo I/MyService: 1394383824 07-29 10:52:43.002 2794-2794/guo.ping.activitydemo I/MainActivity: onServiceConnected()1394383824 07-29 10:52:46.854 2794-2794/guo.ping.activitydemo I/MyService: MyService onUnbind() 07-29 10:52:46.854 2794-2794/guo.ping.activitydemo I/MyService: MyService onDestroy()
這種方式下啦撮,Activity和Service是通過ServiceConnection相關聯(lián)的谭网,如果Activity銷毀則這個鏈接是要斷開的,會引起ServiceConnectionLeaked異常:
07-29 11:00:39.046 2794-2794/guo.ping.activitydemo E/ActivityThread: Activity guo.ping.activitydemo.MainActivity has leaked ServiceConnection guo.ping.activitydemo.MainActivity$1@5320601c that was originally bound here android.app.ServiceConnectionLeaked: Activity guo.ping.activitydemo.MainActivity has leaked ServiceConnection guo.ping.activitydemo.MainActivity$1@5320601c that was originally bound here
需要我們在Activity的onDestory方法中進行解綁操作:
@Override protected void onDestroy() { super.onDestroy(); Log.i(TAG, "onDestroy()"); if(mServiceConnection != null){ Intent intent = new Intent(this, MyService.class); unbindService(mServiceConnection); mServiceConnection = null; } }
Service的生命周期
- 當用一般方式啟動Service時赃春,如果Service已經(jīng)啟動愉择,則不會調用onCreated(),直接調用onStartCommand()方法织中;
- 當有多個Activity等組件和一個Service進行綁定锥涕,必須所有的客戶端全部調用unbindService()方法進行解綁Service端才能調用onUnbind()方法,這一點還是合乎情理的狭吼。
AIDL
- 每個應用程序都運行在自己的獨立進程中层坠,并且可以啟動另一個應用進程的服務,而且經(jīng)常需要在不同的進程間傳遞數(shù)據(jù)對象刁笙。
- 在Android平臺破花,一個進程不能直接訪問另一個進程的內存空間谦趣,所以要想對話,需要將對象分解成操作系統(tǒng)可以理解的基本單元座每,并且有序的通過進程邊界前鹅。
- 如果在一個進程中(例如Activity)要調用另一個進程中(例如Service)對象的操作,就可以使用AIDL生成可序列化的參數(shù)峭梳。
- AIDL (Android Interface Definition Language):用于生成可以在Android設備上兩個進程之間進行進程間通信(interprocess communication, IPC)的代碼舰绘。
使用AIDL的思路理解
整個的流程其實大致是,在本進程(應用)中延赌,實現(xiàn)某一需求需要調用另一個進程(應用)的某個服務(服務中包含某個具體的方法)除盏,所以我們需要先將請求參數(shù)發(fā)給另一個進程的遠程服務,然后遠程服務將參數(shù)接收調用方法處理得出結果挫以,并將結果返回給原來的進程者蠕。而AIDL就是充當這個傳輸數(shù)據(jù)的角色,將參數(shù)送至掐松、結果傳回踱侣。
AIDL文件的書寫規(guī)則
- 接口名和aidl文件名相同.
- 接口和方法前不用加訪問權限修飾符public,private,protected等,也不能用final,static.
- Aidl默認支持的類型包話java基本類型(int,long,boolean等)和(String,List,Map, CharSequence),使用這些類型時不需要import聲明.對于List和Map中的元素類型必須是Aidl支持的類型.如果使用自定義類型作為參數(shù)或返回值,自定義類型必須實現(xiàn)Parcelable接口.
- 自定義類型和AIDL生成的其它接口類型在aidl描述文件中,應該顯式import,即便在該類和定義的包在同一個包中.
- 在aidl文件中所有非Java基本類型參數(shù)必須加上in、out大磺、inout標記,以指明參數(shù)是輸入?yún)?shù)抡句、輸出參數(shù)還是輸入輸出參數(shù).
- Java原始類型默認的標記為int,不能為其它標記.
使用AIDL的Demo
推薦博客:
需求:
- 傳遞倆個int類型數(shù)字a、b杠愧,調用遠程方法計算倆者之和并返回
- 通過id來調用遠程服務查找對應的學生記錄并返回
實現(xiàn):
-
定義Student的JavaBean文件待榔,需要實現(xiàn)Parcelable接口。在打包與解包時流济,順序要一致锐锣,否則會出現(xiàn)錯誤;
package guo.ping.testservice; import android.os.Parcel; import android.os.Parcelable; public class Student implements Parcelable { private int id; private String name; private double weight; public Student() { } public Student(int id, String name, double weight) { this.id = id; this.name = name; this.weight = weight; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getWeight() { return weight; } public void setWeight(double weight) { this.weight = weight; } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", weight=" + weight + '}'; } // 添加一個靜態(tài)成員,名為CREATOR,該對象實現(xiàn)了Parcelable.Creator接口 public static final Parcelable.Creator<Student> CREATOR = new Creator<Student>() { @Override public Student createFromParcel(Parcel source) { int id = source.readInt(); String name = source.readString(); double weight = source.readDouble(); return new Student(id, name, weight); } @Override public Student[] newArray(int size) { return new Student[size]; } }; @Override public int describeContents() { return 0; } //將當前對象的屬性數(shù)據(jù)寫到Parcel包對象中(也就是打包) @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(id); dest.writeString(name); dest.writeDouble(weight); } }
-
定義Student.aidl文件绳瘟。文件名只能是Student雕憔,其他會出錯;
package guo.ping.testservice; parcelable Student;
-
定義IAdd.aidl文件糖声,這個文件里面包含著我們需要遠程服務包含的抽象方法斤彼;
package guo.ping.testservice; import guo.ping.testservice.Student; interface IAdd { int add(int a, int b); Student queryStudentById(int id); }
-
將上述三個文件拷貝至另一個工程,總之客戶端與服務端都需要擁有蘸泻,包名要一致琉苇,否則也是出錯;
AndroidStudio中選擇Build——>Make Project悦施,生成AIDL的代碼翁潘;
-
在服務端編寫Service,并實現(xiàn)AIDL文件中的抽象方法歼争;
public class MyRemoteService extends Service { private IBinder mIBinder = new IAdd.Stub() { @Override public int add(int a, int b) throws RemoteException { return a + b; } @Override public Student queryStudentById(int id) throws RemoteException { return new Student(id, "假裝查了數(shù)據(jù)庫叫Tom", 140.5); } }; @Nullable @Override public IBinder onBind(Intent intent) { return mIBinder; } @Override public boolean onUnbind(Intent intent) { return super.onUnbind(intent); } }
這里定義了成員變量mIBinder拜马,new IAdd.Stub()是Android依據(jù)我們編寫的AIDL文件生成的Java代碼里面寫好的渗勘。然后就是注冊Service,這里需要intent-filter定義好便于隱式意圖啟動俩莽。
<service android:name=".MyRemoteService"> <intent-filter> <action android:name="guo.ping.testservice.MyRemoteService"/> </intent-filter> </service>
-
客戶端編寫啟動遠程Service的方法旺坠;
public void callTheRemoteServiceMethod(View view) throws RemoteException { int add = mIAdd.add(2, 5); Log.i(TAG, add + ""); Student student = mIAdd.queryStudentById(10); Log.i(TAG, student.toString()); } public void startRemoteService(View view) { Intent intent = new Intent("guo.ping.testservice.MyRemoteService"); if (mServiceConnection == null) { mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mIAdd = IAdd.Stub.asInterface(service); Log.i(TAG, "onServiceConnected()"); } @Override public void onServiceDisconnected(ComponentName name) { Log.i(TAG, "onServiceDisconnected()"); } }; bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE); } else { Log.i(TAG, "已經(jīng)綁定..."); } } public void stopRemoteService(View view) { if (mServiceConnection != null) { unbindService(mServiceConnection); mIAdd = null; mServiceConnection = null; } else { Log.i(TAG, "已經(jīng)解綁..."); } }
-
測試結果如下;
補充
在關閉遠程服務的時候扮超,發(fā)現(xiàn)重寫的onServiceDisconnected()方法Log不打印取刃,想著onServiceConnected()要打印必須在Service的onBind()方法中返回IBinder對象,所以便在Service中實現(xiàn)onUnbind()方法出刷,發(fā)現(xiàn)并沒有亂用璧疗。
百度了一下,原來當service所在進程crash或者被kill的時候馁龟,onServiceDisconnected才會被呼叫崩侠。具體參考:我的onServiceDisconnected為什么沒有被呼叫。