第二章:CallsManager调用 startOutgoingCall开始拨号流程之四InCallController.java绑定InCallService.java实现进程间Binder通信

来源:互联网 发布:网络创业类型 编辑:程序博客网 时间:2024/06/06 00:23

四.InCallController.java绑定InCallService.java实现进程间Binder通信

1).上面我们分析了每路通话的管理,介绍了每个层次之间的关系,但是在这个过程中我们看到,最终是通过调用InCallController.java类的onCallAdded方法去通知InCallService进行每路通话的保存处理,InCallController是在packages层的类,而InCallService是存在于framework的,这两个不同层次的类是如何联系的,我们接下来详细分析这一过程。

 

2). 我们先回顾前面的流程,CallsManager.java(packages/services/Telecomm)中的startOutgoingCall()方法调用:

addCall(call);

继而进入到InCallController.java中时调用onCallAdded():

 

bind(call);

这个是怎么做到的,inCallService实例是怎么得到的?这其中到底用到了什么思想设计模式?下面我们一一解析。


3).上面说了,当流程进入到InCallController.java时,回去调用onCallAdded()方法,这个里面回去调用inCallService方法,这个过程是怎样的,是不是直接去实例化inCallService呢,我想没有那么简单,第一inCallService是一个service实例,这两者之间不仅不在一个层次中(一个是services层,一个是framework层)而且还存在进程间通信问题,如何解决这些问题,我想大家应该都会想到进程间通信想到Binder技术,再想到aidl技术,要验证我们的想法,我们可以进入代码查看,首先我们看入口方法也就是onCallAdded():

if(mInCallServices.isEmpty()) {

           bind(call);

}

第一次进来并没有直接去调用 inCallService的方法,而是调用了自身的bind方法:


if(mInCallServices.isEmpty()) {

           PackageManager packageManager =mContext.getPackageManager();

           Intent serviceIntent = new Intent(InCallService.SERVICE_INTERFACE);

           for (ResolveInfo entry :packageManager.queryIntentServices(serviceIntent, 0)) {

               ServiceInfo serviceInfo =entry.serviceInfo;

               if (serviceInfo != null) {

                   booleanhasServiceBindPermission = serviceInfo.permission != null && 

                          serviceInfo.permission.equals(

                                  Manifest.permission.BIND_INCALL_SERVICE);

                   booleanhasControlInCallPermission = packageManager.checkPermission(

                          Manifest.permission.CONTROL_INCALL_EXPERIENCE,

                          serviceInfo.packageName) == PackageManager.PERMISSION_GRANTED;

 

                   if(!hasServiceBindPermission) {

                      Log.w(this, "InCallService does not have BIND_INCALL_SERVICE permission:" +

                              serviceInfo.packageName);

                       continue;

                   }

 

                   if (!hasControlInCallPermission){

                      Log.w(this,

                              "InCall UI does not have CONTROL_INCALL_EXPERIENCEpermission: " +

                                      serviceInfo.packageName);

                       continue;

                   }

 

                   InCallServiceConnectioninCallServiceConnection = new InCallServiceConnection();

                   ComponentNamecomponentName = new ComponentName(serviceInfo.packageName,serviceInfo.name);

 

                   Log.i(this,"Attempting to bind to InCall %s, is dupe? %b ",

                          serviceInfo.packageName,

                          mServiceConnections.containsKey(componentName));

 

                   if(!mServiceConnections.containsKey(componentName)) {

                       Intentintent = new Intent(InCallService.SERVICE_INTERFACE);

                      intent.setComponent(componentName);

 

                       final intbindFlags;

                       if(mInCallComponentName.equals(componentName)) {

                          bindFlags = Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT;

                          if (!call.isIncoming()) {

                              intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS,

                                      call.getExtras());

                             intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,

                                      call.getTargetPhoneAccount());

                          }

                       } else {

                          bindFlags = Context.BIND_AUTO_CREATE;

                       }

 

                       if(mContext.bindServiceAsUser(intent, inCallServiceConnection, bindFlags,

                              UserHandle.CURRENT)) {

                          mServiceConnections.put(componentName, inCallServiceConnection);

                       }

                   }

               }

           }

       }


