现在的位置: 首页 > 综合 > 正文

android Application Component研究之Service

2013年08月31日 ⁄ 综合 ⁄ 共 13209字 ⁄ 字号 评论关闭

本文为原创文章,欢迎转载!转载时请注明出处:http://blog.csdn.net/windskier

    前面2篇文章介绍了acitivity的管理,其中保存task的管理,activity生命周期中各个阶段的操作等问题,这篇文章我们来详细的研究一下android系统中application Service的管理过程。

   Service是android中一个非常重要的组件,作为一个从事android开发的人,service是必须掌握的一个组件,这篇文章不是从如何使用Service角度来分析的,而是从AMS如何管理Service的角度来分析AMS的源码,以期理解AMS针对Service的操作的源码实现。

    我们知道,Service的启动方式有两种,一种是通过startService()方法来启动,这种方法的启动作为client方,只是作为一个启动者存在的,它只能通过onStartCommand()方法去要求Service做某些操作,而通过bind方式的Service则不同,而通过bindService()方法启动并bind Service的话,client可以通过Service提供的业务接口,通过IPC的方式来访问Service的相关业务操作。

    而两种方式的生命周期也不尽相同,当client通过startService()方法来启动service,client通过调用stopService()或者service本身调用stopSelf()来结束当前的Service,假如多个client启动的这个Service,那么只要一个client stop它,那么就将会被stop掉。

  而client通过bindService()方式启动的Service,那么client将通过unbindService()来结束与service的连接,这种方式的生命周期后面会介绍到。

    更多的关于service的应用请参考android的官方文档。

    

     关于通过startService()方式启动serivce的源码,这里就不再分析了,下面着重分析一下通过bindService()方法来启动并bind service的过程,着启动其实已经包含了startService()方式启动serivce的源码。

     

1. ServiceDispatcher

1.1 创建ServiceDispatcher

    ServiceDispatcher从它的名字中可以看出它类似于一个Dispatcher的作用,这个类的作用就是当client bind某个service成功之后,负责向client分配service IBinder的;以及当client unbind service时,负责通知client unbind service的状态。

@ContextImpl.java

  1. @Override  
  2. public boolean bindService(Intent service, ServiceConnection conn,  
  3.         int flags) {  
  4.     IServiceConnection sd;  
  5.     if (mPackageInfo != null) {  
  6.         sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(),  
  7.                 mMainThread.getHandler(), flags);  
  8.     } else {  
  9.         throw new RuntimeException("Not supported in system context");  
  10.     }  
  11.     try {  
  12.         int res = ActivityManagerNative.getDefault().bindService(  
  13.             mMainThread.getApplicationThread(), getActivityToken(),  
  14.             service, service.resolveTypeIfNeeded(getContentResolver()),  
  15.             sd, flags);  
  16.         if (res < 0) {  
  17.             throw new SecurityException(  
  18.                     "Not allowed to bind to service " + service);  
  19.         }  
  20.         return res != 0;  
  21.     } catch (RemoteException e) {  
  22.         return false;  
  23.     }  
  24. }  

    mPackageInfo对象是一个LoadedApk类型,LoadedApk类型存储着当前package的内容。

@ContextImpl.java

  1. /*package*/ LoadedApk mPackageInfo;  

@LoadedApk.java

  1. public final IServiceConnection getServiceDispatcher(ServiceConnection c,  
  2.         Context context, Handler handler, int flags) {  
  3.     synchronized (mServices) {  
  4.         LoadedApk.ServiceDispatcher sd = null;  
  5.         HashMap<ServiceConnection, LoadedApk.ServiceDispatcher> map = mServices.get(context);  
  6.         if (map != null) {  
  7.             sd = map.get(c);  
  8.         }  
  9.         if (sd == null) {  
  10.             sd = new ServiceDispatcher(c, context, handler, flags);  
  11.             if (map == null) {  
  12.                 map = new HashMap<ServiceConnection, LoadedApk.ServiceDispatcher>();  
  13.                 mServices.put(context, map);  
  14.             }  
  15.             map.put(c, sd);  
  16.         } else {  
  17.             sd.validate(context, handler);  
  18.         }  
  19.         return sd.getIServiceConnection();  
  20.     }  
  21. }  

    从上面代码可以看出,对于ServiceDispatcher的管理,是以package为载体的,也就是说对于某个package,定义在其中的component如果请求去bind一个service,那么

LoadedApk将为这个component分配一个ServiceDispatcher。因此,ServiceDispatcher是client方的一个概念。component每请求bind一个不同的service,LoadedApk将会为其分配一个ServiceDispatcher,如下图所示。

    

