BindService详解

Posted 泼墨成空

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了BindService详解相关的知识,希望对你有一定的参考价值。

       接着上一篇,本文就解决《篇Binder详解》末尾抛出的问题,也就是如下的问题:

        我们客户端(即MainActivity)接受远程对象是在自己重写的ServiceConnection的onServiceConnected()方法中接收的,那么系统是何时对ServiceConnection的onServiceConnect()方法进行回调的呢?这一切要从我们调用bindService()说起。

bindService()的发源地

      我们知道,在Activity要启动Service有两种方式,一种就是startService(),还有一种就是bindService(),两者的使用场景不一样,启动的参数也不同,前者是当Service和当前组件在同一个进程时使用,后者是当Service和当前组件不在同一个进程时使用,而且bindService()还需要传递一个ServiceConnection实例。虽然我们在Activity中可以直接使用bindService(),但是bindService()并不是Activity的方法,而是Context的方法,我们都知道,四大组件的超类都是Context,只不过各组件的继承链不一样,比如Activity的继承链是:Activity→ContextThemeWrapper→ContextWrapper→Context。此外Context中的bindService()只是一个abstract方法,真正的实现是在ContextImpl中,而且ContextWrap也把bindService()给包装了一下,包装的目的就是去调用ContextImpl中真正实现了的bindService(),所以我们在Activity中调用bindService()的时候最后实际上是调用了ContextImpl的bindService()。但是在Activity的继承链中我们似乎并没有发现ContextImpl的影子?确实,Activity并没有直接继承ContextImpl,ContextImpl是一个隐藏的类。那么Activity是如何去使用到ContextImpl的bindService的()?我们可以看到在ContextWrap中有一个变量叫做mBase,此mBase就是一个Context实例,确切说是一个ContextImpl实例,而且ContextWrapper的bindService()就是一行代码:mBase.bindService()来实现调用ContextImpl的bindService()的。那么mBase他是在什么时候被赋值的?那就是在Activity被创建时调用其attach()的时候传递过来的,当ActivityThread在创建Activity的时候就会调用ContextImpl的createActivityContext()方法new一个ContextImpl实例,并调用Activity的atatch()方法为mBase赋值。说了这么多,其实就是想说两点,第一bindService()是ContextImpl中的方法,第二既然Activity没有直接实现ContextImpl,那为什么我们可以直接调用bindService()。

