我們的目標(biāo)是提升電話模塊打電話的速度,打電話的話癌刽,有多個(gè)場(chǎng)景役首,通話記錄撥打電話,撥號(hào)盤(pán)撥打電話显拜,通話記錄詳情撥打電話衡奥,聯(lián)系人詳情撥打電話。各個(gè)場(chǎng)景撥打電話的速度不一致讼油,每種場(chǎng)景都會(huì)對(duì)通話界面的顯示存在著不同的影響杰赛,至于是什么影響了呢簸,我們暫時(shí)先不考慮矮台,我們先考慮在一個(gè)空白界面撥打電話的速度是怎么樣的,先從InCallUi的角度上著手進(jìn)行優(yōu)化根时,之后再對(duì)各個(gè)場(chǎng)景進(jìn)行分析和優(yōu)化瘦赫。
我們現(xiàn)在搞清楚了我們要做什么,那我們就要朝這個(gè)方向入手了蛤迎,撥打電話的流程是怎樣的确虱,當(dāng)用戶點(diǎn)擊撥打的時(shí)候,我們會(huì)調(diào)用DialerUtils類(lèi)中的startActivityWithErrorToast方法替裆,其中調(diào)用了TelecomManager.placeCall方法校辩,調(diào)用底層的方法進(jìn)行呼叫窘问,之后會(huì)走InCallServiceImpl類(lèi)的onBind方法,onBind方法中調(diào)用了InCallPresenter類(lèi)中的maybeStartRevealAnimation方法宜咒,會(huì)根據(jù)條件判斷惠赫,是否啟動(dòng)InCallActivity.之后會(huì)走InCallActivity的onCreate方法,onCreate方法中調(diào)用internalResolveIntent方法故黑,會(huì)顯示CallCardFragment儿咱。在CallCardFragment中的onActivityCreated方法中,當(dāng)條件滿足的情況下會(huì)觸發(fā)數(shù)據(jù)查詢场晶。
一混埠、onBind ------->startActivity
在Dialer中撥打電話流程主體是這樣的,那我們接下來(lái)看看哪些步驟是可控的诗轻,哪些不是钳宪,placeCall到onBind這個(gè)是不受我們所控制的,onBind到startActivity這個(gè)過(guò)程可控的扳炬,我們接下來(lái)就看看這一塊具體干了些什么使套,代碼如下:
@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);
}
通過(guò)Log打印,發(fā)現(xiàn)該過(guò)程耗時(shí)110ms左右鞠柄,雖然耗時(shí)不是很長(zhǎng)侦高,但是我們還是要具體分析一下,看看能不能夠縮短時(shí)間厌杜。
在onBind方法開(kāi)頭這里調(diào)用Debug類(lèi)中的startMethodTracing方法奉呛,并在startActivity處調(diào)用Debug類(lèi)中的stopMethodTracing方法,這兩個(gè)函數(shù)運(yùn)行過(guò)程中將采集onBind到startActivity內(nèi)該應(yīng)用所有線程的函數(shù)執(zhí)行情況夯尽。運(yùn)行代碼瞧壮,生成.trace文件,要么是在SD根目錄匙握,要么是在/sdcard/Android/data/包名/files/dmtrace.trace咆槽,將文件獲取之后,我們將其復(fù)制到\sdk\tools這個(gè)文件夾下圈纺,在這文件夾打開(kāi)命令行輸入 traceview.bat test.trace,這種方式打開(kāi)比直接在Eclipse打開(kāi)的好處在于能夠進(jìn)行關(guān)鍵字的搜索蛾娶。
那么我們打開(kāi)了trace文件之后要做些什么呢,我們需要查找以下兩類(lèi):
一類(lèi)是調(diào)用次數(shù)不多蛔琅,但每次調(diào)用卻需要花費(fèi)很長(zhǎng)時(shí)間的函數(shù)
一類(lèi)是那些自身占用時(shí)間不長(zhǎng)胎许,但調(diào)用卻非常頻繁的函數(shù)。
圖中的從左到右
Name :該線程運(yùn)行過(guò)程中所調(diào)用的函數(shù)名
Incl Cpu Time %: 某函數(shù)占用的CPU時(shí)間辜窑,包含內(nèi)部調(diào)用其它函數(shù)的CPU時(shí)間的百分比
Incl Real Time : 某函數(shù)運(yùn)行的真實(shí)時(shí)間(以毫秒為單位),內(nèi)含調(diào)用其它函數(shù)所占用的真實(shí)時(shí)間
Call+Recur Calls/Total:某函數(shù)被調(diào)用次數(shù)以及遞歸調(diào)用占總調(diào)用次數(shù)的百分比
Real Time/Call: 同CPU Time/Call類(lèi)似穆碎,只不過(guò)統(tǒng)計(jì)單位換成了真實(shí)時(shí)間
我們先來(lái)查查調(diào)用次數(shù)不多,但每次調(diào)用確話費(fèi)很長(zhǎng)時(shí)間的函數(shù)惨远,即對(duì)Incl Real Time進(jìn)行排序,排序之后北秽,輸入我們的包名,對(duì)關(guān)鍵字進(jìn)行搜索贺氓。我們可以看到StatusBarNotifier的構(gòu)造方法中checkCam方法比較耗時(shí)蔚叨,那我們就要看該方法是不是可以去除,或者修改調(diào)用順序辙培,或者改用其他的方式實(shí)現(xiàn)蔑水。
之后對(duì)調(diào)用比較頻繁的函數(shù)進(jìn)行排序,Call+Recur Calls/Total進(jìn)行排序扬蕊,排序完之后搀别,輸入我們的包名,對(duì)關(guān)鍵字進(jìn)行搜索尾抑。發(fā)現(xiàn)getSimpleName調(diào)用比較頻繁歇父,該方法是可以刪除或使用字符串替代的。
之后看代碼發(fā)現(xiàn)在InCallPresenter.setUp方法中注冊(cè)了皮套廣播再愈,但是我們有的機(jī)型是不支持皮套功能的榜苫,那我們就可以根據(jù)是否支持皮套功能,來(lái)決定是否注冊(cè)皮套廣播翎冲。進(jìn)行完這些優(yōu)化之后
垂睬,我們?cè)俅未蛴OG,已經(jīng)只需要將近63ms了抗悍。
二驹饺、startIntent------->onCreate
通過(guò)打印Log發(fā)現(xiàn)startIntent到InCallActivity的onCreate方法,耗時(shí)將近218ms檐春,這個(gè)時(shí)間我們也不知道是不是正常的逻淌,那怎么辦呢么伯,我們可以寫(xiě)一個(gè)Demo疟暖,測(cè)試一下簡(jiǎn)單的點(diǎn)擊跳轉(zhuǎn)到另外一個(gè)Activity,startIntent到onCreate所需的時(shí)間,我測(cè)出來(lái)將近130ms左右俐巴,那我們這邊肯定是有問(wèn)題的骨望,需要進(jìn)行優(yōu)化的。
原生中InCallPresente構(gòu)造方法中是沒(méi)有調(diào)用任何方法的欣舵,這是為了優(yōu)化獲取歸屬地擎鸠,所以開(kāi)啟了一個(gè)線程,對(duì)獲取歸屬地方法進(jìn)行初始化缘圈,雖然說(shuō)是開(kāi)了一個(gè)線程劣光,但是還是會(huì)對(duì)主線程有阻塞效果的。為了判斷是不是該方法造成的糟把,只能先將其注釋掉運(yùn)行绢涡,發(fā)現(xiàn)startIntent到onCreate方法所花的時(shí)間頓時(shí)減少了,跟Demos所測(cè)的數(shù)據(jù)差不多雄可〔可以將歸屬地方法的初始化放入到一個(gè)子線程中,放入到DialerApplication的onCreate方法中虐急,正好同樣對(duì)撥號(hào)盤(pán)輸入號(hào)碼獲取歸屬地有著優(yōu)化效果滔迈。但是這樣做的話,會(huì)影響到應(yīng)用的冷啟動(dòng)和第三方應(yīng)用撥打電話赏殃。但是我選擇這樣做的原因是间涵,是因?yàn)槲覀円呀?jīng)準(zhǔn)備將應(yīng)用改為常駐進(jìn)程了,這樣正好可以解決這個(gè)問(wèn)題了抗蠢。
三思劳、onCreate----->onCreateView
抓取trace,發(fā)現(xiàn)在onCrate方法中,getActionBar方法相對(duì)來(lái)說(shuō)耗時(shí)久一點(diǎn),獲取到ActionBar之后將其隱藏秽褒,那我們直接修改InCallActivity的theme,原先是繼承@android:style/Theme.Material.Light销斟,現(xiàn)在我們將其改為@android:style/Theme.Material.Light.NoActionBar。
在onCreate方法中調(diào)用AnimationUtils.loadAnimation方法约谈,但是并不會(huì)立即調(diào)用犁钟,那我們是不是可以改為在調(diào)用的時(shí)候,再加載呢军俊,于是改成在彈出和收縮DialpadFragment的時(shí)候調(diào)用捧存,也不會(huì)影響起動(dòng)畫(huà)效果昔穴。
原生在顯示CallCardFragment布局的過(guò)程中,有一個(gè)過(guò)渡的動(dòng)畫(huà)泳唠,我們因?yàn)樾枨蟮脑蛑姘幔瑢⒃摴δ軇h除了,這樣也對(duì)InCallUi起到優(yōu)化作用脖母。
四闲孤、onCreateView
CallCardFragment的onCreateView方法,耗時(shí)長(zhǎng)達(dá)555ms肥照,這一塊優(yōu)化就是針對(duì)布局進(jìn)行優(yōu)化了勤众。
1.查看是否使用了大圖片
那我們首先來(lái)看看是不是使用了大圖片的布局,正好發(fā)現(xiàn)CallCardFragment使用了大圖片作為背景吕朵,于是跟設(shè)計(jì)進(jìn)行了協(xié)商,將大圖片改為了一張小圖片边锁,我們?cè)賹?duì)圖片進(jìn)行縮放波岛。
2.查看有些布局是不是可使用ViewStub標(biāo)簽
正好我們這邊的三方通話布局和皮套布局只有滿足特定的條件下,才會(huì)去顯示贡蓖,那么我們就可以使用ViewStub標(biāo)簽煌茬,同VideoFragment中的布局一樣。
3.去掉多余的控件
CallButtonFragment中onCreateView方法占用CallCardFragment中onCreateView方法一大半晾蜘。因?yàn)樾枨蟮母膭?dòng)眠屎,導(dǎo)致CallButtonFragment布局中還保留了一些多余的控件改衩,未被使用的。
CallButtonFragment中去掉了多余的控件之后葫督,還有8個(gè)按鈕。但是耗時(shí)還是比較長(zhǎng)偎快,那想著是不是布局嵌套嚴(yán)重洽胶,導(dǎo)致的,為了驗(yàn)證這個(gè)惋戏,我直接忽略布局上的其他問(wèn)題他膳,將布局嵌套減少到一層,測(cè)試發(fā)現(xiàn)數(shù)據(jù)并沒(méi)有減少多少舔亭,那我們就沒(méi)有必要往這個(gè)方向走了。
之后發(fā)現(xiàn)每個(gè)按鈕有多個(gè)狀態(tài)钦铺,不可點(diǎn)擊的,可以點(diǎn)擊的洼哎,按下的狀態(tài)噩峦,每個(gè)狀態(tài)有對(duì)應(yīng)的圖片,我們是直接針對(duì)每個(gè)按鈕寫(xiě)了個(gè)selector识补,再在布局上直接使用凭涂,按照我們以前的想法這應(yīng)該是沒(méi)有啥問(wèn)題的贴妻,都是這樣做的,但是當(dāng)按鈕多了的時(shí)候白翻,加載圖片所耗用的時(shí)間就比較長(zhǎng)了绢片,為了驗(yàn)證這個(gè),我就直接把每個(gè)按鈕的背景設(shè)為null,運(yùn)行發(fā)現(xiàn)時(shí)間大大的減少了巢株。我們現(xiàn)在知道問(wèn)題的關(guān)鍵在哪了熙涤,那我們現(xiàn)在就該想辦法怎么去解決了。我發(fā)現(xiàn)剛開(kāi)始的時(shí)候各個(gè)按鈕都是不可點(diǎn)擊的那槽,只有當(dāng)滿足條件的時(shí)候等舔,才會(huì)將其設(shè)為可以點(diǎn)擊的,那我就直接將布局中按鈕的背景設(shè)為不可點(diǎn)擊對(duì)應(yīng)的圖片甚牲,當(dāng)按鈕設(shè)為可以點(diǎn)擊的時(shí)候,才將各個(gè)按鈕的背景設(shè)為selector丈钙。
4.減少布局的嵌套。
5.合并布局劫笙,用TextView代替圖片加文字
進(jìn)行了這些優(yōu)化之后喉誊,CallCardFragment中onCreateView方法所花的時(shí)間大大減少了纵顾。
五、onCreateView----->startContactInfoSearch
剛開(kāi)始的時(shí)候我認(rèn)為在CallCardFragment中onActivityCreated方法中調(diào)用CallCardPresenter中的init方法敷矫,開(kāi)始數(shù)據(jù)查詢曹仗。
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());
}
但是并不總是會(huì)這樣的怎茫,有可能在InCallServiceImpl中onCallAdded方法回調(diào)的慢妓灌,導(dǎo)致條件不成立,這時(shí)候就不會(huì)去進(jìn)行數(shù)據(jù)的查詢了祥山,當(dāng)回調(diào)onCallAdded方法時(shí)掉伏,會(huì)一步步調(diào)用到CallCardPresenter類(lèi)中的maybeStartSearch方法。這種情況的話會(huì)比在onActivityCreated中調(diào)用慢一些供常,但是沒(méi)有辦法鸡捐,這個(gè)過(guò)程不受我們所控制栈暇。
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,發(fā)現(xiàn)在ProximitySensor類(lèi)中onStateChange方法闯参,mAccelerometerListener.enable(mIsPhoneOffhook)調(diào)用耗時(shí)較長(zhǎng)悲立,這個(gè)方法是開(kāi)關(guān)重力加速器新博,是根據(jù)當(dāng)前手機(jī)是水平還是垂直擺放來(lái)決定是否關(guān)閉距離傳感器的,但是我們這邊正好把這個(gè)功能給去除了原献。那正好我們可以吧這段代碼給注釋掉姑隅。
六倔撞、startContactInfoSearch-------->onWindowFocusChanged
直接抓取trace,進(jìn)行分析。
七鄙陡、總結(jié)
優(yōu)化需要耐心躏啰,一點(diǎn)點(diǎn)去分析,將一個(gè)流程給分段進(jìn)行分析毫捣,再去分析整個(gè)流程蔓同。要學(xué)會(huì)勇于嘗試胡本,忽略一些因素的影響,去判斷是不是哪個(gè)因素導(dǎo)致性能出現(xiàn)問(wèn)題珊佣,再去想辦法解決披粟。優(yōu)化也需要善用工具去進(jìn)行分析,這會(huì)讓我們更加精準(zhǔn)的定位到問(wèn)題所在惑艇。
在優(yōu)化的過(guò)程中,我們需要去對(duì)耗時(shí)點(diǎn)思灌,進(jìn)行分類(lèi)恭取,我的分類(lèi)如下:可控制的,不可控制的耗跛≡芊ⅲ可控制的,我們才能去進(jìn)行優(yōu)化羔砾。不可控制的紊扬,我們可以直接忽略掉唉擂。可控制的腹缩,可以再次分為:可以修改的空扎,不可以修改的。有些點(diǎn)因?yàn)樾枨蠓矫媾坦眩遣豢梢孕薷牡拇榭俏覀兙蜎](méi)用辦法了,那我們就不要去糾結(jié)這個(gè)點(diǎn)了影涉。