Android知識(shí)體系(1)
Android知識(shí)體系(2)
十一.Handler機(jī)制
1木柬、定義&作用
定義:Android中線程之間消息傳遞谭期、異步通信的機(jī)制。
作用:將工作線程的消息傳遞到UI主線程中必盖,從而是實(shí)現(xiàn)工作線程對主線程的更新沫屡,避免線程操作的不安全饵隙。
2、原理
相關(guān)組件:
· Handler:消息處理者沮脖,添加消息Message到MessageQueue中金矛,再通過Looper循環(huán)取出消息。
主要方法:handler.post()勺届、handler.sendMessage()驶俊、handler.dispatchMessage()、handler.handleMessage()
· Message:存儲(chǔ)需要操作的數(shù)據(jù)
· MessageQueue:存放消息的數(shù)據(jù)結(jié)構(gòu)
主要方法:queue.enqueueMessage()
· Looper:消息循環(huán)
主要方法:looper.prepare()免姿、looper.loop()
在主線程中創(chuàng)建Looper和MessageQueue饼酿,通過handler.sendMessage或者h(yuǎn)andler.post發(fā)送消息進(jìn)入MessageQueue。
Looper不斷的循環(huán)從消息隊(duì)列中取出消息胚膊,發(fā)送給消息創(chuàng)建者h(yuǎn)andler故俐。handler接收消息,在handleMessage方法中處理紊婉。
相關(guān)資料文章:
Android異步通信:手把手帶你深入分析 Handler機(jī)制源碼
Android異步通信:這是一份Handler消息傳遞機(jī)制的使用教程
Android 多線程:你的 Handler 內(nèi)存泄露 了嗎药版?
十二.自定義view
-
如圖所示
- img
-
View的繪制是從上往下一層層迭代下來的。DecorView-->ViewGroup(--->ViewGroup)-->View 喻犁,按照這個(gè)流程從上往下槽片,依次measure(測量),layout(布局),draw(繪制)。
- img
View分類:(1)單一視圖 (2)視圖組ViewGroup
無論是measure過程株汉、layout過程筐乳、draw過程都是從樹的根節(jié)點(diǎn)開始(即樹形結(jié)構(gòu)的頂端)歌殃,view的位置是相對于父view而言的乔妈。
1、onMeasure 測量
作用:決定view的大小
onMeasure 過程
頂層ViewGroup->measure->onMeasure->measureChildren->子View->measure->onMeasure->測量完畢
ViewGroup的測量過程需要重寫onMeasure方法氓皱,根據(jù)布局的特性重寫路召。measureSpec測量規(guī)格
measureSpec(測量規(guī)格 32位的int值) = mode(測量模式 高2位 31.32位) + size(具體大小 低30位)
MeasureSpec.getMode()獲取mode
MeasureSpec.getSize()獲取size
MeasureSpec.makeMeasureSpec(size,mode)根據(jù)傳入的size和mode返回對應(yīng)的measureSpecmode測量模式分為三類:
(1)UNSPECIFIED:未指明大小勃刨,父視圖不約束子視圖
(2)EXACTLY:明確大小,父視圖為子視圖明確指定一個(gè)確切的尺寸 使用match_parent或具體數(shù)值
(3)AT_MOST:最大尺寸股淡,父視圖為子視圖指定一個(gè)最大尺寸 wrap_content
2身隐、onLayout 布局
作用:獲取四個(gè)頂點(diǎn),決定View的位置唯灵。
- 布局過程
布局過程也是自上而下贾铝,不同的是ViewGroup先調(diào)用onLayout讓自己布局,然后再讓子View布局埠帕,而onMeasure是先測量子View的大小再確定自身大小垢揩。
(1).單視圖不需要實(shí)現(xiàn)該方法,視圖組需要實(shí)現(xiàn)onLayout()來對子view進(jìn)行布局
(2).對于單視圖確定位置是在基類layout()中方法確定的敛瓷,對于視圖組自身位置也是layout()方法確定叁巨。layout主要用來確定子view的位置
(3).layout方法中確定位置的方法是setFrame和setOpticalFrame
(4).在視圖組中,復(fù)寫onLayout方法呐籽。在其中遍歷子view,對于每個(gè)view依次調(diào)用layout->onLayout
3锋勺、onDraw 繪制
作用:顯示內(nèi)容
- 繪制過程
繪制背景、繪制內(nèi)容狡蝶、繪制子view庶橱、繪制裝飾器
draw -> drawBackground -> onDraw -> dispatchDraw -> onDrawScrollBars
自定義View可重寫onDraw以繪制不同內(nèi)容
4、invalidate/postInvalidate/requestLayout
invalidate\postInvalidate
作用:都是調(diào)用onDraw方法達(dá)到重新繪制的目的
區(qū)別:invalidate只能在主線程中調(diào)用牢酵,postInvalidate能在子線程中調(diào)用悬包,postInvalidate內(nèi)部使用了handler\message機(jī)制最終還是掉用invalidate方法。requestLayout
作用:調(diào)用onMeasure\onLayout重新測量和布局馍乙,有可能調(diào)用onDraw重新繪制布近。
5、ViewRoot/DecorView
- ViewRoot(實(shí)際是ViewRootImpl):連接WindowManagerService和DecorView(最層級(jí)的View)的橋梁丝格。View的三大流程均是通過ViewRootImpl來實(shí)現(xiàn)的撑瞧。
- DecorView:頂級(jí)View,本身是一個(gè)FrameLayout显蝌。分為標(biāo)題欄和內(nèi)容欄预伺,內(nèi)容欄的id是R.android.id.content。Activity中的setContentView()方法最終是通過window.setContentView()
添加到DecorView的內(nèi)容欄中曼尊。
6酬诀、View的繪制流程
- View的繪制流程是從ViewRoot的performTraversals方法開始:
- performTraversals會(huì)依次調(diào)用performMeasure\performLayout\performDraw,分別完成頂層View的測量骆撇、布局瞒御、繪制。
過程中會(huì)對子View完成測量神郊、布局肴裙、繪制趾唱。
7、getMeasuredHeight 和 getHeight 方法的區(qū)別(同理getMeasuredWith/getWith)
1蜻懦、getMeasuredWidth是在onMeasure之后甜癞,getWidth是在onLayout之后。
2宛乃、getMeasuredHeight方法返回的是測量后View的高度悠咱,與屏幕無關(guān)。getHeight返回的是屏幕顯示的高度征炼。當(dāng)View沒有超出屏幕時(shí)乔煞,他們的值
是相等的,但當(dāng)View超出屏幕顯示時(shí)柒室,getMeasuredHeight的值等于getHeight的值加上超出的高度渡贾。為什么有時(shí)候用getWidth()或者getMeasureWidth()得到0?
View的繪制周期和Activity的生命周期不一致,所以在onCreate\onStart\onResume中調(diào)用方法都無法保證View測量雄右、布局完成空骚,所以獲取的結(jié)果為0.
相關(guān)資料文章:
Android 繪制原理淺析【干貨】
十三.多線程編程
1、為何有多線程擂仍?
- 主線程(UI線程)
- 在Android當(dāng)中, 當(dāng)應(yīng)用啟動(dòng)的時(shí)候,系統(tǒng)會(huì)給應(yīng)用分配一個(gè)進(jìn)程,順便一提,大部分應(yīng)用都是單進(jìn)程的,不過也可以通過設(shè)置來使不同組件運(yùn)行在不同的進(jìn)程中囤屹,
在創(chuàng)建進(jìn)程的同時(shí)會(huì)創(chuàng)建一個(gè)線程,應(yīng)用的大部分操作都會(huì)在這個(gè)線程中運(yùn)行逢渔。所以稱為主線程肋坚,同時(shí)所有的UI控件相關(guān)的操作也要求在這個(gè)線程中操作,所以也稱為UI線程肃廓。
- 在Android當(dāng)中, 當(dāng)應(yīng)用啟動(dòng)的時(shí)候,系統(tǒng)會(huì)給應(yīng)用分配一個(gè)進(jìn)程,順便一提,大部分應(yīng)用都是單進(jìn)程的,不過也可以通過設(shè)置來使不同組件運(yùn)行在不同的進(jìn)程中囤屹,
- 為何會(huì)有子線程
- 因?yàn)樗械腢I控件的操作都在UI線程中執(zhí)行智厌,如果在UI線程中執(zhí)行耗時(shí)操作,例如網(wǎng)絡(luò)請求等盲赊,就會(huì)阻塞UI線程铣鹏,導(dǎo)致系統(tǒng)報(bào)ANR(Application Not Response)錯(cuò)誤。
因此對于耗時(shí)操作需要?jiǎng)?chuàng)建工作線程來執(zhí)行而不能直接在UI線程中執(zhí)行哀蘑。這樣就需要在應(yīng)用中使用多線程诚卸,但是Android提供的UI工具包并不是線程安全的,也就是說不能直接在
工作線程中訪問UI控件绘迁,否則會(huì)導(dǎo)致不能預(yù)測的問題合溺, 因此需要額外的機(jī)制來進(jìn)行線程交互,主要是讓其他線程可以訪問UI線程缀台。
- 因?yàn)樗械腢I控件的操作都在UI線程中執(zhí)行智厌,如果在UI線程中執(zhí)行耗時(shí)操作,例如網(wǎng)絡(luò)請求等盲赊,就會(huì)阻塞UI線程铣鹏,導(dǎo)致系統(tǒng)報(bào)ANR(Application Not Response)錯(cuò)誤。
2棠赛、AsyncTask
定義:輕量級(jí)的異步任務(wù)類,內(nèi)部封裝了線程池、handler
缺點(diǎn):1恭朗、使用的是默認(rèn)的線程池
2、與Activity的生命周期不一致依疼,在執(zhí)行完doInBackground后才完結(jié)痰腮。
3、容易造成內(nèi)存泄漏律罢,AsyncTask持有Activity的引用膀值。
4、當(dāng)Activity生命周期異常后误辑,AsyncTask結(jié)果丟失
目前AsyncTask已被遺棄沧踏,推薦使用協(xié)程
3、ThreadPool
-
線程池規(guī)則:
- 當(dāng)線程池中的核心線程數(shù)未達(dá)到最大時(shí)巾钉,啟動(dòng)一個(gè)核心線程去執(zhí)行任務(wù)翘狱。
- 如果核心線程數(shù)達(dá)到最大,任務(wù)會(huì)安排到任務(wù)隊(duì)列中等待砰苍。
- 核心線程達(dá)到最大潦匈、任務(wù)隊(duì)列已滿,啟動(dòng)一個(gè)非核心線程執(zhí)行任務(wù)赚导。
- 核心線程最大茬缩、任務(wù)隊(duì)列已滿、非核心線程達(dá)到最大吼旧,線程池拒絕執(zhí)行任務(wù)凰锡。
-
優(yōu)點(diǎn):
- 降低線程創(chuàng)建和銷毀的系統(tǒng)開銷
- 線程復(fù)用,提高系統(tǒng)吞吐量
- 執(zhí)行大量異步任務(wù)時(shí)圈暗,提高性能
- 提供了相關(guān)管理的API掂为,使用方便
- execute :
service.execute(new Runnable() { public void run() { System.out.println("execute方式"); } });
- submit :
Future<Integer> future = service.submit(new Callable<Integer>() { @Override public Integer call() throws Exception { System.out.println("submit方式"); return 2; } }); try { Integer number = future.get(); } catch (ExecutionException e) { e.printStackTrace(); }
- 線程池關(guān)閉:
- 調(diào)用線程池的
shutdown()
或shutdownNow()
方法來關(guān)閉線程池 - shutdown原理:將線程池狀態(tài)設(shè)置成SHUTDOWN狀態(tài),然后中斷所有沒有正在執(zhí)行任務(wù)的線程员串。
- shutdownNow原理:將線程池的狀態(tài)設(shè)置成STOP狀態(tài)菩掏,然后中斷所有任務(wù)(包括正在執(zhí)行的)的線程,并返回等待執(zhí)行任務(wù)的列表昵济。
-
中斷采用interrupt方法智绸,所以無法響應(yīng)中斷的任務(wù)可能永遠(yuǎn)無法終止。
但調(diào)用上述的兩個(gè)關(guān)閉之一访忿,isShutdown()方法返回值為true瞧栗,當(dāng)所有任務(wù)都已關(guān)閉,表示線程池關(guān)閉完成海铆,則isTerminated()方法返回值為true迹恐。
當(dāng)需要立刻中斷所有的線程,不一定需要執(zhí)行完任務(wù)卧斟,可直接調(diào)用shutdownNow()方法殴边。
- 調(diào)用線程池的
4憎茂、IntentService
定義:可以在內(nèi)部開啟子線程執(zhí)行耗時(shí)任務(wù)的服務(wù)。
原理:繼承至Service锤岸,內(nèi)部封裝了handler竖幔、looper、thread等用于子線程與主線程的交互是偷。
5拳氢、TreadLocal
定義:
存儲(chǔ)一個(gè)數(shù)據(jù),對指定的線程可見蛋铆。原理:
通過使用當(dāng)前線程的TreadLocalMap對set(value)的value進(jìn)行存儲(chǔ)馋评,key為當(dāng)前的TreadLocal。get()方法通過當(dāng)前TreadLocal為key刺啦,在當(dāng)前線程的TreadLocalMap中取值留特。ThreadLocalMap
在Tread類中有TreadLocalMap的成員變量。TreadLocalMap是一種存儲(chǔ)K-V的數(shù)據(jù)結(jié)構(gòu)玛瘸,內(nèi)部使用的是哈希表結(jié)構(gòu)磕秤。
6、java內(nèi)存分區(qū)和java內(nèi)存模型(JMM)
內(nèi)存分區(qū):
·程序計(jì)數(shù)器
·本地方法區(qū)
- 存儲(chǔ)與本地native方法交互的字節(jié)碼
·虛擬機(jī)棧
- 內(nèi)部使用的 棧幀 結(jié)構(gòu):(1)局部變量表(2)操作數(shù)棧(3)動(dòng)態(tài)連接(4)返回地址
·方法區(qū)
- 存儲(chǔ)類信息捧韵、元數(shù)據(jù)市咆、常量池
·堆
- 存儲(chǔ)實(shí)例對象,GC作用的主要區(qū)域JMM:
規(guī)定所有的變量都存儲(chǔ)在主內(nèi)存中再来,每條線程還有自己的工作內(nèi)存蒙兰。線程中的工作內(nèi)存保存了被線程使用的變量的主內(nèi)存副本,線程對變量的操作都必須在工作內(nèi)存中進(jìn)行芒篷,
不能直接讀寫主內(nèi)存中的變量搜变,不同線程之間也無法直接訪問對方工作內(nèi)存中的變量副本。線程間變量值的傳遞需要通過主內(nèi)存來完成针炉。
7挠他、volatile
- 輕量級(jí)線程同步,保證操作數(shù)據(jù)的可見性篡帕、有序性殖侵,不保證原子性
8、synchronized
- 修飾普通方法镰烧、靜態(tài)方法拢军、代碼塊(this\object)
9、ReentrantLock
- reentrantLock 可重入鎖
對比Synchronized那有些區(qū)別和優(yōu)點(diǎn)怔鳖?
·在語法上Synchronized是原生語法提供茉唉。在用法上可修飾方法、代碼塊。而reentrantLock需配合try-catch使用度陆,并且需要手動(dòng)釋放鎖艾凯。
·reentrantLock 等待可中斷,是樂觀鎖懂傀,而Synchronized是獨(dú)占鎖趾诗,悲觀鎖。
·reentrantLock 可以添加多個(gè)鎖條件
·reentrantLock 公平鎖鸿竖,多個(gè)線程等待同一個(gè)鎖時(shí),必須按照申請鎖的時(shí)間順序獲得鎖铸敏。reentrantLock也可通過構(gòu)造方法設(shè)置為非公平鎖缚忧。
10、Atomic原子類
- 定義:適用于單個(gè)元素杈笔,能夠保證一個(gè)基本數(shù)據(jù)類型闪水、對象、或者數(shù)組的原子性蒙具。
原子更新基本類型:AtomicInteger球榆、AtomicBoolean、AtomicLong
原子更新引用類型:AtomicReference禁筏、AtomicStampedReference持钉、AtomicMarkableReference
原子更新數(shù)組類型:AtomicLongArray、AtomicIntegerArray篱昔、AtomicReferenceArray
原子更新對象屬性:AtomicIntegerFieldUpdater每强、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
- 實(shí)現(xiàn)原理:
- CAS(compare and swap):比較并交換 compareAndSwap(v,n,e)
相關(guān)資料文章:
ThreadLocal原理其實(shí)很簡單
Java并發(fā)包中的Atomic原子類
十四.跨進(jìn)程通信(IPC inter-process communication)
- linux系統(tǒng)中使用到的IPC機(jī)制有:管道州刽、共享內(nèi)存空执、socket、binder(android)等穗椅。
1辨绊、Binder機(jī)制
Android系統(tǒng)為什么要選用Binder機(jī)制作為進(jìn)程通信?
答:
·binder數(shù)據(jù)傳遞只需要拷貝一次匹表,效率較高
·安全性能高门坷,通過給應(yīng)用分配UID來鑒別應(yīng)用的身份
Android中的binder機(jī)制是一種高效率、安全性能高的進(jìn)程通信方式-
實(shí)現(xiàn)原理:
· 進(jìn)程隔離:系統(tǒng)為確保自身的安全穩(wěn)定袍镀,將系統(tǒng)內(nèi)核空間和用戶空間分離開來拜鹤。用戶空間的進(jìn)程要進(jìn)行交互需要通過內(nèi)核空間來驅(qū)動(dòng)整個(gè)過程。
· C/S結(jié)構(gòu):Binder作為一個(gè)Service的實(shí)體流椒,對象提供一系列的方法來實(shí)現(xiàn)服務(wù)端和客戶端之間的請求敏簿,只要client拿到這個(gè)引用就可以進(jìn)行通信。
(binder對象是一個(gè)可以跨進(jìn)程引用的對象,它的實(shí)體位于一個(gè)進(jìn)程中惯裕,而它的引用卻遍布系統(tǒng)的各個(gè)進(jìn)程中)
· 通信模型:
· Server : 跨進(jìn)程服務(wù)端温数,運(yùn)行在某個(gè)進(jìn)程,通過Binder驅(qū)動(dòng)在ServiceManager中注冊
· Client : 跨進(jìn)程客戶端蜻势,運(yùn)行在某個(gè)進(jìn)程撑刺,通過Binder驅(qū)動(dòng)獲取ServiceManager中的服務(wù)
· ServiceManager : 提供服務(wù)的注冊和查詢,運(yùn)行在SystemServer進(jìn)程
· Binder驅(qū)動(dòng):前三者位于用戶空間握玛,binder驅(qū)動(dòng)位于內(nèi)核空間够傍,其實(shí)現(xiàn)方式和驅(qū)動(dòng)差不多,負(fù)責(zé)進(jìn)程之間Binder通信的建立挠铲,Binder在進(jìn)程中的傳遞冕屯,Binder引用計(jì)數(shù)管理,
數(shù)據(jù)包在進(jìn)程之間的傳遞和交互拂苹。
· 內(nèi)存映射:Memory Map安聘,將用戶空間的一塊內(nèi)存地址映射到內(nèi)核空間,映射關(guān)系建立后瓢棒,用戶對這塊內(nèi)存的修改可以直接反應(yīng)到內(nèi)核空間中浴韭。
減少了數(shù)據(jù)拷貝的次數(shù),實(shí)現(xiàn)用戶空間和內(nèi)核空間的高效互動(dòng)脯宿。(1)Binder驅(qū)動(dòng)在內(nèi)核空間中創(chuàng)建了一個(gè)數(shù)據(jù)接收緩存區(qū)
(2)并在內(nèi)核空間開辟了一個(gè)內(nèi)核緩存區(qū)念颈,建立內(nèi)核緩存區(qū)和數(shù)據(jù)接收緩存區(qū)的映射關(guān)系,以及數(shù)據(jù)接收緩存區(qū)和用戶空間地址的映射關(guān)系连霉。
(3)發(fā)送方進(jìn)程通過copy_to_user函數(shù)將數(shù)據(jù)發(fā)送到內(nèi)核緩存區(qū)舍肠,由于內(nèi)核緩存區(qū)與數(shù)據(jù)接收區(qū)存在映射,而數(shù)據(jù)接收緩存區(qū)和用戶空間地址映射窘面,所以相當(dāng)于把數(shù)據(jù)發(fā)送到
接收方的用戶空間翠语。
2、AIDL(android interface definition language) Android接口定義語言
- 定義:Android接口定義語言财边,是一套模板代碼肌括,讓某個(gè)service與多個(gè)應(yīng)用程序組件之間跨進(jìn)程通信。
相關(guān)資料文章:
Android Binder原理(一)
十五.圖片相關(guān)(bitmap加載酣难、處理谍夭、緩存)
1、Bitmap加載(本地憨募、網(wǎng)絡(luò))
- 原生方法加載網(wǎng)絡(luò)圖片
private Bitmap returnBitmapFormUrl(String url) {
long l1 = System.currentTimeMillis();
Bitmap bitmap = null;
HttpURLConnection connection = null;
InputStream inputStream = null;
URL myUrl = null;
try {
myUrl = new URL(url);
connection = (HttpURLConnection) myUrl.openConnection();
connection.setReadTimeout(6000);
connection.setConnectTimeout(6000);
connection.setDoInput(true);
connection.connect();
inputStream = connection.getInputStream();
bitmap = BitmapFactory.decodeStream(inputStream);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(inputStream != null){
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
l1 = System.currentTimeMillis();
}
return bitmap;
}
- 使用三方插件Glide
2紧索、Bitmap處理(保存、壓縮)
- 保存圖片至相冊
private void saveImageToFile(Context context, Bitmap bitmap) {
File appDir = new File(Environment.getExternalStorageState(), "test");
if (!appDir.exists()) {
appDir.mkdir();
}
String fileName = System.currentTimeMillis() + ".jpg";
File file = new File(appDir, fileName);
try {
FileOutputStream fos = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
fos.flush();
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
try {
MediaStore.Images.Media.insertImage(context.getContentResolver(), file.getAbsolutePath(), fileName, null);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
Intent intent = new Intent();
intent.setAction(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
intent.setData(Uri.parse("file://"+file.getAbsolutePath()));
context.sendBroadcast(intent);
}
- 圖片壓縮
- Bitmap.compress() 質(zhì)量壓縮菜谣,不會(huì)對內(nèi)存產(chǎn)生影響
- BitmapFactory.Options.inSampleSize 內(nèi)存壓縮
3珠漂、大圖加載
BitmapFactory.decodeStream(): 獲取圖片
BitmapFactory.Option(): 設(shè)置圖片相關(guān)參數(shù)(設(shè)置顯示大小、縮放比例等)
option.inSimpleSize = 2BitmapRegionDecoder: 圖片局部展示
4媳危、圖片緩存
- LurCache
內(nèi)部采用LinkedHashMap存儲(chǔ)數(shù)據(jù)荞彼,其最重要的方法trimToSize是用來移除最少使用的緩存和使用最久的緩存,并添加最新的緩存到隊(duì)列中待笑。
5鸣皂、圖片占用內(nèi)存
getByteCount/getAllocationByteCount
with * height * 單個(gè)像素內(nèi)存大小
6、如何避免加載圖片出現(xiàn)OOM?
- 加載圖片前暮蹂,獲取圖片占用內(nèi)存大小寞缝,決定是否壓縮
- 對比源圖片寬高和控件寬高,決定是否縮放 BitmapFactory.Options的inJustDecodeBounds屬性設(shè)置為true仰泻,解析一次圖片獲取圖片寬高
- 采用局部加載BitmapRegionDecoder
- 多張圖片的采用弱引用