BindService详解

BindService详解Service启动得两种方式分别为startService()、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()的过程已经讲完了,虽然分析得还有点粗糙,今后会慢慢研究慢慢体会了~~

     

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/132819.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • h3c交换机重启_h3c交换机清空配置命令

    h3c交换机重启_h3c交换机清空配置命令h3c交换机清空配置命令H3CCAS云计算管理平台融合了华三通信在网络安全领域的积累,通过对IEEE802.1Qbg(EVB)标准的支持,为虚拟机在安全、可视、可监管的环境下运行奠定了基础。下面是小编收集的h3c交换机清空配置命令,希望大家认真阅读!一.用户配置:system-view[H3C]superpasswordH3C设置用户分级密码[H3C]undosuperpasswor…

  • mysql phpmyadmin配置_phpmyadmin 配置方法与安装教程[通俗易懂]

    mysql phpmyadmin配置_phpmyadmin 配置方法与安装教程[通俗易懂]今天我们来看看phpmyadmin配置教程吧,也可以叫做phpmyadmin安装吧,安装我就不说了,你直接到网上下载一个phpmyadmin包解压到你的站点目录,就行了.下面我们来看个简单的例子吧.安装目录:/admin/好了我们现在打开我们刚才解压的文件夹找到config.sample.inc.php把它改名为config.inc.php下面我们就打开这个文件.找到$cfg[‘PmaAbs…

  • 视频直播技术详解之采集[通俗易懂]

    视频直播技术详解之采集[通俗易懂]声明:本文为CSDN原创投稿文章,未经许可,禁止任何形式的转载。作者:七牛云责编:钱曙光,关注架构和算法领域,寻求报道或者投稿请发邮件qianshg@csdn.net,另有「CSDN高级架构师群」,内有诸多知名互联网公司的大牛架构师,欢迎架构师加微信qshuguang2008申请入群,备注姓名+公司+职位。随着互联网用户消费内容和交互方式的升级,支撑这些内容和交互方式的基…

  • R-L模型算法的优缺点_风筝模型公式

    R-L模型算法的优缺点_风筝模型公式介绍Logistic回归算法,名字虽带有回归,但其实是一个分类模型。输出Y=1的对数几率是由输入x的线性函数表示的模型,直接对分类的可能性进行建模,并不是直接对分类的结果(0或者1)进行建模:假设一个样本属于正样本的概率为p,则:LR模型是在线性回归的基础上,把特征进行线性组合,再把组合的结果通过一层sigmoid函数映射成结果是1或是0的概率。逻辑斯蒂回归模型的特点:…

    2022年10月13日
  • 真正理解exists 和not exists

    真正理解exists 和not exists前言今天看了下mysql训练题,其中有一题很有意思。​    下面也写了sql解答,使用了group_concat()函数,这个函数是分组后将一组的字段(比如name)拼接在一起,默认以逗号分隔。这个思路可以,但是在成绩表插入信息时的顺序是乱的,那又怎么查。    我然后看了几个其他人的答案,还有的用课程数作比较的,写的很乱很杂。想了一会,觉得使用notexists解答是可以的。exists与notexist.

  • java: 找不到符号「建议收藏」

    java: 找不到符号「建议收藏」java:找不到符号符号:类TypeInformationTestBase位置:程序包org.apache.flink.api.common.typeutils程序报这种错误,不是代码造成的。解决办法:0、首先要统一编码格式:file–>settings–>fileencodings1、右键–>maven–>reloadproject2、build或rebuild3、flile–>inv..

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号