1.2 InnerConnection

    从上面bindService()的代码中可以看出,我们在bind 过程中传递给AMS的并不是ServiceConnection,而是一个IServiceConnection interface,它对应的服务实体类型是InnerConnection,InnerConnection是ServiceDispatcher的一个内部静态类。

    疑问1:为什么不直接把ServiceConnection传递给AMS作为回调,而是传递另外一个类型InnerConnection呢?

    首要一点是因为,ServiceConnection是一个Interface(java),我们传递的之时implements ServiceConnection之后的一个内部类对象,根据android IBinder的实现机制,在IPC调用过程中,传递的参数必须是Parcel对象,这就要求传递的对象是Parcel类型的,或者这个类型实现了写入Parcel对象的方法,即writeToParcel()。即使ServiceConnection实现了writeToParcel(),它也不可能将其回调函数传递给AMS,因为writeToParcel()主要是对成员变量的打包过程,Parcel并没有实现对方法的打包,因此系统不可能将ServiceConnection内部类传递给AMS,因此需要提供给AMS另外一种方法来回调ServiceConnection中的方法,这个InnerConnection就起到了这个作用。

    疑问2:既然决定用InnerConnection来提供给AMS远程调用ServiceConnection的方法,那么为什么还需要ServiceDispatcher?

    其实,在我看来ServiceDispatcher完全可以被InnerConnection代替,从功能来看是可以实现的,但是在IPC通信编程中,我们应该尽量的减少IBinder参数的负荷,这么做无可厚非。

2. Client Intent

    在android中有这么一种情况,如果某个application包括了"android.uid.system"的sharedUserId,这其中包括system service。这个application需要bind一个service,并且这个service只允许"android.uid.system"的application来bind。这么做是处于安全方面考虑的,某些Service不想处system uid之外的应用来请求服务。

    在上述描述的情景中,如果一个第三方的appliction,它不可能有"android.uid.system"的sharedUserId,想要去请求上述的service时,就不能够直接bind 这个servcie,而是只能通过上述的system uid的application来请求。那么如何通过system uid的application来请求service呢?AMS为这种情形的需求实现了一套解决方案。下面我们以InputMethodManagerService为例来说明一下。

    先简单介绍一下输入法的逻辑,由于系统中可以存在多个输入法,InputMethodManagerService会去bind系统设置的或者用户选择的输入法service,这些输入法service只能被system uid的application来请求。IMS在bind输入法service时,会提供一个PendingIntent给AMS,由于这个PendingIntent是供Client来访问输入法Service,我们称这个PendingIntent为ClientIntent,相关代码如下所示:

