android Application Component研究之Service

2024-06-03 15:32

本文主要是介绍android Application Component研究之Service,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本文为原创文章,欢迎转载!转载时请注明出处: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

[java]  view plain copy
  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

[java]  view plain copy
  1. /*package*/ LoadedApk mPackageInfo;  
@LoadedApk.java
[java]  view plain copy
  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

[java]  view plain copy
  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

[java]  view plain copy
  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

[java]  view plain copy
  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
[java]  view plain copy
  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

[java]  view plain copy
  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

[java]  view plain copy
  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

[java]  view plain copy
  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

[java]  view plain copy
  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过程均不会去销毁.

这篇关于android Application Component研究之Service的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1027386

相关文章

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

android-opencv-jni

//------------------start opencv--------------------@Override public void onResume(){ super.onResume(); //通过OpenCV引擎服务加载并初始化OpenCV类库,所谓OpenCV引擎服务即是 //OpenCV_2.4.3.2_Manager_2.4_*.apk程序包,存

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动

Android 10.0 mtk平板camera2横屏预览旋转90度横屏拍照图片旋转90度功能实现

1.前言 在10.0的系统rom定制化开发中,在进行一些平板等默认横屏的设备开发的过程中,需要在进入camera2的 时候,默认预览图像也是需要横屏显示的,在上一篇已经实现了横屏预览功能,然后发现横屏预览后,拍照保存的图片 依然是竖屏的,所以说同样需要将图片也保存为横屏图标了,所以就需要看下mtk的camera2的相关横屏保存图片功能, 如何实现实现横屏保存图片功能 如图所示: 2.mtk

android应用中res目录说明

Android应用的res目录是一个特殊的项目,该项目里存放了Android应用所用的全部资源,包括图片、字符串、颜色、尺寸、样式等,类似于web开发中的public目录,js、css、image、style。。。。 Android按照约定,将不同的资源放在不同的文件夹中,这样可以方便的让AAPT(即Android Asset Packaging Tool , 在SDK的build-tools目

Android fill_parent、match_parent、wrap_content三者的作用及区别

这三个属性都是用来适应视图的水平或者垂直大小,以视图的内容或尺寸为基础的布局,比精确的指定视图的范围更加方便。 1、fill_parent 设置一个视图的布局为fill_parent将强制性的使视图扩展至它父元素的大小 2、match_parent 和fill_parent一样,从字面上的意思match_parent更贴切一些,于是从2.2开始,两个属性都可以使用,但2.3版本以后的建议使

Android Environment 获取的路径问题

1. 以获取 /System 路径为例 /*** Return root of the "system" partition holding the core Android OS.* Always present and mounted read-only.*/public static @NonNull File getRootDirectory() {return DIR_ANDR

一种改进的red5集群方案的应用、基于Red5服务器集群负载均衡调度算法研究

转自: 一种改进的red5集群方案的应用: http://wenku.baidu.com/link?url=jYQ1wNwHVBqJ-5XCYq0PRligp6Y5q6BYXyISUsF56My8DP8dc9CZ4pZvpPz1abxJn8fojMrL0IyfmMHStpvkotqC1RWlRMGnzVL1X4IPOa_  基于Red5服务器集群负载均衡调度算法研究 http://ww

Android逆向(反调,脱壳,过ssl证书脚本)

文章目录 总结 基础Android基础工具 定位关键代码页面activity定位数据包参数定位堆栈追踪 编写反调脱壳好用的脚本过ssl证书校验抓包反调的脚本打印堆栈bilibili反调的脚本 总结 暑假做了两个月的Android逆向,记录一下自己学到的东西。对于app渗透有了一些思路。 这两个月主要做的是代码分析,对于分析完后的持久化等没有学习。主要是如何反编译源码,如何找到