bindService()所做的一切

       现在我们开始对bindService()进行分析,通过上面的介绍,那我们的分析自然是从ContextImpl的bindService()开始了,上代码:

	@Override
	public boolean bindService(Intent service, ServiceConnection conn, int flags) 
		//这个方法仅仅是打印日志用于警告,忽略
		warnIfCallingFromSystemProcess();
		//调用了bindServiceCommon()看下面
		return bindServiceCommon(service, conn, flags, Process.myUserHandle());
	
	/**
	 *  启动远程Service
	 * @param service  Intent指定要启动的Service
	 * @param conn      ServiceConnection,用于Service连接后就通过这个对象来回调其方法返回远程的Binder对象
	 * @param flags
	 * @param user
	 * @return
	 */
	private boolean bindServiceCommon(Intent service, ServiceConnection conn,
			int flags, UserHandle user) 
		IServiceConnection sd;
		if (conn == null) 
			throw new IllegalArgumentException("connection is null");
		
		if (mPackageInfo != null) 
			//mPackageInfo是一个LoadApk实例,此方法用于返回一个IServiceConnection实例也就是InnerConnection类型的对象
			sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(),
					mMainThread.getHandler(), flags);
		 else 
			throw new RuntimeException("Not supported in system context");
		
		validateServiceIntent(service);
		try 
			//获取令牌,表示要bindService的身份,这里就是用来表明Activity的身份
			IBinder token = getActivityToken();
			if (token == null
					&& (flags & BIND_AUTO_CREATE) == 0
					&& mPackageInfo != null
					&& mPackageInfo.getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) 
				flags |= BIND_WAIVE_PRIORITY;
			
			service.prepareToLeaveProcess();
			//AMS调用bindService()
			int res = ActivityManagerNative.getDefault().bindService(
					mMainThread.getApplicationThread(), getActivityToken(),
					service, service.resolveTypeIfNeeded(getContentResolver()),
					sd, flags, getOpPackageName(), user.getIdentifier());
			if (res < 0) 
				throw new SecurityException("Not allowed to bind to service "
						+ service);
			
			return res != 0;
		 catch (RemoteException e) 
			throw new RuntimeException("Failure from system", e);
		
	

        可以看到在bindServiceCommon()方法中,会首先通过mPackageInfo.getServiceDispatcher()获取一个IServiceConnection实例,这个mPackageInfo是一个LoadApk实例,我们去看看getServiceDispatcher()是个什么方法:

   

    /**
     * 获取一个IServiceConnection实例,
     * @param c
     * @param context
     * @param handler
     * @param flags
     * @return
     */
    public final IServiceConnection getServiceDispatcher(ServiceConnection c,
            Context context, Handler handler, int flags) 
        synchronized (mServices) 
            LoadedApk.ServiceDispatcher sd = null;
            ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher> map = mServices.get(context);
            if (map != null) 
                sd = map.get(c);
            
            if (sd == null) 
            	//ServiceDispatcher是LoadedApk的内部类,在此方法中就会创建一个InnerConnection实例
                sd = new ServiceDispatcher(c, context, handler, flags);
                //如果本地还没有此链接就创建新的连接并保存到数组
                if (map == null) 
                    map = new ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>();
                    mServices.put(context, map);
                
                map.put(c, sd);
             else 
                sd.validate(context, handler);
            
            //返回InnerConnection实例mIServiceConnection
            return sd.getIServiceConnection();
        
    
       首先会从本地map中读取是否已经存在ServiceDispatcher实例,如果没有就会去创建一个,注意ServiceDispatcher是LoadedApk的内部类,而ServiceDispatcher里还有一个内部类InnerConnection,这个 InnerConnection是很重要的,它就是继承了IServiceConnection.Stub的类。在ServiceDispatcher的构造方法中就会根据我们bindService()时传入的ServiceConnection实例创建InnerConnection,最后在方法的结尾,调用ServiceDispatcher实例返回一个IServiceConnection实例。这里再强调一下, IServiceConnection就相当于上一篇文章的CustomBinder,而InnerConnection相当于TestService的MyBinder!我们可以看到IServiceConnection的结构和CustomBinder的结构一模样的。而且在IServiceConnection.Stub中有一个抽象的connected()方法,此方法就是在InnerConnection中实现的,而InnerConnection中的connected()方法最终会调用到我们在调用bindService()时传入的ServiceConnection的onServiceConnected()方法,从而告知客户端服务已连接,并且返回一个远程对象。那么InnerConnection的connected()方法何时被调用的呢?

       上面讲了一大堆获取了IServiceConnection的过程,我们回到ContextImpl的bindServiceCommon()方法中去,在获取IServiceConnection实例后,就又通过跨进程通信让ActivityManagerService调用bindService()方法,也就是通过下面一段代码:

