Android面试复习资料整理
Activity鞏固和復(fù)習(xí)
1. 什么是Activity
四大組件之一,通常一個(gè)用戶交互界面對(duì)應(yīng)一個(gè)activity。activity是Context的子類,同時(shí)實(shí)現(xiàn)了window.callback和keyevent.callback,可以處理與窗體用戶交互的事件。
開(kāi)發(fā)中常用的有FragmentActivity、ListActivity、TabActivity(Android 4.0被Fragment取代)
2. Activity的4種狀態(tài)
- running:用戶可以點(diǎn)擊,activity處于棧頂狀態(tài)。
- paused:activity失去焦點(diǎn)的時(shí)候,被一個(gè)非全屏的activity占據(jù)或者被一個(gè)透明的activity覆蓋,這個(gè)狀態(tài)的activity并沒(méi)有銷毀,它所有的狀態(tài)信息和成員變量仍然存在,只是不能夠被點(diǎn)擊。(除了內(nèi)存緊張的情況,這個(gè)activity有可能被回收)
- stopped:這個(gè)activity被另外一個(gè)activity完全覆蓋,但是這個(gè)activity的所有狀態(tài)信息和成員變量仍然存在(除了內(nèi)存緊張)
- killed:這個(gè)activity已經(jīng)被銷毀,其所有的狀態(tài)信息和成員變量已經(jīng)不存在了。
3. Activity生命周期
生命周期的基本介紹
- onCreate:當(dāng)Activity第一次啟動(dòng)調(diào)用
- onDestroy:當(dāng)Activity銷毀的時(shí)候調(diào)用
- onStart:當(dāng)Activity變成可見(jiàn)調(diào)用
- onStop:當(dāng)Activity不可見(jiàn)調(diào)用
- onResume:當(dāng)Activity可以交互調(diào)用這個(gè)方法 當(dāng)界面上的按鈕被點(diǎn)擊的時(shí)候調(diào)用
- onPause:當(dāng)Activity不可以交互調(diào)用這個(gè)方法 當(dāng)界面上的按鈕不可以點(diǎn)擊
- onRestart:當(dāng)界面重新啟動(dòng)的時(shí)候調(diào)用
生命周期流程
- Activity啟動(dòng):調(diào)用的依次順序是:onCreate —> onStart —> onResume —> onPause —> onStop —> onDestroy,還有一個(gè)onRestart,其中onRestart是在Activity被onStop后,但是沒(méi)有被onDestroy,在再次啟動(dòng)此Activity時(shí)調(diào)用的(而不再調(diào)用onCreate)方法;如果被onDestroy了,則是調(diào)用onCreate方法。
- 點(diǎn)擊Home鍵回到主界面(Activity不可見(jiàn)):onPause —> onStop
- 當(dāng)我們?cè)俅位氐皆瑼ctivity時(shí): onRestart —> onStart —> onResume
- 退出當(dāng)前Activity時(shí): onPause —> onStop —> onDestroy
4. Activity任務(wù)棧
- 有序地管理Activity的先進(jìn)后出的一種數(shù)據(jù)結(jié)構(gòu)
- 安全退出:任務(wù)棧中所有的Activity都出棧
5. Activity的啟動(dòng)模式
-
standard 標(biāo)準(zhǔn)模式:
特點(diǎn):此模式不管有沒(méi)有已存在的實(shí)例,都生成新的實(shí)例。每次調(diào)用startActivity()啟動(dòng)Activity時(shí)都會(huì)創(chuàng)建一個(gè)新的Activity放在棧頂,每次返回都會(huì)銷毀實(shí)例并出棧,可以重復(fù)創(chuàng)建。
-
singleTop 單一頂部模式/棧頂復(fù)用模式:
特點(diǎn):會(huì)檢查任務(wù)棧棧頂?shù)腁ctivity,如果發(fā)現(xiàn)棧頂已經(jīng)存在實(shí)例,就不會(huì)創(chuàng)建新的實(shí)例,直接復(fù)用,此時(shí)會(huì)調(diào)用onNewIntent。但如果不在棧頂,那么還是會(huì)創(chuàng)建新的實(shí)例。
應(yīng)用場(chǎng)景:瀏覽器書(shū)簽的頁(yè)面,流氓的網(wǎng)站,避免創(chuàng)建過(guò)多的書(shū)簽頁(yè)面 -
singleTask 單一任務(wù)模式/棧內(nèi)復(fù)用模式:
特點(diǎn):這種模式不會(huì)檢查任務(wù)棧的棧頂,檢查當(dāng)前任務(wù)棧,如果發(fā)現(xiàn)有實(shí)例存在,直接復(fù)用。任務(wù)棧中只有一個(gè)實(shí)例存儲(chǔ)(把當(dāng)前activity上面的所有的其它activity都清空,復(fù)用這個(gè)已經(jīng)存在的activity)
應(yīng)用場(chǎng)景:瀏覽器瀏覽頁(yè)面的Activity,播放器播放的activity。 -
singleInstance 單一實(shí)例模式(用得比較少)
特點(diǎn):系統(tǒng)會(huì)為這個(gè)Activity單獨(dú)創(chuàng)建一個(gè)任務(wù)棧,這個(gè)任務(wù)棧里面只有一個(gè)實(shí)例存在并且保證不再有其它activity實(shí)例進(jìn)入。
應(yīng)用場(chǎng)景:來(lái)電頁(yè)面。
6. Scheme跳轉(zhuǎn)協(xié)議
概念
Android中的scheme是一種頁(yè)面內(nèi)跳轉(zhuǎn)協(xié)議,是一種非常好的實(shí)現(xiàn)機(jī)制,通過(guò)定義自己的scheme協(xié)議,可以非常方便跳轉(zhuǎn)app中的各個(gè)頁(yè)面;通過(guò)scheme協(xié)議,服務(wù)器可以定制化告訴app跳轉(zhuǎn)哪個(gè)頁(yè)面,可以通過(guò)通知欄消息定制化跳轉(zhuǎn)頁(yè)面,可以通過(guò)H5頁(yè)面跳轉(zhuǎn)頁(yè)面等。
應(yīng)用場(chǎng)景
- 通過(guò)服務(wù)器下發(fā)跳轉(zhuǎn)路徑跳轉(zhuǎn)相應(yīng)頁(yè)面
- 通過(guò)在H5頁(yè)面的錨點(diǎn)跳轉(zhuǎn)相應(yīng)的頁(yè)面
- 根據(jù)服務(wù)器下發(fā)通知欄消息,App跳轉(zhuǎn)相應(yīng)的頁(yè)面(包括另外一個(gè)APP的頁(yè)面,作為推廣使用)
7. 參考文章
Android面試(一):Activity面試你所需知道的一切
android-Scheme與網(wǎng)頁(yè)跳轉(zhuǎn)原生的三種方式
Fragment
1. 什么是Fragment
Fragment,俗稱碎片,自Android 3.0開(kāi)始被引進(jìn)并大量使用。作為Activity界面的一部分,Fragment的存在必須依附于Activity,并且與Activity一樣,擁有自己的生命周期,同時(shí)處理用戶的交互動(dòng)作。同一個(gè)Activity可以有一個(gè)或多個(gè)Fragment作為界面內(nèi)容,并且可以動(dòng)態(tài)添加、刪除Fragment,靈活控制UI內(nèi)容,也可以用來(lái)解決部分屏幕適配問(wèn)題。
2. Fragment為什么被稱為第五大組件
首先Fragment的使用次數(shù)是不輸于其他四大組件的,而且Fragment有自己的生命周期,比Activity更加節(jié)省內(nèi)存,切換模式也更加舒適,使用頻率不低于四大組件。
3. Fragment的生命周期

