Android 語音TTS 識別全鏈路過程
- 本地錄音 =》
ASR識別?。健氛埱蠛笈_語義?。健氛Z義落域分發(fā)返回 =》 本地仲裁處理落域分發(fā)?。健贰?code>TTS播報 - 下面是語音鏈路的一些基本思路
- 錄音 :Android基本錄音為48K的采樣率 語音這邊需要做降采樣處理 降采樣為16K。通過Android原生錄音將音頻給到引擎
- 喚醒:一般喚醒都是做本地喚醒,所有wakeup喚醒引擎。也可以通過喚醒引擎做一些免喚醒功能
- 語音識別,識別分為兩種:
3.1 離線識別,走本地識別引擎,
優(yōu)點:識別快
缺點:需要精準(zhǔn)識別,并不能做太多泛化處理。對音頻要求比較高
3.2 在線識別,走云端識別引擎
優(yōu)點 :可以模糊匹配,多泛化
缺點 :網(wǎng)路查的情況下識別很慢 - 云端與離線云端技能分發(fā)
- TTS播報:由云端或本地接收文本進(jìn)行語音音頻合成。進(jìn)行播報
Android 屏幕適配相關(guān),方案
- 通過dp加上自適應(yīng)布局可以基本解決屏幕碎片化的問題。也是Android推薦使用的屏幕兼容性適配方案。
- 根據(jù)ui設(shè)計圖的寬度dp值,算出當(dāng)前屏幕每dp占當(dāng)前屏幕多少像素值(也就是
density)。
- 根據(jù)ui設(shè)計圖的寬度dp值,算出當(dāng)前屏幕每dp占當(dāng)前屏幕多少像素值(也就是
- 根據(jù)ui設(shè)計圖的寬度dp值,算出當(dāng)前屏幕分成ui設(shè)計圖的寬高度dp份后,每dp占當(dāng)前屏幕實際多少dp,然后這個實際dp值再根dpi轉(zhuǎn)換成具體的像素值。
- 自定義像素適配,以美工的設(shè)計尺寸為原始尺寸,根據(jù)不同設(shè)備的密度 計算出寬和高 參考
UIAdapter。如果想顯示屏幕的1/3的話就是360了寬度,是根據(jù)設(shè)計師給出來的寬度進(jìn)行設(shè)置
- 自定義像素適配,以美工的設(shè)計尺寸為原始尺寸,根據(jù)不同設(shè)備的密度 計算出寬和高 參考
- 百分比適配。這是Google 提出來的一個解決適配方案,想要使用必須添加依賴
implementation 'com.android.support:percent:28.0.0'
主要就是兩個類
PercentRelativeLayout PercentFrameLayout
多線程,線程池 相關(guān)
- 線程的創(chuàng)建,線程創(chuàng)建的常用方法
- 1.繼承Thread重寫run方法
- 2.實現(xiàn)Runnable重寫run方法
- 3.實現(xiàn)Callable重寫call方法
1.3 實現(xiàn)Callable重寫call方法
實現(xiàn)Callable和實現(xiàn)Runnable類似,但是功能更強(qiáng)大
可以在任務(wù)結(jié)束后提供一個返回值,Runnable不行
call方法可以拋出異常,Runnable的run方法不行
可以通過運行Callable得到的Fulture對象監(jiān)聽目標(biāo)線程調(diào)用call方法的結(jié)果,得到返回值,(fulture.get(),調(diào)用后會阻塞,直到獲取到返回值)
- Android中的四類線程池
- Android中最常見的四類具有不同特性的線程池分別為FixThreadPool、CachedThreadPool、ScheduleThreadPool和SingleThreadExecutor
- FixThreadPool(一堆人排隊上公廁)
public static ExecutorService newFixThreadPool(int nThreads){
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
//使用
Executors.newFixThreadPool(5).execute(r);
- 從配置參數(shù)來看,F(xiàn)ixThreadPool只有核心線程,并且數(shù)量固定的,也不會被回收,所有線程都活動時,因為隊列沒有限制大小,新任務(wù)會等待執(zhí)行。
- FixThreadPool其實就像一堆人排隊上公廁一樣,可以無數(shù)多人排隊,但是廁所位置就那么多,而且沒人上時,廁所也不會被拆遷
- 由于線程不會回收,F(xiàn)ixThreadPool會更快地響應(yīng)外界請求,這也很容易理解,就好像有人突然想上廁所,公廁不是現(xiàn)用現(xiàn)建的
- SingleThreadPool(公廁里只有一個坑位)
public static ExecutorService newSingleThreadPool (int nThreads){
return new FinalizableDelegatedExecutorService ( new ThreadPoolExecutor (1, 1, 0, TimeUnit. MILLISECONDS, new LinkedBlockingQueue<Runnable>()) );
}
//使用
Executors.newSingleThreadPool ().execute(r);
- 從配置參數(shù)可以看出,SingleThreadPool只有一個核心線程,確保所有任務(wù)都在同一線程中按順序完成。因此不需要處理線程同步的問題。
- 可以把SingleThreadPool簡單的理解為FixThreadPool的參數(shù)被手動設(shè)置為1的情況,即Executors.newFixThreadPool(1).execute(r)。所以SingleThreadPool可以理解為公廁里只有一個坑位,先來先上。為什么只有一個坑位呢,因為這個公廁是收費的,收費的大爺上年紀(jì)了,只能管理一個坑位,多了就管不過來了(線程同步問題)
- CachedThreadPool(一堆人去一家很大的咖啡館喝咖啡)
public static ExecutorService newCachedThreadPool(int nThreads){
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit. SECONDS, new SynchronousQueue<Runnable>());
}
//使用
Executors.newCachedThreadPool().execute(r);
- CachedThreadPool只有非核心線程,最大線程數(shù)非常大,所有線程都活動時,會為新任務(wù)創(chuàng)建新線程,否則利用空閑線程(60s空閑時間,過了就會被回收,所以線程池中有0個線程的可能)處理任務(wù)。
- 任務(wù)隊列
SynchronousQueue相當(dāng)于一個空集合,導(dǎo)致任何任務(wù)都會被立即執(zhí)行。 - CachedThreadPool就像是一堆人去一個很大的咖啡館喝咖啡,里面服務(wù)員也很多,隨時去,隨時都可以喝到咖啡。但是為了響應(yīng)國家的“光盤行動”,一個人喝剩下的咖啡會被保留60秒,供新來的客人使用,哈哈哈哈哈,好惡心啊。如果你運氣好,沒有剩下的咖啡,你會得到一杯新咖啡。但是以前客人剩下的咖啡超過60秒,就變質(zhì)了,會被服務(wù)員回收掉。
- 比較適合執(zhí)行大量的耗時較少的任務(wù)。喝咖啡人挺多的,喝的時間也不長
- ScheduledThreadPool(4個里面唯一一個有延遲執(zhí)行和周期重復(fù)執(zhí)行的線程池)
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize){
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize){
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedQueue ());
}
//使用,延遲1秒執(zhí)行,每隔2秒執(zhí)行一次Runnable r
Executors. newScheduledThreadPool (5).scheduleAtFixedRate(r, 1000, 2000, TimeUnit.MILLISECONDS);
- 核心線程數(shù)固定,非核心線程(閑著沒活干會被立即回收)數(shù)沒有限制。
- 從上面代碼也可以看出,ScheduledThreadPool主要用于執(zhí)行定時任務(wù)以及有固定周期的重復(fù)任務(wù)。
Handler 介紹
- 由于Android中主線程是不能進(jìn)行耗時操作的,子線程是不能進(jìn)行更新UI的。所以就有了handler,它的作用就是實現(xiàn)線程之間的通信。 handler整個流程中,主要有四個對象,handler,Message,MessageQueue, Looper。當(dāng)應(yīng)用創(chuàng)建的時候,就會在主線程中創(chuàng)建handler對象, 我們通過要傳送的消息保存到Message中,handler.post handler通過調(diào)用sendMessage方法將Message發(fā)送到MessageQueue中,Looper對象就會不斷的調(diào)用loop()方法 不斷的從MessageQueue中取出Message交給handler進(jìn)行處理。從而實現(xiàn)線程之間的通信
說下 handler 原理
- Handler,Message,looper 和 MessageQueue 構(gòu)成了安卓的消息機(jī)制,handler創(chuàng)建后可以通過 sendMessage 將消息加入消息隊列,然后 looper不斷的將消息從 MessageQueue 中取出來,回調(diào)到 Hander 的 handleMessage方法,從而實現(xiàn)線程的通信。
- 在UI線程創(chuàng)建Handler,此時我們不需要手動開啟looper,因為在應(yīng)用啟動時,在ActivityThread的main方法中就創(chuàng)建了一個當(dāng)前主線程的looper,并開啟了消息隊列,消息隊列是一個無限循環(huán),為什么無限循環(huán)不會ANR ? 因為應(yīng)用的整個生命周期就是運行在這個消息循環(huán)中的,安卓是由事件驅(qū)動的,Looper.loop不斷的接收處理事件,每一個點擊觸摸或者Activity每一個生命周期都是在Looper.loop的控制之下的,looper.loop一旦結(jié)束,應(yīng)用程序的生命周期也就結(jié)束了。我們可以想想什么情況下會發(fā)生ANR,第一,事件沒有得到處理
- 事件正在處理,但是沒有及時完成,而對事件進(jìn)行處理的就是looper,所以只能說事件的處理如果阻塞會導(dǎo)致ANR,而不能說looper的無限循環(huán)會ANR。
另一種情況就是在子線程創(chuàng)建Handler,此時由于這個線程中沒有默認(rèn)開啟的消息隊列,所以我們需要手動調(diào)用looper.prepare(),并通過looper.loop開啟消息 - 主線程Looper從消息隊列讀取消息,當(dāng)讀完所有消息時,主線程阻塞。子線程往消息隊列發(fā)送消息,并且往管道文件寫數(shù)據(jù),主線程即被喚醒,從管道文件讀取數(shù)據(jù),主線程被喚醒只是為了讀取消息,當(dāng)消息讀取完畢,再次睡眠。因此loop的循環(huán)并不會對CPU性能有過多的消耗
Activity A跳轉(zhuǎn)Activity B,再按返回鍵,生命周期執(zhí)行的順序?
- A.onPause() B.onCreate() B.onStart() B.onResume() A.onStop()
- 另外 如果Activity B是透明的 或者Activity B 并未完全遮住Activity A,那么上述操作點擊Activity A 跳轉(zhuǎn) Activity B 生命周期中A.onStop()是不會被調(diào)用的,因為Activity A還可見,所以Activity A不能被停止
View 的繪制流程
- Activity、Window、DecorView之間關(guān)系
public void setContentView(@LayoutRes int layoutResID) {
// 將xml布局傳遞到Window當(dāng)中
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
- 從代碼可以看出,Activity的setContentView實質(zhì)是將View傳遞到Window的setContentView()方法中,Window的setContenView會在內(nèi)部調(diào)用installDecor()方法創(chuàng)建DecorView
- View的繪制是從
ViewRootImpl的performTraversals()方法開始,從最頂層的View(ViewGroup)開始逐層對每個View進(jìn)行繪制操作
- measure:為測量寬高過程,如果是ViewGroup還要在onMeasure中對所有子View進(jìn)行measure操作。
- layout:用于擺放View在ViewGroup中的位置,如果是ViewGroup要在onLayout方法中對所有子View進(jìn)行l(wèi)ayout操作。
- draw:往View上繪制圖像。
- View 的繪制流程是 measure -> layout -> draw
View 事件分發(fā)機(jī)制
- View 的事件分發(fā)機(jī)制主要涉及到以下幾個方法
- dispatchTouchEvent ,這個方法主要是用來分發(fā)事件的
- onInterceptTouchEvent,這個方法主要是用來攔截事件的(需要注意的是 ViewGroup 才有這個方法,View 沒有 onInterceptTouchEvent 這個方法)
- onTouchEvent 這個方法主要是用來處理事件的
- requestDisallowInterceptTouchEvent(true),這個方法能夠影響父View是否攔截事件,true 表示父 View 不攔截事件,false 表示父 View 攔截事件
- 當(dāng)觸摸事件發(fā)生時,首先 Activity 將 TouchEvent 傳遞給最頂層的 View,TouchEvent最先到達(dá)最頂層 view 的 dispatchTouchEvent,然后由 dispatchTouchEvent方法進(jìn)行分發(fā),
如果dispatchTouchEvent返回true 消費事件,事件終結(jié)。
如果dispatchTouchEvent返回 false ,則回傳給父View的onTouchEvent事件處理;
如果dispatchTouchEvent返回super的話,默認(rèn)會調(diào)用自己的onInterceptTouchEvent方法。
默認(rèn)的情況下onInterceptTouchEvent回調(diào)用super方法,super方法默認(rèn)返回false,所以會交給子View的onDispatchTouchEvent方法處理
如果 interceptTouchEvent 返回 true ,也就是攔截掉了,則交給它的 onTouchEvent 來處理,
如果 interceptTouchEvent 返回 false ,那么就傳遞給子 view ,由子 view 的 dispatchTouchEvent 再來開始這個事件的分發(fā)。
Activity 四種啟動模式
-
standard : 默認(rèn)啟動模式,每開啟一個
activity就在任務(wù)棧中創(chuàng)建一個新的實例.Top single 頂部只有一個 不允許存在兩個相同的Activity.使用場景:基本絕大多數(shù)地方都可以用。 -
singleTop: 如果在任務(wù)的棧頂正好存有該
Activity的實例,則會通過調(diào)用onNewIntent()方法進(jìn)行重用,否則就會同 standard 模式一樣,創(chuàng)建新的實例并放入棧頂。即便棧中已經(jīng)存在了該 Activity 的實例,也會創(chuàng)建新的實例
當(dāng)且僅當(dāng)啟動的 Activity 和上一個 Activity 一致的時候才會通過調(diào)用onNewIntent()方法重用 Activity 。使用場景:資訊閱讀類 APP 的內(nèi)容界面。 -
singleTask : 在同一個任務(wù)棧中,如果要啟動的目標(biāo)Activity已經(jīng)在棧中,則會復(fù)用該Activity,并調(diào)用其
onNewIntent()方法,并且該Activity上面的Activity會被清除,如果棧中沒有,則創(chuàng)建新的實例。使用場景:瀏覽器的主頁面,或者大部分 APP 的主頁面。 -
singleInstance: 指定為
singleInstance模式的活動會啟用一個新的返回棧來管理這個活動。在一個新棧中創(chuàng)建該 Activity 的實例,并讓多個應(yīng)用共享該棧中的該 Activity 實例。一旦該模式的 Activity 實例已經(jīng)存在于某個棧中,任何應(yīng)用再激活該 Activity 時都會重用該棧中的實例,是的,依然是調(diào)用onNewIntent()方法。其效果相當(dāng)于多個應(yīng)用共享一個應(yīng)用,不管是誰激活,該 Activity 都會進(jìn)入同一個應(yīng)用中。但值得引起注意的是:singleInstance不要用于中間頁面,如果用戶中間頁面,跳轉(zhuǎn)會出現(xiàn)很難受的問題。 這個在實際開發(fā)中我暫未遇到過, Android 系統(tǒng)的來電頁面,多次來電都是使用的同一個Activity。
Service啟動方式
- startService. startService() 啟動一個 Service。一旦啟動,Service 將一直運行在后臺,即使啟動這個 Service 的組件已經(jīng)被銷毀。通常一個被 start 的 Service 會在后臺執(zhí)行單獨的操作,也并不需要給啟動它的組件返回結(jié)果。只有當(dāng) Service 自己調(diào)用 stopSelf() 或者其它組件調(diào)用 stopService() 才會終止。
- **bindService **. bindService() 來綁定一個 Service。這種方式會讓 Service 和啟動它的組件綁定在一起,當(dāng)啟動它的組件銷毀的時候,Service 也會自動進(jìn)行 unBind 操作。同一個 Service 可以被多個組件綁定,只有所有綁定它的組件都進(jìn)行了 unBind 操作,這個 Service 才會被銷毀
- Service 的生命周期
當(dāng)調(diào)用 startService() 去 start 一個 Service 后,仍然可以 bind 這個 Service。比如:當(dāng)播放音樂的時候,需要調(diào)用 startService() 啟動指定的音樂,當(dāng)需要獲取該音樂的播放進(jìn)度的時候,又需要調(diào)用 bindService(),在這種情況下,除非 Service 被 unbind,此前調(diào)用 stopService() 和 stopSelf() 都不能停止該 Service
完整生命周期(entire lifetime):從 onCreate() 被調(diào)用,到 onDestroy() 返回。和 Activity 類似,一般在 onCreate() 方法中做一些初始化的工作,在 onDestroy() 中做一些資源釋放的工作。如,若 Service 在后臺播放一個音樂,就需要在 onCreate() 方法中開啟一個線程啟動音樂,并在 onDestroy() 中結(jié)束線程。
活動生命周期(activity lifetime):從 onStartCommand() 或 onBind() 回調(diào)開始,由相應(yīng)的 startService() 或 bindService() 調(diào)用。start 方式的活動生命周期結(jié)束就意味著完整證明周期的結(jié)束,而 bind 方式,當(dāng) onUnbind() 返回后,Service 的活動生命周期結(jié)束。
是 startService() 還是 bindService() 啟動 Service,onCreate() 和 onDestroy() 均會被回調(diào)
Service 的 onCreate() 可以執(zhí)行耗時操作嗎?
- Service 運行在主線程中,它并不是一個新的線程,也不是新的進(jìn)程,所以并不能執(zhí)行耗時操作。
如果要在 Service 中執(zhí)行耗時操作,怎么做?
- 使用 AysncTask 或 HandlerThread 來替代 Thread 創(chuàng)建線程。
- IntentService 繼承于 Service,若 Service 不需要同時處理多個請求,那么使用 IntentService 將是最好選擇。只需要重寫 onHandleIntent() 方法,該方法接收一個回調(diào)的 Intent 參數(shù), 在方法內(nèi)進(jìn)行耗時操作,因為它默認(rèn)開啟了一個子線程,操作執(zhí)行完成后也無需手動調(diào)用 stopSelf() 方法,onHandleIntent() 將會自動調(diào)用該方法
Service 與 IntentService區(qū)別
- Service 不是運行在獨立的線程,所以不建議在Service中編寫耗時的邏輯和操作,否則會引起ANR。
- IntentService
- 可用于執(zhí)行后臺耗時的任務(wù),任務(wù)執(zhí)行后會自動停止。
- 具有高優(yōu)先級,適合高優(yōu)先級的后臺任務(wù),且不容易被系統(tǒng)殺死。
- 可以多次啟動,每個耗時操作都會以工作隊列的方式在IntentService的onHandleIntent回調(diào)方法中執(zhí)行
Serializable 和Parcelable的區(qū)別
- 平臺區(qū)別。Serializable是屬于 Java 自帶的,表示一個對象可以轉(zhuǎn)換成可存儲或者可傳輸?shù)臓顟B(tài),序列化后的對象可以在網(wǎng)絡(luò)上進(jìn)行傳輸,也可以存儲到本地。
Parcelable 是屬于 Android 專用。不過不同于Serializable,Parcelable實現(xiàn)的原理是將一個完整的對象進(jìn)行分解。而分解后的每一部分都是Intent所支持的數(shù)據(jù)類型。 - 編寫上的區(qū)別。Serializable代碼量少,寫起來方便
Parcelable代碼多一些,略復(fù)雜 - 選擇的原則
- 如果是僅僅在內(nèi)存中使用,比如activity、service之間進(jìn)行對象的傳遞,強(qiáng)烈推薦使用Parcelable,因為Parcelable比Serializable性能高很多。因為Serializable在序列化的時候會產(chǎn)生大量的臨時變量, 從而引起頻繁的GC。
- 如果是持久化操作,推薦Serializable,雖然Serializable效率比較低,但是還是要選擇它,因為在外界有變化的情況下,Parcelable不能很好的保存數(shù)據(jù)的持續(xù)性。
- 本質(zhì)的區(qū)別
- Serializable的本質(zhì)是使用了反射,序列化的過程比較慢,這種機(jī)制在序列化的時候會創(chuàng)建很多臨時的對象,比引起頻繁的GC、
- Parcelable方式的本質(zhì)是將一個完整的對象進(jìn)行分解,而分解后的每一部分都是Intent所支持的類型,這樣就實現(xiàn)了傳遞對象的功能了
Serializable中為什么要設(shè)置UID,設(shè)置UID與不設(shè)置UID值的區(qū)別和影響 ?
- serialVersionUID 是用來輔助序列化和反序列化的過程。 序列化后的數(shù)據(jù)中的serialVersionUID只有和當(dāng)前類的serialVersionUID一致才能成功的反序列化
- serialVersionUID 適用于java序列化機(jī)制。簡單來說,JAVA序列化的機(jī)制是通過判斷類的serialVersionUID來驗證的版本一致的。在進(jìn)行反序列化時,JVM會把傳來的字節(jié)流中的serialVersionUID于本地相應(yīng)實體類的serialVersionUID進(jìn)行比較。如果相同說明是一致的,可以進(jìn)行反序列化,否則會出現(xiàn)反序列化版本一致的異常,即是InvalidCastException。
- 具體序列化的過程 :序列化操作時會把系統(tǒng)當(dāng)前類的serialVersionUID寫入到序列化文件中,當(dāng)反序列化時系統(tǒng)會自動檢測文件中的serialVersionUID,判斷它是否與當(dāng)前類中的serialVersionUID一致。如果一致說明序列化文件的版本與當(dāng)前類的版本是一樣的,可以反序列化成功,否則就失?。?/li>
- serialVersionUID有兩種顯示的生成方式:
- 默認(rèn)的1L,比如:private static final long serialVersionUID = 1L;
- 根據(jù)包名,類名,繼承關(guān)系,非私有的方法和屬性,以及參數(shù),返回值等諸多因子計算得出的,極度復(fù)雜生成的一個64位的哈希字段。基本上計算出來的這個值是唯一的。比如:private static final long serialVersionUID = xxxxL;
注意:顯示聲明serialVersionUID可以避免對象不一致
內(nèi)存泄漏的場景和解決辦法
- 非靜態(tài)內(nèi)部類的靜態(tài)實例
非靜態(tài)內(nèi)部類會持有外部類的引用,如果非靜態(tài)內(nèi)部類的實例是靜態(tài)的,就會長期的維持著外部類的引用,組織被系統(tǒng)回收,解決辦法是使用靜態(tài)內(nèi)部類 - 多線程相關(guān)的匿名內(nèi)部類和非靜態(tài)內(nèi)部類
匿名內(nèi)部類同樣會持有外部類的引用,如果在線程中執(zhí)行耗時操作就有可能發(fā)生內(nèi)存泄漏,導(dǎo)致外部類無法被回收,直到耗時任務(wù)結(jié)束,解決辦法是在頁面退出時結(jié)束線程中的任務(wù) - Handler內(nèi)存泄漏
Handler導(dǎo)致的內(nèi)存泄漏也可以被歸納為非靜態(tài)內(nèi)部類導(dǎo)致的,Handler內(nèi)部message是被存儲在MessageQueue中的,有些message不能馬上被處理,存在的時間會很長,導(dǎo)致handler無法被回收,如果handler是非靜態(tài)的,就會導(dǎo)致它的外部類無法被回收,解決辦法是1.使用靜態(tài)handler,外部類引用使用弱引用處理2.在退出頁面時移除消息隊列中的消息 - Context導(dǎo)致內(nèi)存泄漏
根據(jù)場景確定使用Activity的Context還是Application的Context,因為二者生命周期不同,對于不必須使用Activity的Context的場景(Dialog),一律采用Application的Context,單例模式是最常見的發(fā)生此泄漏的場景,比如傳入一個Activity的Context被靜態(tài)類引用,導(dǎo)致無法回收 - 靜態(tài)View導(dǎo)致泄漏
使用靜態(tài)View可以避免每次啟動Activity都去讀取并渲染View,但是靜態(tài)View會持有Activity的引用,導(dǎo)致無法回收,解決辦法是在Activity銷毀的時候?qū)㈧o態(tài)View設(shè)置為null(View一旦被加載到界面中將會持有一個Context對象的引用,在這個例子中,這個context對象是我們的Activity,聲明一個靜態(tài)變量引用這個View,也就引用了activity) - WebView導(dǎo)致的內(nèi)存泄漏
WebView只要使用一次,內(nèi)存就不會被釋放,所以WebView都存在內(nèi)存泄漏的問題,通常的解決辦法是為WebView單開一個進(jìn)程,使用AIDL進(jìn)行通信,根據(jù)業(yè)務(wù)需求在合適的時機(jī)釋放掉 - 資源對象未關(guān)閉導(dǎo)致
如Cursor,F(xiàn)ile等,內(nèi)部往往都使用了緩沖,會造成內(nèi)存泄漏,一定要確保關(guān)閉它并將引用置為null - 集合中的對象未清理
集合用于保存對象,如果集合越來越大,不進(jìn)行合理的清理,尤其是入股集合是靜態(tài)的 - Bitmap導(dǎo)致內(nèi)存泄漏
bitmap是比較占內(nèi)存的,所以一定要在不使用的時候及時進(jìn)行清理,避免靜態(tài)變量持有大的bitmap對象 - 監(jiān)聽器未關(guān)閉
很多需要register和unregister的系統(tǒng)服務(wù)要在合適的時候進(jìn)行unregister,手動添加的listener也需要及時移除
EventBus原理
- 主要是維護(hù)了幾個數(shù)組,然后根據(jù)對應(yīng)的key找到對應(yīng)的注冊對象,通過放射的方式調(diào)用對應(yīng)的方法。
- EventBus 2.x 是采用反射的方式對整個注冊的類的所有方法進(jìn)行掃描來完成注冊,當(dāng)然會有性能上的影響。EventBus 3.0 中EventBus提供了EventBusAnnotationProcessor注解處理器來在編譯期通過讀取@Subscribe()注解并解析、處理其中所包含的信息,然后生成java類來保存所有訂閱者關(guān)于訂閱的信息,這樣就比在運行時使用反射來獲得這些訂閱者的信息速度要快
/注冊事件
EventBus.getDefault().register(this);
//注冊方法
@Subscribe
public void event(BaseEventBusBeaan message) {
LogUtils.d("EventBusActivity event");
}
//發(fā)送事件
EventBus.getDefault().post(new BaseEventBusBeaan("123", new Bundle()));
//反注冊
EventBus.getDefault().unregister(this);
總結(jié)一下大概的流程
- 通過apt在編譯期將所有被
@Subscribe注解的函數(shù)添加到MyEventBusIndex對象中。 - 在
register過程中生成subscriptionsByEventType的數(shù)據(jù)。 - 在
post過程中通過subscriptionsByEventType數(shù)據(jù)查找對應(yīng)的函數(shù),然后再通過反射的方式調(diào)用。
優(yōu)先級的問題
這個問題也十分簡單,只需要在插入數(shù)據(jù)的時候,做下優(yōu)先級判斷即可。
Android 熱更新 流程和原理
- 一個完整的項目應(yīng)該有如下分支:
develop分支---- 這個分支中放的是線上的發(fā)布版本。
bugfix分支---- 這個是熱更新分支,由develop中遷出。
master分支---- 這個是開發(fā)中的分支 - 熱更新應(yīng)當(dāng)按照如下步驟進(jìn)行:
- 線上檢測到嚴(yán)重的crash
- 從develop中拉出一個線上的最新版,在bugfix分支上進(jìn)行問題的修復(fù)
- jenkins構(gòu)建和補(bǔ)丁的生成
- app通過推送或主動拉取補(bǔ)丁文件
- 將bugfix代碼合到master上,保證以后不會出現(xiàn)該問題
- 主流熱更新框架介紹
- Dexposed :該框架是阿里巴巴開源的一個Android平臺下的無侵入的運行時AOP(面向方向編程)框架。基于以前開源的一個Xposed框架實現(xiàn)的。Dexposed框架基于Hook技術(shù)實現(xiàn)功能,不僅可以hook你自己的程序代碼,也可以hook你的應(yīng)用程序中調(diào)用的Android框架中的函數(shù)?;趧討B(tài)類加載技術(shù),運行中的app可以加載一小段經(jīng)過編譯的Java的代碼,而且在不需要重寫APP的前提下,就可以實現(xiàn)修改APP
- AndFix:該框架同樣出自阿里。與Dexposed不是同一個團(tuán)隊。AndFix也是基于Xposed思想。而相比第一個,它是一個更純粹的熱修復(fù)的框架。
- Nuwa:它其實是基于類加載器ClassLoader加載Dex文件。如果多個Dex文件存在相同的類,那么排在前面的Dex文件就將優(yōu)先被選擇。這是熱更新最主要的思想,它通過不斷地去輪詢,去遍歷Dex的一個數(shù)組,然后把我們需要修改的類的dex文件加到最前面,這樣當(dāng)輪詢遍歷時就不會加載有問題的那個類。
- 熱更新的原理
- Android的類加載機(jī)制 :Android的類加載器主要有這樣兩個:PathClassLoader和DexClassLoader。PathClassLoader主要用于加載系統(tǒng)的類和應(yīng)用類,DexClassLoader主要加載Dex文件,Jar文件,apk文件等。
- 熱修復(fù)機(jī)制:在BaseClassLoader中會創(chuàng)建一個dexElements數(shù)組,然后我們會通過ClassLoader遍歷這個數(shù)組,加載這個數(shù)組中的dex文件。這樣當(dāng)BaseClassLoader加載到正確的類以后,就不會去加載有Crash的那個類。因此我們就將這個有問題修復(fù)后的類放入Dex文件當(dāng)中,讓這個Dex文件排在dexElements前面。這樣BaseClassLoader就不會加載到處于后面的那個Dex文件。這樣就完成了整個熱修復(fù)過程。
Android線程間通信四種方式:
- 通過
Handler機(jī)制
主線程中定義Handler,子線程發(fā)消息,通知Handler完成UI更新,Handler對象必須定義在主線程中,如果是多個類直接互相調(diào)用,就不是很方便,需要傳遞content對象或通過接口調(diào)用。另外Handler機(jī)制與Activity生命周期不一致的原因,容易導(dǎo)致內(nèi)存泄漏,不推薦使用。
- 通過
-
runOnUiThread方法,用Activity對象的runOnUiThread方法更新,在子線程中通過runOnUiThread()方法更新UI,強(qiáng)烈推薦使用。
-
- 3.
View.post(Runnable r)這種方法更簡單,但需要傳遞要更新的View過去,推薦使用 -
AsyncTask,即異步任務(wù),是Android給我們提供的一個處理異步任務(wù)的類.通過此類,可以實現(xiàn)UI線程和后臺線程進(jìn)行通訊,后臺線程執(zhí)行異步任務(wù),并把結(jié)果返回給UI線程
-
Android中有哪些進(jìn)程間通信方式?
-
Binder簡單易用 只能傳輸Bundle支持的數(shù)據(jù)類型 四大組件間的進(jìn)程間通信
文件共享 簡單易用 不適用高并發(fā)場景,并且無法做到進(jìn)程間即時通信 適用于無關(guān)發(fā)的情況下,交換簡單的數(shù)據(jù),對實時性要求不高的場景。 -
AIDL功能強(qiáng)大,支持一對多實時并發(fā)通信 使用稍復(fù)雜,需要處理好線程間的關(guān)系 一對多通信且有RPC需求 -
Messenger功能一般,支持一對多串行通信,支持實時通信 不能很好地處理高并發(fā)的情形,不支持RPC,由于數(shù)據(jù)通過Message傳輸,因此只能傳輸Bundle支持的數(shù)據(jù)類型 低并發(fā)的一對多實時通信,無RPC需求,或者無需要返回結(jié)果的RPC需求
-ContentProvider支持一對多的實時并發(fā)通信,在數(shù)據(jù)源共享方面功能強(qiáng)大,可通過Call方法擴(kuò)展其它操作 可以理解為受約束的AIDL,主要提供對數(shù)據(jù)源的CRUD操作 一對多的進(jìn)程間數(shù)據(jù)共享 -
BroadcastReceiver操作簡單,對持一對多實時通信 只支持?jǐn)?shù)據(jù)單向傳遞,效率低且安全性不高 一對多的低頻率單向通信 -
Socket功能強(qiáng)大,可通過網(wǎng)絡(luò)傳輸字節(jié)流,支持一對多實時并發(fā)通信 實現(xiàn)細(xì)節(jié)步驟稍繁瑣,不支持直接的RPC 網(wǎng)絡(luò)間的數(shù)據(jù)交換
websocket 和 http相關(guān)
- 什么是
websocket?
websocket是HTML5的一種新協(xié)議,允許服務(wù)器想客戶端傳遞信息,實現(xiàn)瀏覽器和客戶端雙工通信。
- 什么是
-
websocket特點
(1)與http協(xié)議有良好的兼容性;
(2)建立在TCP協(xié)議之上,和http協(xié)議同屬于應(yīng)用層;
(3)數(shù)據(jù)格式比較輕量,性能開銷小,通信高效;
(4)可以發(fā)送文本,也可以發(fā)送二進(jìn)制;
(5)沒有同源限制,可以與任意服務(wù)器通信。
-
-
http和websocket的區(qū)別
3.1http協(xié)議是短鏈接,因為請求之后,都會關(guān)閉連接,下次請求需要重新打開鏈接。
3.2websocket協(xié)議是一種長連接,只需要通過一次請求來初始化連接,然后所有請求和響應(yīng)都是通過TCP鏈接進(jìn)行通信。
-
- 特點
最大特點就是,服務(wù)器可以主動向客戶端推送信息,客戶端也可以主動向服務(wù)器發(fā)送信息,是真正的雙向平等對話,屬于服務(wù)器推送技術(shù)的一種。
其他特點包括:
(1)建立在 TCP 協(xié)議之上,服務(wù)器端的實現(xiàn)比較容易。
(2)與 HTTP 協(xié)議有著良好的兼容性。默認(rèn)端口也是80和443,并且握手階段采用 HTTP 協(xié)議,因此握手時不容易屏蔽,能通過各種 HTTP 代理服務(wù)器。
(3)數(shù)據(jù)格式比較輕量,性能開銷小,通信高效。
(4)可以發(fā)送文本,也可以發(fā)送二進(jìn)制數(shù)據(jù)。
(5)沒有同源限制,客戶端可以與任意服務(wù)器通信。
(6)協(xié)議標(biāo)識符是ws(如果加密,則為wss),服務(wù)器網(wǎng)址就是 URL
- 特點