Android面试题:bindService获取代理是同步还是异步

Android中bindService是一个异步的过程,什么意思呢?使用bindService无非是想获得一个Binder服务的Proxy,但这个代理获取到的时机并非由bindService发起端控制,而是由Service端来控制,也就是说bindService之后,APP端并不会立刻获得Proxy,而是要等待Service通知APP端,具体流程可简化如下:

  • APP端先通过bindService去AMS登记,说明自己需要绑定这样一个服务,并留下派送地址
  • APP回来,继续做其他事情,可以看做是非阻塞的
  • AMS通知Service端启动这个服务
  • Service启动,并通知AMS启动完毕
  • AMS跟住之前APP端留下的地址通知APP端,并将Proxy代理传递给APP端

通过代码来看更直接

    void test(){
        bindService(intent, new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
               iMyAidlInterface = IMyAidlInterface.Stub.asInterface(iBinder);
               Log.v(TAG, "onServiceConnected..." );
            }
           @Override
            public void onServiceDisconnected(ComponentName componentName) {
           }
        }, Context.BIND_AUTO_CREATE);
        Log.v(TAG, "end..." );
    }

bindService的过程中,上面代码的Log应该是怎么样的呢?如果bindService是一个同步过程,那么Log应该如下:

TAG  onServiceConnected ...
TAG  end ...

但是由于是个异步过程,真实的Log如下

TAG  end ...    
TAG  onServiceConnected ...

也就是说bindService不会阻塞等待APP端获取Proxy,而是直接返回,这些都可以从源码获得支持,略过,直接去ActivityManagerNative去看

public int bindService(IApplicationThread caller, IBinder token,
        Intent service, String resolvedType, IServiceConnection connection,
        int flags, int userId) throws RemoteException {
    Parcel data = Parcel.obtain();
    Parcel reply = Parcel.obtain();
    data.writeInterfaceToken(IActivityManager.descriptor);
    data.writeStrongBinder(caller != null ? caller.asBinder() : null);
    data.writeStrongBinder(token);
    service.writeToParcel(data, 0);
    data.writeString(resolvedType);
    data.writeStrongBinder(connection.asBinder());
    data.writeInt(flags);
    data.writeInt(userId);
    <!--阻塞等待-->
    mRemote.transact(BIND_SERVICE_TRANSACTION, data, reply, 0);
    reply.readException();
    int res = reply.readInt();
    data.recycle();
    reply.recycle();
    return res;
}

mRemote.transact(BIND_SERVICE_TRANSACTION, data, reply, 0)确实会让APP端调用线程阻塞,等待AMS执行BIND_SERVICE_TRANSACTION请求,不过AMS在执行这个请求的时候并非是唤醒Service才返回,它返回的时机更早,接着看ActivityManagerService,

public int bindService(IApplicationThread caller, IBinder token,
        Intent service, String resolvedType,
        IServiceConnection connection, int flags, int userId) {
    ...
    synchronized(this) {
        return mServices.bindServiceLocked(caller, token, service, resolvedType,
                connection, flags, userId);
    }
}

ActivityManagerService直接调用ActiveServices的函数bindServiceLocked,请求绑定Service,到这里APP端线程依旧阻塞,等待AMS端返回,假定Service所处的进程已经启动但是Service没有启动,这时ActiveServices会进一步调用bindServiceLocked->realStartServiceLocked来启动Service,有趣的就在这里:

 private final void realStartServiceLocked(ServiceRecord r,
            ProcessRecord app) throws RemoteException {
        ...
        <!--请求Service端启动Service-->
            app.thread.scheduleCreateService(r, r.serviceInfo,
                    mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo));
        ...
        <!--请求绑定Service-->
        requestServiceBindingsLocked(r);

app.thread.scheduleCreateService也是一个Binder通信过程,他其实是请求ActivityThread中的ApplicationThread服务,当然这个时候AMS端也是阻塞的,

// 插入消息,等待主线程执行
public final void scheduleCreateService(IBinder token,
        ServiceInfo info, CompatibilityInfo compatInfo) {
    CreateServiceData s = new CreateServiceData();
    s.token = token;
    s.info = info;
    s.compatInfo = compatInfo;
    <!--向Loop的MessagerQueue插入一条消息就返回-->
    queueOrSendMessage(H.CREATE_SERVICE, s);
}

不过,这个请求直接向Service端的ActivityThread线程中直接插入一个消息就返回了,而并未等到该请求执行,因为AMS使用的非常频繁,不可能老等待客户端完成一些任务,所以AMS端向客户端发送完命令就直接返回,这个时候其实Service还没有被创建,也就是这个请求只是完成了一半,onServiceConnected也并不会执行,onServiceConnected什么时候执行呢?app.thread.scheduleCreateService向APP端插入第一条消息,是用来创建Service的, requestServiceBindingsLocked其实就是第二条消息,用来处理绑定的

 private final boolean requestServiceBindingLocked(ServiceRecord r,
            IntentBindRecord i, boolean rebind) {
                ...
           <!-- 第二个消息,请求处理绑定-->
            r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind);

