欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

android Application Component研究之Service

程序员文章站 2022-07-15 12:28:56
...

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

android Application Component研究之Service

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


android Application Component研究之Service

1. ServiceDispatcher

1.1 创建ServiceDispatcher

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

@ContextImpl.java

    @Override
    public boolean bindService(Intent service, ServiceConnection conn,
            int flags) {
        IServiceConnection sd;
        if (mPackageInfo != null) {
            sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(),
                    mMainThread.getHandler(), flags);
        } else {
            throw new RuntimeException("Not supported in system context");
        }
        try {
            int res = ActivityManagerNative.getDefault().bindService(
                mMainThread.getApplicationThread(), getActivityToken(),
                service, service.resolveTypeIfNeeded(getContentResolver()),
                sd, flags);
            if (res < 0) {
                throw new SecurityException(
                        "Not allowed to bind to service " + service);
            }
            return res != 0;
        } catch (RemoteException e) {
            return false;
        }
    }

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

@ContextImpl.java

    /*package*/ LoadedApk mPackageInfo;
@LoadedApk.java
    public final IServiceConnection getServiceDispatcher(ServiceConnection c,
            Context context, Handler handler, int flags) {
        synchronized (mServices) {
            LoadedApk.ServiceDispatcher sd = null;
            HashMap<ServiceConnection, LoadedApk.ServiceDispatcher> map = mServices.get(context);
            if (map != null) {
                sd = map.get(c);
            }
            if (sd == null) {
                sd = new ServiceDispatcher(c, context, handler, flags);
                if (map == null) {
                    map = new HashMap<ServiceConnection, LoadedApk.ServiceDispatcher>();
                    mServices.put(context, map);
                }
                map.put(c, sd);
            } else {
                sd.validate(context, handler);
            }
            return sd.getIServiceConnection();
        }
    }
从上面代码可以看出,对于ServiceDispatcher的管理,是以package为载体的,也就是说对于某个package,定义在其中的component如果请求去bind一个service,那么

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

android Application Component研究之Service

1.2InnerConnection

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

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

首要一点是因为,ServiceConnection是一个Interface(java),我们传递的之时implementsServiceConnection之后的一个内部类对象,根据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

        mCurIntent = new Intent(InputMethod.SERVICE_INTERFACE);
        mCurIntent.setComponent(info.getComponent());
        mCurIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
                com.android.internal.R.string.input_method_binding_label);
        mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
                mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0));
        if (mContext.bindService(mCurIntent, this, Context.BIND_AUTO_CREATE)) {
            mLastBindTime = SystemClock.uptimeMillis();
            mHaveConnection = true;
            mCurId = info.getId();
            mCurToken = new Binder();
            try {
                if (DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken);
                mIWindowManager.addWindowToken(mCurToken,
                        WindowManager.LayoutParams.TYPE_INPUT_METHOD);
            } catch (RemoteException e) {
            }
            return new InputBindResult(null, mCurId, mCurSeq);
        } else {
            mCurIntent = null;
            Slog.w(TAG, "Failure connecting to input method service: "
                    + mCurIntent);
        }

android Application Component研究之Service

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

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

bindService()@ActivityManagerService.java

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

3. Service相关数据结构

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

android Application Component研究之Service

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

android Application Component研究之Service

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相关的几种数据结构关系如下:

android Application Component研究之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

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

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
        if (s.app != null && s.app.thread != null && b.intent.apps.size() == 0
                && b.intent.hasBound) {
            try {
                bumpServiceExecutingLocked(s, "unbind");
                updateOomAdjLocked(s.app);
                b.intent.hasBound = false;
                // Assume the client doesn't want to know about a rebind;
                // we will deal with that later if it asks for one.
                b.intent.doRebind = false;
                s.app.thread.scheduleUnbindService(s, b.intent.intent.getIntent());
            } catch (Exception e) {
                Slog.w(TAG, "Exception when unbinding service " + s.shortName, e);
                serviceDoneExecutingLocked(s, true);
            }
        }

5.2 destroy Service

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

removeConnectionLocked()@ActivityManagerService.java

        if ((c.flags&Context.BIND_AUTO_CREATE) != 0) {
            bringDownServiceLocked(s, false);
        }
如果没有设置flagBIND_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

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

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

bringDownServiceLocked()@ActivityManagerService.java

            Iterator<ArrayList<ConnectionRecord>> it = r.connections.values().iterator();
            while (it.hasNext()) {
                ArrayList<ConnectionRecord> c = it.next();
                for (int i=0; i<c.size(); i++) {
                    try {
                        c.get(i).conn.connected(r.name, null);
                    } catch (Exception e) {
                        Slog.w(TAG, "Failure disconnecting service " + r.name +
                              " to connection " + c.get(i).conn.asBinder() +
                              " (in " + c.get(i).binding.client.processName + ")", e);
                    }
                }
            }

并且unbind掉所有的client.

bringDownServiceLocked()@ActivityManagerService.java

        if (r.bindings.size() > 0 && r.app != null && r.app.thread != null) {
            Iterator<IntentBindRecord> it = r.bindings.values().iterator();
            while (it.hasNext()) {
                IntentBindRecord ibr = it.next();
                if (DEBUG_SERVICE) Slog.v(TAG, "Bringing down binding " + ibr
                        + ": hasBound=" + ibr.hasBound);
                if (r.app != null && r.app.thread != null && ibr.hasBound) {
                    try {
                        bumpServiceExecutingLocked(r, "bring down unbind");
                        updateOomAdjLocked(r.app);
                        ibr.hasBound = false;
                        r.app.thread.scheduleUnbindService(r,
                                ibr.intent.getIntent());
                    } catch (Exception e) {
                        Slog.w(TAG, "Exception when unbinding service "
                                + r.shortName, e);
                        serviceDoneExecutingLocked(r, true);
                    }
                }
            }
        }
总而言之,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过程均不会去销毁.