上述方法看似很复杂,不过我们可以一一分解,首先:

IntentserviceIntent = new Intent(InCallService.SERVICE_INTERFACE);

for (ResolveInfoentry : packageManager.queryIntentServices(serviceIntent, 0)) {


这两行代码首先定义了一个intent,查看InCallService.SERVICE_INTERFACE类型,我们搜索到其值是android.telecom.InCallService,然后调用 PackageManager的queryIntentServices方法,这个方法有什么作用呢,看官方的解释:

 

通过PackageManager的queryIntentActivities方法,查询系统中所有满足ACTION_MAIN和CATEGORY_LAUNCHER的应用程序,获取他们的程序名、包名、入口类名。

 

好了,这下我们知道了,这个方法主要是查询android.telecom.InCallService对应的类我们去搜,会发现最后在packages/apps/Dialer中的AndroidManifest.xml中有如下几行:

 

serviceandroid:name="com.android.incallui.InCallServiceImpl"

               android:permission="android.permission.BIND_INCALL_SERVICE">

           <intent-filter>

               <actionandroid:name="android.telecom.InCallService"/>

           </intent-filter>

</service>

 

一目了然, android.telecom.InCallService对应的是类 com.android.incallui.InCallServiceImpl,因为Dialer包含了InCallUI模块,所以在这里定义并没有什么不妥,我们继续看bind方法下面的逻辑:

 

 InCallServiceConnectioninCallServiceConnection = new InCallServiceConnection();

 ComponentNamecomponentName = new ComponentName(serviceInfo.packageName,serviceInfo.name);

 

首先定义了一个 InCallServiceConnection对象,然后实例化了一个 componentName,上面说了需要找到的是InCallServiceImpl类,同时我们打log可以看到:

serviceInfo.packageName=com.android.dialer

serviceInfo.name=com.android.incallui.InCallServiceImpl

果然如此,serviceInfo.name的值是InCallServiceImpl,继续略过往下看:

 

if(mContext.bindServiceAsUser(intent, inCallServiceConnection, bindFlags,

                              UserHandle.CURRENT)) {

                          mServiceConnections.put(componentName, inCallServiceConnection);

 }

 

这下就更清楚了,调用到了 bindServiceAsUser方法,明显使用到了进程间通信binder技术,接下来我们定位到packages/apps/InCallUI中的InCallServiceImpl.java类:

 

public classInCallServiceImpl extends InCallService

 

这就更加合乎情理了, InCallServiceImpl是 InCallService的子类,他们之间是有联系的,同时我们找到了onBind方法,更加验证了我们的想法:

 

 InCallPresenter.getInstance().setUp(

               getApplicationContext(),

               CallList.getInstance(),

               AudioModeProvider.getInstance());

       InCallPresenter.getInstance().onServiceBind();

       InCallPresenter.getInstance().maybeStartRevealAnimation(intent)

       return super.onBind(intent);

 

其中做了 InCallPresenter的bind操作,关键的一行:

 

super.onBind(intent);

 

调用了父类InCallService的 onBind方法:

 

return newInCallServiceBinder();

 

返回了 InCallServiceBinder类的实例,查看此类是一个内部类,先看定义:

 

private finalclass InCallServiceBinder extends IInCallService.Stub

 

继承了IInCallService.Stub,可以预见IInCallService是一个aidl文件,现在可以确定使用到了aidl技术,

这里重写了addCall方法,这个方法等会会被用到,我们现在回到InCallController.java中的bind方法继续看:

 

if(mContext.bindServiceAsUser(intent, inCallServiceConnection, bindFlags,

                              UserHandle.CURRENT)) {

                          mServiceConnections.put(componentName, inCallServiceConnection);

                       }

 

if条件中:

 

mContext.bindServiceAsUser(intent,inCallServiceConnection, bindFlags,UserHandle.CURRENT)

 

刚才我们看了第一个参数intent是InCallServiceImpl.java,看第二个参数 inCallServiceConnection,我们可以定位到内部类InCallServiceConnection:

 

private classInCallServiceConnection implements ServiceConnection {

       /** {@inheritDoc} */

       @Override public void onServiceConnected(ComponentName name,IBinder service) {

           Log.d(this, "onServiceConnected: %s",name);

           onConnected(name, service);

       }

 

       /** {@inheritDoc} */

       @Override public void onServiceDisconnected(ComponentName name) {

           Log.d(this, "onDisconnected: %s", name);

           onDisconnected(name);}}

这里就更加明显了,一个 ServiceConnection的子类,明显是要建立binder连接实现进程间通信,看 onServiceConnected方法,这个方法会在调用上述bindServiceAsUser()方法时去InCallService.java中返回一个 InCallServiceBinder实例,在此处的onServiceConnected第二个参数中传进来,这里会有人说为什么InCallServiceBinder是IInCallService.Stub类型的,而这里的形参是 IBinder类型的,binder机制可以告诉你,IInCallService.Stub内部实现其实就是继承了IBinder了的,所以这里并没有什么错,我们继续看 onConnected():

 

IInCallServiceinCallService = IInCallService.Stub.asInterface(service);

 

       try {

           inCallService.setInCallAdapter(newInCallAdapter(CallsManager.getInstance(),

                   mCallIdMapper));

           mInCallServices.put(componentName, inCallService);

       } catch (RemoteException e) {

           Log.e(this, e, "Failed to set the in-calladapter.");

           Trace.endSection();

           return;

       }

 

       // Upon successful connection, send the state of the world to theservice.

       Collection<Call> calls =CallsManager.getInstance().getCalls();

       if (!calls.isEmpty()) {

           Log.i(this, "Adding %s calls to InCallServiceafter onConnected: %s", calls.size(),

                   componentName);

           for (Call call : calls) {

               try {

                   // Track the call if wedon't already know about it.

                   Log.i(this, "addCallafter binding: %s", call);

                   addCall(call);

 

                  inCallService.addCall(toParcelableCall(call,

                          componentName.equals(mInCallComponentName) /* includeVideoProvider */));

               } catch (RemoteException ignored) {

               }

           }

           onAudioStateChanged(null,CallsManager.getInstance().getAudioState());

          onCanAddCallChanged(CallsManager.getInstance().canAddCall());

       } else {

           unbind();

       }

       Trace.endSection();

 

我们逐行分析:

 

IInCallServiceinCallService = IInCallService.Stub.asInterface(service);

 

这里首先将 service还原出来了,并且赋值给inCallService,然后:

 

inCallService.setInCallAdapter(newInCallAdapter(CallsManager.getInstance(),

                   mCallIdMapper));

mInCallServices.put(componentName,inCallService);

 

在这里调用了 setInCallAdapter保存了一个InCallAdapter实例,会调用到inCallService 中InCallServiceBinder的setInCallAdapter方法去调用onPhoneCreated方法,然后将此service作为键值对保存到了mInCallServices中,继续往下看onConnected方法:

 

inCallService.addCall(toParcelableCall(call,

                   componentName.equals(mInCallComponentName) /* includeVideoProvider */));

 

在这里最终调用到了返回的service也就是InCallServiceBinder中的addCall方法,实现了进程间方法调用,使逻辑从service层流转到了framework层中,InCallService中做了很多中间的通话逻辑处理,继而就调用到了Phone中的各种方法。

 

4).以上就是通话拨号的中间处理流转过程,主要是应用到了binder技术以及aidl技术,总而言之是进程间通信的一个应用场景,在通话过程中使用到了,这样会帮助我们理解整个通话过程的框架结构,继而更加清晰的了解整套代码的构建原理与好处所在,5.1的通话流程在原来基础上做了很大的改变,与4.4截然不同,所以我们想要更好的了解通话这一块,就需要深入查看源码。之前只是介绍了通话连接在哪里建立,并没有解释通话连接到底是什么,是如何建立的,在通话过程中扮演什么角色,下面我们会详细分析通话连接的建立过程。

0 0
原创粉丝点击