4. Fragment創(chuàng)建/加載到Activity的兩種方式
-
靜態(tài)加載
- 創(chuàng)建Fragment的xml布局文件
- 在Fragment的onCreateView中inflate布局,返回
- 在Activity的布局文件中的適當(dāng)位置添加fragment標(biāo)簽,指定name為Fragment的完整類名(這時(shí)候Activity中可以直接通過(guò)findViewById找到Fragment中的控件)
-
動(dòng)態(tài)加載(需要用到事務(wù)操作,常用)
-
創(chuàng)建Fragment的xml布局文件
-
在Fragment的onCreateView中inflate布局,返回
@Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {return inflater.inflate(R.layout.activity_main, container, false); } -
在Activity中通過(guò)獲取FragmentManager(SupportFragmentManager),通過(guò)beginTransaction()方法開(kāi)啟事務(wù)
-
進(jìn)行add()/remove()/replace()/attach()/detach()/hide()/addToBackStack()事務(wù)操作(都是對(duì)Fragment的棧進(jìn)行操作,其中add()指定的tag參數(shù)可以方便以后通過(guò)findFragmentByTag()找到這個(gè)Fragment)
-
提交事務(wù):commit()
示例代碼:
@Override protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, new TestFragment(), "test").commit();TestFragment f = (TestFragment) getSupportFragmentManager().findFragmentByTag("test"); }
5. Fragment通信問(wèn)題
通過(guò)findFragmentByTag或者getActivity獲得對(duì)方的引用(強(qiáng)轉(zhuǎn))之后,再相互調(diào)用對(duì)方的public方法。
優(yōu)點(diǎn):簡(jiǎn)單粗暴
缺點(diǎn):引入了“強(qiáng)轉(zhuǎn)”的丑陋代碼,另外兩個(gè)類之間各自持有對(duì)方的強(qiáng)引用,耦合較大,容易造成內(nèi)存泄漏
通過(guò)Bundle的方法進(jìn)行傳值,在添加Fragment的時(shí)候進(jìn)行通信
@Override protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Fragment fragment = new TestFragment();Bundle bundle = new Bundle();bundle.putString("key", "value");//Activity中對(duì)fragment設(shè)置一些參數(shù)fragment.setArguments(bundle);getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, fragment, "test").commit(); }優(yōu)點(diǎn):簡(jiǎn)單粗暴
缺點(diǎn):只能在Fragment添加到Activity的時(shí)候才能使用,屬于單向通信
利用eventbus進(jìn)行通信
優(yōu)點(diǎn):實(shí)時(shí)性高,雙向通信,Activity與Fragment之間可以完全解耦
缺點(diǎn):反射影響性能,無(wú)法獲取返回?cái)?shù)據(jù),EventBUS難以維護(hù)
利用接口回調(diào)進(jìn)行通信(Google官方推薦)
//MainActivity實(shí)現(xiàn)MainFragment開(kāi)放的接口 public class MainActivity extends FragmentActivity implements FragmentListener {@overridepublic void toH5Page() {//...其他處理代碼省略} } //Fragment的實(shí)現(xiàn) public class MainFragment extends Fragment {//接口的實(shí)例,在onAttach Activity的時(shí)候進(jìn)行設(shè)置public FragmentListener mListener;//MainFragment開(kāi)放的接口public static interface FragmentListener {//跳到h5頁(yè)面void toH5Page();}@Overridepublic void onAttach(Activity activity) {super.onAttach(activity);//對(duì)傳遞進(jìn)來(lái)的Activity進(jìn)行接口轉(zhuǎn)換if (activity instance FragmentListener){mListener = ((FragmentListener) activity);}}...其他處理代碼省略 }優(yōu)點(diǎn):既能達(dá)到復(fù)用,又能達(dá)到很好的可維護(hù)性,并且性能得到保證
缺點(diǎn):假如項(xiàng)目很大了,Activity與Fragment的數(shù)量也會(huì)增加,這時(shí)候?yàn)槊繉?duì)Activity與Fragment交互定義交互接口就是一個(gè)很麻煩的問(wèn)題(包括為接口的命名,新定義的接口相應(yīng)的Activity還得實(shí)現(xiàn),相應(yīng)的Fragment還得進(jìn)行強(qiáng)制轉(zhuǎn)換)
通過(guò)Handler進(jìn)行通信(其實(shí)就是把接口的方式改為Handler)
優(yōu)點(diǎn):既能達(dá)到復(fù)用,又能達(dá)到很好的可維護(hù)性,并且性能得到保證
缺點(diǎn):Fragment對(duì)具體的Activity存在耦合,不利于Fragment復(fù)用和維護(hù),沒(méi)法獲取Activity的返回?cái)?shù)據(jù)
通過(guò)廣播/本地廣播進(jìn)行通信
優(yōu)點(diǎn):簡(jiǎn)單粗暴
缺點(diǎn):大材小用,存在性能損耗,傳播數(shù)據(jù)必須實(shí)現(xiàn)序列化接口
父子Fragment之間通信,可以使用getParentFragment()/getChildFragmentManager()的方式進(jìn)行
6. FragmentPageAdapter和FragmentPageStateAdapter的區(qū)別
-
FragmentPageAdapter在每次切換頁(yè)面的時(shí)候,是將Fragment進(jìn)行分離,適合頁(yè)面較少的Fragment使用以保存一些內(nèi)存,對(duì)系統(tǒng)內(nèi)存不會(huì)多大影響
@Override public void destroyItem(ViewGroup container, int position, Object object) {if (mCurTransaction == null) {mCurTransaction = mFragmentManager.beginTransaction();}if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object+ " v=" + ((Fragment)object).getView());//FragmentPageAdapter在destroyItem的時(shí)候調(diào)用detachmCurTransaction.detach((Fragment)object); } -
FragmentPageStateAdapter在每次切換頁(yè)面的時(shí)候,是將Fragment進(jìn)行回收,適合頁(yè)面較多的Fragment使用,這樣就不會(huì)消耗更多的內(nèi)存
@Override public void destroyItem(ViewGroup container, int position, Object object) {Fragment fragment = (Fragment) object;if (mCurTransaction == null) {mCurTransaction = mFragmentManager.beginTransaction();}if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object+ " v=" + ((Fragment)object).getView());while (mSavedState.size() <= position) {mSavedState.add(null);}mSavedState.set(position, fragment.isAdded()? mFragmentManager.saveFragmentInstanceState(fragment) : null);mFragments.set(position, null);//FragmentPageStateAdapter在destroyItem的時(shí)候調(diào)用removemCurTransaction.remove(fragment); }
7. 參考文章
Android:Activity與Fragment通信(99%)完美解決方案
Service
1. 什么是Service
Service是四大組件之一,它可以在后臺(tái)執(zhí)行長(zhǎng)時(shí)間運(yùn)行操作而沒(méi)有用戶界面的應(yīng)用組件
2. Service的兩種啟動(dòng)方式與生命周期
startService特點(diǎn):
bindService特點(diǎn):
3. Service和Thread的區(qū)別
- Service是安卓中系統(tǒng)的組件,它運(yùn)行在獨(dú)立進(jìn)程的主線程中,默認(rèn)情況下不可以執(zhí)行耗時(shí)操作(否則ANR)
- Thread是程序執(zhí)行的最小單元,分配CPU的基本單位,可以開(kāi)啟子線程執(zhí)行耗時(shí)操作
- Service在不同Activity中可以獲取自身實(shí)例,可以方便的對(duì)Service進(jìn)行操作
- Thread的運(yùn)行是獨(dú)立于Activity的,也就是說(shuō)當(dāng)一個(gè)Activity被finish之后,如果沒(méi)有主動(dòng)停止Thread或者Thread里的run方法沒(méi)有執(zhí)行完畢的話,Thread也會(huì)一直執(zhí)行,引發(fā)內(nèi)存泄漏;另一方面,沒(méi)有辦法在不同的Activity中對(duì)同一Thread進(jìn)行控制。
Broadcast
1. BroadcastReceiver是什么
BroadcastReceiver是四大組件之一,是一種廣泛運(yùn)用在應(yīng)用程序之間傳輸信息的機(jī)制,通過(guò)發(fā)送Intent來(lái)傳送我們的數(shù)據(jù)。
2. BroadcastReceiver的使用場(chǎng)景
- 不同組件之間的消息通信(應(yīng)用內(nèi)/應(yīng)用內(nèi)不同進(jìn)程/不同進(jìn)程(應(yīng)用))
- 與Android系統(tǒng)在特定情況下的通信(如電話呼入、藍(lán)牙狀態(tài)變化等)
- 線程之間的通信
3. Broadcast種類
普通廣播(Normal Broadcast)
- 通過(guò)sendBroadcast進(jìn)行發(fā)送,如果注冊(cè)了Action匹配的接受者則會(huì)收到
- 若發(fā)送廣播有相應(yīng)權(quán)限,那么廣播接收者也需要相應(yīng)權(quán)限
系統(tǒng)廣播(System Broadcast)
- Android中內(nèi)置了多個(gè)系統(tǒng)廣播:只要涉及到手機(jī)的基本操作(如開(kāi)機(jī)、網(wǎng)絡(luò)狀態(tài)變化、拍照等等),都會(huì)發(fā)出相應(yīng)的廣播
- 每個(gè)廣播都有特定的Intent - Filter(包括具體的action)
- 系統(tǒng)廣播由系統(tǒng)發(fā)送,不需要手動(dòng)發(fā)送,只需要注冊(cè)監(jiān)聽(tīng)
有序廣播(Ordered Broadcast)
- 通過(guò)sendOrderedBroadcast發(fā)送
- 發(fā)送出去的廣播被廣播接收者按照先后順序接收(有序是針對(duì)廣播接收者而言的)
- 廣播接受者接收廣播的順序規(guī)則:Priority大的優(yōu)先;動(dòng)態(tài)注冊(cè)的接收者優(yōu)先
- 先接收的可以對(duì)廣播進(jìn)行截?cái)嗪托薷?/li>
App應(yīng)用內(nèi)廣播(本地廣播、Local Broadcast)
- 通過(guò)LocalBroadcastManager.getInstance(this).sendBroadcastSync();
- App應(yīng)用內(nèi)廣播可理解為一種局部廣播,廣播的發(fā)送者和接收者都同屬于一個(gè)App
- 相比于全局廣播(普通廣播),App應(yīng)用內(nèi)廣播優(yōu)勢(shì)體現(xiàn)在:安全性高 & 效率高(本地廣播只會(huì)在APP內(nèi)傳播,安全性高;不允許其他APP對(duì)自己的APP發(fā)送廣播,效率高)
粘性廣播(Sticky Broadcast)
- 在Android5.0 & API 21中已經(jīng)失效,所以不建議使用
- 通過(guò)sendStickyBroadcast發(fā)送
- 粘性廣播在發(fā)送后就一直存在于系統(tǒng)的消息容器里面,等待對(duì)應(yīng)的處理器去處理,如果暫時(shí)沒(méi)有處理器處理這個(gè)廣播則一直在消息容器里面處于等待狀態(tài)
- 粘性廣播的Receiver如果被銷毀,那么下次重新創(chuàng)建的時(shí)候會(huì)自動(dòng)接收到消息數(shù)據(jù)
4. 廣播的注冊(cè)方式
- 靜態(tài)注冊(cè):也稱為清單注冊(cè),就是在AndroidManifest.xml中注冊(cè)的廣播。此類廣播接收器在應(yīng)用尚未啟動(dòng)的時(shí)候就可以接收到相應(yīng)廣播。
- 動(dòng)態(tài)注冊(cè):也稱為運(yùn)行時(shí)注冊(cè),也就是在Service或者Activity組件中,通過(guò)Context.registerReceiver()注冊(cè)廣播接收器。此類廣播接收器是在應(yīng)用已啟動(dòng)后,通過(guò)代碼進(jìn)行注冊(cè)。生命周期與組件一致。
5. 廣播的實(shí)現(xiàn)機(jī)制
6. 本地廣播的使用以及實(shí)現(xiàn)機(jī)制
-
基本使用:可以通過(guò)intent.setPackage(packageName)指定包名,也可以使用localBroadcastManager(常用),示例代碼如下:
//注冊(cè)應(yīng)用內(nèi)廣播接收器 //步驟1:實(shí)例化BroadcastReceiver子類 & IntentFilter mBroadcastReceiver mBroadcastReceiver = new mBroadcastReceiver(); IntentFilter intentFilter = new IntentFilter();//步驟2:實(shí)例化LocalBroadcastManager的實(shí)例 localBroadcastManager = LocalBroadcastManager.getInstance(this);//步驟3:設(shè)置接收廣播的類型 intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);//步驟4:調(diào)用LocalBroadcastManager單一實(shí)例的registerReceiver()方法進(jìn)行動(dòng)態(tài)注冊(cè) localBroadcastManager.registerReceiver(mBroadcastReceiver, intentFilter);//取消注冊(cè)應(yīng)用內(nèi)廣播接收器 localBroadcastManager.unregisterReceiver(mBroadcastReceiver);//發(fā)送應(yīng)用內(nèi)廣播 Intent intent = new Intent(); intent.setAction(BROADCAST_ACTION); localBroadcastManager.sendBroadcast(intent); -
localBroadcastManager的實(shí)現(xiàn)機(jī)制
- LocalBroadcastManager高效的原因主要是因?yàn)樗鼉?nèi)部是通過(guò)Handler實(shí)現(xiàn)的,它的sendBroadcast()方法含義和我們平時(shí)所用的全局廣播不一樣,它的sendBroadcast()方法其實(shí)是通過(guò)handler發(fā)送一個(gè)Message實(shí)現(xiàn)的。
- 既然是它內(nèi)部是通過(guò)Handler來(lái)實(shí)現(xiàn)廣播的發(fā)送的,那么相比與系統(tǒng)廣播通過(guò)Binder實(shí)現(xiàn)那肯定是更高效了,同時(shí)使用Handler來(lái)實(shí)現(xiàn),別的應(yīng)用無(wú)法向我們的應(yīng)用發(fā)送該廣播,而我們應(yīng)用內(nèi)發(fā)送的廣播也不會(huì)離開(kāi)我們的應(yīng)用
- LocalBroadcastManager內(nèi)部協(xié)作主要是靠這兩個(gè)Map集合:mReceivers和mActions,當(dāng)然還有一個(gè)List集合mPendingBroadcasts,這個(gè)主要就是存儲(chǔ)待接收的廣播對(duì)象
7. 參考文章
Android四大組件:BroadcastReceiver史上最全面解析
Android 粘性廣播StickyBroadcast的使用
咦,Oreo怎么收不到廣播了?
LocalBroadcastManager—?jiǎng)?chuàng)建更高效、更安全的廣播
WebView
1. WebView遠(yuǎn)程代碼執(zhí)行安全漏洞
漏洞描述
Android API level 16以及之前的版本存在遠(yuǎn)程代碼執(zhí)行安全漏洞,該漏洞源于程序沒(méi)有正確限制使用WebView.addJavascriptInterface方法,遠(yuǎn)程攻擊者可通過(guò)使用Java Reflection API利用該漏洞執(zhí)行任意Java對(duì)象的方法。
簡(jiǎn)單的說(shuō)就是通過(guò)addJavascriptInterface給WebView加入一個(gè)JavaScript橋接接口,JavaScript通過(guò)調(diào)用這個(gè)接口可以直接操作本地的JAVA接口。
示例代碼
WebView代碼如下所示:
mWebView = new WebView(this); mWebView.getSettings().setJavaScriptEnabled(true); mWebView.addJavascriptInterface(this, "injectedObj"); mWebView.loadUrl("file:///android_asset/www/index.html");發(fā)送惡意短信:
<html><body><script>var objSmsManager = injectedObj.getClass().forName("android.telephony.SmsManager").getM ethod("getDefault",null).invoke(null,null);objSmsManager.sendTextMessage("10086",null,"this message is sent by JS when webview is loading",null,null);</script></body> </html>利用反射機(jī)制調(diào)用Android API getRuntime執(zhí)行shell命令,最終操作用戶的文件系統(tǒng):
<html><body><script>function execute(cmdArgs){return injectedObj.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs);}var res = execute(["/system/bin/sh", "-c", "ls -al /mnt/sdcard/"]);document.write(getContents(res.getInputStream()));</script></body> </html>漏洞檢測(cè)
漏洞修復(fù)
允許被調(diào)用的函數(shù)必須以@JavascriptInterface進(jìn)行注解(API Level小于17的應(yīng)用也會(huì)受影響)
建議不要使用addJavascriptInterface接口,以免帶來(lái)不必要的安全隱患,采用動(dòng)態(tài)地生成將注入的JS代碼的方式來(lái)代替
如果一定要使用addJavascriptInterface接口:
移除Android系統(tǒng)內(nèi)部的默認(rèn)內(nèi)置接口
removeJavascriptInterface("searchBoxJavaBridge_"); removeJavascriptInterface("accessibility"); removeJavascriptInterface("accessibilityTraversal");2. JSBridge
客戶端和服務(wù)端之間可以通過(guò)JSBridge來(lái)互相調(diào)用各自的方法,實(shí)現(xiàn)雙向通信
3. WebView的正確銷毀與內(nèi)存泄漏問(wèn)題
由于WebView是依附于Activity的,Activity的生命周期和WebView啟動(dòng)的線程的生命周期是不一致的,這會(huì)導(dǎo)致WebView一直持有對(duì)這個(gè)Activity的引用而無(wú)法釋放,解決方案如下
獨(dú)立進(jìn)程,簡(jiǎn)單暴力,不過(guò)可能涉及到進(jìn)程間通信(推薦)
動(dòng)態(tài)添加WebView,對(duì)傳入WebView中使用的Context使用弱引用
正確銷毀WebView,WebView在其他容器上時(shí)(如:LinearLayout),當(dāng)銷毀Activity時(shí),需要:
4. WebView后臺(tái)耗電
問(wèn)題
在WebView加載頁(yè)面的時(shí)候,會(huì)自動(dòng)開(kāi)啟線程去加載,如果不很好的關(guān)閉這些線程,就會(huì)導(dǎo)致電量消耗加大。
解決方法
可以采用暴力的方法,直接在onDestroy方法中System.exit(0)結(jié)束當(dāng)前正在運(yùn)行中的java虛擬機(jī)
5. WebView硬件加速
WebView硬件加速以及缺點(diǎn)
Android3.0引入硬件加速,默認(rèn)會(huì)開(kāi)啟,WebView在硬件加速的情況下滑動(dòng)更加平滑,性能更加好,但是會(huì)出現(xiàn)白塊或者頁(yè)面閃爍的副作用。
解決方案
建議在需要的地方WebView暫時(shí)關(guān)閉硬件加速
6. WebViewClient的onPageFinished問(wèn)題
問(wèn)題
WebViewClient.onPageFinished在每次頁(yè)面加載完成的時(shí)候調(diào)用,但是遇到未加載完成的頁(yè)面跳轉(zhuǎn)其他頁(yè)面時(shí),就會(huì)被一直調(diào)用
解決方案
使用WebChromeClient.onProgressChanged替代WebViewClient.onPageFinished
7. 參考文章
WebView 遠(yuǎn)程代碼執(zhí)行漏洞淺析
Android WebView遠(yuǎn)程執(zhí)行代碼漏洞淺析
Android WebView 遠(yuǎn)程代碼執(zhí)行漏洞簡(jiǎn)析
在WebView中如何讓JS與Java安全地互相調(diào)用
Android系統(tǒng)架構(gòu)與Framework源碼分析
1. Android系統(tǒng)架構(gòu)
根據(jù)上圖,Android系統(tǒng)架構(gòu)從上往下分別是:
2. Android Framework源碼分析
寫給Android App開(kāi)發(fā)人員看的Android底層知識(shí)(1)- Binder與AIDL
寫給Android App開(kāi)發(fā)人員看的Android底層知識(shí)(2)- AMS與APP、Activity的啟動(dòng)流程
寫給Android App開(kāi)發(fā)人員看的Android底層知識(shí)(3)- AMS與APP、Activity的啟動(dòng)流程
寫給Android App開(kāi)發(fā)人員看的Android底層知識(shí)(4)- Context
寫給Android App開(kāi)發(fā)人員看的Android底層知識(shí)(5)- Service
寫給Android App開(kāi)發(fā)人員看的Android底層知識(shí)(6)- BroadcastReceiver
寫給Android App開(kāi)發(fā)人員看的Android底層知識(shí)(7)- ContentProvider
寫給Android App開(kāi)發(fā)人員看的Android底層知識(shí)(8)- PMS及App安裝過(guò)程
除此之外,還有消息機(jī)制、窗口管理等源碼分析,推薦《開(kāi)發(fā)藝術(shù)探索》,以及LooperJing的文集:
Android源碼解析
- 備注:源碼分析部分先放一放,后續(xù)補(bǔ)充一些簡(jiǎn)要概括性的
消息機(jī)制與Handler
1. 基本概念
Android的消息機(jī)制主要包括Handler、MessageQueue和Looper。
Handler是Android中引入的一種讓開(kāi)發(fā)者參與處理線程中消息循環(huán)的機(jī)制。每個(gè)Handler都關(guān)聯(lián)了一個(gè)線程,每個(gè)線程內(nèi)部都維護(hù)了一個(gè)消息隊(duì)列MessageQueue,這樣Handler實(shí)際上也就關(guān)聯(lián)了一個(gè)消息隊(duì)列。可以通過(guò)Handler將Message和Runnable對(duì)象發(fā)送到該Handler所關(guān)聯(lián)線程的MessageQueue(消息隊(duì)列)中,然后該消息隊(duì)列一直在循環(huán)拿出一個(gè)Message,對(duì)其進(jìn)行處理,處理完之后拿出下一個(gè)Message,繼續(xù)進(jìn)行處理,周而復(fù)始。
2. 為什么要有消息機(jī)制
Android的UI控件不是線程安全的,如果在多線程中訪問(wèn)UI控件則會(huì)導(dǎo)致不可預(yù)期的狀態(tài)。那為什么不對(duì)UI控件訪問(wèn)加鎖呢?
訪問(wèn)加鎖缺點(diǎn)有兩個(gè):
那我們不用線程來(lái)操作不就行了嗎?但這是不可能的,因?yàn)锳ndroid的主線程不能執(zhí)行耗時(shí)操作,否則會(huì)出現(xiàn)ANR。
所以,從各方面來(lái)說(shuō),Android消息機(jī)制是為了解決在子線程中無(wú)法訪問(wèn)UI的矛盾。
3. Handler的工作原理
如圖所示,在主線程ActivityThread中的main方法入口中,先是創(chuàng)建了系統(tǒng)的Handler(H),創(chuàng)建主線程的Looper,將Looper與主線程綁定,調(diào)用了Looper的loop方法之后開(kāi)啟整個(gè)應(yīng)用程序的主循環(huán)。Looper里面有一個(gè)消息隊(duì)列,通過(guò)Handler發(fā)送消息到消息隊(duì)列里面,然后通過(guò)Looper不斷去循環(huán)取出消息,交給Handler去處理。通過(guò)系統(tǒng)的Handler,或者說(shuō)Android的消息處理機(jī)制就確保了整個(gè)Android系統(tǒng)有條不紊地運(yùn)作,這是Android系統(tǒng)里面的一個(gè)比較重要的機(jī)制。
我們的APP也可以創(chuàng)建自己的Handler,可以是在主線程里面創(chuàng)建,也可以在子線程里面創(chuàng)建,但是需要手動(dòng)創(chuàng)建子線程的Looper并且手動(dòng)啟動(dòng)消息循環(huán)。
4. Handler的內(nèi)存泄漏問(wèn)題
原因
非靜態(tài)內(nèi)部類持有外部類的匿名引用,導(dǎo)致Activity無(wú)法釋放(生命周期不一致)
解決方案
- Handler內(nèi)部持有外部Activity的弱引用
- Handler改為靜態(tài)內(nèi)部類
- 在適當(dāng)時(shí)機(jī)移除Handler的所有Callback()
5. 為什么在子線程中創(chuàng)建Handler會(huì)拋異常?
Handler的工作是依賴于Looper的,而Looper(與消息隊(duì)列)又是屬于某一個(gè)線程(ThreadLocal是線程內(nèi)部的數(shù)據(jù)存儲(chǔ)類,通過(guò)它可以在指定線程中存儲(chǔ)數(shù)據(jù),其他線程則無(wú)法獲取到),其他線程不能訪問(wèn)。
因此Handler就是間接跟線程是綁定在一起了。因此要使用Handler必須要保證Handler所創(chuàng)建的線程中有Looper對(duì)象并且啟動(dòng)循環(huán)。因?yàn)樽泳€程中默認(rèn)是沒(méi)有Looper的,所以會(huì)報(bào)錯(cuò)。
正確的在子線程中創(chuàng)建Handler的方法如下(可以使用HandlerThread代替):
handler = null;new Thread(new Runnable() {private Looper mLooper;@Overridepublic void run() {//必須調(diào)用Looper的prepare方法為當(dāng)前線程創(chuàng)建一個(gè)Looper對(duì)象,然后啟動(dòng)循環(huán)//prepare方法中實(shí)質(zhì)是給ThreadLocal對(duì)象創(chuàng)建了一個(gè)Looper對(duì)象//如果當(dāng)前線程已經(jīng)創(chuàng)建過(guò)Looper對(duì)象了,那么會(huì)報(bào)錯(cuò)Looper.prepare();handler = new Handler();//獲取Looper對(duì)象mLooper = Looper.myLooper();//啟動(dòng)消息循環(huán)Looper.loop();//在適當(dāng)?shù)臅r(shí)候退出Looper的消息循環(huán),防止內(nèi)存泄漏mLooper.quit();}}).start();注意:
- 主線程中默認(rèn)是創(chuàng)建了Looper并且啟動(dòng)了消息的循環(huán)的,因此不會(huì)報(bào)錯(cuò)。
- 應(yīng)用程序的入口是ActivityThread的main方法,在這個(gè)方法里面會(huì)創(chuàng)建Looper,并且執(zhí)行Looper的loop方法來(lái)啟動(dòng)消息的循環(huán),使得應(yīng)用程序一直運(yùn)行。
- 有時(shí)候出于業(yè)務(wù)需要,主線程可以向子線程發(fā)送消息。子線程的Handler必須按照上述方法創(chuàng)建,并且關(guān)聯(lián)Looper。
6. 為什么不能在子線程更新UI?
UI更新的時(shí)候,會(huì)對(duì)當(dāng)前線程進(jìn)行檢驗(yàn),如果不是主線程,則拋出異常:
void checkThread() {if (mThread != Thread.currentThread()) {throw new CalledFromWrongThreadException("Only the original thread that created a view hierarchy can touch its views.");} }比較特殊的三種情況:
- 在Activity創(chuàng)建完成后(Activity的onResume之前ViewRootImpl實(shí)例沒(méi)有建立),mThread被賦值為主線程(ViewRootImpl),所以直接在onCreate中創(chuàng)建子線程是可以更新UI的
- 在子線程中添加 Window,并且創(chuàng)建 ViewRootImpl,可以在子線程中更新view
- SurfaceView可以在其他線程更新
7. 參考文章
Android 源碼分析之旅3.1–消息機(jī)制源碼分析
android消息機(jī)制原理詳解
Android中Handler的使用
AsyncTask
1. AsyncTask的基本概念與基本工作原理
它本質(zhì)上就是一個(gè)封裝了線程池和Handler的異步框架。
AsyncTask執(zhí)行任務(wù)時(shí),內(nèi)部會(huì)創(chuàng)建一個(gè)進(jìn)程作用域的線程池來(lái)管理要運(yùn)行的任務(wù),也就是說(shuō)當(dāng)你調(diào)用了AsyncTask.execute()后,AsyncTask會(huì)把任務(wù)交給線程池,由線程池來(lái)管理創(chuàng)建Thread和運(yùn)行Thread。
2. AsyncTask使用方法
三個(gè)參數(shù)
- Params:表示后臺(tái)任務(wù)執(zhí)行時(shí)的參數(shù)類型,該參數(shù)會(huì)傳給AysncTask的doInBackground()方法
- Progress:表示后臺(tái)任務(wù)的執(zhí)行進(jìn)度的參數(shù)類型,該參數(shù)會(huì)作為onProgressUpdate()方法的參數(shù)
- Result:表示后臺(tái)任務(wù)的返回結(jié)果的參數(shù)類型,該參數(shù)會(huì)作為onPostExecute()方法的參數(shù)
五個(gè)方法
- onPreExecute():異步任務(wù)開(kāi)啟之前回調(diào),在主線程中執(zhí)行
- doInBackground():執(zhí)行異步任務(wù),在線程池中執(zhí)行
- onProgressUpdate():當(dāng)doInBackground中調(diào)用publishProgress時(shí)回調(diào),在主線程中執(zhí)行
- onPostExecute():在異步任務(wù)執(zhí)行之后回調(diào),在主線程中執(zhí)行
- onCancelled():在異步任務(wù)被取消時(shí)回調(diào)
3. AsyncTask的版本差異
內(nèi)部的線程池的版本差異
串行、并行的版本差異
4. AsyncTask的缺陷
內(nèi)存泄漏問(wèn)題
原因
非靜態(tài)內(nèi)部類持有外部類的匿名引用,導(dǎo)致Activity無(wú)法釋放(生命周期不一致,與Handler一樣)
解決方案
- AsyncTask內(nèi)部持有外部Activity的弱引用
- AsyncTask改為靜態(tài)內(nèi)部類
- 在Activity銷毀之前,調(diào)用AsyncTask.cancel()取消AsyncTask的運(yùn)行,以此來(lái)保證程序的穩(wěn)定
結(jié)果丟失問(wèn)題
原因
在屏幕旋轉(zhuǎn)、Activity在內(nèi)存緊張時(shí)被回收等造成Activity重新創(chuàng)建時(shí)AsyncTask數(shù)據(jù)丟失的問(wèn)題。當(dāng)Activity銷毀并重新創(chuàng)建后,還在運(yùn)行的AsyncTask會(huì)持有一個(gè)Activity的非法引用即之前的Activity實(shí)例。導(dǎo)致onPostExecute()沒(méi)有任何作用(一般是對(duì)UI更新無(wú)效)。
解決方案
5. 參考文章
AsyncTask 使用和缺陷
HandlerThread
1. HandlerThread產(chǎn)生背景
重點(diǎn)(防止線程多次創(chuàng)建、銷毀):當(dāng)系統(tǒng)有多個(gè)耗時(shí)任務(wù)需要執(zhí)行時(shí),每個(gè)任務(wù)都會(huì)開(kāi)啟一個(gè)新線程去執(zhí)行耗時(shí)任務(wù),這樣會(huì)導(dǎo)致系統(tǒng)多次創(chuàng)建和銷毀線程,從而影響性能。為了解決這一問(wèn)題,Google提供了HandlerThread,HandlerThread是在線程中創(chuàng)建一個(gè)Looper循環(huán)器,讓Looper輪詢消息隊(duì)列,當(dāng)有耗時(shí)任務(wù)進(jìn)入隊(duì)列時(shí),則不需要開(kāi)啟新線程,在原有的線程中執(zhí)行耗時(shí)任務(wù)即可,否則線程阻塞。
HandlerThread集Thread和Handler之所長(zhǎng),適用于會(huì)長(zhǎng)時(shí)間在后臺(tái)運(yùn)行,并且間隔時(shí)間內(nèi)(或適當(dāng)情況下)會(huì)調(diào)用的情況,比如上面所說(shuō)的實(shí)時(shí)更新。
2. HandlerThread的特點(diǎn)
-
HandlerThread本質(zhì)上是一個(gè)線程,繼承自Thread,與線程池不同,HandlerThread是一個(gè)串行隊(duì)列,背后只有一個(gè)線程
-
HandlerThread有自己的Looper對(duì)象,可以進(jìn)行Looper循環(huán),可以創(chuàng)建Handler
public class HandlerThread extends Thread {Looper mLooper;private @Nullable Handler mHandler; } -
HandlerThread可以在Handler的handleMessage中執(zhí)行異步方法,異步不會(huì)堵塞,減少對(duì)性能的消耗
-
HandlerThread缺點(diǎn)是不能同時(shí)繼續(xù)進(jìn)行多任務(wù)處理,需要等待進(jìn)行處理,處理效率較低
IntentService
1. IntentService是什么
- 重點(diǎn)(本質(zhì)上也是為了節(jié)省資源)
- IntentService是繼承自Service并處理異步請(qǐng)求的一個(gè)類,其內(nèi)部采用HandlerThread和Handler實(shí)現(xiàn)的,在IntentService內(nèi)有一個(gè)工作線程來(lái)處理耗時(shí)操作,其優(yōu)先級(jí)比普通Service高
- 當(dāng)任務(wù)完成后,IntentService會(huì)自動(dòng)停止,而不需要手動(dòng)調(diào)用stopSelf()
- 可以多次啟動(dòng)IntentService,每個(gè)耗時(shí)操作都會(huì)以工作隊(duì)列的方式在IntentService中onHandlerIntent()回調(diào)方法中執(zhí)行,并且每次只會(huì)執(zhí)行一個(gè)工作線程
2. IntentService使用方法
3. IntentService工作原理
-
IntentService繼承自Service,內(nèi)部有一個(gè)HandlerThread對(duì)象
-
在onCreate的時(shí)候會(huì)創(chuàng)建一個(gè)HandlerThread對(duì)象,并啟動(dòng)線程
-
緊接著創(chuàng)建ServiceHandler對(duì)象,ServiceHandler繼承自Handler,用來(lái)處理消息。ServiceHandler將獲取HandlerThread的Looper就可以開(kāi)始正常工作了
@Overridepublic void onCreate() {super.onCreate();HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");thread.start();mServiceLooper = thread.getLooper();mServiceHandler = new ServiceHandler(mServiceLooper);} -
每啟動(dòng)一次onStart方法,就會(huì)把數(shù)消息和數(shù)據(jù)發(fā)給mServiceHandler,相當(dāng)于發(fā)送了一次Message消息給HandlerThread的消息隊(duì)列。
@Overridepublic void onStart(@Nullable Intent intent, int startId) {Message msg = mServiceHandler.obtainMessage();msg.arg1 = startId;msg.obj = intent;mServiceHandler.sendMessage(msg);} -
mServiceHandler會(huì)把數(shù)據(jù)傳給onHandleIntent方法,onHandleIntent是個(gè)抽象方法,需要在IntentService實(shí)現(xiàn),所以每次onStart方法之后都會(huì)調(diào)用我們自己寫的onHandleIntent方法去處理。處理完畢使用stopSelf通知HandlerThread已經(jīng)處理完畢,HandlerThread繼續(xù)觀察消息隊(duì)列,如果還有未執(zhí)行玩的message則繼續(xù)執(zhí)行,否則結(jié)束。
private final class ServiceHandler extends Handler {public ServiceHandler(Looper looper) {super(looper);}@Overridepublic void handleMessage(Message msg) {onHandleIntent((Intent)msg.obj);stopSelf(msg.arg1);}}
Android項(xiàng)目構(gòu)建過(guò)程
下圖展示了從一個(gè)Android項(xiàng)目構(gòu)建出一個(gè)帶有簽名、對(duì)齊操作的APK包的完整過(guò)程(省略了代碼混淆過(guò)程、NDK的編譯過(guò)程):
下面是具體描述:
詳細(xì)版本如下:
代碼混淆
1. 代碼混淆及其優(yōu)點(diǎn)
代碼混淆的過(guò)程
混淆其實(shí)是包括了代碼壓縮、代碼混淆以及資源壓縮等的優(yōu)化過(guò)程。
這四個(gè)流程默認(rèn)開(kāi)啟,在Android項(xiàng)目中我們可以選擇將“優(yōu)化”和“預(yù)校驗(yàn)”關(guān)閉:
代碼混淆的優(yōu)點(diǎn)
代碼混淆的優(yōu)點(diǎn)如下:
- ProGuard混淆流程將檢測(cè)主項(xiàng)目以及依賴庫(kù)中未被使用的類、類成員、方法、屬性并移除,這有助于規(guī)避64K方法數(shù)的瓶頸
- 將類、類成員、方法重命名為無(wú)意義的簡(jiǎn)短名稱,增加了逆向工程的難度(由于Java是一門跨平臺(tái)的解釋性語(yǔ)言,其源代碼被編譯成class字節(jié)碼來(lái)適應(yīng)其他平臺(tái),而class文件包含了Java源代碼信息,很容易被反編譯)
- 移除未被使用的資源,可以有效減小apk安裝包大小
2. 代碼混淆操作、調(diào)試步驟
開(kāi)啟混淆、開(kāi)啟資源壓縮
android {buildTypes {release {minifyEnabled trueshrinkResources trueproguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'}} }在proguard-rules.pro添加自定義混淆規(guī)則
-
第三方庫(kù)所需的混淆規(guī)則。正規(guī)的第三方庫(kù)一般都會(huì)在接入文檔中寫好所需混淆規(guī)則,使用時(shí)注意添加。
-
在運(yùn)行時(shí)動(dòng)態(tài)改變的代碼,例如反射。比較典型的例子就是會(huì)與 json 相互轉(zhuǎn)換的實(shí)體類。假如項(xiàng)目命名規(guī)范要求實(shí)體類都要放在model包下的話,可以添加類似這樣的代碼把所有實(shí)體類都保持住:
-keep public class **.*Model*.** {*;} -
JNI中調(diào)用的類。
-
WebView中JavaScript調(diào)用的方法
-
Layout布局使用的View構(gòu)造函數(shù)、android:onClick等。
檢查混淆結(jié)果,避免因混淆引入的bug。一方面,需要從代碼層面檢查。使用上文的配置進(jìn)行混淆打包后在 /build/outputs/mapping/release/ 目錄下會(huì)輸出以下文件:
- dump.txt:描述APK文件中所有類的內(nèi)部結(jié)構(gòu)
- mapping.txt:提供混淆前后類、方法、類成員等的對(duì)照表
- seeds.txt:列出沒(méi)有被混淆的類和成員
- usage.txt:列出被移除的代碼
gi
開(kāi)啟代碼混淆后的調(diào)試
retrace.bat|retrace.sh [-verbose] mapping.txt [<stacktrace_file>]3. 代碼混淆的工作原理
ProGuard過(guò)程中將無(wú)用的字段或方法存入到EntryPoint中,將非EntryPoint的字段和方法進(jìn)行替換,其中Entry Point是在ProGuard過(guò)程中不會(huì)被處理的類或方法。詳細(xì)描述如下:
4. 參考文章
寫給Android開(kāi)發(fā)者的混淆使用手冊(cè)
持續(xù)集成
1. 持續(xù)集成的基本概念
- 持續(xù)集成(Continuous Integration),持續(xù)集成是一種軟件開(kāi)發(fā)實(shí)踐,通過(guò)自動(dòng)化的構(gòu)建(包括編譯、發(fā)布和自動(dòng)化測(cè)試)來(lái)驗(yàn)證,從而幫助盡快發(fā)現(xiàn)集成錯(cuò)誤。
- 持續(xù)集成一直被認(rèn)為是敏捷開(kāi)發(fā)的重要實(shí)踐之一,也是提升軟件質(zhì)量的重要手段。特別在團(tuán)隊(duì)協(xié)作開(kāi)發(fā)中,為項(xiàng)目添加持續(xù)集成還是非常有必要的,確保了任何時(shí)間、任何地點(diǎn)生成可部署的軟件。
2. Jenkins+Git+Gradle實(shí)現(xiàn)持續(xù)集成
3. 參考文章
Android Jenkins+Git+Gradle持續(xù)集成-實(shí)在太詳細(xì)
ANR
1. ANR是什么
Android中,主線程(UI線程)如果在規(guī)定時(shí)內(nèi)沒(méi)有處理完相應(yīng)工作,就會(huì)出現(xiàn)ANR(Application Not Responding),彈出頁(yè)面無(wú)響應(yīng)的對(duì)話框。
2. ANR分類
3. ANR的核心原因
- 主線程在做一些耗時(shí)的工作
- 主線程被其他線程鎖
- cpu被其他進(jìn)程占用,該進(jìn)程沒(méi)被分配到足夠的cpu資源。
4. ANR的原理
5. ANR的分析方法(主要是分析是否有死鎖、通過(guò)調(diào)用棧定位耗時(shí)操作、系統(tǒng)資源情況)
從/data/anr/traces.txt中找到ANR反生的信息:可以從log中搜索“ANR in”或“am_anr”,會(huì)找到ANR發(fā)生的log,該行會(huì)包含了ANR的時(shí)間、進(jìn)程、是何種ANR等信息,如果是BroadcastReceiver的ANR可以懷疑BroadCastReceiver.onReceive()的問(wèn)題,如果的Service或Provider就懷疑是否其onCreate()的問(wèn)題。
在該條log之后會(huì)有CPU usage的信息,表明了CPU在ANR前后的用量(log會(huì)表明截取ANR的時(shí)間),從各種CPU Usage信息中大概可以分析如下幾點(diǎn):
- 如果某些進(jìn)程的CPU占用百分比較高,幾乎占用了所有CPU資源,而發(fā)生ANR的進(jìn)程CPU占用為0%或非常低,則認(rèn)為CPU資源被占用,進(jìn)程沒(méi)有被分配足夠的資源,從而發(fā)生了ANR。這種情況多數(shù)可以認(rèn)為是系統(tǒng)狀態(tài)的問(wèn)題,并不是由本應(yīng)用造成的。
- 如果發(fā)生ANR的進(jìn)程CPU占用較高,如到了80%或90%以上,則可以懷疑應(yīng)用內(nèi)一些代碼不合理消耗掉了CPU資源,如出現(xiàn)了死循環(huán)或者后臺(tái)有許多線程執(zhí)行任務(wù)等等原因,這就要結(jié)合trace和ANR前后的log進(jìn)一步分析了。
- 如果CPU總用量不高,該進(jìn)程和其他進(jìn)程的占用過(guò)高,這有一定概率是由于某些主線程的操作就是耗時(shí)過(guò)長(zhǎng),或者是由于主進(jìn)程被鎖造成的。
除了上述的情況1以外,分析CPU usage之后,確定問(wèn)題需要我們進(jìn)一步分析trace文件。trace文件記錄了發(fā)生ANR前后該進(jìn)程的各個(gè)線程的stack。對(duì)我們分析ANR問(wèn)題最有價(jià)值的就是其中主線程的stack,一般主線程的trace可能有如下幾種情況:
- 主線程是running或者native而對(duì)應(yīng)的棧對(duì)應(yīng)了我們應(yīng)用中的函數(shù),則很有可能就是執(zhí)行該函數(shù)時(shí)候發(fā)生了超時(shí)。
- 主線程被block:非常明顯的線程被鎖,這時(shí)候可以看是被哪個(gè)線程鎖了,可以考慮優(yōu)化代碼。如果是死鎖問(wèn)題,就更需要及時(shí)解決了。
- 由于抓trace的時(shí)刻很有可能耗時(shí)操作已經(jīng)執(zhí)行完了(ANR -> 耗時(shí)操作執(zhí)行完畢 ->系統(tǒng)抓trace)。
6. 如何避免ANR的方法(常見(jiàn)場(chǎng)景)
主線程避免執(zhí)行耗時(shí)操作(文件操作、IO操作、數(shù)據(jù)庫(kù)操作、網(wǎng)絡(luò)訪問(wèn)等):
Activity、Service(默認(rèn)情況下)的所有生命周期回調(diào)
BroadcastReceiver的onReceive()回調(diào)方法
AsyncTask的回調(diào)除了doInBackground,其他都是在主線程中
沒(méi)有使用子線程Looper的Handler的handlerMessage,post(Runnable)都是執(zhí)行在主線程中
盡量避免主線程的被鎖的情況,在一些同步的操作主線程有可能被鎖,需要等待其他線程釋放相應(yīng)鎖才能繼續(xù)執(zhí)行,這樣會(huì)有一定的死鎖、從而ANR的風(fēng)險(xiǎn)。對(duì)于這種情況有時(shí)也可以用異步線程來(lái)執(zhí)行相應(yīng)的邏輯。
7. 參考文章
Android ANR問(wèn)題總結(jié)
內(nèi)存管理
1. 內(nèi)存管理的兩大核心與目標(biāo)
內(nèi)存管理的兩大核心
- 內(nèi)存分配機(jī)制
- 內(nèi)存回收機(jī)制
內(nèi)存管理的目標(biāo)
- 更少的占用內(nèi)存,讓更多的進(jìn)程存活在內(nèi)存當(dāng)中
- 在合適的時(shí)候,合理的釋放系統(tǒng)資源,保證新的進(jìn)程能夠被創(chuàng)建合分配內(nèi)存(注意這里沒(méi)有說(shuō)是立即釋放,因?yàn)轭l繁的創(chuàng)建釋放會(huì)造成內(nèi)存抖動(dòng))
- 在系統(tǒng)內(nèi)存緊張的時(shí)候,能釋放掉大部分不重要的資源
- 能合理的在特殊生命周期中,保存或還原重要數(shù)據(jù)
2. Android中內(nèi)存管理機(jī)制的特點(diǎn)
-
Android系統(tǒng)是基于Linux 2.6內(nèi)核開(kāi)發(fā)的開(kāi)源操作系統(tǒng),而linux系統(tǒng)的內(nèi)存管理有其獨(dú)特的動(dòng)態(tài)存儲(chǔ)管理機(jī)制。
-
Android系統(tǒng)對(duì)Linux的內(nèi)存管理機(jī)制進(jìn)行了優(yōu)化。
-
Android分配機(jī)制上面的優(yōu)化:
- Android會(huì)為每個(gè)進(jìn)程分配一個(gè)初始內(nèi)存大小heapstartsiz(初始分配小內(nèi)存,使得系統(tǒng)運(yùn)行更多的進(jìn)程)
- 當(dāng)應(yīng)用需要大內(nèi)存的時(shí)候,繼續(xù)分配更多的內(nèi)存,最大限制為heapgrowthlimit,否則觸發(fā)OOM
-
Android內(nèi)存回收機(jī)制上面的優(yōu)化:
- Linux系統(tǒng)會(huì)在進(jìn)程活動(dòng)停止后就結(jié)束該進(jìn)程;Android把這些進(jìn)程都保留在內(nèi)存中,直到系統(tǒng)需要更多內(nèi)存為止。這些保留在內(nèi)存中的進(jìn)程通常情況下不會(huì)影響整體系統(tǒng)的運(yùn)行速度,并且當(dāng)用戶再次激活這些進(jìn)程時(shí),提升了進(jìn)程的啟動(dòng)速度。
- Android會(huì)根據(jù)進(jìn)程的內(nèi)存占用、進(jìn)程優(yōu)先級(jí)等方面,采用LRU算法進(jìn)行回收
3. 常見(jiàn)的內(nèi)存問(wèn)題的相關(guān)概念
- 內(nèi)存溢出:指程序在申請(qǐng)內(nèi)存時(shí),沒(méi)有足夠的空間供其使用
- 內(nèi)存泄漏:指程序分配出去的內(nèi)存不再使用,無(wú)法進(jìn)行回收
- 內(nèi)存抖動(dòng):指程序短時(shí)間內(nèi)大量創(chuàng)建對(duì)象,然后回收的現(xiàn)象
4. 參考文章
Android內(nèi)存管理機(jī)制
淺談Android內(nèi)存管理
內(nèi)存泄漏
1. 什么是內(nèi)存泄漏
內(nèi)存泄漏是一個(gè)對(duì)象已經(jīng)不需要再使用了,但是因?yàn)槠渌膶?duì)象持有該對(duì)象的引用,導(dǎo)致它的內(nèi)存不能被垃圾回收器回收。內(nèi)存泄漏的慢慢積累,最終會(huì)導(dǎo)致OOM的發(fā)生。
2. 內(nèi)存泄漏的主要原因
長(zhǎng)生命周期的對(duì)象持有短生命周期對(duì)象的引用就很可能發(fā)生內(nèi)存泄漏。(短生命周期的對(duì)象不能被正確回收)
3. Java內(nèi)存分配策略
- 靜態(tài)存儲(chǔ)區(qū)(方法區(qū)):主要存儲(chǔ)全局變量和靜態(tài)變量,在整個(gè)程序運(yùn)行期間都存在
- 棧區(qū):方法體的局部變量會(huì)在棧區(qū)創(chuàng)建空間,并在方法執(zhí)行結(jié)束后會(huì)自動(dòng)釋放變量的空間和內(nèi)存
- 堆區(qū):保存動(dòng)態(tài)產(chǎn)生的數(shù)據(jù),如:new出來(lái)的對(duì)象和數(shù)組,在不使用的時(shí)候由Java回收器自動(dòng)回收
4. 常見(jiàn)的內(nèi)存泄漏及其解決方案
- 單例造成的內(nèi)存泄漏:在單例中,使用context.getApplicationContext()作為單例的context
- 匿名內(nèi)部類造成的內(nèi)存泄漏:由于非靜態(tài)內(nèi)部類持有匿名外部類的引用,必須將內(nèi)部類設(shè)置為static、或者使用弱引用
- Handler造成的內(nèi)存泄漏:使用static的Handler內(nèi)部類,同時(shí)在實(shí)現(xiàn)內(nèi)部類中持有Context的弱引用
- 避免使用static變量:由于static變量會(huì)跟Activity生命周期一致,當(dāng)Activity退出后臺(tái)被后臺(tái)回收時(shí),static變量是不安全,所以也要管理好static變量的生命周期
- 資源未關(guān)閉造成的內(nèi)存泄漏:比如Socket、Broadcast、Cursor、Bitmap、ListView、集合容器等,使用完后要關(guān)閉
- AsyncTask造成的內(nèi)存泄漏:由于非靜態(tài)內(nèi)部類持有匿名內(nèi)部類的引用而造成內(nèi)存泄漏,可以通過(guò)AsyncTask內(nèi)部持有外部Activity的弱引用同時(shí)改為靜態(tài)內(nèi)部類或在onDestroy()中執(zhí)行AsyncTask.cancel()進(jìn)行修復(fù)
- WebView造成的內(nèi)存泄漏:頁(yè)面銷毀的時(shí)候WebView需要正確移除并且調(diào)用其destroy方法
5. LeakCanary檢測(cè)內(nèi)存泄漏核心原理
6. 參考文章
常見(jiàn)的內(nèi)存泄漏原因及解決方法
用 LeakCanary 檢測(cè)內(nèi)存泄漏
InputMethodManager.mLastSrvView memory leak in Android6.0 with huawei mobile phone #572
內(nèi)存溢出
1. 內(nèi)存溢出是什么?
OOM指Out of memory(內(nèi)存溢出),當(dāng)前占用內(nèi)存 + 我們申請(qǐng)的內(nèi)存資源 超過(guò)了虛擬機(jī)的最大內(nèi)存限制就會(huì)拋出Out of memory異常。
2. 應(yīng)用的內(nèi)存限制與申請(qǐng)大內(nèi)存
-
Android虛擬機(jī)對(duì)單個(gè)應(yīng)用的最大內(nèi)存分配值定義在/system/build.prop文件中
//堆分配的初始大小,它會(huì)影響到整個(gè)系統(tǒng)對(duì)RAM的使用程度,和第一次使用應(yīng)用時(shí)的流暢程度。它值越小,系統(tǒng)ram消耗越慢,但一些較大應(yīng)用一開(kāi)始不夠用,需要調(diào)用gc和堆調(diào)整策略,導(dǎo)致應(yīng)用反應(yīng)較慢。它值越大,這個(gè)值越大系統(tǒng)ram消耗越快,但是應(yīng)用更流暢。 dalvik.vm.heapstartsize=xxxm //單個(gè)應(yīng)用可用最大內(nèi)存。最大內(nèi)存限制主要針對(duì)的是這個(gè)值,它表示單個(gè)進(jìn)程內(nèi)存被限定在xxxm,即程序運(yùn)行過(guò)程中實(shí)際只能使用xxxm內(nèi)存,超出就會(huì)報(bào)OOM。(僅僅針對(duì)dalvik堆,不包括native堆) dalvik.vm.heapgrowthlimit=xxxm //單個(gè)進(jìn)程可用的最大內(nèi)存,但如果存在heapgrowthlimit參數(shù),則以heapgrowthlimit為準(zhǔn)。heapsize表示不受控情況下的極限堆,表示單個(gè)虛擬機(jī)或單個(gè)進(jìn)程可用的最大內(nèi)存。 dalvik.vm.heapsize=xxxm ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); int memoryClass = am.getMemoryClass(); -
每開(kāi)一個(gè)應(yīng)用就會(huì)打開(kāi)一個(gè)獨(dú)立的虛擬機(jī)(這樣設(shè)計(jì)就會(huì)在單個(gè)程序崩潰的情況下不會(huì)導(dǎo)致整個(gè)系統(tǒng)的崩潰)
-
在android開(kāi)發(fā)中,如果要使用大堆,需要在manifest中指定android:largeHeap為true,這樣dvm heap最大可達(dá)heapsize。盡量少使用large heap。使用額外的內(nèi)存會(huì)影響系統(tǒng)整體的用戶體驗(yàn),并且會(huì)使得GC的每次運(yùn)行時(shí)間更長(zhǎng)。在任務(wù)切換時(shí),系統(tǒng)的性能會(huì)變得大打折扣。
3. 內(nèi)存優(yōu)化-常見(jiàn)內(nèi)存溢出及其解決方案
- 解決應(yīng)用中的內(nèi)存泄漏問(wèn)題。
- 圖片:正確縮放、壓縮、解碼、回收。
- 在UI不可見(jiàn)的時(shí)候,適當(dāng)釋放其UI資源。
- 列表控件:重用convertView、使用LRU緩存算法、滾動(dòng)的時(shí)候進(jìn)行監(jiān)聽(tīng),此時(shí)不應(yīng)該加載圖片。
- View:避免在onDraw方法中創(chuàng)建對(duì)象。
- 謹(jǐn)慎使用多進(jìn)程:使用多進(jìn)程可以把應(yīng)用中的部分組件運(yùn)行在單獨(dú)的進(jìn)程當(dāng)中,這樣可以擴(kuò)大應(yīng)用的內(nèi)存占用范圍,但是這個(gè)技術(shù)必須謹(jǐn)慎使用,使用多進(jìn)程會(huì)使得代碼邏輯更加復(fù)雜,使用不當(dāng)可能反而會(huì)導(dǎo)致顯著增加內(nèi)存。當(dāng)應(yīng)用需要運(yùn)行一個(gè)常駐后臺(tái)的任務(wù),而且這個(gè)任務(wù)并不輕量,可以考慮使用這個(gè)技術(shù)。
- 使用更加輕量的數(shù)據(jù)結(jié)構(gòu)。例如,我們可以考慮使用ArrayMap/SparseArray而不是HashMap等傳統(tǒng)數(shù)據(jù)結(jié)構(gòu)。
- 避免在Android里面使用Enum,而用靜態(tài)常量代替。(因?yàn)槊杜e的內(nèi)存消耗是靜態(tài)常量的兩倍左右)
- 字符串拼接:在有些時(shí)候,代碼中會(huì)需要使用到大量的字符串拼接的操作,這種時(shí)候有必要考慮使用StringBuilder來(lái)替代頻繁的“+”。
- Try catch某些大內(nèi)存分配的操作。
- 資源文件需要選擇合適的文件夾進(jìn)行存放。hdpi/xhdpi/xxhdpi等等不同dpi的文件夾下的圖片在不同的設(shè)備上會(huì)經(jīng)過(guò)scale的處理,拉伸之后內(nèi)存消耗更大。對(duì)于不希望被拉伸的圖片,需要放到assets或者nodpi的目錄下。
- 在onLowMemory()與onTrimMemory()中適當(dāng)釋放內(nèi)存。
- 珍惜Services資源。如果你的應(yīng)用需要在后臺(tái)使用service,除非它被觸發(fā)并執(zhí)行一個(gè)任務(wù),否則其他時(shí)候Service都應(yīng)該是停止?fàn)顟B(tài)。另外需要注意當(dāng)這個(gè)service完成任務(wù)之后因?yàn)橥V箂ervice失敗而引起的內(nèi)存泄漏。建議使用IntentService。
- 優(yōu)化布局層次,減少內(nèi)存消耗。越扁平化的視圖布局,占用的內(nèi)存就越少,效率越高。
- 謹(jǐn)慎使用依賴注入框架:使用之后,代碼是簡(jiǎn)化了不少。然而,那些注入框架會(huì)通過(guò)掃描你的代碼執(zhí)行許多初始化的操作,這會(huì)導(dǎo)致你的代碼需要大量的內(nèi)存空間來(lái)mapping代碼,而且mapped pages會(huì)長(zhǎng)時(shí)間的被保留在內(nèi)存中。
- 使用ProGuard來(lái)剔除不需要的代碼,通過(guò)移除不需要的代碼,重命名類,域與方法等等對(duì)代碼進(jìn)行壓縮,優(yōu)化與混淆。使得代碼更加緊湊,能夠減少mapping代碼所需要的內(nèi)存空間。
- 謹(jǐn)慎使用第三方libraries。很多開(kāi)源的library代碼都不是為移動(dòng)網(wǎng)絡(luò)環(huán)境而編寫的,如果運(yùn)用在移動(dòng)設(shè)備上,并不一定適合。即使是針對(duì)Android而設(shè)計(jì)的library,也需要特別謹(jǐn)慎,特別是如果你知道引入的library具體做了什么事情的時(shí)候。
- 考慮不同的實(shí)現(xiàn)方式、方案、策略來(lái)優(yōu)化內(nèi)存占用。
4. 參考文章
Android 性能優(yōu)化(內(nèi)存之OOM)
Android 查看每個(gè)應(yīng)用的最大可用內(nèi)存
Lint與代碼優(yōu)化工具
1. 什么是Lint?
Android Lint是一個(gè)靜態(tài)代碼分析工具,能夠?qū)z測(cè)代碼質(zhì)量——對(duì)項(xiàng)目中潛在的Bug、可優(yōu)化的代碼、安全性、性能、可用性、可訪問(wèn)性、國(guó)際化等進(jìn)行檢查(注:Lint檢測(cè)不局限于代碼,功能十分強(qiáng)大)。
通過(guò)下面的gradle命令開(kāi)啟Lint:
./gradlew lint2. Lint的工作流程
-
App項(xiàng)目源文件:包括Java代碼,XML代碼,圖標(biāo),以及ProGuard配置文件、Git忽略文件等
-
lint.xml:Lint 檢測(cè)的執(zhí)行標(biāo)準(zhǔn)配置文件,我們可以修改它來(lái)允許或者禁止報(bào)告一些問(wèn)題
-
Lint工具按照標(biāo)準(zhǔn)配置文件中指定的規(guī)則去檢測(cè)App項(xiàng)目源文件,發(fā)現(xiàn)問(wèn)題,并且進(jìn)行報(bào)告。常見(jiàn)的問(wèn)題有:
- Correctness:不夠完美的編碼,比如硬編碼、使用過(guò)時(shí)API等
- Performance:對(duì)性能有影響的編碼,比如:靜態(tài)引用,循環(huán)引用等
- Internationalization:國(guó)際化,直接使用漢字,沒(méi)有使用資源引用等,適配國(guó)際化的時(shí)候資源漏翻譯
- Security:不安全的編碼,比如在WebView中允許使用JavaScriptInterface等
- …
3. 忽略Lint警告
- Java代碼中忽略Lint警告:使用注解。注解跟@SuppressWarnings很類似,@SuppressLint(“忽略的警告名稱”),如果不清楚警告名稱,直接寫all表示忽略所有警告
- XML代碼中忽略Lint警告:使用 tools:ignore=”忽略的警告名”
4. Debug構(gòu)建中關(guān)閉Lint檢測(cè)
執(zhí)行Gradle命令的時(shí)候,通過(guò)-x參數(shù)不執(zhí)行某個(gè)action
./gradlew build -x lint5. 自定義Lint
-
創(chuàng)建lint.xml到根目錄下,可以自定義Lint安全等級(jí)、忽略文件等
-
自定義Lint檢查規(guī)則(比如日志不通過(guò)LogUtils打印則警告):
- 依賴Android官方的lint的庫(kù)
- 創(chuàng)建類繼承Detector,實(shí)現(xiàn)一些規(guī)則
- 并且提供IssueRegistry向外提供Detector注冊(cè)匯總信息
- 輸出jar包或者aar包,主項(xiàng)目進(jìn)行依賴
- 進(jìn)行l(wèi)int檢測(cè)即可
6. 一些其他的代碼優(yōu)化工具
- KW(Klockwork)掃描工具(這個(gè)工具需要授權(quán)才能使用,屬于動(dòng)態(tài)檢測(cè),依賴項(xiàng)目編譯生成的文件。目前還是存在一些BUG,比如空指針的檢測(cè)、IO流的關(guān)閉檢測(cè)等)
- 阿里巴巴的編碼規(guī)約掃描工具(IDEA的一個(gè)插件)
- Uber的NullAway空指針掃描工具(空指針的檢測(cè)工具,接入比較麻煩)
- ……
7. 參考文章
Android 性能優(yōu)化:使用 Lint 優(yōu)化代碼、去除多余資源
Android工具:被你忽視的Lint
自動(dòng)規(guī)避代碼陷阱——自定義Lint規(guī)則
Android Lint
美團(tuán)點(diǎn)評(píng)技術(shù)團(tuán)隊(duì)-Android自定義Lint實(shí)踐2——改進(jìn)原生Detector
美團(tuán)自定義Lint示例
Writing a Lint Check
Idea 阿里代碼規(guī)約插件安裝
使用Klockwork進(jìn)行代碼分析簡(jiǎn)單操作流程
NullAway:Uber用于檢測(cè)Android上的NullPointerExceptions的開(kāi)源工具
UI卡頓優(yōu)化
1. UI卡頓原理
View的繪制幀數(shù)保持60fps是最佳,這要求每幀的繪制時(shí)間不超過(guò)16ms(1000/60),如果安卓不能在16ms內(nèi)完成界面的渲染,那么就會(huì)出現(xiàn)卡頓現(xiàn)象。而UI的繪制在主線程中進(jìn)行的,因此UI卡頓本質(zhì)上就是主線程卡頓。
2. UI卡頓常見(jiàn)原因及其解決方案
-
布局Layout過(guò)于復(fù)雜,無(wú)法在16ms內(nèi)完成渲染。
-
過(guò)度繪制overDraw,導(dǎo)致像素在同一幀的時(shí)間內(nèi)被繪制多次,使CPU和GPU負(fù)載過(guò)重。
-
View頻繁的觸發(fā)measure、layout,導(dǎo)致measure、layout累計(jì)耗時(shí)過(guò)多和整個(gè)View頻繁的重新渲染。
布局優(yōu)化
- 通過(guò)開(kāi)發(fā)者工具檢查過(guò)度繪制
- 使用include復(fù)用布局、使用ViewStub延遲加載布局、使用merge減少代碼層級(jí)、使用RelativeLayout也能大大減少視圖的層級(jí)、慎重設(shè)置整體背景顏色防止過(guò)度繪制
- 使用自定義View取代復(fù)雜的View
-
使用TraceView工具檢測(cè)UI卡頓、方法耗時(shí)
-
在UI線程中做輕微的耗時(shí)操作,導(dǎo)致UI線程卡頓:應(yīng)該把耗時(shí)操作放在子線程中進(jìn)行。
-
同一時(shí)間動(dòng)畫(huà)執(zhí)行的次數(shù)過(guò)多,導(dǎo)致CPU和GPU負(fù)載過(guò)重。
-
頻繁的觸發(fā)GC操作導(dǎo)致線程暫停、內(nèi)存抖動(dòng),會(huì)使得安卓系統(tǒng)在16ms內(nèi)無(wú)法完成繪制:可以考慮使用享元模式、避免在onDraw方法中創(chuàng)建對(duì)象等。
-
冗余資源及邏輯等導(dǎo)致加載和執(zhí)行緩慢。
-
UI卡頓最嚴(yán)重的后果是ANR,因此需要在開(kāi)發(fā)中避免和解決ANR問(wèn)題。
-
列表控件滑動(dòng)卡頓:復(fù)用convertView、滑動(dòng)不進(jìn)行加載、使用壓縮圖片、加載縮略圖等。
3. BlockCanary及其原理
BlockCanary會(huì)在發(fā)生卡頓的時(shí)候記錄各種信息,輸出到配置目錄下的文件,并彈出消息欄通知,輕松找出Android App界面卡頓元兇。
BlockCanary的核心原理是:通過(guò)Android的消息機(jī)制在mainLooperPrinter中判斷start和end,來(lái)獲取主線程dispatch該message的開(kāi)始和結(jié)束時(shí)間,并判定該時(shí)間超過(guò)閾值(如2000毫秒)為主線程卡慢發(fā)生,并dump出各種信息,提供開(kāi)發(fā)者分析性能瓶頸。
核心代碼如下:
Looper.getMainLooper().setMessageLogging(mainLooperPrinter);@Override public void println(String x) {if (!mStartedPrinting) {mStartTimeMillis = System.currentTimeMillis();mStartThreadTimeMillis = SystemClock.currentThreadTimeMillis();mStartedPrinting = true;} else {final long endTime = System.currentTimeMillis();mStartedPrinting = false;if (isBlock(endTime)) {notifyBlockEvent(endTime);}} }private boolean isBlock(long endTime) {return endTime - mStartTimeMillis > mBlockThresholdMillis; }4. 參考文章
BlockCanary — 輕松找出Android App界面卡頓元兇
冷啟動(dòng)與熱啟動(dòng)
1. 冷啟動(dòng)與熱啟動(dòng)是什么?
-
冷啟動(dòng):當(dāng)啟動(dòng)應(yīng)用時(shí),后臺(tái)沒(méi)有該應(yīng)用的進(jìn)程,這時(shí)系統(tǒng)會(huì)重新創(chuàng)建一個(gè)新的進(jìn)程分配給該應(yīng)用,這個(gè)啟動(dòng)方式就是冷啟動(dòng)。
冷啟動(dòng)大致流程:實(shí)例化Application -> 實(shí)例化入口Activity -> 顯示Activity(配置主題中背景等屬性 -> 顯示測(cè)量布局繪制最終顯示在界面上)
-
熱啟動(dòng):當(dāng)啟動(dòng)應(yīng)用時(shí),后臺(tái)已有該應(yīng)用的進(jìn)程,所以在已有進(jìn)程的情況下,這種啟動(dòng)會(huì)從已有的進(jìn)程中來(lái)啟動(dòng)應(yīng)用,這個(gè)方式叫熱啟動(dòng)。(例:按back鍵、home鍵,應(yīng)用雖然會(huì)退出,但是該應(yīng)用的進(jìn)程是依然會(huì)保留在后臺(tái),可進(jìn)入任務(wù)列表查看)
熱啟動(dòng)大致流程(不需要實(shí)例化Application):實(shí)例化入口Activity -> 顯示Activity
2. 應(yīng)用啟動(dòng)時(shí)間測(cè)量
-
使用命令
adb shell am start -W [packageName]/[packageName.XXXActivity] -
使用Activity.reportFullyDrawn在Logcat中打印出來(lái),例子:
ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms -
使用TraceView精確測(cè)量(TraceView工具可以檢測(cè)UI卡頓、方法耗時(shí))
// start tracing to "/sdcard/calc.trace" Debug.startMethodTracing("calc"); // ... // stop tracing Debug.stopMethodTracing(); -
使用高速攝像機(jī)進(jìn)行抓幀
3. 冷啟動(dòng)優(yōu)化常見(jiàn)方案
- 通過(guò)懶加載方式初始化第三方SDK,可以嘗試在閃屏Activity中加載
- 不要在mainThread中加載資源
- 減少第一個(gè)界面onCreate()方法的工作量
- 不要讓Application參與業(yè)務(wù)的操作、耗時(shí)操作
- 不要以靜態(tài)變量的方式在Application中保存數(shù)據(jù)
- 減少布局的復(fù)雜性和深度
4. 解決冷啟動(dòng)白/黑屏問(wèn)題的兩種方案
-
設(shè)置要啟動(dòng)的Activity主題為透明,可以給用戶造成一個(gè)視覺(jué)上的假象,例如:
<style name="AppTransparentTheme" parent="Theme.AppCompat.Light.NoActionBar.Fullscreen"><item name="android:windowIsTranslucent">true</item><item name="android:windowBackground">@android:color/transparent</item> </style> -
為要啟動(dòng)的Activity設(shè)置主題為一張背景圖,例如:
<style name="AppBackgroundTheme" parent="Theme.AppCompat.Light.NoActionBar.Fullscreen"><item name="android:windowBackground">@mipmap/bg_welcome</item> </style>
5. 參考文章
Android Study 之冷啟動(dòng)優(yōu)化(解決啟動(dòng)短暫白屏or黑屏)
Android冷啟動(dòng)實(shí)現(xiàn)APP秒開(kāi)
Android 性能優(yōu)化 冷啟動(dòng)速度優(yōu)化
APK瘦身
1. APK文件的組成
直接在Android Studio中打開(kāi)APK文件,通過(guò)APK分析器,可以看到APK文件的組成成分與比例(實(shí)際上是調(diào)用AAPT工具的功能):
- asserts:存放一些配置文件或資源文件,比如WebView的本地html,React Native的jsbundle等
- lib:lib目錄下會(huì)有各種so文件,分析器會(huì)檢查出項(xiàng)目自己的so和各種庫(kù)的so。
- resources.arsc:編譯后的二進(jìn)制資源文件,里面是id-name-value的一個(gè)Map。
- res:res目錄存放的是資源文件。包括圖片、字符串。raw文件夾下面是音頻文件,各種xml文件等等。
- dex:dex文件是Java代碼打包后的字節(jié)碼,一個(gè)dex文件最多只支持65536個(gè)方法,開(kāi)啟了dex分包的話會(huì)有多個(gè)。
- META-INF:META-INF目錄下存放的是簽名信息,分別是MANIFEST.MF、CERT.SF、CERT.RSA。用來(lái)保證apk包的完整性和系統(tǒng)的安全性,幫助用戶避免安裝來(lái)歷不明的盜版APK。
- AndroidManifest.xml:Android清單文件。
2. 常見(jiàn)APK瘦身方案
-
優(yōu)化assets
- 資源動(dòng)態(tài)下載,字體、js代碼這樣的資源能動(dòng)態(tài)下載的就做動(dòng)態(tài)下載,雖然復(fù)雜度提高,但是實(shí)現(xiàn)了動(dòng)態(tài)更新
- 壓縮資源文件,用到的時(shí)候再進(jìn)行解壓
- 刪除不必要的字體文件中的字體
- 減少圖標(biāo)字體(Icon-Font)的使用,多用SVG代替
-
優(yōu)化lib
-
配置abiFilters精簡(jiǎn)so動(dòng)態(tài)庫(kù),而已根據(jù)需求保留需要的平臺(tái)
defaultConfig {//armeabi是必須包含的,v7是一個(gè)圖形加強(qiáng)版本,x86是英特爾平臺(tái)的支持庫(kù)ndk {abiFilters "armeabi", "armeabi-v7a" ,"x86"} } -
統(tǒng)計(jì)分析用戶手機(jī)的cpu類型,排除沒(méi)有或少量用戶才會(huì)用到的so
-
-
優(yōu)化resources.arsc
- 刪除不必要的string entry,你可以借助android-arscblamer來(lái)檢查出可以優(yōu)化的部分,比如一些空的引用
- 使用微信的資源混淆工具AndResGuard,它將資源的名稱進(jìn)行了混淆(需要重點(diǎn)配置白名單)
-
優(yōu)化META-INF:除了公鑰CERT.RSA沒(méi)有壓縮機(jī)會(huì)外,其余的兩個(gè)文件都可以通過(guò)混淆資源名稱的方式進(jìn)行壓縮
-
優(yōu)化res
-
動(dòng)態(tài)下載資源
-
通過(guò)Android Studio的重構(gòu)工具刪除無(wú)用資源
-
打包時(shí)剔除無(wú)用資源
release {zipAlignEnabled trueminifyEnabled true shrinkResources true // 是否去除無(wú)效的資源文件 proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'signingConfig signingConfigs.release} -
刪除無(wú)用的語(yǔ)言(排除了所有的依賴庫(kù)的資源)
android {//...defaultConfig {resConfigs "zh"} } -
控制圖片、視頻、音頻資源的大小,推薦使用有損壓縮等其他格式(ogg、svg、webp等)
-
統(tǒng)一應(yīng)用風(fēng)格,減少shape文件
-
使用toolbar,減少menu文件
-
通過(guò)復(fù)用等方式減少layout文件
-
…
-
-
優(yōu)化dex:
- 利用工具(dexcount、statistic、apk-method-count、Lint)分析、精簡(jiǎn)不必要的方法、空行、依賴庫(kù)等
- 通過(guò)proguard來(lái)刪除無(wú)用代碼
- 剔除無(wú)用的測(cè)試代碼
- 依賴第三方庫(kù)的時(shí)候,在打包發(fā)版中不要將某些不必要的庫(kù)進(jìn)行releaseCompile(比如LeakCanary)
- 使用更小庫(kù)或合并現(xiàn)有庫(kù)
- 減少方法數(shù)、使用插件化等方案,不用mulitdex
3. 參考文章
App瘦身最佳實(shí)踐
Android中5種app瘦身方式
進(jìn)程及進(jìn)程?;?、跨進(jìn)程通信
1. 查看進(jìn)程信息
使用ps命令可以查看進(jìn)程信息:
adb shell ps|grep <package_name>返回的結(jié)果分別為:
- 進(jìn)程當(dāng)前用戶
- 進(jìn)程ID
- 進(jìn)程的父進(jìn)程ID
- 進(jìn)程的虛擬內(nèi)存大小
- 實(shí)際駐留”在內(nèi)存中”的內(nèi)存大小
- 進(jìn)程名
2. Android進(jìn)程優(yōu)先級(jí)
-
前臺(tái)進(jìn)程:Foreground process(用戶正在使用的程序,一般系統(tǒng)是不會(huì)殺死前臺(tái)進(jìn)程的,除非用戶強(qiáng)制停止應(yīng)用或者系統(tǒng)內(nèi)存不足等極端情況會(huì)殺死。)
- 用戶正在交互的Activity(已調(diào)用onResume)
- 當(dāng)某個(gè)Service綁定正在交互的Activity
- 被主動(dòng)調(diào)用為前臺(tái)Service(startForeground())
- 組件正在執(zhí)行生命周期的回調(diào)(onCreate()、onStart()、onDestory())
- BroadcastReceiver正在執(zhí)行onReceive()
-
可見(jiàn)進(jìn)程:Visible process(用戶正在使用,看得到,但是摸不著,沒(méi)有覆蓋到整個(gè)屏幕,只有屏幕的一部分可見(jiàn)進(jìn)程不包含任何前臺(tái)組件,一般系統(tǒng)也是不會(huì)殺死可見(jiàn)進(jìn)程的,除非要在資源吃緊的情況下,要保持某個(gè)或多個(gè)前臺(tái)進(jìn)程存活)
- Activity不在前臺(tái)、但仍對(duì)用戶可見(jiàn)(已調(diào)用onPause(),沒(méi)有調(diào)用onStop()))
- 綁定到可見(jiàn)(前臺(tái))Activity的Service
-
服務(wù)進(jìn)程:Service process(在內(nèi)存不足以維持所有前臺(tái)進(jìn)程和可見(jiàn)進(jìn)程同時(shí)運(yùn)行的情況下,服務(wù)進(jìn)程會(huì)被殺死)
- 簡(jiǎn)單的startService()啟動(dòng),與用戶看見(jiàn)的Activity沒(méi)有直接關(guān)聯(lián)。
-
后臺(tái)進(jìn)程:Background process(系統(tǒng)可能隨時(shí)終止它們,回收內(nèi)存)
- 在用戶按了"back"或者"home"后,程序本身看不到了,但是其實(shí)還在運(yùn)行的程序。對(duì)用戶沒(méi)有直接影響,Activity處于onStop()的時(shí)候。
- 應(yīng)用開(kāi)啟的進(jìn)程:android:process=":xxx"
-
空進(jìn)程:Empty process(某個(gè)進(jìn)程不包含任何活躍的組件時(shí)該進(jìn)程就會(huì)被置為空進(jìn)程,完全沒(méi)用,優(yōu)先級(jí)最低,殺了它只有好處沒(méi)壞處,第一被回收)
- 不含有任何的活動(dòng)的組件。(Android設(shè)計(jì)的,處于緩存的目的,為了第二次啟動(dòng)更快,采取的一個(gè)權(quán)衡)
3. 進(jìn)程回收策略(Low memory Killer)
Low memory Killer:定時(shí)執(zhí)行,一旦發(fā)現(xiàn)內(nèi)存低于某個(gè)內(nèi)存閾值,Low memory Killer會(huì)根據(jù)進(jìn)程的優(yōu)先級(jí)、進(jìn)程占用的內(nèi)存大小等因素通過(guò)復(fù)雜的評(píng)分機(jī)制,對(duì)進(jìn)程進(jìn)行打分,然后將分?jǐn)?shù)高的進(jìn)程判定為bad進(jìn)程,殺死并釋放內(nèi)存
4. 進(jìn)程?;罘桨?/h4>
- 與手機(jī)廠商合作,加入白名單
- 監(jiān)聽(tīng)鎖屏廣播,在RemoteService中開(kāi)啟/關(guān)閉一個(gè)像素的Activity
- 利用Android5.0以下系統(tǒng)同一時(shí)間只能殺死一個(gè)進(jìn)程的漏洞(5.0之后同一個(gè)Group的進(jìn)程都會(huì)被殺死),開(kāi)啟雙進(jìn)程守護(hù)
- 利用前臺(tái)服務(wù),startForeground(ID, new Notification()),發(fā)送空的通知
- 利用系統(tǒng)黏性服務(wù)機(jī)制拉活
- 利用開(kāi)機(jī),網(wǎng)絡(luò)切換、拍照、拍視頻等系統(tǒng)廣播也能喚醒,不過(guò)Android N已經(jīng)將這三種廣播取消了
- 利用Native進(jìn)程監(jiān)聽(tīng)進(jìn)程是否存活,否則拉活
- 利用JobScheduler機(jī)制代替Native進(jìn)程實(shí)現(xiàn)拉活
- 利用賬號(hào)同步機(jī)制拉活。用戶強(qiáng)制停止都?xì)⒉黄饎?chuàng)建一個(gè)賬號(hào)并設(shè)置同步器,創(chuàng)建周期同步,系統(tǒng)會(huì)自動(dòng)調(diào)用同步器,這樣就能激活A(yù)PP,局限是國(guó)產(chǎn)機(jī)會(huì)修改最短同步周期,并且需要聯(lián)網(wǎng)才能使用。
5. 跨進(jìn)程通信方式
6. 參考文章
Android進(jìn)程?;畹囊话闾茁?/p>
關(guān)于進(jìn)程?;畹膬扇隆率稚?jí)經(jīng)驗(yàn)卡
Android里帳戶同步的實(shí)現(xiàn)
Bitmap
1. Bitmap的理解
Bitmap是Android系統(tǒng)中的圖像處理的最重要類之一。用它可以獲取圖像文件信息,進(jìn)行圖像剪切、旋轉(zhuǎn)、縮放等操作,并可以指定格式保存圖像文件。
2. Bitmap的內(nèi)存分配策略
在Androin3.0之前的版本,Bitmap像素?cái)?shù)據(jù)存放在Native內(nèi)存中,而且Nativie內(nèi)存的釋放是不確定的,容易內(nèi)存溢出而Crash,不使用的圖片要調(diào)用recycle()進(jìn)行回收。
從Androin3.0開(kāi)始,Bitmap像素?cái)?shù)據(jù)和Bitmap對(duì)象一起存放在虛擬機(jī)的堆內(nèi)存中(從源代碼上看是多了一個(gè)byte[] buffer用來(lái)存放數(shù)據(jù)),也就是我們常說(shuō)的Java Heap內(nèi)存。
從Androin8.0開(kāi)始,Bitmap像素?cái)?shù)據(jù)存重新回到Native內(nèi)存中
3. Bitmap的內(nèi)存占用計(jì)算
Bitmap的內(nèi)存占用大小的計(jì)算:
-
一般情況下的計(jì)算
Bitmap占用的內(nèi)存 = width * height * 一個(gè)像素所占的內(nèi)存
-
在Android中,考慮各種因素情況下的計(jì)算
Bitmap占用的內(nèi)存 = width * height * nTargetDensity/inDensity * nTargetDensity/inDensity * 一個(gè)像素所占的內(nèi)存
Bitmap的內(nèi)存占用大小與三個(gè)因素有關(guān):
- 色彩格式,前面我們已經(jīng)提到,如果是ARGB_8888那么就是一個(gè)像素4個(gè)字節(jié),如果是RGB_565那就是2個(gè)字節(jié)
- 原始文件存放的資源目錄(分辨率越小,內(nèi)存占用越小)
- 目標(biāo)屏幕的密度(屏幕的密度越小,內(nèi)存占用越小)
有關(guān)Bitmap的色彩格式:
- ARGB_8888的內(nèi)存消耗是RGB_565的2倍
- ARGB_8888格式比RGB_565多了一個(gè)透明通道
- 如果使用RGB_565格式解析ARGB_8888格式的圖片(png),可能會(huì)導(dǎo)致圖片變綠
4. Bitmap的回收
- 在Android3.0以前以及Android8.0之后Bitmap的像素?cái)?shù)據(jù)是存放Native內(nèi)存中,我們需要回收Native層和Java層的內(nèi)存。
- 在Android3.0以后以及Android8.0之前Bitmap的像素?cái)?shù)據(jù)是存放在Java層的內(nèi)存中的,我們只要回收堆內(nèi)存即可。
- 官方建議我們3.0以后使用recycle方法進(jìn)行回收,該方法也可以不主動(dòng)調(diào)用,因?yàn)槔厥掌鲿?huì)自動(dòng)收集不可用的Bitmap對(duì)象進(jìn)行回收。
- recycle方法會(huì)判斷Bitmap在不可用的情況下,將發(fā)送指令到垃圾回收器,讓其回收native層和Java層的內(nèi)存,則Bitmap進(jìn)入dead狀態(tài)。
- recycle方法是不可逆的,如果再次調(diào)用getPixels()等方法,則獲取不到想要的結(jié)果。
5. Bitmap的復(fù)用
Android在3.0之后BitmapFactory.Options引入了inBitmap屬性,設(shè)置該屬性之后解碼圖片時(shí)會(huì)嘗試復(fù)用一張已經(jīng)存在的Bitmap,避免了內(nèi)存的回收以及重新申請(qǐng)的過(guò)程。
Bitmap復(fù)用的限制:
- 聲明可被復(fù)用的Bitmap必須設(shè)置inMutable為true
- Android4.4(API 19)之前只有格式為jpg、png,同等寬高(要求苛刻),inSampleSize為1的Bitmap才可以復(fù)用
- Android4.4(API 19)之前被復(fù)用的Bitmap的inPreferredConfig會(huì)覆蓋待分配內(nèi)存的Bitmap設(shè)置的inPreferredConfig
- Android4.4(API 19)之前待加載Bitmap的Options.inSampleSize必須明確指定為1
- Android4.4(API 19)之后被復(fù)用的Bitmap的內(nèi)存必須大于需要申請(qǐng)內(nèi)存的Bitmap的內(nèi)存
6. Bitmap加載大圖與防止OOM
加載大圖的時(shí)候注意點(diǎn):
- 在Android系統(tǒng)中,讀取位圖Bitmap時(shí),分給虛擬機(jī)中的圖片的堆棧大小只有8M,如果超出了,就會(huì)出現(xiàn)OutOfMemory異常
- 在加載大圖、長(zhǎng)圖等操作當(dāng)中,推薦對(duì)OutOfMemoryError進(jìn)行捕獲,并且返回一張默認(rèn)圖片
- 使用采樣率(inSampleSize),如果需要顯示縮列圖,并不需要加載完整的圖片數(shù)據(jù),只需要按一定的比例加載即可
- 使用Matrix變形等,比如使用Matrix進(jìn)行放大,雖然圖像大了,但并沒(méi)有占用更多的內(nèi)存
- 推薦使用一些成熟的開(kāi)源圖片加載庫(kù),它們幫我們完成了很多工作。比如異步加載、Facebook的Fresco還自己開(kāi)辟了Native內(nèi)存用于存儲(chǔ)圖片,以得到更大的內(nèi)存空間(兼容性問(wèn)題)
- 使用分塊解碼(BitmapRegionDecoder)、硬解碼等方案
獲取圖片縮略圖的模板代碼如下(主要分為3個(gè)步驟):
public static Bitmap thumbnail(String path, int width, int height, boolean autoRotate) {//1. 獲得Bitmap的寬高,但是不加載到內(nèi)存BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds = true;BitmapFactory.decodeFile(path, options);int srcWidth = options.outWidth;int srcHeight = options.outHeight;//2. 計(jì)算圖片縮放倍數(shù)int inSampleSize = 1;if (srcHeight > height || srcWidth > width) {if (srcWidth > srcHeight) {inSampleSize = Math.round(srcHeight / height);} else {inSampleSize = Math.round(srcWidth / width);}}//3. 真正加載圖片到內(nèi)存當(dāng)中options.inJustDecodeBounds = false;options.inSampleSize = inSampleSize;//ARGB_8888格式的圖片,每像素占用 4 Byte,而 RGB565則是 2 Byteoptions.inPreferredConfig = Bitmap.Config.RGB_565;options.inPurgeable = true;options.inInputShareable = true;return BitmapFactory.decodeFile(path, options); }7. LRU緩存機(jī)制
LRU緩存機(jī)制的核心原理:
- LruCache中維護(hù)了一個(gè)以訪問(wèn)順序排序的集合LinkedHashMap(雙向循環(huán)鏈表)
- 新數(shù)據(jù)插入到鏈表頭部
- 每當(dāng)緩存命中(即緩存數(shù)據(jù)被訪問(wèn)),則將數(shù)據(jù)移到鏈表頭部
- 當(dāng)鏈表滿的時(shí)候,將鏈表尾部的數(shù)據(jù)丟棄
8. 圖片的三級(jí)緩存
關(guān)于Android圖片的多級(jí)緩存,其中主要的就是內(nèi)存緩存和硬盤緩存。它的核心思想是:
- 在獲取一張圖片時(shí),首先到內(nèi)存緩存(LruCache)中去加載
- 如果未加載到,則到硬盤緩存(DiskLruCache)中加載,如果加載到將其返回并添加進(jìn)內(nèi)存緩存
- 否則通過(guò)網(wǎng)絡(luò)加載一張新的圖片,并將新加載的圖片添加進(jìn)入內(nèi)存緩存和硬盤緩存
9. 參考文章
Bitmap詳解與Bitmap的內(nèi)存優(yōu)化
Android性能調(diào)優(yōu)(5)—Bitmap內(nèi)存模型
Android面試一天一題(Day 22: 圖片到底是什么)
Android使用BitmapRegionDecoder加載超大圖片方案
Android性能調(diào)優(yōu)(6)—Bitmap優(yōu)化
徹底解析Android緩存機(jī)制——LruCache
淺談圖片加載的三級(jí)緩存
數(shù)據(jù)存儲(chǔ)方式與數(shù)據(jù)緩存
1. Android中5大數(shù)據(jù)存儲(chǔ)方式
- 文件:存儲(chǔ)一些簡(jiǎn)單的文本數(shù)據(jù)或者二進(jìn)制數(shù)據(jù),包括內(nèi)存、外部存儲(chǔ)。
- SharedPreferences(本質(zhì)上屬于文件存儲(chǔ)):適用于存儲(chǔ)一些鍵值對(duì),一般用來(lái)存儲(chǔ)配置信息。
- SQLite數(shù)據(jù)庫(kù):適用于存儲(chǔ)一些復(fù)雜的關(guān)系型數(shù)據(jù)。
- 網(wǎng)絡(luò):存儲(chǔ)比較重要的數(shù)據(jù),比如賬號(hào)密碼等。
- ContentProvider:訪問(wèn)(增刪改查)其他應(yīng)用程序中私有數(shù)據(jù)。
2. 有關(guān)數(shù)據(jù)安全問(wèn)題
- 避免使用靜態(tài)變量保存核心、重要的數(shù)據(jù)。因?yàn)殪o態(tài)變量在進(jìn)程被殺死的時(shí)候,會(huì)重新初始化
- 官方推薦使用五大數(shù)據(jù)存儲(chǔ)方式
3. 數(shù)據(jù)緩存
數(shù)據(jù)緩存
- 推薦使用LRU策略緩存
- 內(nèi)存緩存:存儲(chǔ)在集合當(dāng)中,重要數(shù)據(jù)不推薦使用靜態(tài)變量存儲(chǔ)
- 磁盤緩存:根據(jù)數(shù)據(jù)類型、應(yīng)用場(chǎng)合選擇Android中5大數(shù)據(jù)存儲(chǔ)方式實(shí)現(xiàn)緩存
- 使用一些實(shí)現(xiàn)了內(nèi)存緩存、磁盤緩存、緩存時(shí)間等強(qiáng)大功能的第三方開(kāi)源的緩存庫(kù):ACache、RxCache等
緩存統(tǒng)計(jì)顯示與緩存清理
- 緩存統(tǒng)計(jì)與顯示:統(tǒng)計(jì)/data/你的應(yīng)用包名/cache/目錄的大小
- 緩存清理:刪除/data/你的應(yīng)用包名/cache/目錄
4. 參考文章
安卓中五種數(shù)據(jù)存儲(chǔ)方式
【Android開(kāi)源項(xiàng)目分析】android輕量級(jí)開(kāi)源緩存框架——ASimpleCache(ACache)源碼分析
【從 0 開(kāi)始開(kāi)發(fā)一款直播 APP】6 緩存 ACache 源碼解析
Android記錄20-獲取緩存大小和清除緩存功能
SharedPreferences
1. SharedPreferences基本概念以及優(yōu)缺點(diǎn)
- SharedPreferences用來(lái)保存基于XML文件存儲(chǔ)的key-value鍵值對(duì)數(shù)據(jù),通常用來(lái)存儲(chǔ)一些簡(jiǎn)單的配置信息。數(shù)據(jù)存儲(chǔ)在/data/data//shared_prefs目錄下。
- 只能存儲(chǔ)少量boolean、int、float、long、String五種簡(jiǎn)單的數(shù)據(jù)類型,操作簡(jiǎn)單,但是無(wú)法完全替代其他數(shù)據(jù)存儲(chǔ)方式。
- 無(wú)法進(jìn)行條件查詢等復(fù)雜操作,對(duì)于數(shù)據(jù)的處理只能是簡(jiǎn)單的處理。
2. SharedPreferences的實(shí)現(xiàn)原理
SharedPreferences是個(gè)單例,具體實(shí)現(xiàn)在SharedPrefencesImpl。任意Context拿到的都是同一個(gè)實(shí)例。
SharedPreferences在實(shí)例化的時(shí)候會(huì)把SharedPreferences對(duì)應(yīng)的xml文件內(nèi)容通過(guò)pull解析全部讀取到內(nèi)存當(dāng)中(mMap)。
關(guān)于讀操作:對(duì)于非多進(jìn)程兼容的SharedPreferences的讀操作是從內(nèi)存讀取的,不涉及IO操作。寫入的時(shí)候由于內(nèi)存已經(jīng)保存了完整的xml數(shù)據(jù),然后新寫入的數(shù)據(jù)也會(huì)同步更新到內(nèi)存,所以無(wú)論是用commit還是apply都不會(huì)影響立即讀取。
關(guān)于寫操作:除非需要關(guān)心xml是否寫入文件成功,否則你應(yīng)該在所有調(diào)用commit的地方改用apply。
3. 同步與異步提交
commit方法的特點(diǎn)
- 存儲(chǔ)的過(guò)程是原子操作
- commit方法有返回值,設(shè)置成功為ture,否則為false
- 同時(shí)對(duì)一個(gè)SharedPreferences設(shè)置值最后一次的設(shè)置會(huì)直接覆蓋前次值
- 如果不關(guān)心設(shè)置成功與否,并且是在主線程設(shè)置值,建議用apply方法
apply方法的特點(diǎn)
- 存儲(chǔ)的過(guò)程也是原子操作
- apply沒(méi)有返回值,存儲(chǔ)是否成功無(wú)從知道
- apply寫入過(guò)程分兩步,第一步先同步寫入內(nèi)存,第二部在異步寫入物理磁盤
- apply寫入的過(guò)程會(huì)阻塞同一個(gè)SharedPreferences對(duì)象的其他寫入操作
總結(jié)
apply比commit效率高,commit直接是向物理介質(zhì)寫入內(nèi)容,而apply是先同步將內(nèi)容提交到內(nèi)存,然后在異步的向物理介質(zhì)寫入內(nèi)容。這樣做顯然提高了效率。
4. SharedPreferences不能實(shí)現(xiàn)跨進(jìn)程同步問(wèn)題
現(xiàn)象
- 數(shù)據(jù)安全問(wèn)題:數(shù)據(jù)讀寫不能實(shí)時(shí)更新而造成數(shù)據(jù)寫入丟失等問(wèn)題,每個(gè)進(jìn)程都會(huì)維護(hù)一個(gè)SharedPreferences的內(nèi)存副本,副本之間互不干擾
- getSharedPreferences時(shí)候的空指針問(wèn)題
解決方案
- 通過(guò)查看 API 文檔發(fā)現(xiàn),在API Level > 11即Android 3.0可以通過(guò)Context.MODE_MULTI_PROCESS屬性來(lái)實(shí)現(xiàn)多進(jìn)程間的數(shù)據(jù)共享
- 但是在API 23時(shí)該屬性被廢棄。官方文檔中明確寫明SharedPreferences不適用于多進(jìn)程間共享數(shù)據(jù),推薦使用ContentProvider等方式
5. SharedPreferences存儲(chǔ)的數(shù)據(jù)不能過(guò)大
- SharedPreferences存儲(chǔ)的基本的配置型數(shù)據(jù),不能存儲(chǔ)大量數(shù)據(jù)
- SharedPreferences的讀寫操作可能會(huì)阻塞主線程,引起界面卡頓甚至ANR
- SharedPreferences的Key-Value的mMap是一直存放在內(nèi)存當(dāng)中的,這樣會(huì)帶來(lái)極大的內(nèi)存消耗,甚至產(chǎn)生泄漏、OOM
- SharedPreferences對(duì)Key-Value頻繁讀寫會(huì)產(chǎn)生大量的臨時(shí)對(duì)象,會(huì)造成內(nèi)存抖動(dòng),頻繁GC會(huì)造成界面卡頓等問(wèn)題
6. 參考文章
Android面試一天一題(14 Day:SharedPreferences)
SharedPreferences多進(jìn)程共享數(shù)據(jù)爬坑之旅
深入理解Android SharedPreferences的commit與apply
SharedPreferences commit跟apply的區(qū)別
組件化
1. 組件化的基本概念
組件化開(kāi)發(fā)就是將一個(gè)app分成多個(gè)模塊,每個(gè)模塊都是一個(gè)組件(Module),開(kāi)發(fā)的過(guò)程中我們可以讓這些組件相互依賴或者單獨(dú)調(diào)試部分組件等,但是最終發(fā)布的時(shí)候是將這些組件合并統(tǒng)一成一個(gè)apk,這就是組件化開(kāi)發(fā)。
2. 組件化的特點(diǎn)
- 公共資源、業(yè)務(wù)、模塊混在一起耦合度太高,組件化可以實(shí)現(xiàn)模塊之間的解耦、單獨(dú)測(cè)試驗(yàn)證,又實(shí)現(xiàn)了模塊共享資源和工具類
- 組件化可以提高開(kāi)發(fā)效率:每個(gè)模塊可以獨(dú)立開(kāi)發(fā)編譯運(yùn)行
- 利用組件化的思想對(duì)開(kāi)源框架進(jìn)行一次封裝,除了防止代碼入侵以外,同時(shí)也簡(jiǎn)化了使用,實(shí)現(xiàn)了項(xiàng)目的需求
3. 組件化的實(shí)現(xiàn)
組件化只是一種項(xiàng)目架構(gòu)的思想,并沒(méi)有具體的實(shí)現(xiàn)方案,需要根據(jù)公司的業(yè)務(wù)、項(xiàng)目性質(zhì)等進(jìn)行具體實(shí)現(xiàn)。一般的套路如下:
組件化中,最好提供一個(gè)統(tǒng)一路由方案實(shí)現(xiàn)模塊之間的分發(fā)和跳轉(zhuǎn)。
4. 參考文章
Android組件化和插件化開(kāi)發(fā)
Android組件化方案
插件化
1. 插件化的基本概念
插件化開(kāi)發(fā)和組件化開(kāi)發(fā)略有不用,插件化開(kāi)發(fā)時(shí)將整個(gè)app拆分成很多模塊,這些模塊包括一個(gè)宿主和多個(gè)插件,與組件化最大的不同是:插件化中每個(gè)模塊都是一個(gè)單獨(dú)的apk(組件化的每個(gè)模塊是個(gè)lib),最終打包的時(shí)候?qū)⑺拗鱝pk和插件apk分開(kāi)或者聯(lián)合打包。
2. 插件化的特點(diǎn)
- 解決應(yīng)用越來(lái)越大所帶來(lái)的技術(shù)限制,比如65535方法數(shù)量限制
- 應(yīng)用越來(lái)越大的時(shí)候多人合作開(kāi)發(fā)的問(wèn)題:插件化可以實(shí)現(xiàn)宿主和插件分開(kāi)編譯、并行開(kāi)發(fā),提高開(kāi)發(fā)效率并且分工明確
- 插件模塊的動(dòng)態(tài)按需下載,減少宿主APK的體積
- 插件模塊的動(dòng)態(tài)更新可以解決線上BUG或者上架活動(dòng),達(dá)到熱更新、熱修復(fù)的目的
- 帶有插件化、熱更新的APK不能在Google Play上線,也就是沒(méi)有海外市場(chǎng)
3. 插件化的核心原理
- 通過(guò)類加載機(jī)制(DexClassLoader)加載插件APK
- 通過(guò)代理機(jī)制(主要是動(dòng)態(tài)代理)實(shí)現(xiàn)Activity等組件的生命周期
- 通過(guò)Java的反射機(jī)制結(jié)合面向接口(抽象)編程、面向切面程,實(shí)例化并且調(diào)用插件中的代碼
- 訪問(wèn)插件中的資源:通過(guò)反射生成AssetManager對(duì)象并且通過(guò)反射調(diào)用addAssetPath方法加載插件中的資源(資源、主題等)
- 利用Hook機(jī)制對(duì)ActivityManagerService進(jìn)行Hook,實(shí)現(xiàn)啟動(dòng)一個(gè)沒(méi)有在清單文件中注冊(cè)的插件Activity
- 不同框架的具體實(shí)現(xiàn)和原理都不一樣……
4. 常見(jiàn)插件化框架(按照時(shí)間先后排列)
AndroidDynamicLoader:Android 動(dòng)態(tài)加載框架,他不是用代理 Activity 的方式實(shí)現(xiàn)而是用 Fragment 以及 Schema 的方式實(shí)現(xiàn)
PluginMgr:不需要插件規(guī)范的apk動(dòng)態(tài)加載框架。
Dynamic-load-apk:Android 使用動(dòng)態(tài)加載框架DL進(jìn)行插件化開(kāi)發(fā)
Direct-Load-apk:Direct - load - apk 能夠加載插件的全部 資源. 支持 插件間 Activity跳轉(zhuǎn). 不像 “dynamic load - apk” 這個(gè)項(xiàng)目, “Direct - load - apk” 不需要對(duì)插件有任何約束,也不需要在插件中引入jar和繼承自定義Activity,可以直接使用this指針。
Android-Plugin-Framework:此項(xiàng)目是Android插件開(kāi)發(fā)框架完整源碼及示例。用來(lái)通過(guò)動(dòng)態(tài)加載的方式在宿主程序中運(yùn)行插件APK
ACDD:非代理Android動(dòng)態(tài)部署框架
DynamicAPK:實(shí)現(xiàn)Android App多apk插件化和動(dòng)態(tài)加載,支持資源分包和熱修復(fù).攜程App的插件化和動(dòng)態(tài)加載框架
比較新的,有代表性的有下面4個(gè):
DroidPlugin:是360手機(jī)助手在Android系統(tǒng)上實(shí)現(xiàn)了一種新的插件機(jī)制
Small:世界那么大,組件那么小。Small,做最輕巧的跨平臺(tái)插件化框架
VirtualAPK:VirtualAPK是滴滴出行自研的一款優(yōu)秀的插件化框架
RePlugin:RePlugin是一套完整的、穩(wěn)定的、適合全面使用的,占坑類插件化方案,由360手機(jī)衛(wèi)士的RePlugin Team研發(fā),也是業(yè)內(nèi)首個(gè)提出”全面插件化“(全面特性、全面兼容、全面使用)的方案
5. 參考文章
Android插件化技術(shù)入門
插件化開(kāi)發(fā)小結(jié)
Android博客周刊專題之 插件化開(kāi)發(fā)
Android開(kāi)源插件化框架匯總
熱修復(fù)
1. 熱修復(fù)主要解決的問(wèn)題
- 版本發(fā)布之后發(fā)現(xiàn)嚴(yán)重BUG,需要緊急動(dòng)態(tài)修復(fù)
- 小功能即時(shí)上線、下線,比如節(jié)日活動(dòng)
2. 傳統(tǒng)開(kāi)發(fā)流程與熱修復(fù)開(kāi)發(fā)流程對(duì)比
從流程來(lái)看,傳統(tǒng)的開(kāi)發(fā)流程存在很多弊端:
- 重新發(fā)布版本代價(jià)太大
- 用戶下載安裝成本太高
- BUG修復(fù)不及時(shí),用戶體驗(yàn)太差
而熱修復(fù)的開(kāi)發(fā)流程顯得更加靈活,優(yōu)勢(shì)很多:
- 無(wú)需重新發(fā)版,實(shí)時(shí)高效熱修復(fù)
- 用戶無(wú)感知修復(fù)(甚至無(wú)需重啟應(yīng)用),無(wú)需下載新的應(yīng)用,代價(jià)小
- 修復(fù)成功率高,把損失降到最低
3. 熱修復(fù)補(bǔ)丁修復(fù)詳細(xì)工作流程
4. 熱修復(fù)的兩大核心原理
- ClassLoader加載方案
- Native層替換方案(Hook Native)
5. 熱修復(fù)主流框架及實(shí)現(xiàn)原理
QQ空間的超級(jí)補(bǔ)丁技術(shù)
超級(jí)補(bǔ)丁技術(shù)基于DEX分包方案,使用了多DEX加載的原理,大致的過(guò)程就是:把BUG方法修復(fù)以后,放到一個(gè)單獨(dú)的DEX里,插入到dexElements數(shù)組的最前面,讓虛擬機(jī)去優(yōu)先加載修復(fù)完后的方法。
當(dāng)patch.dex中包含Test.class時(shí)就會(huì)優(yōu)先加載,在后續(xù)的DEX中遇到Test.class的話就會(huì)直接返回而不去加載,這樣就達(dá)到了修復(fù)的目的。
微信的Tinker
微信針對(duì)QQ空間超級(jí)補(bǔ)丁技術(shù)的不足提出了一個(gè)提供DEX差量包,整體替換DEX的方案。主要的原理是與QQ空間超級(jí)補(bǔ)丁技術(shù)基本相同,區(qū)別在于不再將patch.dex增加到elements數(shù)組中,而是差量的方式給出patch.dex,然后將patch.dex與應(yīng)用的classes.dex合并,然后整體替換掉舊的DEX文件,以達(dá)到修復(fù)的目的。
阿里的AndFix
AndFix不同于QQ空間超級(jí)補(bǔ)丁技術(shù)和微信Tinker通過(guò)增加或替換整個(gè)DEX的方案,提供了一種運(yùn)行時(shí)通過(guò)Hook Native方法,在Native修改Filed指針的方式,實(shí)現(xiàn)Java方法的替換,達(dá)到即時(shí)生效無(wú)需重啟,對(duì)應(yīng)用無(wú)性能消耗的目的。
美團(tuán)的Robust
主要原理:在每個(gè)方法前加入一段代碼,如果patch.jar存在,則加載patch.jar中的代碼片段,否則執(zhí)行原本的代碼片段。
餓了么的Amigo
Amigo 原理與 Tinker 基本相同,但是在 Tinker 的基礎(chǔ)上,進(jìn)一步實(shí)現(xiàn)了 so 文件、資源文件、Activity、BroadcastReceiver 的修復(fù),幾乎可以號(hào)稱全面修復(fù),不愧 Amigo(朋友)這個(gè)稱號(hào),能在危急時(shí)刻送來(lái)全面的幫助。
6. 三大主流框架對(duì)比
7. 參考文章
Android熱修復(fù)技術(shù)選型——三大流派解析
Android 插件化和熱修復(fù)知識(shí)梳理
路由
1. 什么是路由?
根據(jù)路由表將頁(yè)面請(qǐng)求分發(fā)到指定頁(yè)面。
2. 為什么需要路由?(路由的應(yīng)用場(chǎng)景)
- 解耦:APP中使用到組件化、插件化等技術(shù)的時(shí)候,需要解耦模塊、頁(yè)面之間的依賴關(guān)系
- 跳轉(zhuǎn)一致化:原生、H5頁(yè)面之間的互相跳轉(zhuǎn)規(guī)范統(tǒng)一
- 頁(yè)面動(dòng)態(tài)配置:APP收到消息推送,點(diǎn)擊通知/用戶點(diǎn)擊活動(dòng)Banner廣告等需要跳轉(zhuǎn)到某個(gè)頁(yè)面,該頁(yè)面可以動(dòng)態(tài)配置
- 頁(yè)面跳轉(zhuǎn)的攔截:比如不合法的頁(yè)面的屏蔽、登錄驗(yàn)證攔截、頁(yè)面不存在需要攔截并且跳轉(zhuǎn)到下載頁(yè)面等
3. 路由的優(yōu)勢(shì)(與不使用路由對(duì)比)
- 路由實(shí)現(xiàn)了頁(yè)面之間解耦:顯式Intent在項(xiàng)目龐大以后,類依賴耦合太大,不適合組件化拆分
- 路由簡(jiǎn)化了頁(yè)面之間的協(xié)作:隱式Intent協(xié)作困難,調(diào)用時(shí)候不知道調(diào)什么參數(shù)
- 路由的攔截功能提高了頁(yè)面訪問(wèn)的安全性:每個(gè)注冊(cè)了Scheme的Activity都可以直接打開(kāi),有安全風(fēng)險(xiǎn)
- 路由表的動(dòng)態(tài)生成簡(jiǎn)化了頁(yè)面的管理:解決了傳統(tǒng)的AndroidMainfest集中式管理,這樣比較臃腫的問(wèn)題
- 路由的動(dòng)態(tài)降級(jí)功能:不使用路由,如果頁(yè)面出錯(cuò),無(wú)法動(dòng)態(tài)降級(jí)
- 路由的動(dòng)態(tài)攔截功能:不使用路由,每次打開(kāi)新的頁(yè)面都需要做登錄驗(yàn)證
- 路由實(shí)現(xiàn)了頁(yè)面統(tǒng)一跳轉(zhuǎn):H5、Android、iOS地址不一樣,不利于統(tǒng)一相互跳轉(zhuǎn)協(xié)議、不利于服務(wù)器端(例如推送、活動(dòng)Banner等)對(duì)APP頁(yè)面進(jìn)行動(dòng)態(tài)配置
4. 路由的常見(jiàn)功能及其實(shí)現(xiàn)思路和核心原理
-
路由注冊(cè)
- AndroidManifest里面的Activity聲明scheme碼是不安全的,所有App都可以打開(kāi)這個(gè)頁(yè)面,這里就產(chǎn)生有三種方式去注冊(cè):
- 注解產(chǎn)生路由表,通過(guò)DispatchActivity轉(zhuǎn)發(fā)Intent
- AndroidManifest注冊(cè),將其export=false,再通過(guò)DispatchActivity轉(zhuǎn)發(fā)Intent,天貓就是這么做的,比上面的方法的好處是路由查找都是系統(tǒng)調(diào)用,省掉了維護(hù)路由表的過(guò)程,但是AndroidManifest配置還是比較不方便的
- 注解自動(dòng)修改AndroidManifest,這種方式可以避免路由表匯總的問(wèn)題,方案是這樣的,用自定義Lint掃描出注解相關(guān)的Activity,然后在processManifestTask后面修改AndroidManifest,該方案不穩(wěn)定
-
路由表生成
- 用APT(Annotation Processing Tool)生成URL和Activity的對(duì)應(yīng)關(guān)系、并且(結(jié)合路由分組策略)進(jìn)行路由匯總
-
路由分發(fā)
- 現(xiàn)在所有路由方案分發(fā)都是用Activity做分發(fā)的
-
結(jié)果返回
- 捕獲onActivityResult
-
動(dòng)態(tài)攔截
- 攔截器是重中之重,有了攔截器可以做好多事情。ARouter是用線程等待實(shí)現(xiàn)的(等所有注冊(cè)的攔截器處理完成之后才會(huì)進(jìn)行下一步分發(fā)跳轉(zhuǎn))
-
參數(shù)獲取
- 大部分路由庫(kù)都是手動(dòng)獲取參數(shù)的,這樣還要傳入?yún)?shù)key比較麻煩,有三種做法:
- Hook掉Instrumentation的newActivity方法,注入?yún)?shù)
- 注冊(cè)ActivityLifecycleCallbacks方法,注入?yún)?shù)
- APT生成注入代碼,onCreate的時(shí)候bind一下
5. 常見(jiàn)的路由框架對(duì)比
6. 參考文章
需要給activity跳轉(zhuǎn)增加路由么?
Android 組件化 —— 路由設(shè)計(jì)最佳實(shí)踐
Android 路由框架ARouter最佳實(shí)踐
開(kāi)源最佳實(shí)踐:Android平臺(tái)頁(yè)面路由框架ARouter
一款可能是最容易使用的對(duì)頁(yè)面、服務(wù)的路由框架。使用APT實(shí)現(xiàn)
RouterKit
Android權(quán)限機(jī)制和動(dòng)態(tài)權(quán)限適配
1. 什么是Android權(quán)限機(jī)制
- Android6.0以前:開(kāi)發(fā)者在AndroidManifest文件中聲明需要的權(quán)限,APP安裝時(shí),系統(tǒng)提示用戶APP將獲取的權(quán)限,需要用戶同意授權(quán)才能繼續(xù)安裝,從此APP便永久的獲得了授權(quán)。
- Android6.0之后:引入動(dòng)態(tài)權(quán)限的機(jī)制。在APP運(yùn)行時(shí),用戶可以根據(jù)自身的需要,決定是否授予APP危險(xiǎn)權(quán)限,同時(shí),用戶也可以很方便回收授予的權(quán)限。動(dòng)態(tài)權(quán)限管理的機(jī)制,對(duì)于用戶的隱私保護(hù)是更加適用的。
2. 危險(xiǎn)權(quán)限(組)與一般權(quán)限
- 危險(xiǎn)權(quán)限(組)Dangerous Permissions:聯(lián)系人、電話、相機(jī)、定位、存儲(chǔ)、錄音、短信、日歷、傳感器
- 一般權(quán)限Normal Permissions:除危險(xiǎn)權(quán)限之外的權(quán)限,比如網(wǎng)絡(luò)、WIFI、藍(lán)牙、鬧鐘、壁紙、NFC等
注意:
- 對(duì)于危險(xiǎn)權(quán)限的分組,如果申請(qǐng)某個(gè)危險(xiǎn)的權(quán)限,假設(shè)app早已被用戶授權(quán)了同一組的某個(gè)危險(xiǎn)權(quán)限,那么系統(tǒng)會(huì)立即授權(quán),而不需要用戶去點(diǎn)擊授權(quán)。
- 不要對(duì)權(quán)限組過(guò)多的依賴,盡可能對(duì)每個(gè)危險(xiǎn)權(quán)限都進(jìn)行正常流程的申請(qǐng),因?yàn)樵诤笃诘腁ndroid版本中這個(gè)權(quán)限組可能會(huì)產(chǎn)生變化。
3. 如何適配Android6.0動(dòng)態(tài)權(quán)限及其兼容性問(wèn)題
動(dòng)態(tài)權(quán)限適配
-
APP要適配Android6.0非常簡(jiǎn)單,只需要在清單文件中配聲明權(quán)限,然后將targetSdkVersion升級(jí)到23及以上,同時(shí)加入權(quán)限檢查、申請(qǐng)、處理申請(qǐng)結(jié)果等代碼邏輯即可。
-
相關(guān)重要API:
- 檢查權(quán)限:ContextCompat.checkSelfPermission()
- 申請(qǐng)授權(quán):ActivityCompat.requestPermissions()
- 處理回調(diào):onRequestPermissionsResult()
-
權(quán)限適配的代碼封裝,同時(shí)也可以使用一些封裝好的開(kāi)源庫(kù):MPermission、RxPermission等
動(dòng)態(tài)權(quán)限的運(yùn)行兼容性問(wèn)題
- 首先,舊版本APP(targetSdkVersion低于23),因?yàn)闆](méi)有適配權(quán)限的申請(qǐng)相關(guān)邏輯,在Android6.0以上機(jī)型運(yùn)行的時(shí)候,仍然采用安裝時(shí)授權(quán)的方案。
- 適配了Android6.0的APP,在低版本Android系統(tǒng)上運(yùn)行的時(shí)候,仍然采用安裝時(shí)授權(quán)的方案。
- 開(kāi)發(fā)者需要注意的是,權(quán)限申請(qǐng)的代碼邏輯只應(yīng)該在Android6.0及以上的機(jī)型被執(zhí)行。(因此推薦使用XXXCompat的類,這種類已經(jīng)對(duì)Android版本進(jìn)行了判斷)
4. 參考文章
Android 權(quán)限機(jī)制與適配經(jīng)驗(yàn)(QQ音樂(lè))
Android 6.0 運(yùn)行時(shí)權(quán)限處理完全解析(鴻洋)
View的繪制以及事件傳遞機(jī)制
http://hencoder.com/
動(dòng)畫(huà)機(jī)制
屏幕適配
Android新特性介紹,ConstraintLayout完全解析
設(shè)計(jì)模式與架構(gòu)
Kotlin
開(kāi)源框架源碼分析
Java高級(jí)基礎(chǔ)
大綱
Android工程師之Android面試大綱
面試復(fù)習(xí)——Android工程師之Android面試大綱
歡迎進(jìn)入Hensen_的博客目錄(全站式導(dǎo)航)
總結(jié)
以上是生活随笔為你收集整理的Android面试复习资料整理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 第13节 IIS之WEB服务器—用于发布
- 下一篇: 易燃易爆炸。你根本不爱我。