startInputInnerLocked()@InputMethodManagerService.java

  1. mCurIntent = new Intent(InputMethod.SERVICE_INTERFACE);  
  2. mCurIntent.setComponent(info.getComponent());  
  3. mCurIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,  
  4.         com.android.internal.R.string.input_method_binding_label);  
  5. mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(  
  6.         mContext, 0new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0));  
  7. if (mContext.bindService(mCurIntent, this, Context.BIND_AUTO_CREATE)) {  
  8.     mLastBindTime = SystemClock.uptimeMillis();  
  9.     mHaveConnection = true;  
  10.     mCurId = info.getId();  
  11.     mCurToken = new Binder();  
  12.     try {  
  13.         if (DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken);  
  14.         mIWindowManager.addWindowToken(mCurToken,  
  15.                 WindowManager.LayoutParams.TYPE_INPUT_METHOD);  
  16.     } catch (RemoteException e) {  
  17.     }  
  18.     return new InputBindResult(null, mCurId, mCurSeq);  
  19. else {  
  20.     mCurIntent = null;  
  21.     Slog.w(TAG, "Failure connecting to input method service: "  
  22.             + mCurIntent);  
  23. }  

   

    由上图所示,输入法service只能被system uid的application请求,系统服务InputMethodManagerService bind了它,并且向AMS提供了一个client Intent,第三方的application如果需要对输入法service进行请求,只能通过IMS提供的这个client Intent指示的activity来进行操作,这个activity是LanguageSettings。这样就达到了避免输入法service不被第三方应用随意操作引起的安全问题。

    AMS中对client Intent的管理如下代码所示:

bindService()@ActivityManagerService.java

  1. if (callerApp.info.uid == Process.SYSTEM_UID) {  
  2.     // Hacky kind of thing -- allow system stuff to tell us  
  3.     // what they are, so we can report this elsewhere for  
  4.     // others to know why certain services are running.  
  5.     try {  
  6.         clientIntent = (PendingIntent)service.getParcelableExtra(  
  7.                 Intent.EXTRA_CLIENT_INTENT);  
  8.     } catch (RuntimeException e) {  
  9.     }  
  10.     if (clientIntent != null) {  
  11.         clientLabel = service.getIntExtra(Intent.EXTRA_CLIENT_LABEL, 0);  
  12.         if (clientLabel != 0) {  
  13.             // There are no useful extras in the intent, trash them.  
  14.             // System code calling with this stuff just needs to know  
  15.             // this will happen.  
  16.             service = service.cloneFilter();  
  17.         }  
  18.     }  
  19. }  

3. Service相关数据结构

    下面简单的介绍一下AMS进行Service管理时涉及到的数据结构,通过这些数据结构我们就能够很好的了解到AMS是如何对Service进行管理的,这部分没有什么很复杂的难以理解的内容,有一个直观的认识即可,能够方便阅读代码即可。

    

    每个Service均可能有不同的应用进程来bind,同时bind该Service的Intent也可能有多个,也就是说可能存在多个进程来bind该service,并且多个不同进程可能使用同一个Intent来bind serice。下图示意了这种存在的可能。

    

    AMS为每个bind 该service的Intent分配了一个IntentBindRecord类型对象,并存储在ServiceRecord.bindings成员变量中;

    AMS为每个bind该service的进程分配一个AppBindRecord类型对象,因为不同的应用程序可能使用同一个Intent来bind service,因此AMS将这个AppBindRecord对象存储在IntentBindRecord.apps中,多进程使用同一个Intent的典型案例就是上节所说的Client Intent;

    AMS为每次bind的连接分配一个ConnectionRecord类型对象,每个应用进程中可能有多个Component来使用相同的Intent来bind该Service,那么处于同一个进程的ConnectionRecord对象被存储在AppBindRecord.connections中;

    而ActivityRecord以及ProcessRecord中的connections则包含了所有的bind不同的Service的ConnectionRecord对象。它与AppBindRecord.connections范围不同。

    总而言之,与Service相关的几种数据结构关系如下:

   

4. 设置Forground属性

    在系统空间吃紧的情况下,系统需要杀死一些进程并收回其内存供其他进程使用,当然service所在的进程与不能幸免,但是如果当前的service非常重要,不允许被系统回收,那么就需要在service启动时设置其Forground属性,很简单就通过Service的一个方法startForeground()来设置。系统默认Service是background的。

    如果Service不想保持forground属性,那么可以调用Service的stopForeground()方法来实现,使其回到background属性。

    在设置Service 为forground属性时,可以要求系统显示一个notification在status bar,user可以通过notification来执行设定的操作。

    如果进程中只要有一个Service处在forground属性状态,那么这个进程被回收的oom_adj值就为PERCEPTIBLE_APP_ADJ,从而达到避免被系统回收的目的。关于AMS的oom机制以后专门分析。

computeOomAdjLocked()@ActivityManagerService.java

  1. else if (app.foregroundServices) {  
  2.    // The user is aware of this app, so make it visible.  
  3.    adj = PERCEPTIBLE_APP_ADJ;  
  4.    schedGroup = Process.THREAD_GROUP_DEFAULT;  
  5.    app.adjType = "foreground-service";  
  6.    

5. unbind Service

    bind Service过程是一个从无到有的集创建和bind的过程,而unbind过程则是集unbind和销毁Service的过程,但是unbind何时才会包含销毁service的过程,这个规则还是有必要研究一下。

5.1 unbind Service

    对应client来说,unbind Service过程将会unbind掉所有通过同一IServiceConnection  bind的client;
    对于service来说,只有当某一个Intent记录的通过该Intent bind service的进程个数为0时,service的进程才会去执行unbind的操作,如下面代码所描述。
removeConnectionLocked()@ActivityManagerService.java
  1. if (s.app != null && s.app.thread != null && b.intent.apps.size() == 0  
  2.         && b.intent.hasBound) {  
  3.     try {  
  4.         bumpServiceExecutingLocked(s, "unbind");  
  5.         updateOomAdjLocked(s.app);  
  6.         b.intent.hasBound = false;  
  7.         // Assume the client doesn't want to know about a rebind;  
  8.         // we will deal with that later if it asks for one.  
  9.         b.intent.doRebind = false;  
  10.         s.app.thread.scheduleUnbindService(s, b.intent.intent.getIntent());  
  11.     } catch (Exception e) {  
  12.         Slog.w(TAG, "Exception when unbinding service " + s.shortName, e);  
  13.         serviceDoneExecutingLocked(s, true);  
  14.     }  
  15. }  

5.2 destroy Service

    unbind一个Service过程中,何时需要destroy这个Service,首先还是需要看bind该Service时设置的flag,如果设置了flag BIND_AUTO_CREATE,那么在unbind之后会去检查是否应该destroy这个Service,如下面代码所示:

removeConnectionLocked()@ActivityManagerService.java

  1. if ((c.flags&Context.BIND_AUTO_CREATE) != 0) {  
  2.     bringDownServiceLocked(s, false);  
  3. }  

    如果没有设置flag BIND_AUTO_CREATE,则AMS根本就不考虑destoy service的操作。下面来分析一下如果设置了BIND_AUTO_CREATE,那么AMS满足哪些条件就会去destroy 这个Service。

    ●  unbind过程中,remove掉当前这个ConnectionRecord之后,发现目前Service中尚有其他的ConnectionRecord仍旧设置了BIND_AUTO_CREATE,那么这个Service将不会被destroy;

        只有当Service中不再有任何的ConnectionRecord设置BIND_AUTO_CREATE之后,才去destroy这个Service。因此,可以看出BIND_AUTO_CREATE对Service的销毁过程起到了决定性的作用。

bringDownServiceLocked()@ActivityManagerService.java

  1. if (!force) {  
  2.     // XXX should probably keep a count of the number of auto-create  
  3.     // connections directly in the service.  
  4.     Iterator<ArrayList<ConnectionRecord>> it = r.connections.values().iterator();  
  5.     while (it.hasNext()) {  
  6.         ArrayList<ConnectionRecord> cr = it.next();  
  7.         for (int i=0; i<cr.size(); i++) {  
  8.             if ((cr.get(i).flags&Context.BIND_AUTO_CREATE) != 0) {  
  9.                 return;  
  10.             }  
  11.         }  
  12.     }  
  13. }  

    ●  如果Service中没有了任何ConnectionRecord设置了BIND_AUTO_CREATE,那么将会销毁这个Service,在销毁之前,还有通过IServiceConnection.connected()通知所有的Client。

bringDownServiceLocked()@ActivityManagerService.java

  1. Iterator<ArrayList<ConnectionRecord>> it = r.connections.values().iterator();  
  2. while (it.hasNext()) {  
  3.     ArrayList<ConnectionRecord> c = it.next();  
  4.     for (int i=0; i<c.size(); i++) {  
  5.         try {  
  6.             c.get(i).conn.connected(r.name, null);  
  7.         } catch (Exception e) {  
  8.             Slog.w(TAG, "Failure disconnecting service " + r.name +  
  9.                   " to connection " + c.get(i).conn.asBinder() +  
  10.                   " (in " + c.get(i).binding.client.processName + ")", e);  
  11.         }  
  12.     }  
  13. }  

    并且unbind掉所有的client.

bringDownServiceLocked()@ActivityManagerService.java

  1. if (r.bindings.size() > 0 && r.app != null && r.app.thread != null) {  
  2.     Iterator<IntentBindRecord> it = r.bindings.values().iterator();  
  3.     while (it.hasNext()) {  
  4.         IntentBindRecord ibr = it.next();  
  5.         if (DEBUG_SERVICE) Slog.v(TAG, "Bringing down binding " + ibr  
  6.                 + ": hasBound=" + ibr.hasBound);  
  7.         if (r.app != null && r.app.thread != null && ibr.hasBound) {  
  8.             try {  
  9.                 bumpServiceExecutingLocked(r, "bring down unbind");  
  10.                 updateOomAdjLocked(r.app);  
  11.                 ibr.hasBound = false;  
  12.                 r.app.thread.scheduleUnbindService(r,  
  13.                         ibr.intent.getIntent());  
  14.             } catch (Exception e) {  
  15.                 Slog.w(TAG, "Exception when unbinding service "  
  16.                         + r.shortName, e);  
  17.                 serviceDoneExecutingLocked(r, true);  
  18.             }  
  19.         }  
  20.     }  
  21. }  

    总而言之,unbind的过程需要注意4点:

    1. unbind过程会unbind掉所有使用同一个IServiceConnection bind 该Service的Client;

    2. unbind过程是针对Intent进行的,因此只有bind Service的IntentBindRecord中记录的进程个数为0时,才会去执行unbind过程。也就是说假如有多个进程通过同一个Intent来bind Service,那么只有当基于这个Intent的进程均被remove出IntentBindRecord之后采取真正的去unbind。

    3. unbind的ConnectionRecord如果设置了BIND_AUTO_CREATE,那么AMS就会尝试去销毁这个Service,如果Service中剩余的所有ConnectionRecord均未设置BIND_AUTO_CREATE,则会去销毁这个Service,换句话说,如果当Service中有多个ConnectionRecord设置了BIND_AUTO_CREATE,那么只有当最后一个设置了BIND_AUTO_CREATE ConnectionRecord在被unbind时,才会去销毁这个Service,即使当前还有其他的ConnectionRecord
bind着这个Service。

    4. 如果当前的Service是通过startService()来启动的,那么任何的unbind过程均不会去销毁.

抱歉!评论已关闭.