Android中bindService的细节之四:bindService时,你所忽略的代码流程细节
2017-01-30 01:10
567 查看
Android中bindService的细节之四:bindService时,你所忽略的代码流程细节
场景: App A绑定App B的MyService时,App B进程之前没有启动,即需要创建App B进程。
本文要讲的内容是,从App B的ActivityThread的main()执行开始到Looper.loop()执行这段时间内的代码流程,这部分流程是 《Android中bindService的细节之一:从进程的角度分析绑定Service的流程【Service所在进程首次启动】》 的一部分,看完整的流程,可以参考这个链接。
本文从创建App B进程后,在App B进程中执行ActivityThread的main()讲起。
注:AMS所在进程为system_server,为突出AMS,将AMS所在进程称为AMS进程。
创建ActivityThread,执行attach()。
参数system为false
bindApplication() @ ApplicationThread @ ActivityThread
attachApplicationLocked() @ ActiveServices
scheduleCreateService() @ ApplicationThread @ ActivityThread
requestServiceBindingsLocked() @ ActiveServices
以上这些代码都是由于执行ActivityThread的main()方法中的
在本文讨论的场景中,最后ActivityThread将执行Looper.loop()。
之前放入App B的消息队列中的3个消息:
(1)BIND_APPLICATION消息:
触发消息:AMS调用
处理消息:handleBindApplication() @ ActivityThread
(2)CREATE_SERVICE消息:
触发消息:AMS调用
处理消息:handleCreateService() @ ActivityThread
(3)BIND_SERVICE消息:
触发消息:AMS调用
处理消息:handleBindService() @ ActivityThread
这3个消息都是在Looper.loop()执行的时候,才被处理的,而且按照队列先进先出的顺序,依次处理BIND_APPLICATION消息、CREATE_SERVICE消息和BIND_SERVICE消息。
可以通过在AMS和ActivityThread中加log来验证这个过程,也可以做一个模拟的测试过程。
(2)实现IMyActivityManager接口
(2)通过Service + Thread来模拟App B的ActivityThread
其中pid 4537为模拟App B的进程,pid 15782为AMS进程。
为了描述方便,下面提到的AMS和App B都是上面的代码模拟的出来的,不再说模拟的AMS和模拟的App B进程。
App B进程启动后,调用AMS的attachApplication();
AMS在attachApplication()中调用App B的接口,向App B的消息队列放入了3个消息:BIND_APP、CREATE_SVC和BIND_SVC。可以看到此时3个消息都放入了队列,但是没有被处理;
几秒后,Looper.loop()执行了,立即处理消息队列中的3个消息,而且是依次处理。
(2)模拟App B进程已经启动:MyHandlerService已经运行,此时往消息队列放入消息
可以看到,将消息放入消息队列,消息是被立即处理的。
注:这里说的“立即处理”不是同步处理,AMS进程和App B进程是并行的。
(2)在App进程首次创建的时候,在Looper.loop()运行之前,AMS调用IApplicationThread接口放入App 消息队列的消息不会立即被处理。在Looper.loop()运行的时候,将依次处理消息队列中的所有消息。
继续阅读:
《Android中bindService的细节之一:从进程的角度分析绑定Service的流程【Service所在进程首次启动】》
《Android中bindService的细节之二:从进程的角度分析绑定Service的流程【Service所在进程已存在】》
0. 说明
事先声明:本文所讲的内容,即使被你忽略了,也不影响对bindService()代码流程的理解。场景: App A绑定App B的MyService时,App B进程之前没有启动,即需要创建App B进程。
本文要讲的内容是,从App B的ActivityThread的main()执行开始到Looper.loop()执行这段时间内的代码流程,这部分流程是 《Android中bindService的细节之一:从进程的角度分析绑定Service的流程【Service所在进程首次启动】》 的一部分,看完整的流程,可以参考这个链接。
本文从创建App B进程后,在App B进程中执行ActivityThread的main()讲起。
注:AMS所在进程为system_server,为突出AMS,将AMS所在进程称为AMS进程。
1. bindService()的代码流程
1.1 ActivityThread的main()
public static void main(String[] args) { ... Looper.prepareMainLooper();// 创建Looper ActivityThread thread = new ActivityThread(); thread.attach(false);// 执行这里 ... }
创建ActivityThread,执行attach()。
1.2 thread.attach(false)
attach() @ ActivityThread参数system为false
private void attach(boolean system) { sCurrentActivityThread = this; mSystemThread = system; if (!system) { ... final IActivityManager mgr = ActivityManagerNative.getDefault(); try { // 执行这里 mgr.attachApplication(mAppThread); } catch ... ... } else { ... } ... }
1.3 mgr.attachApplication(mAppThread)
attachApplication() @ ActivityManagerServicepublic final void attachApplication(IApplicationThread thread) { synchronized (this) { int callingPid = Binder.getCallingPid(); final long origId = Binder.clearCallingIdentity(); attachApplicationLocked(thread, callingPid); Binder.restoreCallingIdentity(origId); } }
1.4 attachApplicationLocked(thread, callingPid)
attachApplicationLocked() @ ActivityManagerServiceprivate final boolean attachApplicationLocked(IApplicationThread thread, int pid) { ProcessRecord app; if (pid != MY_PID && pid >= 0) { synchronized (mPidsSelfLocked) { // 执行这里,因为App B进程刚刚创建,ProcessRecord是在Zygote fork进程前创建的 app = mPidsSelfLocked.get(pid); } } else ... ... // makeActive()把App B进程传过来的IApplicationThread binder对象保存到ProcessRecord的成员变量thread中 app.makeActive(thread, mProcessStats); ... // 在创建进程时,设置了超时处理的消息,在这里取消掉 mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app); // 获取App B中的provider信息 boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info); List<ProviderInfo> providers = normalMode ? generateApplicationProvidersLocked(app) : null; try { ... // 执行这里,将向App B的消息队列中放入BIND_APPLICATION消息 thread.bindApplication(processName, appInfo, providers, app.instrumentationClass, profilerInfo, ...); ... } catch (Exception e) { ... } boolean badApp = false; boolean didSomething = false; // See if the top visible activity is waiting to run in this process... if (normalMode) { try { // 执行这里,但是在此场景中不创建新的Activity,所以if语句条件不为true if (mStackSupervisor.attachApplicationLocked(app)) { didSomething = true;// 不执行这里 } } catch ... } // Find any services that should be running in this process... if (!badApp) { try { // 执行这里 didSomething |= mServices.attachApplicationLocked(app, processName); } catch ... } ... return true; }
1.5 thread.bindApplication(processName, appInfo, providers…)
切换到App B进程。bindApplication() @ ApplicationThread @ ActivityThread
public final void bindApplication(String processName, ApplicationInfo appInfo, List<ProviderInfo> providers, ComponentName instrumentationName, ProfilerInfo profilerInfo, Bundle instrumentationArgs, ... ) { ... AppBindData data = new AppBindData(); data.processName = processName; data.appInfo = appInfo; data.providers = providers; ... /* 向App B的消息队列中放入BIND_APPLICATION消息, 因为Looper.loop()还没有执行,所以BIND_APPLICATION消息放入队列中,没有立即执行 */ sendMessage(H.BIND_APPLICATION, data); }
1.6 mServices.attachApplicationLocked(app, processName)
从App B进程回来,继续执行。attachApplicationLocked() @ ActiveServices
boolean attachApplicationLocked(ProcessRecord proc, String processName) throws RemoteException { boolean didSomething = false; /* 在启动App B进程的时候,将要绑定的服务ServiceRecord放入了mPendingServices,代码在bringUpServiceLocked()中,执行mAm.startProcessLocked()之后,执行了 if (!mPendingServices.contains(r)) { mPendingServices.add(r); } */ if (mPendingServices.size() > 0) { ServiceRecord sr = null; try { for (int i=0; i<mPendingServices.size(); i++) { sr = mPendingServices.get(i); ... mPendingServices.remove(i); i--; ... // 执行这里 realStartServiceLocked(sr, proc, sr.createdFromFg); didSomething = true; ... } } catch ... } return didSomething; }
1.7 realStartServiceLocked(sr, proc, sr.createdFromFg)
realStartServiceLocked() @ ActiveServicesprivate final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException { ... r.app = app; // 此时将ServiceRecord中的app赋值 ... boolean created = false; try { ... // 执行这里,将向App B的消息队列中放入CREATE_SERVICE消息 app.thread.scheduleCreateService(r, r.serviceInfo, mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo), app.repProcState); r.postNotification(); created = true; } catch (DeadObjectException e) { ... } finally { ... } ... // 执行这里 requestServiceBindingsLocked(r, execInFg); ... }
1.8 app.thread.scheduleCreateService(r, r.serviceInfo,…)
进入App B进程。scheduleCreateService() @ ApplicationThread @ ActivityThread
public final void scheduleCreateService(IBinder token, ServiceInfo info, CompatibilityInfo compatInfo, int processState) { updateProcessState(processState, false); CreateServiceData s = new CreateServiceData(); s.token = token; s.info = info; s.compatInfo = compatInfo; // 向App B的消息队列中放入CREATE_SERVICE消息 // 因为Looper.loop()还没有执行,所以CREATE_SERVICE消息放入队列中,没有立即执行。此时队列中已经有2个消息,BIND_APPLICATION消息和CREATE_SERVICE消息 sendMessage(H.CREATE_SERVICE, s); }
1.9 requestServiceBindingsLocked(r, execInFg)
回到AMS进程。requestServiceBindingsLocked() @ ActiveServices
private final void requestServiceBindingsLocked(ServiceRecord r, boolean execInFg) throws TransactionTooLargeException { for (int i=r.bindings.size()-1; i>=0; i--) { IntentBindRecord ibr = r.bindings.valueAt(i); if (!requestServiceBindingLocked(r, ibr, execInFg, false)) { break; } } }
1.10 requestServiceBindingLocked(r, ibr, execInFg, false)
requestServiceBindingLocked() @ ActiveServicesprivate final boolean requestServiceBindingLocked(ServiceRecord r, IntentBindRecord i, boolean execInFg, boolean rebind) throws TransactionTooLargeException { // 在realStartServiceLocked()中已经将r.app赋值 if (r.app == null || r.app.thread == null) { // 不执行这里 return false; } if ((!i.requested || rebind) && i.apps.size() > 0) { try { ... // 执行这里,将向App B的消息队列中放入BIND_SERVICE消息 r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind, r.app.repProcState); if (!rebind) { i.requested = true; } i.hasBound = true; i.doRebind = false; } catch (TransactionTooLargeException e) { ... } catch (RemoteException e) { ... } } return true; }
1.11 r.app.thread.scheduleBindService(r, …)
public final void scheduleBindService(IBinder token, Intent intent, boolean rebind, int processState) { updateProcessState(processState, false); BindServiceData s = new BindServiceData(); s.token = token; s.intent = intent; s.rebind = rebind; ... // 向App B的消息队列中放入BIND_SERVICE消息 // 因为Looper.loop()还没有执行,所以BIND_SERVICE消息放入队列中,没有立即执行。此时队列中已经有3个消息,BIND_APPLICATION消息、CREATE_SERVICE消息和BIND_SERVICE消息 sendMessage(H.BIND_SERVICE, s); }
以上这些代码都是由于执行ActivityThread的main()方法中的
thread.attach(false)引起的,所以,执行完上面的代码将回到ActivityThread的attach(),继续执行
thread.attach(false)之后的代码。
在本文讨论的场景中,最后ActivityThread将执行Looper.loop()。
1.12 回到ActivityThread的main()
当前在App B进程。public static void main(String[] args) { ... Looper.prepareMainLooper();// 创建Looper ActivityThread thread = new ActivityThread(); // 之前的代码都是由thread.attach()引起的,AMS调用IApplicationThread接口向App B的消息队列中放入3个消息:BIND_APPLICATION消息、CREATE_SERVICE消息和BIND_SERVICE消息 thread.attach(false); ... // 此时执行这里,监听消息队列中是否有新消息,如果没有消息,则阻塞在这里。 // 但是,此时队列中已经有了3个待处理的消息,loop()之后将依次处理消息 Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); }
1.13 Looper.loop()
public static void loop() { final Looper me = myLooper(); ... final MessageQueue queue = me.mQueue; ... for (;;) { // 阻塞在这里,监听消息队列是否有消息到来 Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } ... try { // 处理之前放入的3个消息 msg.target.dispatchMessage(msg); } finally { ... } ... } }
1.14 处理App B消息队列中的3个消息
【注:本文只关注3个消息,其实还有其他的消息】之前放入App B的消息队列中的3个消息:
(1)BIND_APPLICATION消息:
触发消息:AMS调用
thread.bindApplication(processName, appInfo, providers,...)
处理消息:handleBindApplication() @ ActivityThread
(2)CREATE_SERVICE消息:
触发消息:AMS调用
app.thread.scheduleCreateService(r, r.serviceInfo,...)
处理消息:handleCreateService() @ ActivityThread
(3)BIND_SERVICE消息:
触发消息:AMS调用
r.app.thread.scheduleBindService(r, i.intent.getIntent(),...)
处理消息:handleBindService() @ ActivityThread
1.15 小结
在App B进程创建后,从ActivityThread的main()开始,到Looper.loop()执行这段初始化的过程中,AMS调用App B的IApplicationThread接口,向App B的消息队列中放入了3个消息:BIND_APPLICATION消息、CREATE_SERVICE消息和BIND_SERVICE消息,但是没有立即被处理。这3个消息都是在Looper.loop()执行的时候,才被处理的,而且按照队列先进先出的顺序,依次处理BIND_APPLICATION消息、CREATE_SERVICE消息和BIND_SERVICE消息。
可以通过在AMS和ActivityThread中加log来验证这个过程,也可以做一个模拟的测试过程。
2. 测试Handler、Looper的消息处理
2.1 模拟AMS
(1)用于与App B通信的IMyActivityManager.aidlpackage com.galian.ams; interface IMyActivityManager { void attachApplication(IBinder appThread); }
(2)实现IMyActivityManager接口
package com.galian.ams; ... import com.galian.app_a.IMyAppThread; public class TestHandlerLooperActivity extends Activity { private static final String TAG = "AMS/Test"; private EditText mDelaySecondsEditText = null; private IMyAppThread mAppThread = null; private class MyActivityManagerService extends IMyActivityManager.Stub { @Override public void attachApplication(IBinder appThread) throws RemoteException { Log.d(TAG, "attachApplication() start"); mAppThread = IMyAppThread.Stub.asInterface(appThread); if (mAppThread != null) { mAppThread.bindApplication(); mAppThread.scheduleCreateService(); mAppThread.scheduleBindService(); Log.d(TAG, "call bindApplication()/scheduleCreateService()/scheduleBindService()"); Log.d(TAG, "attachApplication() end"); } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test_handler_looper); mDelaySecondsEditText = (EditText) findViewById(R.id.delay_seconds_edittext); Button test_handler_looper_Button = (Button) findViewById(R.id.test_handler_looper); test_handler_looper_Button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Editable edit = mDelaySecondsEditText.getText(); int delaySeconds = 3; if (!edit.toString().trim().equals("")) { delaySeconds = Integer.parseInt(edit.toString()); } Intent intent =new Intent(); intent.setClassName("com.galian.app_a", "com.galian.app_a.MyHandlerService"); // 通过Intent将binder对象传递给模拟的App B IMyActivityManager binder = new MyActivityManagerService(); Bundle extras = new Bundle(); extras.putBinder("binder", (IBinder)binder); extras.putInt("delay", delaySeconds); intent.putExtras(extras); Log.d(TAG, "star MyHandlerService"); // App B的ActivityThread的模拟是通过Service来实现的 startService(intent); } }); Button finish_test_handler_looper_Button = (Button) findViewById(R.id.finish_test_handler_looper); finish_test_handler_looper_Button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent =new Intent(); intent.setClassName("com.galian.app_a", "com.galian.app_a.MyHandlerService"); stopService(intent); } }); } }
2.2 模拟App B的ActivityThread
(1)用于与AMS通信的IMyAppThread.aidlpackage com.galian.app_a; interface IMyAppThread { void bindApplication(); void scheduleCreateService(); void scheduleBindService(); }
(2)通过Service + Thread来模拟App B的ActivityThread
package com.galian.app_a; ... import com.galian.ams.IMyActivityManager; public class MyHandlerService extends Service { private static final String TAG = "AppA/MyHandlerService"; IMyAppThread myAppThread = new MyAppThread(); private int delaySeconds = 0; private MyHandler mHandler = null; private Looper mLooper = null; private boolean isLooperOK = false; @Override public void onCreate() { super.onCreate(); // 模拟ActivityThread启动 Thread thread = new MyThread(); thread.start(); Log.d(TAG, "onCreate()"); } @Override public void onDestroy() { mLooper.quitSafely(); super.onDestroy(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { if (intent != null) { Bundle extras = intent.getExtras(); IMyActivityManager activityManager = IMyActivityManager.Stub.asInterface(extras.getBinder("binder")); try { // 服务启动 if (activityManager != null) { Log.d(TAG, "attachApplication()"); activityManager.attachApplication(myAppThread.asBinder()); } } catch (RemoteException e) { e.printStackTrace(); } delaySeconds = extras.getInt("delay"); isLooperOK = true;// Looper在延迟几秒后可以正常运行了 } return Service.START_NOT_STICKY; } @Override public IBinder onBind(Intent intent) { return null; } private class MyAppThread extends IMyAppThread.Stub { @Override public void bindApplication() throws RemoteException { Message msg = Message.obtain(); msg.what = MyHandler.BIND_APP; Log.d(TAG, "send BIND_APP msg"); mHandler.sendMessage(msg); } @Override public void scheduleCreateService() throws RemoteException { Message msg = Message.obtain(); msg.what = MyHandler.CREATE_SVC; Log.d(TAG, "send CREATE_SVC msg"); mHandler.sendMessage(msg); } @Override public void scheduleBindService() throws RemoteException { Message msg = Message.obtain(); msg.what = MyHandler.BIND_SVC; Log.d(TAG, "send BIND_SVC msg"); mHandler.sendMessage(msg); } } public class MyHandler extends Handler { public static final int BIND_APP = 1; public static final int CREATE_SVC = 2; public static final int BIND_SVC = 3; @Override public void handleMessage(Message msg) { switch (msg.what) { case BIND_APP: Log.d(TAG, "handleBindApplication()"); break; case CREATE_SVC: Log.d(TAG, "handleCreateService()"); break; case BIND_SVC: Log.d(TAG, "handleBindService()"); break; default: break; } } } private class MyThread extends Thread { @Override public void run() { Log.d(TAG, "thread is running."); // 创建Handler、Looper Looper.prepare(); mHandler = new MyHandler(); mLooper = Looper.myLooper(); // 在服务没有执行onStartCommand()的时候,线程处于等待状态 while (!isLooperOK) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } // 延迟的目的是,确保处理消息的代码一定在规定的延迟时间之后执行 // 这样可以看出只有Looper.loop()执行之后,放入队列的消息才会被处理 Log.d(TAG, "sleep " + delaySeconds + " seconds"); try { Thread.sleep(delaySeconds*1000); } catch (InterruptedException e) { e.printStackTrace(); } Log.d(TAG, "Looper.loop()"); Looper.loop(); } } }
2.3 模拟测试的结果
(1)模拟App B进程初次创建:MyHandlerService初次创建01-30 00:40:06.767 15782 15782 D AMS/Test: star MyHandlerService 01-30 00:40:06.947 4537 4537 D AppA/MyHandlerService: onCreate() 01-30 00:40:06.947 4537 4567 D AppA/MyHandlerService: thread is running. 01-30 00:40:06.947 4537 4537 D AppA/MyHandlerService: attachApplication() 01-30 00:40:06.947 15782 15795 D AMS/Test: attachApplication() start 01-30 00:40:06.947 4537 4537 D AppA/MyHandlerService: send BIND_APP msg 01-30 00:40:06.947 4537 4537 D AppA/MyHandlerService: send CREATE_SVC msg 01-30 00:40:06.947 4537 4537 D AppA/MyHandlerService: send BIND_SVC msg 01-30 00:40:06.957 15782 15795 D AMS/Test: call bindApplication()/scheduleCreateService()/scheduleBindService() 01-30 00:40:06.957 15782 15795 D AMS/Test: attachApplication() end 01-30 00:40:07.447 4537 4567 D AppA/MyHandlerService: sleep 6 seconds 01-30 00:40:13.447 4537 4567 D AppA/MyHandlerService: Looper.loop() 01-30 00:40:13.447 4537 4567 D AppA/MyHandlerService: handleBindApplication() 01-30 00:40:13.447 4537 4567 D AppA/MyHandlerService: handleCreateService() 01-30 00:40:13.447 4537 4567 D AppA/MyHandlerService: handleBindService()
其中pid 4537为模拟App B的进程,pid 15782为AMS进程。
为了描述方便,下面提到的AMS和App B都是上面的代码模拟的出来的,不再说模拟的AMS和模拟的App B进程。
App B进程启动后,调用AMS的attachApplication();
AMS在attachApplication()中调用App B的接口,向App B的消息队列放入了3个消息:BIND_APP、CREATE_SVC和BIND_SVC。可以看到此时3个消息都放入了队列,但是没有被处理;
几秒后,Looper.loop()执行了,立即处理消息队列中的3个消息,而且是依次处理。
(2)模拟App B进程已经启动:MyHandlerService已经运行,此时往消息队列放入消息
01-30 00:40:52.797 15782 15782 D AMS/Test: star MyHandlerService 01-30 00:40:52.797 4537 4537 D AppA/MyHandlerService: attachApplication() 01-30 00:40:52.807 15782 15795 D AMS/Test: attachApplication() start 01-30 00:40:52.807 4537 4537 D AppA/MyHandlerService: send BIND_APP msg 01-30 00:40:52.807 4537 4567 D AppA/MyHandlerService: handleBindApplication() 01-30 00:40:52.807 4537 4537 D AppA/MyHandlerService: send CREATE_SVC msg 01-30 00:40:52.807 4537 4537 D AppA/MyHandlerService: send BIND_SVC msg 01-30 00:40:52.807 15782 15795 D AMS/Test: call bindApplication()/scheduleCreateService()/scheduleBindService() 01-30 00:40:52.807 15782 15795 D AMS/Test: attachApplication() end 01-30 00:40:52.807 4537 4567 D AppA/MyHandlerService: handleCreateService() 01-30 00:40:52.817 4537 4567 D AppA/MyHandlerService: handleBindService()
可以看到,将消息放入消息队列,消息是被立即处理的。
注:这里说的“立即处理”不是同步处理,AMS进程和App B进程是并行的。
3. 总结
(1)本文所讲的内容,即使被你忽略了,也不影响对bindService()代码流程的理解(2)在App进程首次创建的时候,在Looper.loop()运行之前,AMS调用IApplicationThread接口放入App 消息队列的消息不会立即被处理。在Looper.loop()运行的时候,将依次处理消息队列中的所有消息。
继续阅读:
《Android中bindService的细节之一:从进程的角度分析绑定Service的流程【Service所在进程首次启动】》
《Android中bindService的细节之二:从进程的角度分析绑定Service的流程【Service所在进程已存在】》
相关文章推荐
- Android中bindService的细节之二:从进程的角度分析绑定Service的流程【Service所在进程已存在】
- Android中bindService的细节之一:从进程的角度分析绑定Service的流程【Service所在进程首次启动】
- Android ActivityManagerService(AMS)的启动分析 << 代码讲的比较细致,在了解主要流程后再看这篇
- Android进阶笔记:bindService的流程--源码解析
- Android中bindService的细节之三:多次调用bindService(),为什么onBind()只执行一次?
- Android应用程序绑定服务(bindService)的过程源代码分析(3)
- 从Alarm看Android上层UI到内核代码的流程分析
- Android Bind Service机制详解
- Android TabActivity无法正常bindService解决方法
- 浅析android通过jni控制service服务程序的简易流程
- Android学习札记三:初涉Service(1)之Context.startService()与Context.bindService()区别(转)
- Android应用程序绑定服务(bindService)的过程源代码分析(2)
- 浅析android通过jni控制service服务程序的简易流程
- Android中BindService方式使用的理解
- Android之使用bindService启动服务
- Android 中的WiFi学习笔记(转载)----WIFI启动 代码流程走读---网络连接流程
- Android Service 之三(Bind Service, 继承自 Binder 类)
- 不要忽略内存的一些细节 读X264代码
- 浅析android通过jni控制service服务程序的简易流程
- 【转】Android TabActivity无法正常bindService解决方法