service的異步創(chuàng)建
做過(guò)Android開發(fā)的基本都清楚求类,當(dāng)進(jìn)行bindService調(diào)用的時(shí)候霜大,需要傳遞一個(gè)ServiceConnection癞谒,當(dāng)service創(chuàng)建完畢之后啊掏,會(huì)回調(diào)ServiceConnection的方法來(lái)通知調(diào)用方梗搅,也就是說(shuō)bindService是異步的禾唁。
根本原因在于service的onCreate/onStartCommand/onStart
生命周期相關(guān)的方法總是在 主線程
上執(zhí)行的,如果bindService在主線程上阻塞的話无切,這個(gè)時(shí)候荡短,service就無(wú)法執(zhí)行上述生命周期相關(guān)的方法,完成初始化工作订雾。既然不能阻塞肢预,只好通過(guò)ServiceConnection來(lái)回調(diào)通知了。
進(jìn)一步而言洼哎,bindService之后烫映,需要立刻返回,從而執(zhí)行ActivityThread中的消息循環(huán)噩峦,從而處理service創(chuàng)建等消息锭沟。詳見: http://androidxref.com/5.0.0_r2/xref/frameworks/base/core/java/android/app/ActivityThread.java#1360
用官方文檔來(lái)說(shuō),就是service是運(yùn)行在主線程上的识补,也因此族淮,可能會(huì)引發(fā)ANR,所以耗時(shí)的工作應(yīng)該在service創(chuàng)建工作者線程來(lái)完成凭涂。
https://developer.android.com/guide/components/services.html?hl=zh-cn
注意:服務(wù)在其托管進(jìn)程的主線程中運(yùn)行祝辣,它既不創(chuàng)建自己的線程,也不在單獨(dú)的進(jìn)程中運(yùn)行(除非另行指定)切油。 這意味著蝙斜,如果服務(wù)將執(zhí)行任何 CPU 密集型工作或阻止性操作(例如 MP3 播放或聯(lián)網(wǎng)),則應(yīng)在服務(wù)內(nèi)創(chuàng)建新線程來(lái)完成這項(xiàng)工作澎胡。通過(guò)使用單獨(dú)的線程孕荠,可以降低發(fā)生“應(yīng)用無(wú)響應(yīng)”(ANR) 錯(cuò)誤的風(fēng)險(xiǎn),而應(yīng)用的主線程仍可繼續(xù)專注于運(yùn)行用戶與 Activity 之間的交互攻谁。
這樣做稚伍,其實(shí)有很多優(yōu)勢(shì),畢竟戚宦,主線程并非會(huì)一直運(yùn)行个曙,例如Activity后臺(tái)化等情況,如果service在獨(dú)立的線程中執(zhí)行受楼,顯然會(huì)消耗一定的資源垦搬。
不過(guò)這里說(shuō)的是service和調(diào)用方是同一進(jìn)程的情況祠挫,如果不同的進(jìn)程,理論是不是可以實(shí)現(xiàn)為同步呢悼沿?顯然不是的等舔,如果bindService阻塞主線程,導(dǎo)致5秒內(nèi)沒(méi)響應(yīng)輸入事件糟趾,這個(gè)時(shí)候會(huì)引發(fā)ANR慌植,而service執(zhí)行超時(shí)是20秒,因此為了規(guī)避這種問(wèn)題义郑,bindService采用異步設(shè)計(jì)蝶柿。
工作者線程阻塞方式bindService
很多時(shí)候,異步方式使用起來(lái)不是那么方便非驮,如果是在工作者線程交汤,可以使用同步方式來(lái)bindService,下面的代碼來(lái)自于: http://androidxref.com/5.0.0_r2/xref/frameworks/base/keystore/java/android/security/KeyChain.java
public static KeyChainConnection bindAsUser(Context context, UserHandle user)
throws InterruptedException {
if (context == null) {
throw new NullPointerException("context == null");
}
// 如果是非工作者線程劫笙,則拋出異常
ensureNotOnMainThread(context);
// 聲明一個(gè)阻塞隊(duì)列芙扎,bindService之后,等待這個(gè)隊(duì)列有數(shù)據(jù)入隊(duì)
final BlockingQueue<IKeyChainService> q = new LinkedBlockingQueue<IKeyChainService>(1);
ServiceConnection keyChainServiceConnection = new ServiceConnection() {
volatile boolean mConnectedAtLeastOnce = false;
@Override public void onServiceConnected(ComponentName name, IBinder service) {
if (!mConnectedAtLeastOnce) {
mConnectedAtLeastOnce = true;
try {
q.put(IKeyChainService.Stub.asInterface(service));
} catch (InterruptedException e) {
// will never happen, since the queue starts with one available slot
}
}
}
@Override public void onServiceDisconnected(ComponentName name) {}
};
Intent intent = new Intent(IKeyChainService.class.getName());
ComponentName comp = intent.resolveSystemService(context.getPackageManager(), 0);
intent.setComponent(comp);
boolean isBound = context.bindServiceAsUser(intent,
keyChainServiceConnection,
Context.BIND_AUTO_CREATE,
user);
if (!isBound) {
throw new AssertionError("could not bind to KeyChainService");
}
return new KeyChainConnection(context, keyChainServiceConnection, q.take());
}
private static void ensureNotOnMainThread(Context context) {
Looper looper = Looper.myLooper();
if (looper != null && looper == context.getMainLooper()) {
throw new IllegalStateException(
"calling this from your main thread can lead to deadlock");
}
}