int res = ActivityManagerNative.getDefault().bindService(
					mMainThread.getApplicationThread(), getActivityToken(),
					service, service.resolveTypeIfNeeded(getContentResolver()),
					sd, flags, getOpPackageName(), user.getIdentifier());
      关于他是如何跨进程通信到ActivityManagerService的,请看另一篇文章 http://blog.csdn.net/u012481172/article/details/49658633; 好,接下来进入到AMS的bindService(),看下AMS的bindService()方法如下:

 

   public int bindService(IApplicationThread caller, IBinder token, Intent service,
            String resolvedType, IServiceConnection connection, int flags, String callingPackage,
            int userId) throws TransactionTooLargeException 
        enforceNotIsolatedCaller("bindService");

        // Refuse possible leaked file descriptors
        if (service != null && service.hasFileDescriptors() == true) 
            throw new IllegalArgumentException("File descriptors passed in Intent");
        

        if (callingPackage == null) 
            throw new IllegalArgumentException("callingPackage cannot be null");
        

        synchronized(this) 
        	//这个mServices是一个ActivityService实例
            return mServices.bindServiceLocked(caller, token, service,
                    resolvedType, connection, flags, callingPackage, userId);
        
    

      忽略掉不重要的信息,直接看方法的最后一行,发现是调用了ActivityService的bindServiceLocked()方法,好我们去看看这个方法:
  int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service,
            String resolvedType, IServiceConnection connection, int flags,
            String callingPackage, int userId) throws TransactionTooLargeException 
        //根据调用者来获取正在运行的当前app的所有进程信息
        final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller);
        //如果没有找到进程记录就说明进程跪了,抛出异常
        if (callerApp == null) 
            throw new SecurityException(
                    "Unable to find app for caller " + caller
                    + " (pid=" + Binder.getCallingPid()
                    + ") when binding service " + service);
        

        ActivityRecord activity = null;
        if (token != null) 
        	//根据token获取ActivityRecord,也就是获取调用bindService()的那个Activity
        	//这一步就是从任务栈中去查找Activity
            activity = ActivityRecord.isInStackLocked(token);
            //如果没有找到就会绑定失败
            if (activity == null) 
                return 0;
            
        

        int clientLabel = 0;
        PendingIntent clientIntent = null;
        final boolean callerFg = callerApp.setSchedGroup != Process.THREAD_GROUP_BG_NONINTERACTIVE;
        //这里根据Intent获取Service
        ServiceLookupResult res =
            retrieveServiceLocked(service, resolvedType, callingPackage,
                    Binder.getCallingPid(), Binder.getCallingUid(), userId, true, callerFg);
        ServiceRecord s = res.record;
        final long origId = Binder.clearCallingIdentity();
        try 
            if (unscheduleServiceRestartLocked(s, callerApp.info.uid, false)) 
            

            if ((flags&Context.BIND_AUTO_CREATE) != 0) 
                s.lastActivity = SystemClock.uptimeMillis();
            

            mAm.startAssociationLocked(callerApp.uid, callerApp.processName,
                    s.appInfo.uid, s.name, s.processName);

            AppBindRecord b = s.retrieveAppBindingLocked(service, callerApp);
            //创建一个Connection记录,Service的连接
            //ConnectionRecord的解释是:Description of a single binding to a service
            ConnectionRecord c = new ConnectionRecord(b, activity,
                    connection, flags, clientLabel, clientIntent);

            IBinder binder = connection.asBinder();
            ArrayList<ConnectionRecord> clist = s.connections.get(binder);
            if (clist == null) 
                clist = new ArrayList<ConnectionRecord>();
                s.connections.put(binder, clist);
            
            clist.add(c);
            b.connections.add(c);
            if (activity != null) 
                if (activity.connections == null) 
                    activity.connections = new HashSet<ConnectionRecord>();
                
                //把ConnectionRecord保存到ActivityRecord中也就是云行的Activity,
                activity.connections.add(c);
            

            if (s.app != null && b.intent.received) 
                try 
                	//这里开始就是调用了InnerConnection的connected()方法,这种调用是跨进程的,即它调用时会首先调用IServiceConnection.Stub.Proxy的connected()方法
                	//这个流程在上一篇文章已经讲过了,不再赘述,总之,它最后就调用了我们的MainActivity中定义的MyServiceConnection的onServiceConnected()方法,这个b.intent.binder就是远程的对象即IServiceConnection对象。
                    c.conn.connected(s.name, b.intent.binder);
                 catch (Exception e) 
                
             else if (!b.intent.requested) 
                requestServiceBindingLocked(s, b.intent, callerFg, false);
            
         finally 
            Binder.restoreCallingIdentity(origId);
        
        return 1;
    
      其实这个方法很长,已经省了不少代码了,可以看到它里面调用了c.conn.connected(s.name,b.intent.binder),这就是回调了MainActivity中我们定义的MyServiceConnection的onServiceConnected()方法了。至此,整个binderService()的过程已经讲完了,虽然分析得还有点粗糙,今后会慢慢研究慢慢体会了~~

     


以上是关于BindService详解的主要内容,如果未能解决你的问题,请参考以下文章

从AILD与bindService谈Binder进程间通信原理(上)

Android Service(服务)

从AILD与bindService谈Binder进程间通信原理(下)

从AILD与bindService谈Binder进程间通信原理(下)

服务两种启动方式(startService与bindService)的区别

Service的生命周期