第二条消息是处理一些绑定需求,Android的Hanlder消息处理机制保证了第二条消息一定是在第一条消息之后执行,

 public final void scheduleBindService(IBinder token, Intent intent,
        boolean rebind) {
    BindServiceData s = new BindServiceData();
    s.token = token;
    s.intent = intent;
    s.rebind = rebind;
    queueOrSendMessage(H.BIND_SERVICE, s);
}      

以上两条消息插入后,AMS端被唤醒,进而重新唤醒之前阻塞的bindService端,而这个时候,Service并不一定被创建,所以说这是个未知的异步过程,Service端处理第一条消息的时会创建Service,

 private void handleCreateService(CreateServiceData data) {
    ...
    LoadedApk packageInfo = getPackageInfoNoCheck(
            data.info.applicationInfo, data.compatInfo);
    Service service = null;
    try {
        java.lang.ClassLoader cl = packageInfo.getClassLoader();
        service = (Service) cl.loadClass(data.info.name).newInstance();
   ...

执行第二条消息的时候, 会向AMS请求publishService,其实就是告诉AMS,服务启动完毕,可以向之前请求APP端派发代理了。

 private void handleBindService(BindServiceData data) {
    Service s = mServices.get(data.token);
    if (s != null) {
       try {
        data.intent.setExtrasClassLoader(s.getClassLoader());
        try {
            if (!data.rebind) {
                IBinder binder = s.onBind(data.intent);
                ActivityManagerNative.getDefault().publishService(
                        data.token, data.intent, binder);
            ...                       

AMS端收到publishService消息之后,才会向APP端发送通知,进而通过Binder回调APP端onServiceConnected函数,同时传递Proxy Binder服务代理

void publishServiceLocked(ServiceRecord r, Intent intent, IBinder service) {
    ...
     try {
    <!--通过binder 回到APP端的onServiceConnected--> 
        c.conn.connected(r.name, service);
    } catch (Exception e) {

到这里,onServiceConnected才会被回调,不过,至于Service端那两条消息什么时候执行,谁也不能保证,也许因为特殊原因,那两条消息永远不被执行,那onServiceConnected也就不会被回调,但是这不会影响AMS与APP端处理其他问题,因为这些消息是否被执行已经不能阻塞他们两个了,简单流程如下:

bindService的异步流程

最后,其实startService也是异步。

作者:看书的小蜗牛 Android面试题:bindService获取代理是同步还是异步

仅供参考,欢迎指正

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏developerHaoz 的安卓之旅

Android 撸起袖子,自己封装 DialogFragment

DialogFragment 在 Android 3.0 时被引入,是一种特殊的 Fragment,用于在 Activity 的内容之上显示一个静态的对话框。...

903
来自专栏何俊林

实现视频播放器倍速、清晰度切换、m3u8下载功能

5616
来自专栏一“技”之长

Bootstrap响应式前端框架笔记十——导航栏相关组件

    Bootstrap中提供的导航栏分为两种模式,使用nav-tabs类可以创建页卡模式的导航栏,使用nav-pills类可以创建胶囊模式的导航栏,示例如下...

772
来自专栏前端儿

CSS布局 -- 左右定宽,中间自适应

左右设置绝对定位,定宽,中间设置margin-left  margin-right

1041
来自专栏我的博客

JavaScript获取高度和宽度

屏幕分辨率为:screen.width screen.height 屏幕可用大小:screen.availWidth screen.availHeight ...

2686
来自专栏代码GG之家

google 分屏 横屏模式 按home键界面错乱故障分析(一)

你确定你了解分屏的整个流程? ? 之前分析文章列表: Android 关机对话框概率没有阴影故障分析 android recent key长按事件弹起触发最近列...

1858
来自专栏哲学驱动设计

技术总结:自动扩张WPF树型表格列宽

问题描述     今天测试人员提了一个易用性的BUG,主要是说系统目前使用的树型控件不支持自动扩张列的宽度。其实客户那边已经对这个问题提了多次,不过由于对WPF...

2255
来自专栏技术小黑屋

Android处理崩溃的一些实践

对于任何程序来说,崩溃都是一件很难避免的事情,当然Android程序也不例外。在Android程序中,引起崩溃的多属于运行时异常或者错误,对于这些异常我们很难做...

552
来自专栏IMWeb前端团队

开源跨平台移动项目Ngui【CSS样式表规则及用法】

Ngui简介 这是一个GUI的排版显示引擎和跨平台的GUI应用程序开发框架,基于NodeJS/OpenGL,这也是第一个在移动端Android/iOS融合Nod...

1988
来自专栏数据小魔方

半圆型动态仪表盘风格图表

今天跟大家分享一款半圆型动态仪表盘风格图表!真的可以动起来哦! ▼ 下午在浏览论坛的时候,刚好看到秋叶老师的一个图表教程,风格是半圆型饼图,用VBA控制。 ? ...

3306

扫码关注云+社区