android基本功
這幾天一直在看Android一些基本知識,都比較零散,先各個知識點分析記錄總結著,等日后整理成各個模塊。也為自己以后面試做準備吧,未完待續。
一、 Android四大組件
1. Application相關
1.1 Application實例
在一個Dalvik虛擬機里只會存在一個實例,一個app可以有多個Dalvik,每個Dalvik都會存在一個Application實例,這就是多進程模式。
1.2 Application的繼承關系
Application實質上是一個Context,它繼承自ContextWrapper,而ContextWrapper繼承自Cotext,對Context的一個包裝。
1.3 Application生命周期
應用啟動時,會先調用Application.attach()方法,再調用Application.onCreate()方法
但別在此方法中進行太多耗時操作,否則會影響app啟動速度
1.4 怎么獲取Application實例
獲取Application對象可通過Context的getApplicationContext()或是通過Activity.getApplication()\Service.getApplication,三者獲取到的都是同一個Application對象。
1.5 為什么最好不要在Application對象中緩存數據
在低內存情況下,Application可能被銷毀,而Activity棧并沒有為空,這時如果要恢復的Activity用到了Application中緩存的數據,很可以報空指針異常。解決辦法,如果在Application保存數據,最好先判空,并將數據持久化保存,在使用時如果為空再從手機中讀取。
2. Activity生命周期
1) 整個的生命周期,從onCreate(Bundle)開始到onDestroy()結束。
2) 可見的生命周期,從onStart()開始到onStop()結束。在這段時間,可以看到Activity在屏幕上,盡管有可能不在前臺,不能和用戶交互。在這兩個接口之間,需要保持顯示給用戶的UI數據和資源等,例如:可以在onStart中注冊一個IntentReceiver來監聽數據變化導致UI的變動,當不再需要顯示時候,可以在onStop()中注銷它。
3) 前臺的生命周期,從onResume()開始到onPause()結束。在這段時間里,該Activity處于所有 Activity的最前面,和用戶進行交互。Activity可以經常性地在resumed和paused狀態之間切換。
4) 從界面A跳轉到界面B,生命周期變化情況:
當用戶點擊A中按鈕來到B時,假設B全部遮擋住了A,將依次執行A:onPause -> B:onCreate -> B:onStart -> B:onResume -> A:onStop。
此時如果點擊Back鍵,將依次執行B:onPause -> A:onRestart -> A:onStart -> A:onResume -> B:onStop -> B:onDestroy。
5) 對于棧最頂上的界面A,按Back鍵和按Home鍵的區別:
如果按下Back鍵,系統返回到桌面,并依次執行A:onPause -> A:onStop -> A:onDestroy。
此時如果按下Home鍵(非長按),系統返回到桌面,并依次執行A:onPause -> A:onStop。由此可見,Back鍵和Home鍵主要區別在于是否會執行onDestroy。
3. Activity的4種啟動模式
3.1 standard:默認的標準啟動模式
不管有沒有已存在的實例,都生成新的實例。即使是A startActivity A,也會重新生成一個新的實例,再回退時,A也會出現兩次;
3.2 singleTop
如果發現有對應的Activity實例正位于棧頂,則重復利用,不再生成新的實例,如A啟動A,不會生成新的實例,會走A的onNewIntent方法,而不是onCreate方法,回退時,也只會回退一次;
3.3 singleTask
所在Activity棧中有對應的Activity實例,則使此Activity實例之上的其他Activity實例統統出棧,使此Activity實例成為棧頂對象,顯示到幕前,這一般用在程序的主界面上;
3.4 singleInstance
當被啟動時,系統會首先判斷系統其他棧中是否已經存在此Activity實例,有則直接使用,并且其所在的Activity棧理論上只有它一個Activity元素。
singleInstance表示該Activity在系統范圍內“實例唯一”。ingInstance和singleTask主要區別在與系統范圍內的“實例唯一”還是當前Activity棧“實例唯一”。
4. Activity的狀態保存
4.1 onSaveInstanceState()、onRestoreInstanceState()
Activity的狀態保存onSaveInstanceState()、onRestoreInstanceState()
Activity的 onSaveInstanceState() 和onRestoreInstanceState()并不是生命周期方法,當應用遇到意外情況(如:內存不足、用戶直接按Home鍵)由系統銷毀一個Activity時,onSaveInstanceState()會被調用。但是當用戶主動去銷毀一個Activity時,例如在應用中按返回鍵,onSaveInstanceState()就不會被調用。通常onSaveInstanceState()只適合用于保存一些臨時性的狀態,而onPause()適合用于數據的持久化保存。當某個activity變得“容易”被系統銷毀時,該activity的onSaveInstanceState就會被執行,開發者可以覆寫onSaveInstanceState()方法。
4.2 如何保存數據
onSaveInstanceState()方法接受一個Bundle類型的參數, 開發者可以將狀態數據存儲到這個Bundle對象中, 這樣即使activity被系統摧毀, 當用戶重新啟動這個activity而調用它的onCreate()方法時, 上述的Bundle對象會作為實參傳遞給onCreate()方法, 開發者可以從Bundle對象中取出保存的數據, 然后利用這些數據將activity恢復到被摧毀之前的狀態。如在橫豎屏切換時,應用的Activity可能要保存edittext中的值,需要調用onSaveInstanceState()來何存輸入值。
5. Activity啟動方式
5.1 startActivityForResult會立馬執行情況
如果被啟動的與Activity在AndroidManifest.xml文件中配置的launchMode設為”singleTask”的時候,startActivityForResult執行后,onActivityResult立即會被執行到。
原因:startActivityForResult(Intent,int,Bundle)有說明:if the activity you are launching uses thesingleTask launch mode, it will not run in your task and thus you willimmediately receive a cancel result.因為Activity1用startActivityForResult啟動Activity2, 如果一個Activity2的啟動方式是singleTask時,若Activity2之前在任務棧中存在,再次啟動它時,它會把它上面所有的Activity實例都pop出棧,這樣再調setResult時,返回的是Activity2任務棧下面的界面,而不是Activity1,所以,Activity1收到的一個cancel的Result。
5.2 要求requestCode>0
startActivityForResult的requestCode值必須要大于等于0,不然,startActivityForResult就變成了 startactivity。
6. Activity的isFinish/finish()和destroy的區別
activity的finish()方法有兩個層面含義:將此Activity從Activity棧中移除;調用了此Activity的onDestroy方法。
1) finish()方法用于結束一個Activity的生命周期,但是并沒有釋放他的資源。
2) onDestory()方法則是Activity的一個生命周期方法,其作用是在一個Activity對象被銷毀之前,Android系統會調用該方法,用于釋放此Activity之前所占用的資源。
有可能程序在運行了finish()方法而沒有運行onDestory()方法,因為Activity生命周期里的方法,不確定什么時候會被調用,皆是由系統確定;
3) isFinishing方法來判斷Activity是否處于銷毀狀態。
7. Activity和Fragment的區別
一個fragment必須總是嵌入在一個activity中,同時fragment的生命周期受activity而影響,一個Activity可以運行多個 Fragment,一個fragment也可以在多個activity中作為一個模塊,fragment有自己的生命周期,接收自己的輸入事件,可以從運行中的activity添加或移除。
Activity 的后退棧由系統管理,而 Fragment 的后退棧由所在的Activity 管理。
8.Broadcast廣播相關
8.1 Broadcast的生命周期
廣播接收器僅在它執行這個方法時處于活躍狀態。當 onReceive() 返回后,它即為失活狀態。
8.2 Broadcast的兩種注冊方式
1) 靜態注冊,在AndroidManifest.xml中用標簽聲明注冊,并在標簽內用標簽設置過濾器。
2) 動態注冊:動態地在代碼中先定義并設置好一個 IntentFilter對象,然后在需要注冊的地方調 Context.registerReceiver()方法,如果取消時就調用Context.unregisterReceiver()方法。如果用動 態方式注冊的BroadcastReceiver的Context對象被銷毀時,BroadcastReceiver也就自動取消注冊了。
注意事項:若在使用sendBroadcast()的方法是指定了接收權限,則只有在AndroidManifest.xml中用標簽聲明了擁有此權限的BroascastReceiver才會有可能接收到發送來的Broadcast。
9. Service服務相關
9.1 定義
一個專門在后臺處理時間任務,沒有UI界面;
9.2 Service的生命周期
① 通過bindService啟動的Service生命周期:onCreate()->onBind()->onUnbind()->onDestory()
② 通過startService啟動的Service生命周期:onCreate()->onStartCommand()->onDestory()
9.3 Service兩種啟動方式和區別:bindService, startService
① startService只是啟動Service,與啟動的組件沒有關聯,當Service調用stopSelf或是相關組件調用stopService服務才會終止;
② bindService,和組件綁定,其他組件可通過回調獲取Service代理來和Service交互,當啟動方銷毀,Service也會調用unBinder進行解綁,只有所有綁定該Service的組件都解綁了,Service才會銷毀。
9.4 Service和Activity怎么進行通信
9.5 Service與IntentService
(1) Service:與啟動它的應用位于同一個進程中; Service還是在主線程中跑,所以不應該在Service中直接處理耗時的任務;
(2) IntentService:IntentService是Service的子類,比普通的Service增加了額外的功能。
1) 它會創建獨立的worker線程來處理所有的Intent請求;
2) 會創建獨立的worker線程來處理onHandleIntent()方法實現的代碼,無需處理多線程問題;
3) 所有請求處理完成后,IntentService會自動停止,無需調用stopSelf()方法停止Service;
4) 為Service的onBind()提供默認實現,返回null,故適合用startService來啟動;
5) 為Service的onStartCommand提供默認實現,將請求Intent添加到隊列中;
10. Fragment相關
10.1 Fragment的生命周期
10.2 Fragment之間數據傳遞方式
10.2.1 通過共同的Activity傳遞
在Activity中定義一個字段,然后添加set和get方法,在相應的Fragment中只要采用(XXXActivity(getActivity)).getXXX();方法叉可以獲取到變量。
10.2.2 使用bundle進行參數傳遞
DemoFragment demoFragment = new DemoFragment(); Bundle bundle = new Bundle(); bundle.putString("key", "這是方法二"); demoFragment.setArguments(bundle); ft.add(R.id.fragmentRoot, demoFragment, SEARCHPROJECT); ft.commit();10.2.3 采取接口回調的方式
實現步驟如下:
1) 在FragmentA中創建一個接口以及接口對應的set方法:
2) 在FragmentB中實現此接口,并在相應的地方調用setXX方法
10.2.4 使用RxBus或是EventBus
10.3 Fragment與Activity之間數據傳遞方式
10.3.1 通過Bundle傳值
10.3.2 使用RxBus或是EventBus
10.3.3 采取接口回調的方式
二、 開發中遇到的一些問題
1. app被殺死怎么啟動
1.1 場景
用戶按了home鍵,系統內存不足,導致應用被強殺;
1.2 可能引起的問題
當類中設置了static變量而在A類中初始化,在B類頁面調用,應用被強殺時,再點擊應用回到B類,由于static尚未初始化,會導致空指針,應用崩潰;
1.3 被強殺后應用變量情況
當app在后臺被強殺后,app中所有變量都被清空(包括application實例),整個app進程都被銷毀,但Activity的棧信息依然保存著,所以在回到應用時還能得知頁面的打開順序;就用被強殺時,會自動調用onSaveInstance方法保存一些核心變量。
1.4 解決辦法
1.4.1 如何判斷應用是否被強殺
可以在Application中設置一個靜態變量,并賦一個初始值,在應用的啟動頁,對Application中的靜態變量進行重新賦其他值,在其他Activity進行檢測該值,如果是Application中賦的初始值,說明應用被強殺了直接恢復該Activity,如果是啟動頁重新賦過的值,則說明應用是正常啟動的;
1.4.2 應用被強殺后該如何處理避免Crash
1) 將需要的變量,保存在手機本地一份,如SharedPerference或是數據庫/sd卡等,在Application的onCreate()中進行相應靜態變量的初始化操作,如用戶Id等信息,保證其他Activity使用的變量不會因為應用重新初化Applciation而靜態變量變為空;
2) 如果應用中有BaseActivity,可以在BaseActivity的onCreate()函數中進行判斷應用是否被強殺,如果應用被強殺,重新啟動應用,重在應用流程。
if(isForceKilled) {
startActivity(new Intent(this, SplashActivity.class));
}
3) Activity中跳轉的變量盡量采用Intent傳值,少用靜態變量;
2. 如何完全退出一個應用
2.1 Activity管理棧
做一個自定義的棧來管理相應的Activity,需要退出時再pop出棧,相應Activity finish。
2.2 BaseActivity + RxBus(EventBus)
如果采用Rxjava開發,直接采用RxBus, 也可以自己寫個廣播,所有的類都繼承自BaseActivity。
直接用RxBus發送退出應用的消息,在BaseActivty接收消息,再調用finish();所有棧內繼承于BaseActivity的Activity都會接收到事件,并調用finish結束Activity。在finish之后還可以使用System.exit(0),這樣會提示虛擬機kill掉進程。
3. 為什么service里startActivity必須添加FLAG_ACTIVITY_NEW_TASK否則拋異常,而Activity里不會?
3.1 啟動activity的兩種方式
一種是直接調用context類的startActivity方法,這種方式啟動的Activity沒有Activity棧,因此不能以standard方式啟動,必須添加上FLAG_ACTIVITY_NEW_TASK這個Flag,服務就是通過context調用;
另一種方式是調用被Activity類重載過的tartActivity方法;
3.2 兩種啟動方式為什么會不同
Activity是繼承自ContextThemeWrapper,而service是繼承自ContextWrapper,ContextWrapper里是通過調用ContextImpl的startActivity方法,它會判斷intent.getFlags有沒有還FLAG_ACTIVITY_NEW_TASK標記。而ContextThemeWrapper調用startActivity方法不會去檢查標記。最后它們都通過Instrumentation輔助類的ActivityManagerNative這個方法。
三、 Android自定義View
1. 事件分發機制
1.1 事件傳遞
ViewGroup接收到事件后進行事件分派(dispatchTouchEvent),如果自己需要處理事件則攔截(interceptTouchEvent返回true),調用自己的onTouchEvent進行事件處理;不處理(interceptTouchEvent返回false)則傳給子view進行處理,再由子view進行分派(dispatchTouchEvent),處理/攔截;
1.2 事件處理
子View的onTouchEvent進行事件處理,若返回true,則消耗事件,不再繼續傳遞;返回false則不處理,把這個事件往上一級的viewGroup進行傳遞,由上一級進行處理。
1.3 view的dispatchTouchEvent
1) view之所有dispatchTouchEvent方法,因為view可以注冊很多事件監聽器,如onClick, onLongClick, onTouch, view自身的onTouchEvent方法,這么多事件需要管理者dispatchTouchEvent。所以view也會有事件分發。
2) view事件相關的方法調用順序:onTouchListener > onTouchEvent > onLongClickListener > onClickListener
1.4 注意點
1) 不論view自身是否注冊事件,只要view可點擊就會消費事件,如view注冊了onClickListener\ onLongClickListener\ onContextClickListener\ android:clickable = “true”;
onTouchListener返回false才會調用onClickListener
2) 即使給view注冊了onTouchListener也不會影響view的可點擊狀態,只要不返回true就不會消費事件;
3) ViewGroup和ChildView同時注冊了事件監聽器,由ChildView消費;
4) 一次觸摸流程中產生事件應被同一個view消費,全部接收或全部拒絕;
2. view繪制原理
view繪制到手機屏幕上需要經過三個步驟:measure(測量控件大小)、layout(view擺放位置)、draw(view的繪制)
2.1 view的測量measure
1) 作用:測量視圖大小,即視圖寬高,在view中measure為final型,子類不能修改,它內部會調用onMeasure(),視圖大小最終在這里確定,調用setMeasuredDimension保存計算結果;
2) MeasureSpec:view的測量模式MeasureSpec封裝的是父容器傳遞給子容器的布局要求,MeasureSpec是由父view的MeasureSpec和子view的LayoutParams(定義view的layout_width\layout_height)通過計算得出子view的測量要求。
MeasureSpec是由一個32位整型數組成,高2位是mode,后面30位是size。
1) mode一共有三種模式:UNSPECIFIED:父容器對子容器沒有任何限制,任意大;EXACTLY:父容器為子容器設置了尺寸,子容器應該服務這些邊界;AT_MOST:子容器可以聲指指定大小內的任意大小,最大不得超過指定大小。
2) measure會遍歷整棵view樹,測量每個view真實尺寸,ViewGrou向它內部的每個子view發送measure命令,子view在onMeasure()函數來測量自己的尺寸,再調用setMeasuredDimension()方法將測量結果保存至View的mMeasuredWidth和mMeasureHeight中,單位是像素。
2.2 layout
layout的主要作用 :根據子視圖的大小以及布局參數將View樹放到合適的位置上。
ViewGroup:1)根據parent傳遞的位置信息,設置自己的位置;2)根據自己的layout規則,為每一個子view計算出準確的位置,并將layout流程傳遞給子view
View:根據parent傳遞的位置信息,設置自己的位置
2.3 draw
根據measure, layout得到的參數將視圖顯示在屏幕上,子類不能修改此方法;
繪制步驟:
1)繪制背景(drawBackground)
2)保存canvas的layer,準備fading;
3)繪制view的content(onDraw方法);
4)繪制children(dispatchDraw方法);
5)繪制fading edges,再還原layer;
6)繪制裝飾器,如scrollBar
四、性能方面
1、 耗電太多怎么優化
1.1 應用耗電主要體現在以下幾方面
1) 常規使用:大數據量的傳輸(IO),不停的在網絡間切換(網絡請求操作);解析大量的文本數據;
2) 對設備的頻繁喚醒:在Android中AlarmManager可喚醒設備,WakeLock能讓CPU保持喚醒狀態,這兩者不恰當使用,沒有合理釋放掉,將會使系統長時間無法進入休眠,導致高耗電;
3) GPS:對GPS設備的使用控制,GPS實時性所以電量消耗也比較嚴重;
4) 傳感器和屏幕;
1.2 優化途徑
1) 在需要網絡連接的程序中,先檢查網絡連接是否正常,若沒有網絡連接,就不需要執行相應程序;在蜂窩移動網絡下,批量執行網絡請求,盡量避免頻繁間隔網絡請求;應該盡量減少移動網絡下數據傳輸,多在WiFi環境下傳輸數據;
2) 使用效率高的數據格式和解析方法,如JSON和Protobuf;
3) 在進行大數據下載時,盡量使用GZIP方式下載;
4) 回收Java對象,特別是較大的java對象,使用reset方法,對定位要求不太高的不要使用GPS定位,使用wifi和移動網絡cell定位;盡量不要使用浮點運算;獲取屏幕尺寸等信息可以使用緩存技術;使用AlarmManager定時啟動服務替代使用sleep方式的定時任務。
5)使用JobScheduler來管理后臺工作,將一些不是特別緊急的任務放到更合適的時機批量處理;
6) 監控電池當前的電量和充放電狀態,在程序中做相應參數設置以減少耗電量。
2. 怎么統計crash
可集成第三方sdk進行統計,如友盟、騰訊Bugly,可以分析出每個版本出現的bug數,每個bug出現的時間、頻次、機型、應用版本、bug出現的位置;計算bug率可以通過計算bug的次數/應用啟動次數
3. 怎么減少用戶流量消耗
3.1 流量怎么監控
1) Android Studio自帶的Network Monitor可看出時間段內網絡請求數量及訪問速率;
2) 使用代理工具,如Wireshark, Fiddler, Charles,可以查看每一次網絡請求,傳輸的數據量;
3.2 怎么優化手機流量使用
1) 使用GZIP壓縮進行數據傳輸,不僅可以減少流量消耗,還可減少傳輸時間;
2) 使用IP直連,而不用域名,省去DNS解析過程;
3) 圖片方面:圖片使用WebP格式,可節省流量;使用壓縮圖,圖片按需加載,只有用戶查看大圖時才加載原圖;圖片上傳時,采用分片傳輸,每個分片失敗重傳,根據網絡類型及傳輸過程中的變化動態修改分片大小;
4) 協議層優化,Http1.1引入了“持久連接”,多個請求被復用,無需重建TCP連接;Http2引入了“多頭”、頭信息壓縮、服務器推送等;
5) 合并網絡請求,減少請求次數(這樣頭信息僅需上傳一次,減少流量也節省了資耗);
6) 網絡緩存,對服務端返回數據緩存,設定有效時間,也可以使用第三方庫如Okhttp, Volley
7) 斷點續傳,重試策略,Protocol Buffer;避免客戶端輪詢,使用服務器推送方式,數據采用增量更新,應用可設置僅在wifi環境下升級應用;善用緩存服務;在WIFI環境下下載文件;使用離線地圖;暫時關閉同步;壓縮數據
4. 方法數超過65535的解決辦法
4.1 出現原因
1) android中單個dex文件能包含的最大方法數65535,類的數量也有這個限制,這包含android framework, 依賴的jar包及應用本身的代碼中所有的方法。
2) 超過了LinearAlloc緩沖區大小,應用在安裝時,系統會通過dexopt來優化dex文件,在優化過程中dexopt采用了一個固定大小的緩沖區來存儲應用中所有的方法信息,該緩沖區就是LinearAlloc。它在新版本中是8mb或16mb,但在android2.2,2.3上只有5mb。有時方法數沒超,但是存儲空間超過了5mb,dexopt程序會報錯,導致安裝失敗。
4.2 解決方案
1) 刪除無用的代碼和第三方庫
2) 利用插件來動態加載部分dex
3) multidex解決方案(可從apk中加載多個dex文件)
使用方法:build.gradle中添加multidex的依賴;
讓自定義的Application繼承MultidexApplication(在attachBaseContext方法中加入multiDex.install(this)),或是在mainfest文件的Application直接設成MultidexApplication。
4.3 multidex存在的問題
1) 應用啟動速度會降低,由于應用啟動時會加載額外的dex文件,將會造成啟動速度除低,還可能造成ANR,所以盡可能第二個dex不要太大;
2) 由于dalvik LinearAlloc的bug,這可能導致multidex的應用無法在android 4.0之前的手機上運行。
4.4 針對multidex的問題,可改進的地方
1) 啟動時間過長
Application.attachBaseContext是在Application.onCreate()之前運行的,如果首次啟動Dalvik虛擬機會對classes.dex執行dexopt操作,生成ODEX文件,此過程非常耗時,而執行MultiDex.install()必然會再次對class2.dex執行dexopt等操作,所以如果首次啟動應用可以將MultiDex.install(context)放在子線程中執行;
2) ANR/Crash
在classes2.dex沒有加載完,程序調用了classes2.dex中的類或方法,關于MultixDex編譯過程相關的任務主要有:
collectDebugMultiDexComponents:掃描AndroidMainfest.xml中的application, activity, receiver, provider,service等相關類,將信息寫入mainfest_keep.txt中;
shinkDebugMultiDexComponents:再壓縮,根據proguard規則及mainfest_keep.txt文件進一步優化mainfest_keep.txt,將沒有用到的類刪除,最終生成componentClasses.jar文件;
createDebugMainDexClassList: 上一步生成的componentClasses.jar文件中的類,遞歸掃描這些類所有相關的依賴類,最終形成maindexlist.txt文件,最終打包進classes.dex中。
如果遇到找不到類的情況,只需要將該類的完整路徑添加到maindexlist.text中即可。
5. anr
5.1 android怎么定義應用出現ANR
1) 應用在5秒內未響應用戶的輸入事件;
2) BroadcastReceiver未在10秒內完成相關的處理;
5.2 出現場景
1) 主線程阻塞、掛起、死循環;應用進程的其他線程CPU占用率高,使主線程無法搶占CPU時間片;
2) 其他應用進程搶占CPU時間片,使當前應用無法搶占到;其他進程CPU占用率高;當前應用進程進行進程間通信請求其他進程,其他進程操作長時間沒有反饋等;
5.3 從哪可以查看到引起的原因
當前運行的程序發生ANR時,會將函數的堆棧信息輸出到/data/anr/trace.txt文件中,最新的ANR信息在最開始部分;
后臺的ANR一般不彈框,可以在開發者選項里勾選“應用程序無響應”即可對后臺ANR也進行彈窗顯示。
5.4 如何避免
1) 使用AsyncTask或是Thread/HandlerThread來進行耗時操作,還可設置Thread的優先級設置成Process.THREAD_PRIORITY_BACKGROUND;
2) 使用Handler處理工作線程結果,而不使用Thread.wait()或Thread.sleep()來阻塞主線程;
3) 在onCreate和onResume回調中盡量避免耗時操作;
4) BoardCastReceiver的onReceive代碼要盡量減少耗時,可使用IntentService處理。
6. listview優化
6.1 每次getView執行時間最長不得超過多少否則給用戶卡頓感覺
1S內屏幕大概繪制30幀,用戶才會覺得它比較流暢,每幀可用時間為33.33ms,每一屏一般要6個ListItem,加上一個重用的convertView:33.33ms/7=4.76ms,即每個getView要在4.76ms內完成工作才會較流暢。但每個getView間的調用會有一定間隔,所以留給getView使用的時間應該控制在4ms內,否則滑動ListView會有卡頓的感覺。
6.2 優化方法
1) 使用ViewHolder+converView模式,這樣避免每次在調用getView時,都重新inflate一個view,通過findViewById實例化數據;
2) adapter中有耗時操作,異步加載;
3) 為圖片設置緩存;圖片加載可使用第三主庫如Glide, Picasso
4) ListView滑動時停止加載圖片和分頁加載;
5) 在adapter的getView中盡可能減少邏輯判斷;
6) 將ListView的scrollingCache和animateCache的屬性設置為false;
7) 盡可能減少List Item的Layout層次,如可用RelativeLayout替換LinearLaout
7. bitmap怎么避免OOM
7.1 加載大圖時,可先對圖片進行壓縮
在一個很小的ImageView上顯示一張大圖,是不值當的。BitmapFactory提供了多個解析方法(dcodeByteArray, decodeFile, decodeResource)用于創建Bitmap對象,它提供了一個可選的BitmapFactory.Options參數,將inJustDecodeBounds屬性設置為true就可以讓解析方法禁止為bitmap分配內存,返回值不再是一個bitmap,而是null,但BitmapFactory.Options的outWidth, outHeight和outMineType屬性都會被賦值,此時可根據圖片情況進行壓縮。能過設置BitmapFactory.Options中的inSampleSize值就可以實現。
7.2 使用圖片緩存技術
LrcCache來緩存圖片,LruCache
7.3 選擇RGB_565或是ARGB_4444的編碼格式,ARGB_8888的編碼格式占4個字節。
7.4 android系統怎么判斷發生OOM
在Android2.x系統: dalvik allocated + external allocated + 新分配的大小 >= getMemoryClass值時就會發生OOM
在Android4.x系統廢除了external的計數器bitmap的分配改到dalvik的java heap中申請,只要allocated + 新分配的內存 >= getMemoryClass()就會發生OOM;
7.5 其他避免OOM辦法
使用輕量級數據結構,如用SparseArray代替HashMap, int代替Integer等;少用枚舉;
內存對象復用,ListView/recyclerView使用viewholder;用StringBuilder/Stringbuffer代替String字符串拼接;減少內存泄漏
8. android UI性能優化
8.1 卡頓(Jack)
安卓設備的屏幕刷新率一般是60幀每秒,所要要渲染的內容能在16ms內完成,每丟一幀,用戶就會感覺動畫在跳動。
8.2 Choreographer幀率檢測方案
8.2.1 VSync信號及作用
Android系統一幀繪制完成,顯示器會發出一個垂直同步信息(vertical synchronization)VSync信號,顯示器通常以固定頻率進行刷新,這個刷新率就是VSync信號產生的頻率。為解決GPU和視頻控制器不同步,GPU有一個垂直同步(V-Sync)來通知界面進行渲染、繪制,每一次同步周期為16.6ms,代表一幀的刷頻率,一次界面渲染會回調doFrame方法,如果兩次doFrame之間的間隔大于16.6ms,說明發生了卡頓,可保存兩次doFrame時間進行相減再除以刷新頻率,這樣算出來的結果就是兩次doFrame的掉幀數。
8.2.2 Choreographer怎么檢測掉幀
Choregrapher對VSync信號做了監聽,當有VSync消息的時候會執行onVsync,最終走到Choregrapher的doFrame,這里會將callback隊列中取出runnable進行繪制。
通過Choreographer設置它的FrameCallback,在每一幀被渲染時記錄下它開始渲染的時間,在下一幀渲染過程中判斷是否出現掉幀。
3)Android的一個顯示周期可表現為以下幾部分:
CPU+GPU繪制幀數據,繪制結束的數據存放在緩沖區BufferQueue中;
SurfaceFlinger從BufferQueue中取數據并計算;
Choreographer完成最終繪制;
9. android app啟動過程及優化
9.1 App應用啟動方式
9.1.1 冷啟動
后臺沒有被啟動應用的進程,系統會重新創建一個新的進程分配給當前應用(AMS調用startProcessLocked()方法創建新進程)。它會先創建和初始化Application類,再將進程和指定的Applciation綁定起來,再創建和初始化MainActivity類(包括一系列的測量、布局、繪制),最后顯示在界面上。
Launcher是一個進程,被啟動的應用是另一個進程,這里涉及到跨進程調用,Launcher通知Binder通過ActivityManagerService來啟動另一個應用Activity.
具體可控制流程:
Application的構造方法——》attachBaseContext() ——》onCreate() ——》Activity構造方法->onCreate() –> 配置主題中背景 –》 onStart() –> onResume() –> 測量布局繪制顯示界面。
9.1.2 熱啟動
后臺已有被啟動應用的進程,會直接從已有的進程中來啟動應用,不會再走Application,直接到MainActivity就行。
調用application線程對象中的scheduleLaunchActivity()發送一個LAUNCH_ACTIVITY消息到消息隊列中,通過handleLaunchActivity()來處理該消息。
9.2 測量應用啟動時間
應用啟動時間是指從點擊應用啟動圖標開始創建出一個進程直到看到界面上第一幀。
在命令行中運行命令:adb shell am start -W [packageName]/[packageName.MainActivity]
會有三個時間,ThisTime(調用過程中最后一個Activity啟動時間到這個Activity的startActivityAndWait調用結束)、WaitTime(startActivityAndWait方法的耗時)、TotalTime(調用過程中第一個Activity的啟動時間到最后一個Activity的startActivityAndWait結束)
9.3 app啟動的優化(冷啟動)
1) Application的創建過程中盡量少進行耗時操作
2) 如果用到SharePreference,盡量在異步線程中操作
3) 首次啟動的Activity減少布局層次,并在生命周期回調的方法盡量減少耗時操作
4) 為主界面單獨寫一個主題style,并設置成啟動圖,然后在啟動的Activity設置成主題,在Activity加載布局前把主題重新設置回來。
10. apk 瘦包機制
上圖是一般的apk安裝包含的文件,如果加入混淆等還會有proguard.cfg、project.properties等文件,從圖中可以看出AndroidManifest.xml、META-INF這些本身就很小沒有必要做進一步壓縮的文件,而其它文件或者文件夾都可以考慮進行優化,從而減小APK的體積。下面具體說說android apk的一些瘦包實用技巧:
10.1 使用lint減小resources.arsc體積
使用lint除一些無用的代碼、無用的資源、無用的變量等;還可以借助lint消除一些隱藏的bug,從頁減小resources.arsc體積,用法是:Android Studio→Inspect Code…對工程做靜態代碼檢查;
10.2 代碼混淆減少classes.dex大小
代碼混淆可以減小該文件的大小,因為混淆后的代碼將較長的文件名、實例、變量、方法名等等做了簡化,從而實現字節長度上的優化;
10.3 壓縮資源
1) 使用一些小圖片代替大圖,有些適配圖片可能只需要保留一套如xxhdpi,有些背景圖片用代碼實現,用.9.png;
2) 使用tinypng壓縮圖片;
3) 對圖片質量要求不是很嚴格,可以考慮不帶alpha值的jpg圖片、同等質量下文件更小的webP圖片格式;
4) 借助微信提供的AndResGuard資源文件混淆工具對資源文件做混淆,進一步壓縮資源文件所占用的空間;
10.4 去掉一些不用的適配
1) 如果只需要支持中文,可在build.grade中添加resConfigs “zh”去除無用的語言資源;
2) 去掉一些so包支持
一種CPU架構 = 一種ABI = 一種對應的SO庫;
現在手機市場總共支持以下七種不同的CPU架構:ARMv5,ARMv7,x86,MIPS,ARMv8,MIPS64和x86_64, 這7種CPU類型對應的SO庫的文件夾名是:armeabi,armeabi-v7a,x86,mips,arm64-v8a,mips64,x86_64。所有的x86/x86_64/armeabi-v7a/arm64-v8a設備都支持armeabi架構的.so文件,64位設備(arm64-v8a, x86_64, mips64)能夠運行32位的函數庫,所以應用不需要支持很多設備,建議實際工作的配置是只保留armable、armable-x86下的so文件,當然so包是向下兼容的,如果提供了其他cpu類型的文件夾,也需要在相應的文件夾里補全所有的so包,要不手機到時適配找不到合適的so包導致crash。
10.5 減小或慎用第三方依賴庫
如果只使用第三方依賴庫的少部分功能,可以考慮只提取少部分代碼,而不是直接把第三庫全部引入;
用較少的庫替換大庫;
10.6 插件化和動態加載
上面提到的so包,可以只提供一套,在應用運行時,需要用到so包的地方,可以從服務器下載so包,再動態加載;
10.7 使用7zip打包
五、源碼或實現原理
1. 消息機制
handler消息機制,它可以將一個任務切換到handler所在的線程中去執行;handler需要MessageQueue\Looper\Message的支持;各自的任務是:
Handler–負責消息的發送、接收處理;
MessageQueue–消息隊列,一個消息存儲單位,經常需要進行增減,內部使用單鏈表結構;
Looper–消息循環,不停地從MessageQueue中取消息,如有新消息就會立即處理,否則一直阻塞;
Message–消息載體。
1.1 Handler是怎么接收到消息
1) Looper的prepare()會為當前線程創建一個Looper實例,該實例會創建一個消息隊列MessageQueue對象,將線程設為當前線程;最后將該實例保存在當前線程中;
2) 構建Handler時,會先獲取到當前Handler所在線程的Looper并得到其中的MessageQueue;
2) 使用Handler發送消息時,會將一個Message保存到當前線程Looper中的MessageQueue中;
3) 調用loop()方法會開啟消息循環,不斷從MessageQueue中取消息,當收到消息后,會調用msg.target.dispatchMessage(msg);來處理消息;最終會調用Handler的handlerMessage進行處理消息;
1.2 Handler是怎么發送消息
Handler的post一個Runnable對象和sendMessage一個Message對象
2. binder
2.1 Android為什么要采用Binder作為IPC機制
1)性能方面:Binder數據拷貝只需一次,而管道、消息隊列、Socket都需要2次,共享內存不需要拷貝,性能解度,Binder僅次于共享內存;
2) 穩定性方面:Binder是基于C/S架構,架構清晰,穩定性好。共享內存實現方式復雜,沒有客戶和服務端之別,需要充分考慮訪問臨界資源的并發同步問題,否則會出現死鎖等問題,故Binder穩定性優于共享內存;
3) 安全方面:傳統的IPC接收方無法獲得對方進程可靠的UID/PID,無法鑒別對方身份。而Binder機制的Server端可根據權限控制策略,判斷UID/PID是否滿足訪問權限。
2.2 實現方法
1) 在服務端新建需要的AIDL類(聲明以.aidl為后綴的接口類,并聲明相應的方法),保存后會在gen目錄下生成相應的java類,它是系統為我們聲明的aidl接口生成的Binder類,繼承自android.os.IInterface,自己也還是一個接口。
2) 編寫服務端Service類,新建一個.Stub對象,在onBind方法中返回定義的aidl的Binder對象。
3) 在服務端注冊服務;
4) 在客戶端新建ServiceConnection對象,重寫onServiceConnected方法和onServiceDisconnected方法,在onServiceConnected方法可以獲取到服務端的Binder對象,從而可以調用服務端的方法進行通信。
5) 在客戶端綁定service對象
3. LeankCanary檢測內存泄漏原理
1) RefWatcher.watch()會以監控對象來創建一個KeyedWeakReference弱引用對象;
2) 在AndroidWatchExecutor的后臺線程里,來檢查弱引用是否被清除,沒有則執行一次GC;
3) 若弱引用對象仍沒有被清除,說明內存泄漏了,系統導出hprof文件,保存在app的文件系統目錄下;
4) HeapAnalyzerService啟動一個單獨進程,使用HeapAnalyzer來分析hprof文件,它使用了另一個開源庫HAHA.
5) HeapAnalyzer通過查找KeyedWeakReference弱引用對象來查找內在泄漏;
6) HeapAnalyzer計算KeyedWeakReference所引用對象的最短強引用路徑,來分析內存泄漏,并構建出對象引用鏈出來;
7) 內存泄漏信息送回給DisplayLeakService,它是運行在app進程的一個服務,再在設備通知欄顯示內存泄漏信息。
六、其他
1. 雙親委派機制
1.1 類加載器的作用
類加載器(class loader):用來加載Java類到Java虛擬機中。Java源程序(.java)在經過Java編譯器后被轉換成Java字節代碼(.class)。類加載器負責讀取Java字節代碼,并轉換成java.lang.Class類的一個實例。
1.2 什么叫雙親委派
某個特定類的加載器在接到加載類的請求時,先將加載任務委托給父類加載器,依次遞歸(如果沒有繼續向上尋找父類加載器),如果父類加載器可以完成類加載任務,就成功返回;只有父類加載器無法完成加載任務,才自己加載;
android中加載類的順序:
自定義類加載器CustomClassLoader > 應用程序類加載器AppClassLoader > 擴展類加載器 ExtClassLoader > 啟動類加載器(BootStrapClassLoader)
1.3 雙親委派的意義
防止內存中出現多份同樣的字節碼;只有當使用該class時才會去裝載,一個classloader只會裝載同一個class一次。
2. 子線程和UI線程
2.1 android為什么不能在子線程更新UI
因為Android的UI線程訪問是非線程安全,沒有加鎖,這樣在多個線程訪問UI是不安全的, 所以Android中規定只能在UI線程中訪問UI。
2.2 子線程一定不能更新UI線程么
當訪問UI控件時,android是在ViewRootImpl的checkThread方法中檢測當前線程是否是主線程,如果不是會報錯。而ViewRootImpl的在WindowManagerGlobal的addView方法中創建,而addView方法是在在onResume方法回調之后。所以如果在onCreate方法中創建了子線程并訪問UI,ViewRootImpl還沒創建,無法檢測當前線程是否是UI線程,程序可以在子線程中訪問UI線程。
3. RxBus和EventBus
3.1 EventBus作為一個消息總線,主要有三個元素
1) Event:要發送的事件對象
2) Publisher: 事件發布者,用于通知Subscriber有事件發生。直接調用eventBus.post(Object)可以在任意線程任意位置發送事件,根據post函數參數類型,會自動調用訂閱相應類型事件的函數。
3) Subscriber: 事件訂閱者,接收特定事件,所有事件訂閱都是以onEvent開頭的函數,有onEvent, onEventMainThread, onEventBackgroundThread, onEventAsync這四個。與ThreadMode有關。
3.2 RxBus,結合Rxajva的便利
先要定義一個RxBus的類:
里邊包含一個單例RxBus類對象:private static volatile RxBus instance;
一個final類型的Subject對象:bus = new SerilizedSubject<>(PublishSubject.create());
一個發送事件的方法:public void post(Object o){bus.onNext(o);}
一個接收事件的方法:public Observable toObservable(Class eventType){return bus.ofType(eventType);}
Subject同時充當了Observer和Observable角色,Subject是非線程安全,所以將它轉成SerilizedSubject,包裝成線程安全的Subject.
4.RecyclerView和listview的比較
1) ViewHolder模式————ListView通過創建ViewHolder來提升性能不是必須的,RecyclerView的Adapter必須至少一個ViewHolder,必須遵循ViewHolder設計模式;
2) Item布局————ListView只能實現垂直線性布局,RecyclerView可以實現垂直、水平、瀑布流等;
3) Item動畫,ListView沒有提供增刪Item的動畫,RecyclerView可以在RecyclerView.ItemAnimator來為條目增加動畫效果;
4) 設置數據源————ListView對Adapter封裝了一些數據,如ArrayAdapter, CursorAdapter,而RecyclerView.Adapter則必須實現RecyclerView.Adapter
5) 分隔線————ListView可以直接設置android:divider設置分割線,RecyclerView必須使用RecyclerView.ItemDecoration來設置;
6) 點擊響應————ListView提供了AdapterView.OnItemClickListener接口,RecyclerView必須使用RecyclerView.OnItemTouchListener來響應條目觸摸事件;
7) 緩存————ListView采用兩級緩存,RecyclerView采用四級緩存
ListView的緩存,先判斷數據源數據是否改變,a)沒有則從mActiveViews中通過匹配pos讀取view緩存;b)有則從mScrapViews中讀取view緩存;再通過mAdapter.getView(pos, ScrapView, parent)將緩存用于getView(),若可重用,則避免createView耗時,無法避免bindView耗時;
RecyclerView的緩存,a) 從mAttachedScrap中通過匹配pos獲取holder緩存,成功直接顯示,b) 不成功從mCacheViews中匹配pos獲取holder緩存,c) 不成功再從mViewCacheExtension中自定義獲取holder緩存;d) 不成功再從mRecycledViewPool中獲取holder緩存;成功則清除holder所有標志位,不成功則調用mAdapter.createViewHolder(parent, type)
ListView通過pos獲取view, pos–>view
RecyclerView通過pos獲取viewHolder,pos–>(view, viewHolder, flag)
5. Serializable和Parcelable的比較
5.1 設計作用
Serializable: 標記接口,為了保存對象的屬性到本地文件、數據庫、網絡流、rmi以方便數據傳輸;
Parcelable:為了在程序內不同組件間及Android程序間高效的傳輸數據而設計,這些數據僅在內存中存在;
5.2 效率方面
Parcelable性能比Serializable好,因為后者在反射過程頻繁GC
Parcelabe: 適合在內存中數據傳輸,如activity間傳輸數據;
5.3 使用方面
Serializable: 只需要實現Serializable接口
Parcelabe:需要實現writeToParcel, describeContents函數及靜太CREATOR變量
6. java靜態內部類和內部類區別
6.1 持有引用
非靜態內部類會隱式持有一個外圍類的引用,而靜態內部類不會持有外部類的引用;
6.2 訪問外部類
1) 非靜態內部類能訪問外圍類的一切成員,包括私有變量,外部類不能直接訪問內部類的成員,但可通過內部類的實例訪問;靜態內部類不能訪問外圍類的非公開成員,因為它們是兩個不同的類
6.3 構造內部類
非靜態內部類的構造需要借助外部類的實例進行構造:
OuterClass.InnerClass innerObject = new OuterClass().new InnerClass();靜態內部類可以直接進行實例化:
OuterClass.StaticNestedClass nestedObject = new OuterClass.StaticNestedClass();7. TCP/IP五層通信體系結構
應用層(應用程序數據)》傳輸層(Segment/Datagram)傳輸的是TCP數據或是UDP數據 》網絡層(Packet,路由) 》數據鏈路層(Frame,以太網交換機) 》物理層(Bit,集線器,雙絞線)
總結
以上是生活随笔為你收集整理的android基本功的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 谭浩强-习题6.5
- 下一篇: 电容三点式LC振荡器电路组成及工作原理简