在我上一篇文章里边里边怎样科学的学习安卓体系的framework 我从前提出过一个观点。便是咱们不应该神话安卓framework里边的代码,framework里边的代码也不值得咱们逐行剖析。由于framework经过这么多年的迭代,许多当地很臃肿,并且谷歌的工程师也不是各个都是一顶一的天才,也会不细心写出各式各样的bug。
所以当咱们学习framework代码的时分,应该站在一个更高层次去剖析每个componenet的规划初衷。举个比方,
-
某一个功用或许模块,为什么谷歌的工程师要将其放在AOSP framework里边,而不是放在google play service (gms) 里边,相同反之亦然。
-
某两个gms的模块为什么不能直接经过AIDL通讯,而是被规划成要经过framework通讯,这样的优点是什么。
我觉得多思索这些问题,能对架构有更清晰的知道,一起也能够训练自己规划的能力。而不是拘泥于一些小细节。比方,熟读View.java里边事情分发的代码我觉得是无用的,你只需求大略的知道到安卓framework是利用DFS算法(深度优先)来 遍历父子节点就行了。知道到这一点,你就能够自己了解为什么咱们能够在子节点设置,经过不允许父节点事情阻拦来到达撤销nested scroll的作用 (NestedScrollView里边的Webview无法滑动)。
说了上面这么多废话,其实也是想强调咱们要先有一个学习意图,也便是学习framework上规划好的模块,提高自己的规划能力。这次的文章我想用打电话这个framework里边的功用来作为一个比方,阐述一个比较抽象的问题:
作为一个渠道,你能给开发者供给什么 (Framework API,开发标准),
作为一个渠道,你期望开发者给你供给什么 (第三方完成)
友谊提示,本文有些链接需求咱们学习怎样科学上网之后才干访问。。。
打电话?
咱们都知道安卓手机里边有一个打电话的app,Dialer app。可是Dialer app并不是100%独立完成通话功用的。任何一个Dialer app,比方华为,OPPO手机自带的Dialer app都需求和framework做通讯。咱们先不说为什么,先看看怎样做。
首先咱们要知道一点,现代的通话办法千变万化,最有名的,也是安卓当时支撑的有三种
-
Telephony call,也便是咱们常说的2G通话,或许4G/LTE 通话,便是经过运营商的网络来通话,需求用户有一个有效的电话卡在手机里边(当然也能够是虚拟电话卡eSIM)
-
VoIP call, 比方微信,WhatsAPP 等等即时通讯软件的语音通话 (许多人会猎奇这些不都是第三方开发的功用,为什么会和framework有关系? 我会在下面的章节持续解说),
-
蓝牙通话,比方咱们都知道安卓手机能够衔接蓝牙耳机,用户能够经过操作蓝牙耳机来进行接,挂电话的操作。可是咱们有没有想过一个安卓设备也能够充任“蓝牙耳机”的人物?安卓设备(纷歧定是一个手机,能够是一个任何跑安卓体系的硬件)也能够经过自己本身的操作,操控与其衔接的安卓手机上VoIP,Teleohony call。
怎样写一个Dialer App
安卓官方从前发过一篇文章,讲述怎样让开发者自己写一个自己的Dialer app,期望咱们在持续看我这篇文章之前先细心看一遍。
developer.android.com/guide/topic…
developer.android.com/reference/a…
其间这两篇文章要点讲了两个类,一个是ConnectionService,一个是InCallService。其间前者是有必要的,后者只在Dialer app想替代体系自带的Dialer app成为default dialer的时分才需求。
前者是开发者露出给framework的一个类,经过完成自己的Connection,奉告framework自己当时的Dialer app怎样进行具体的通话来。比方说ConnectionService需求完成一个callback来奉告framework自己的connection怎样作业
比方说,假如你的Dialer app是经过网络把当时用户的语音发出去的话,你或许就需求再这个callback里边完成一个Connection,并且这个connection是具体完成怎样把语音转成byte data并且经过网络传出去的逻辑。
后者InCallService能够当成是一个Global callback,是体系奉告当时default dialer app应该怎样烘托UI的。比方,当你的InCallService完成接收到onCallAdded的时分,
你或许最好就要调用你的正在通话的Activity,奉告用户当时正在通话中,
反之,当onCallRemoved被调用的时分
你或许最好就要dismiss你当时通话的UI,并且用一个Toast message 奉告用户当时通话现已完毕了。
Framework中通话的流程图
或许咱们在阅读了这些内容之后仍是有点懵逼,我来画一个图来再具体解说一下通话的流程,
第一步: 我自己写的Dialer app完成了一个叫QingConnectionService 的ConnectionService
和一个叫QingInCallService的QingInCallService 的InCallService
经过调用Framework的API TelecomManager#placeCall() -> 文档 来奉告framework咱们需求敞开一个通话。
第二步当体系接收到Dialer app通话的恳求之后,会向Dialer app讨取Dialer app自己完成的Connection,也便是QingConnectionService的Connection完成。并且敞开Connection具体通话完成的逻辑。
第三步当通话开端之后,体系会检索当时default的InCallService,并且调用onCallAdded 回调,提示Dialer app应该改写UI啦! 所以在这一步QingIncallService#onCallAdded会被调用,咱们的Dialer app就应该在此刻更新咱们的通话Activity里边的UI了。
第四步,通话完毕,体系相同会检索当时default的InCallService,并且调用onCallRemoved 回调,提示Dialer app应该奉告用户通话完毕了!
Framework怎样检索这些Service的?
其实咱们假如细心看了文档就会发现这两个Service都被要求在Manifest里边做一些特别的标识
ConnectionService:
InCallService
做这些特别的标识便是为了Framework在遍历一切app的时分,能经过这些标识知道当时Service是ConnectionService或许InCallService。
比方InCallService。在InCallController.java源码里边,咱们能够看到这一个办法
android.googlesource.com/platform/pa…
这个办法便是体系查找InCallService完成的当地,没有什么奇特的当地,无非便是创立Intent,给Intent设置咱们在文档里边看到的那个action name,然后经过intent来遍历当时App里边的一切Service直到找到一个IncallService。
ConnectionService也是类似的办法,不过逻辑更复杂一些,这儿就不多解说了,有兴趣的朋友们能够自己查阅代码看看。
这个办法也是体系常用的招数,当体系需求你完成某个Service的时分,依照开发标准一般都会要求开发者在Manifest里边填写一些特别的标识,到达便利体系查找遍历该Service的意图。这个招数相同适用于Activity。
Sample App
由于做Dialer的第三方真实不多,网上资源又比较少,我也不太有时间自己撸一个(主要是懒。。。).我就打算用这个博主写的比方:
medium.com/nerd-for-te…
这个博主是运用一个VoIP的第三方SDK来完成的一个Dialer app,咱们能够默认一切的网络通话部分的完成(Connection部分)现已又第三方SDK完成了。
这儿最重要的便是这个博主的app自行完成了一个ConnectionService
github.com/developersp…
并且完成了自己的Connection,也是便是在onAwswer 里边调用SDK 的calling api
并且自习观察,你会发现该App并没有完成自己的IncallService,也便是说这个App无意替代体系自带的Dialer app,一起也说明这个App在通话的时分,体系自带的Dialer app的通话UI也会被显现出来。
为了证明咱们猜测,看看sample app的运用视频:
当用户点击通话之前,这个UI是sample app的通讯录UI
当用户开端通话之后,InCallService#onCallAdded 被调用了。可是这个InCallService是体系自带的Dialer App的InCallService, 所以体系自带Dialer的UI被唤醒,而不是Sample 的UI。
即运用户把体系自带Dialer 的 界面最小化,仍是能够看到体系自带Dialer app的悬浮按钮(由于当时体系自带的Dialer app现已被唤醒,一起监听着InCallService的callback,当onCallRemoved被调用后,这个悬浮按钮会消失)
以上App的行为证明了咱们之前的猜测!!
Framework为什么要这么要求
说了这么多,是时分解说一下写一个打电话的App为啥要这么复杂,为什么placeCall最好不要绕过体系,要让体系经过回调来奉告Calling app UI怎样烘托。为啥咱们不能把一切功用都完成在App里边呢?
答案是,你当然能够这么做,可是这样做会失掉一些体系对通话体验的协助。
两个最重要的比方:
紧迫通话
假如咱们看一下telecom下面的源代码 (能够把这儿边的PhoneAccount类当成一个通话App的ConnectionService类一一对应来了解):
android.googlesource.com/platform/pa…
你会发现,体系纷歧定会运用你自己写的Dialer App里边的Connection来完成通话的,即便你是在自己完成的Dialer App内调用TelecomManager#placeCall()。其间一个很大的原因便是紧迫通话。
咱们看源代码里边的adjustForEmergencyCall办法
即便你当时想运用自己的Connection,体系也会查看你的Connection是否支撑紧迫通话,比方拨打110。假如当时拨打号码是紧迫号码,而你自己的Dialer app并不支撑紧迫通话的话,体系会主动运用一个支撑紧迫通话的ConnectionService,比方(并且很大概率是体系自带的)TelephonyConnection, 该connection的通话都会经过手机modem拨出去。
拒绝通话
不知道咱们以前有没有这样的阅历。比方正在和家人经过微信语音,突然外卖小帅哥到了给你一个电话提醒上门承受外卖,你的微信语音就主动被挂断了。。。。
这个其间的一个原因便是(我猜测的哈),便是微信的语音通话并没有经过完成体系的ConnectionService和调用TelecomManager#placeCall来做。由于微信一切的语音通话完成都在app里边自己完成,体系并不知道用户正在通话中(微信当时的运转状态和其他任何一个运用网络的app没有一点不同),所以当用户一起接收到一个拨入电话的时分,体系默认的Default Dialer就会回收MediaFocus的操控权并且开端响铃,微信在失掉MediaFocus之后就主动挂断了微信语音。 (这儿有没有微信的开发朋友,问一下为啥不做啊哈哈)
其间一个正面的比便利是Facebook Messenger的电话功用,咱们能够看一个实例截图
咱们能够看到,当我正在和朋友进行facebook ip通话的时分,即便有拨入电话,也会经由体系在告诉栏来显现该通话正在拨入中。这样能够把选择权交给用户来决议是否承受。而不是粗暴的完毕当时的Ip 通话。
我自己看了一下logcat,能够看到facebook messenger完成了一个 叫 InCallForegroundService
的类,并且log也对应通话进行或许完毕。
盲猜便是fb的通话软件的确是经过体系来完成的啦!
所以,
让体系知道当时用户在运用自己的App通话,是一件十分重要的事情。
Telecom Framework规划的初衷
回到咱们这篇文章的最核心问题。Telecom Framework的规划者肯定不想,也没有权利干涉第三方app对其规划通话的完成办法。可是一起又期望第三方软件能承受体系的协助,去主动获得一些体系等级能供给的功用(比方拒绝通话,主动选择紧迫通话办法等等)。
要到达以上意图,咱们就自然的需求第三方软件奉告体系他当时具体完成通话的办法。怎样告诉?当然就靠咱们自己完成的ConnectionService了。
一起体系也经过IncallService来奉告App当时的通话状态,是否开端是否现已完毕。这样能够大大简化第三方App开发的UI的流程。
所以最初的两个话题
作为一个渠道,你能给开发者供给什么 (Framework API,开发标准,体系等级的功用),
在通话的功用中,体系供给了一些抽象类,强迫开发者完成一些抽象办法。一起也由于体系知晓这些抽象的interface,体系能够因此对这些功用(通话)进行统一管控,供给体系等级的功用(拒绝通话)。
体系也经过发布开发者文档(标准),奉告第三方开发者需求怎样样露出体系需求的完成.(在manifest里边做一些特别标识)。
作为一个渠道,你期望开发者给你供给什么 (第三方完成)
正由于体系界说了完成的抽象,第三方开发者能够在框架下,完成自己的功用(怎样进行通话?)
所以对于一个简略的通话功用,体系和第三方app之间会进行来回的信息交流,增加了程序的复杂度,这也解说了为什么许多第三方app并不想做这样的接入。
比方ConnectionService,是API23之后才参加体系的。像微信这样庞大的app,语音通话功用早在API 23之前就有了。现在要做这样的体系等级的适配想必是非常复杂的,一起收益也没有那么大(比方很少人会用微信电话打110吧。。。),所以微信没有这样做也能够了解。这也是软件开发的权衡(trade off)。
总结
这篇文章我经过通话功用来简略的介绍了一下Framework下面的一个模块的规划理念。期望能给咱们一些启示,给读者们自己在规划渠道的时分带来一丢丢协助!