我们的目标是提升电话模块打电话的速度,打电话的话,有多个场景,通话记录拨打电话,拨号盘拨打电话,通话记录详情拨打电话,联系人详情拨打电话。各个场景拨打电话的速度不一致,每种场景都会对通话界面的显示存在着不同的影响,至于是什么影响了,我们暂时先不考虑,我们先考虑在一个空白界面拨打电话的速度是怎么样的,先从InCallUi的角度上着手进行优化,之后再对各个场景进行分析和优化。
我们现在搞清楚了我们要做什么,那我们就要朝这个方向入手了,拨打电话的流程是怎样的,当用户点击拨打的时候,我们会调用DialerUtils类中的startActivityWithErrorToast方法,其中调用了TelecomManager.placeCall方法,调用底层的方法进行呼叫,之后会走InCallServiceImpl类的onBind方法,onBind方法中调用了InCallPresenter类中的maybeStartRevealAnimation方法,会根据条件判断,是否启动InCallActivity.之后会走InCallActivity的onCreate方法,onCreate方法中调用internalResolveIntent方法,会显示CallCardFragment。在CallCardFragment中的onActivityCreated方法中,当条件满足的情况下会触发数据查询。
一、onBind ------->startActivity
在Dialer中拨打电话流程主体是这样的,那我们接下来看看哪些步骤是可控的,哪些不是,placeCall到onBind这个是不受我们所控制的,onBind到startActivity这个过程可控的,我们接下来就看看这一块具体干了些什么,代码如下:
@Override
public IBinder onBind(Intent intent) {
Log.d(this, "onBind");
final Context context = getApplicationContext();
/// M: [plugin]ensure a context is valid.
ExtensionManager.registerApplicationContext(context);
final ContactInfoCache contactInfoCache = ContactInfoCache.getInstance(context);
InCallPresenter.getInstance().setUp(
getApplicationContext(),
CallList.getInstance(),
AudioModeProvider.getInstance(),
new StatusBarNotifier(context, contactInfoCache),
contactInfoCache,
new ProximitySensor(context, AudioModeProvider.getInstance())
);
InCallPresenter.getInstance().onServiceBind();
InCallPresenter.getInstance().maybeStartRevealAnimation(intent);
TelecomAdapter.getInstance().setInCallService(this);
return super.onBind(intent);
}
通过Log打印,发现该过程耗时110ms左右,虽然耗时不是很长,但是我们还是要具体分析一下,看看能不能够缩短时间。
在onBind方法开头这里调用Debug类中的startMethodTracing方法,并在startActivity处调用Debug类中的stopMethodTracing方法,这两个函数运行过程中将采集onBind到startActivity内该应用所有线程的函数执行情况。运行代码,生成.trace文件,要么是在SD根目录,要么是在/sdcard/Android/data/包名/files/dmtrace.trace,将文件获取之后,我们将其复制到\sdk\tools这个文件夹下,在这文件夹打开命令行输入 traceview.bat test.trace,这种方式打开比直接在Eclipse打开的好处在于能够进行关键字的搜索。
那么我们打开了trace文件之后要做些什么呢,我们需要查找以下两类:
一类是调用次数不多,但每次调用却需要花费很长时间的函数
一类是那些自身占用时间不长,但调用却非常频繁的函数。
图中的从左到右
Name :该线程运行过程中所调用的函数名
Incl Cpu Time %: 某函数占用的CPU时间,包含内部调用其它函数的CPU时间的百分比
Incl Real Time : 某函数运行的真实时间(以毫秒为单位),内含调用其它函数所占用的真实时间
Call+Recur Calls/Total:某函数被调用次数以及递归调用占总调用次数的百分比
Real Time/Call: 同CPU Time/Call类似,只不过统计单位换成了真实时间
我们先来查查调用次数不多,但每次调用确话费很长时间的函数,即对Incl Real Time进行排序,排序之后,输入我们的包名,对关键字进行搜索。我们可以看到StatusBarNotifier的构造方法中checkCam方法比较耗时,那我们就要看该方法是不是可以去除,或者修改调用顺序,或者改用其他的方式实现。
之后对调用比较频繁的函数进行排序,Call+Recur Calls/Total进行排序,排序完之后,输入我们的包名,对关键字进行搜索。发现getSimpleName调用比较频繁,该方法是可以删除或使用字符串替代的。
之后看代码发现在InCallPresenter.setUp方法中注册了皮套广播,但是我们有的机型是不支持皮套功能的,那我们就可以根据是否支持皮套功能,来决定是否注册皮套广播。进行完这些优化之后
,我们再次打印LOG,已经只需要将近63ms了。
二、startIntent------->onCreate
通过打印Log发现startIntent到InCallActivity的onCreate方法,耗时将近218ms,这个时间我们也不知道是不是正常的,那怎么办呢,我们可以写一个Demo,测试一下简单的点击跳转到另外一个Activity,startIntent到onCreate所需的时间,我测出来将近130ms左右,那我们这边肯定是有问题的,需要进行优化的。
原生中InCallPresente构造方法中是没有调用任何方法的,这是为了优化获取归属地,所以开启了一个线程,对获取归属地方法进行初始化,虽然说是开了一个线程,但是还是会对主线程有阻塞效果的。为了判断是不是该方法造成的,只能先将其注释掉运行,发现startIntent到onCreate方法所花的时间顿时减少了,跟Demos所测的数据差不多。可以将归属地方法的初始化放入到一个子线程中,放入到DialerApplication的onCreate方法中,正好同样对拨号盘输入号码获取归属地有着优化效果。但是这样做的话,会影响到应用的冷启动和第三方应用拨打电话。但是我选择这样做的原因是,是因为我们已经准备将应用改为常驻进程了,这样正好可以解决这个问题了。
三、onCreate----->onCreateView
抓取trace,发现在onCrate方法中,getActionBar方法相对来说耗时久一点,获取到ActionBar之后将其隐藏,那我们直接修改InCallActivity的theme,原先是继承@android:style/Theme.Material.Light,现在我们将其改为@android:style/Theme.Material.Light.NoActionBar。
在onCreate方法中调用AnimationUtils.loadAnimation方法,但是并不会立即调用,那我们是不是可以改为在调用的时候,再加载呢,于是改成在弹出和收缩DialpadFragment的时候调用,也不会影响起动画效果。
原生在显示CallCardFragment布局的过程中,有一个过渡的动画,我们因为需求的原因,将该功能删除了,这样也对InCallUi起到优化作用。
四、onCreateView
CallCardFragment的onCreateView方法,耗时长达555ms,这一块优化就是针对布局进行优化了。
1.查看是否使用了大图片
那我们首先来看看是不是使用了大图片的布局,正好发现CallCardFragment使用了大图片作为背景,于是跟设计进行了协商,将大图片改为了一张小图片,我们再对图片进行缩放。
2.查看有些布局是不是可使用ViewStub标签
正好我们这边的三方通话布局和皮套布局只有满足特定的条件下,才会去显示,那么我们就可以使用ViewStub标签,同VideoFragment中的布局一样。
3.去掉多余的控件
CallButtonFragment中onCreateView方法占用CallCardFragment中onCreateView方法一大半。因为需求的改动,导致CallButtonFragment布局中还保留了一些多余的控件,未被使用的。
CallButtonFragment中去掉了多余的控件之后,还有8个按钮。但是耗时还是比较长,那想着是不是布局嵌套严重,导致的,为了验证这个,我直接忽略布局上的其他问题,将布局嵌套减少到一层,测试发现数据并没有减少多少,那我们就没有必要往这个方向走了。
之后发现每个按钮有多个状态,不可点击的,可以点击的,按下的状态,每个状态有对应的图片,我们是直接针对每个按钮写了个selector,再在布局上直接使用,按照我们以前的想法这应该是没有啥问题的,都是这样做的,但是当按钮多了的时候,加载图片所耗用的时间就比较长了,为了验证这个,我就直接把每个按钮的背景设为null,运行发现时间大大的减少了。我们现在知道问题的关键在哪了,那我们现在就该想办法怎么去解决了。我发现刚开始的时候各个按钮都是不可点击的,只有当满足条件的时候,才会将其设为可以点击的,那我就直接将布局中按钮的背景设为不可点击对应的图片,当按钮设为可以点击的时候,才将各个按钮的背景设为selector。
4.减少布局的嵌套。
5.合并布局,用TextView代替图片加文字
进行了这些优化之后,CallCardFragment中onCreateView方法所花的时间大大减少了。
五、onCreateView----->startContactInfoSearch
刚开始的时候我认为在CallCardFragment中onActivityCreated方法中调用CallCardPresenter中的init方法,开始数据查询。
public void init(Context context, Call call) {
mContext = Preconditions.checkNotNull(context);
/// M: For volte @{
// Here we will use "mContext", so need add here, instead of "onUiReady()"
ContactInfoCache.getInstance(mContext).addContactInfoUpdatedListener(mContactInfoUpdatedListener);
/// @}
// Call may be null if disconnect happened already.
if (call != null) {
mPrimary = call;
CallList.getInstance().addCallUpdateListener(call.getId(), this);
// start processing lookups right away.
if (!call.isConferenceCall()) {
startContactInfoSearch(call, true, call.getState() == Call.State.INCOMING);
} else {
updateContactEntry(null, true);
}
}
onStateChange(null, InCallPresenter.getInstance().getInCallState(), CallList.getInstance());
}
但是并不总是会这样的,有可能在InCallServiceImpl中onCallAdded方法回调的慢,导致条件不成立,这时候就不会去进行数据的查询了,当回调onCallAdded方法时,会一步步调用到CallCardPresenter类中的maybeStartSearch方法。这种情况的话会比在onActivityCreated中调用慢一些,但是没有办法,这个过程不受我们所控制。
private void maybeStartSearch(Call call, boolean isPrimary) {
// no need to start search for conference calls which show generic info.
/**
* M: [VoLTE conference] incoming call still need to search.
* google original code:
if (call != null && !call.isConferenceCall()) {
* @{
*/
if ((call != null && !call.isConferenceCall()) || isIncomingVolteConference(call)) {
/** @} */
startContactInfoSearch(call, isPrimary, call.getState() == Call.State.INCOMING);
}
}
抓取CallCardFragment中onActivityCreated方法和CallCardPresenter中startContactInfoSearch方法中的trace,发现在ProximitySensor类中onStateChange方法,mAccelerometerListener.enable(mIsPhoneOffhook)调用耗时较长,这个方法是开关重力加速器,是根据当前手机是水平还是垂直摆放来决定是否关闭距离传感器的,但是我们这边正好把这个功能给去除了。那正好我们可以吧这段代码给注释掉。
六、startContactInfoSearch-------->onWindowFocusChanged
直接抓取trace,进行分析。
七、总结
优化需要耐心,一点点去分析,将一个流程给分段进行分析,再去分析整个流程。要学会勇于尝试,忽略一些因素的影响,去判断是不是哪个因素导致性能出现问题,再去想办法解决。优化也需要善用工具去进行分析,这会让我们更加精准的定位到问题所在。
在优化的过程中,我们需要去对耗时点,进行分类,我的分类如下:可控制的,不可控制的。可控制的,我们才能去进行优化。不可控制的,我们可以直接忽略掉。可控制的,可以再次分为:可以修改的,不可以修改的。有些点因为需求方面,是不可以修改的,那我们就没用办法了,那我们就不要去纠结这个点了。