久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

雜談

 gearss 2016-04-15

轉(zhuǎn)載請注明出處
博客地址:http://blog.csdn.net/JonsTank2013/article/details/51118563
作者:李中權(quán)

前言

好久沒有寫博客了,,瞬間感覺好多學(xué)了的東西不進(jìn)行一個自我的總結(jié)與消化總歸變不成自己的,。通過博客可能還可以找到一些當(dāng)初在學(xué)習(xí)的時候沒有想到的問題。想了半天,,從大二上學(xué)期自學(xué)Android以來還沒有對Android從啟動到程序運(yùn)行期間進(jìn)行一個完整的歸納,,剛好最近又學(xué)到了一些新東西,,那就以這篇博客為媒介,總結(jié)一下從Android啟動到程序運(yùn)行期間發(fā)生的所有事吧,。包括什么ClassLoader, JVM,IPC, 消息處理機(jī)制要是總結(jié)到了就順帶BB一下,。但是這里就不包含很多細(xì)節(jié)了,比如為什么PMS內(nèi)部為什么要這么構(gòu)造,,好處是什么,,如果我來設(shè)計(jì)的話我會怎么設(shè)計(jì)啊這種暫時就不總結(jié)了,因?yàn)槲矣X得以我現(xiàn)在的水平還有學(xué)習(xí)精力來說把這些細(xì)節(jié)都一個個的弄清楚有點(diǎn)沒抓住重點(diǎn)?,F(xiàn)階段還是先能夠了解整個流程,,有個大局觀才是最重要的。至于以后如果有需要或者是有精力的時候再一個個的突破,。

發(fā)現(xiàn)本文的錯誤或者遺漏后會立刻更改

在正式開始之前還是忍不住想要BB一下最近參加的京東筆試,,被坑得有點(diǎn)憋屈。憋屈啥勒,,被編譯器坑了,。這次京東的筆試說實(shí)話感覺真的好簡單,真的沒有什么技術(shù)上的難點(diǎn),,但是尼瑪編程題把我坑了,。提前一個小時把代碼在本地編譯器上編譯完成并通過,當(dāng)時心里還有些小激動,,一提交,,在線編譯器說得不到指定結(jié)果,尼瑪,,頓時整個人都斯巴達(dá)了,。最開始的時候還以為是自己本身代碼的Bug,后來順著思路又理了幾遍,,完全沒問題啊,,又自己創(chuàng)了幾個新的輸入也都能夠運(yùn)行,返回正常結(jié)果,。整個人都是崩潰的,,在這上面花了20多分鐘時候不經(jīng)意間瞥了一下左邊的樣例輸入和輸出,哦豁,,這下全懂了,。
因?yàn)槲覜]有很多這種參加在線筆試的經(jīng)驗(yàn),也沒在網(wǎng)上怎么刷題,,所以在樣例輸入和輸出那里摻雜了一些自己想當(dāng)然的想法。
題目要求的樣例輸入是一直輸入,,有兩種情況,,一種情況返回No,一種情況返回Yes并返回對應(yīng)的結(jié)果。是要求連續(xù)輸入的,也就是你在輸入的時候我至少要用一個數(shù)組或者是List,、Map來保存你的輸入,。當(dāng)檢測到輸入為空也就是直接按了回車的同時就開始運(yùn)行,然后再一次性的打印出結(jié)果,。我不知道啊,,第一次看這種樣例輸入輸出,一看以為只要能返回就好了,,然后就是分開做的,,輸入錯的就返回No,輸入對的就返回Yes和結(jié)果,,并不能夠一起輸入及返回,。而這個時候時間又過了好多了,改代碼的話整個代碼的架構(gòu)都要變,,時間上完全來不及,。這筆試要是編程題錯了那估計(jì)是沒戲了。
這其實(shí)也怪自己吧,,怨不得別的,,只好等下次了,只是這次的題真的簡單,,錯過了好可惜,,畢竟還是非常想進(jìn)京東鍛煉鍛煉的,就算進(jìn)不了去體驗(yàn)京東的面試,,知道哪里有不足也是好的,。

正式開始

上面BB了這么多,也是超過了我的預(yù)料,,這里就正式開始這篇博客了,。

首先,我們知道,,Android是基于Linux的一個操作系統(tǒng),,它可以分為五層,下面是它的層次架構(gòu)圖,,可以記一下,,因?yàn)楹竺鎽?yīng)該會總結(jié)到SystemServer這些Application Framework層的東西

Android的五層架構(gòu)從上到下依次是應(yīng)用層,應(yīng)用框架層,,庫層,,運(yùn)行時層以及Linux內(nèi)核層。

而在Linux中,,它的啟動可以歸為一下幾個流程:
Boot Loader-》初始化內(nèi)核-》,。,。。,。,。。
當(dāng)初始化內(nèi)核之后,,就會啟動一個相當(dāng)重要的祖先進(jìn)程,,也就是init進(jìn)程,在Linux中所有的進(jìn)程都是由init進(jìn)程直接或間接fork出來的,。

而對于Android來說,,前面的流程都是一樣的,而當(dāng)init進(jìn)程創(chuàng)建之后,,會fork出一個Zygote進(jìn)程,,這個進(jìn)程是所有Java進(jìn)程的父進(jìn)程。我們知道,,Linux是基于C的,,而Android是基于Java的(當(dāng)然底層也是C)。所以這里就會fork出一個Zygote Java進(jìn)程用來fork出其他的進(jìn)程,。【斷點(diǎn)1】

總結(jié)到了這里就提一下之后會談到的幾個非常重要的對象以及一個很重要的概念,。

  • ActivityManagerServices(AMS):它是一個服務(wù)端對象,負(fù)責(zé)所有的Activity的生命周期,,ActivityThread會通過Binder與之交互,,而AMS與Zygote之間進(jìn)行交互則是通過Socket通信(IPC通信在之后會總結(jié)到)
  • ActivityThread:它也就是我們俗稱的UI線程/主線程,它里面存在一個main()方法,,這也是APP的真正入口,,當(dāng)APP啟動時,就會啟動ActivityThread中的main方法,,它會初始化一些對象,,然后開啟消息循環(huán)隊(duì)列(之后總結(jié)),之后就會Looper.loop死循環(huán),,如果有消息就執(zhí)行,,沒有就等著,也就是事件驅(qū)動模型(edt)的原理,。
  • ApplicationThread:它實(shí)現(xiàn)了IBinder接口,,是Activity整個框架中客戶端和服務(wù)端AMS之間通信的接口,同時也是ActivityThread的內(nèi)部類,。這樣就有效的把ActivityThread和AMS綁定在一起了,。
  • Instrumentation:這個東西我把它理解為ActivityThread的一個工具類,也算是一個勞動者吧,,對于生命周期的所有操作例如onCreate最終都是直接由它來執(zhí)行的,。

Android系統(tǒng)中的客戶端和服務(wù)器的概念
在Android系統(tǒng)中其實(shí)也存在著服務(wù)器和客戶端的概念,,服務(wù)器端指的就是所有App共用的系統(tǒng)服務(wù),比如上面的AMS,,PackageManagerService等等,這些系統(tǒng)服務(wù)是被所有的App共用的,,當(dāng)某個App想要實(shí)現(xiàn)某個操作的時候,,就會通知這些系統(tǒng)服務(wù)。

繼續(xù)斷點(diǎn)1

當(dāng)Zygote被初始化的時候,,會fork出System Server進(jìn)程,,這個進(jìn)程在整個的Android進(jìn)程中是非常重要的一個,地位和Zygote等同,,它是屬于Application Framework層的,,Android中的所有服務(wù),例如AMS, WindowsManager, PackageManagerService等等都是由這個SystemServer fork出來的,。所以它的地位可見一斑,。

而當(dāng)System Server進(jìn)程開啟的時候,就會初始化AMS,,同時,,會加載本地系統(tǒng)的服務(wù)庫,創(chuàng)建系統(tǒng)上下文,,創(chuàng)建ActivityThread及開啟各種服務(wù)等等,。而在這之后,就會開啟系統(tǒng)的Launcher程序,,完成系統(tǒng)界面的加載與顯示,。【斷點(diǎn)2】

Context總結(jié)

Context是一個抽象類,下面是它的注釋信息,,摘自源碼,。

/**
 * Interface to global information about an application environment.  This is
 * an abstract class whose implementation is provided by
 * the Android system.  It
 * allows access to application-specific resources and classes, as well as
 * up-calls for application-level operations such as launching activities,
 * broadcasting and receiving intents, etc.
 */
public abstract class Context {
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

從上面的這段話可以簡單理解一下,Context是一個關(guān)于應(yīng)用程序環(huán)境的全局變量接口,,通過它可以允許去獲得資源或者類,,例如啟動Activity,廣播,intent等等,。

我的理解:Context的具體實(shí)現(xiàn)是Application, Activity,Service,,通過Context能夠有權(quán)限去做一些事情,其實(shí)我覺得就是一個運(yùn)行環(huán)境的問題,。

需要注意的地方
Android開發(fā)中由于很多地方都包含了Context的使用,,因此就必須要注意到內(nèi)存泄露或者是一些可能會引起的問題。

例如在Toast中,,它的Context就最好設(shè)置為Application Context,,因?yàn)槿绻鸗oast在顯示東西的時候Activity關(guān)閉了,,但是由于Toast仍然持有Activity的引用,那么這個Activity就不會被回收掉,,也就造成了內(nèi)存泄露,。

Toast的相關(guān)總結(jié)

上面舉例的時候舉到了Toast,其實(shí)Toast也是很有意思的一個東西,,它的show方法其實(shí)并不是顯示一個東西這么簡單,。
Toast實(shí)際上是一個隊(duì)列,會通過show方法把新的任務(wù)加入到隊(duì)列當(dāng)中去,,列隊(duì)中只要存在消息就會彈出來使用,而隊(duì)列的長度據(jù)說默認(rèn)是40個(這是網(wǎng)上搜出來的,,我在源碼中沒找到對應(yīng)的設(shè)置,,感覺也沒啥必要就沒找了)。
所以這里就要注意一下show這個操作了,,它并不是顯示內(nèi)容,,而是把內(nèi)容入隊(duì)列。

/**
     * Show the view for the specified duration.
     */
    public void show() {
        if (mNextView == null) {
            throw new RuntimeException("setView must have been called");
        }

        INotificationManager service = getService();
        String pkg = mContext.getOpPackageName();
        TN tn = mTN;
        tn.mNextView = mNextView;

        try {
            service.enqueueToast(pkg, tn, mDuration);
        } catch (RemoteException e) {
            // Empty
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

Handler的內(nèi)存泄露

對于Handler來說,如果我們直接在AndroidStudio中創(chuàng)建一個非靜態(tài)內(nèi)部類Handler,,那么Handler這一大片的區(qū)域會被AS標(biāo)記為黃色,,這個應(yīng)該很多人都遇到過吧。實(shí)際上是因?yàn)檫@樣設(shè)置會造成內(nèi)存泄露,,因?yàn)槊恳粋€非靜態(tài)內(nèi)部類都會持有一個外部類的引用,,那么這里也就產(chǎn)生了一個內(nèi)存泄露的可能點(diǎn),如果當(dāng)Activity被銷毀時沒有與Handler解除,,那么Handler仍然會持有對該Activity的引用,那么就造成了內(nèi)存泄露,。

解決方案
使用static修飾Handler,,這樣也就成了一個靜態(tài)內(nèi)部類,那么就不會持有對外部類的引用了,。而這個時候就可以在Handler中創(chuàng)建一個WeakReference(弱引用)來持有外部的對象。只要外部解除了與該引用的綁定,,那么垃圾回收器就會在發(fā)現(xiàn)該弱引用的時候立刻回收掉它,。

垃圾回收

關(guān)于垃圾回收的相關(guān)總結(jié)看我之前的博客,傳送門:JVM原理及底層探索

四種引用方式

上面扯到了弱引用,,就再BB一下四種引用方式吧,。

  • 強(qiáng)引用:垃圾回收器打死都不會回收掉一個強(qiáng)引用的,那怕是出現(xiàn)OOM也不會回收掉強(qiáng)引用,,所有new出來的都是強(qiáng)引用,。
  • 軟引用:垃圾回收器會在內(nèi)存不足的情況下回收掉軟引用,如果內(nèi)存充足的話不會理它
  • 弱引用:它跟軟引用類似,,但是它更脆弱,,只要垃圾回收器一發(fā)現(xiàn)它,就會立刻回收掉它,。比如一個對象持有一個強(qiáng)引用和弱引用,,當(dāng)強(qiáng)引用被取消時,那么只要GC發(fā)現(xiàn)了它,,就會立刻回收掉,。只是GC發(fā)現(xiàn)它的這個過程是不確定的,有可能不會馬上發(fā)生,,所以它可能還會多活一會,,中間存在一個優(yōu)先級。
  • 虛引用:它跟上面3種方式都不同,。我對虛引用的理解就是如果一個對象持有虛引用,,那么就可以在被GC回收前進(jìn)行一些設(shè)定好的工作等等,。因?yàn)樘撘糜袀€機(jī)制,因?yàn)樘撘帽仨毢鸵藐?duì)列聯(lián)合使用,,當(dāng)垃圾回收器準(zhǔn)備回收一個對象時,,如果發(fā)現(xiàn)它還有虛引用,就回在回收對象的內(nèi)存前,,把這個虛引用加入到與之關(guān)聯(lián)的引用隊(duì)列中,。而程序如果判斷到引用隊(duì)列中已經(jīng)加入了虛引用,那么就可以了解到被引用的對象馬上就要被垃圾回收了,,這個時候就可以做些被回收之前的事情啦,。

ClassLoader

類加載器按層次從頂層到下依次為Boorsrtap ClassLoader(啟動類加載器),Extension ClassLoader(拓展類加載器),ApplicationClassLoader(應(yīng)用程序類加載器)

判斷兩個類是否是同一個類就是看它們是否是由同一個類加載器加載而來,。

這里就需要介紹一下雙親委派模式了:
雙親委派模式的意思就是:除了啟動類加載器之外,,其余的加載器都需要指定一個父類的加載器,當(dāng)需要加載的時候會先讓父類去試著加載,,如果父類無法加載也就是找不到這個類的話就會讓子類去加載

好處:防止內(nèi)存中出現(xiàn)多份同樣的字節(jié)碼

比如類A和類B都要加載system類,,如果不是委托的話,類A就會加載一份,,B也會加載一份,,那么就會出現(xiàn)兩份SYstem字節(jié)碼
如果使用委托機(jī)制,會遞歸的向父類查找,,也就是首選用Bootstrap嘗試加載,,如果找不到再向下,如果A用這個已經(jīng)加載了的話會直接返回內(nèi)存中的system而不需要重新加載,。那么就只會存在一份

延遲加載的應(yīng)用:單例模式

對于Java來說,,類是需要使用到時才會加載,這里也就出現(xiàn)了一個延遲加載的效果,。而在延遲加載的時候,,會默認(rèn)保持同步。這也就產(chǎn)生了一種單例模式的方式,,具體的看我之前的博客:設(shè)計(jì)模式_單例模式

我覺得在android所有的創(chuàng)建單例模式方法中里延遲加載方式是最好吧,,雖然枚舉比延遲加載更好,effiective java中也很推薦,,但是并不怎么適用于Android,,Android里枚舉的消耗是static的兩倍,延遲加載的話只要我們在使用延遲加載方式時做好反序列化的返回值readResolve()準(zhǔn)備就好了,。

繼續(xù)斷點(diǎn)2

上面BB了太多其他的,,現(xiàn)在有點(diǎn)緩不過來,下次自己看自己博客的時候會不會都被自己的思路帶得亂七八糟的。

上面的時候我們就已經(jīng)完成了整個Android系統(tǒng)的開機(jī)以及初始化,。接下來就可以B一下從點(diǎn)擊APP圖標(biāo)開始到APP內(nèi)部程序運(yùn)行起來的流程了,。

當(dāng)我們點(diǎn)擊屏幕時,觸摸屏的兩層電極會連接在一起,,也就產(chǎn)生了一個電壓(具體的我忘了,,書上有,圖找不到了),,當(dāng)產(chǎn)生電壓的時候,,就可以通過對應(yīng)的驅(qū)動把當(dāng)前按壓點(diǎn)的XY坐標(biāo)傳給上層,這里也就是操作系統(tǒng),。操作系統(tǒng)在獲取到XY值的時候,,就會對按壓點(diǎn)的范圍進(jìn)行一個判斷,如果確定按壓點(diǎn)處于一個APP圖標(biāo)或者是Button等等的范圍中時,,操作系統(tǒng)也就會認(rèn)為用戶當(dāng)前已經(jīng)點(diǎn)擊了這個東西,,啟動對應(yīng)的監(jiān)聽。

而當(dāng)系統(tǒng)判斷我們點(diǎn)擊的是APP圖標(biāo)時,,該App就由Launcher開始啟動了【斷點(diǎn)3】

Launcher

Launcher是一個繼承自Activity,同時實(shí)現(xiàn)了點(diǎn)擊事件,,長按事件等等的一個應(yīng)用程序,。

public final class Launcher extends Activity
        implements View.OnClickListener,OnLongClickListener, LauncherModel.Callbacks,View.OnTouchListener
  • 1
  • 2

當(dāng)我們點(diǎn)擊一個APP的圖標(biāo)時,會調(diào)用Launcher內(nèi)部的startActivitySafely()方法,,而這個方法則會進(jìn)行兩件事,,一個是啟動目標(biāo)activity,另一個功能就是捕獲異常ActivityNotFoundException,,也就是常見的“找不到activity,是否已經(jīng)在androidmenifest文件中注冊,?”。而在startActivity方法中,,經(jīng)過一系列的轉(zhuǎn)換最終會調(diào)用到startActivityForResult這個方法,。

    @Override
    public void startActivity(Intent intent, @Nullable Bundle options) {
        if (options != null) {
            startActivityForResult(intent, -1, options);
        } else {
            // Note we want to go through this call for compatibility with
            // applications that may have overridden the method.
            startActivityForResult(intent, -1);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

所以實(shí)際上,我對整個Android的界面是這樣理解的:
當(dāng)系統(tǒng)完成初始化以及各種服務(wù)的創(chuàng)建之后,,就會啟動Launcher這個應(yīng)用程序(它也是繼承自Activity的,,包含自己對應(yīng)的xml布局文件),然后再把各種圖標(biāo)按照一個正常APP布局的方式放在上面,,當(dāng)我們點(diǎn)擊APP圖標(biāo)時,,也就相當(dāng)于在Launcher這個APP應(yīng)用程序中通過startActivity(在底層最后會轉(zhuǎn)為startActivityForResult)來啟動這個APP。簡單的講,,我覺得就是一個主要的APP(Launcher)里面啟動了其他的功能APP,,例如QQ、微信這些。【個人理解,,如果以后發(fā)現(xiàn)不對再修改】

Android中點(diǎn)擊事件的處理

當(dāng)我們手指按下時,,Android是如何處理點(diǎn)擊事件的呢?如何確定是讓哪一個控件來處理呢,?
簡單一句話:層層傳遞-冒泡的方式處理
舉個例子:現(xiàn)在公司來了個小項(xiàng)目,,老板一看分配給經(jīng)理做,經(jīng)理一看分配給小組長,,小組長一看好簡單,,分配給組員。如果在這個傳遞過程中(也就是還為分配到最底部時),,某一層覺得我來負(fù)責(zé)這個比較好的話就會攔截掉這個消息,,然后把它處理了,下面的就收不到有消息的這個通知,。如果一直到了底層的話,,組員如果能完成,就完成它,。如果不能完成,,那么就報告給組長,說組長我做不來,,邊學(xué)邊做要影響進(jìn)度,。組長一看我也做不來,就給經(jīng)理,,經(jīng)理一看我也不會,,就給老板。這樣也就一層層的傳遞了,。
總結(jié)一下就是消息從上到下依次傳遞,,如果在傳遞的過程中被攔截了就停止下傳。如果沒有被攔截,,就一直傳遞到底部,,如果底部不能夠消耗該消息,那么就又一層層的返回來,,返給上層,,直到被消耗或者是到達(dá)最頂層。

在Android中,,存在三個重要的方法:

  • dispathTouchEvent(MotionEvent ev)
  • onInterceptTouchEvent(MotionEvent ev)
  • onTouchEvent(MotionEvent ev)

第一個方法負(fù)責(zé)事件的分發(fā),,它的返回值就是表示是否消耗當(dāng)前事件。
第二個方法是用于判斷是否攔截該消息,,如果當(dāng)前View攔截了某個時間,,那么在同一個事件序列中,,此方法不會被再次調(diào)用。返回結(jié)果表示是否攔截當(dāng)前事件
第三個方法就是處理事件,。返回結(jié)果表示是否消耗當(dāng)前事件,,如果不小號,則在同一時間序列中,,當(dāng)前View無法再次接收到事件,。

對于一個根ViewGroup來說,點(diǎn)擊事件產(chǎn)生后,,首先會傳遞給它,,調(diào)用它的dispath方法。如果這個ViewGroup的onIntercept方法返回true就表示它要攔截當(dāng)前事件,,false就表示不攔截,,這個時候事件就會繼續(xù)傳遞給子元素,接著調(diào)用子元素的dispath方法,,直到被處理,。

滑動沖突

順帶總結(jié)一下滑動沖突的解決吧
View的滑動沖突一般可以分為三種:

  • 外部滑動和內(nèi)部滑動方向不一致
  • 外部滑動方向和內(nèi)部滑動方向一致
  • 嵌套上面兩種情況

比如說一個常見的,外部一個ListView,,里面一個ScrollView,。這個時候該怎么解決呢?其實(shí)這里想到了ViewPager,,它里面實(shí)際上是解決了滑動沖突的,,可以借鑒一下它的。

滑動處理規(guī)則
一般來說,,我們可以根據(jù)用戶手指滑動的方向以及角度來判斷用戶是要朝著哪個方向去滑動。而很多時候還可以根據(jù)項(xiàng)目的需求來指定一套合適的滑動方案,。

外部攔截法
這種方法就是指所有的點(diǎn)擊時間都經(jīng)過父容器的攔截處理,,如果父容器需要此時間就攔截,如果不需要此事件就不攔截,。通過重寫父容器的onInterceptTouchEvent方法:

case MotionEvent.ACTION_DOWN:
    intercepted = false;
break;

case MotionEvent.ACTION_MOVE:
if(父類容器需要) {
    intercepted = true;
} else {
    intercepted = false;
}
break;

case MotionEvent.ACTION_UP:
    intercepted = false;
break;

return intercepted;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

這里有一點(diǎn)需要注意,,ACTION_DOWN事件父類容器就必須返回false,因?yàn)槿绻割惾萜鲾r截了的話,,后面的Move等所有事件都會直接由父類容器處理,,就無法傳給子元素了。UP事件也要返回false,,因?yàn)樗旧韥碚f沒有太多的意義,,但是對于子元素就不同了,如果攔截了,,那么子元素的onClick事件就無法觸發(fā),。

內(nèi)部攔截法
這種方法指的是父容器不攔截任何時間,,所有的事件都傳遞給子元素,如果子元素需要此事件就直接消耗掉,,否則就交給父容器進(jìn)行處理,。它需要配合requestDisallowInterceptTouchEvent方法才能正常工作。我們需要重寫子元素的dispatch方法

case MotionEvent.ACTION_DOWN:
    parent.requestDisallowInterceptTouchEvent(true);
break;

MotionEvent.ACTION_MOVE:
    if(父容器需要此類點(diǎn)擊事件) {
    parent.requestDisallowInterceptTouchEvent(false);
    }
break;

return super.dispatchTouchEvent(event);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

這種方法的話父類容器需要默認(rèn)攔截除了ACTION_DOWN以外的其他時間,,這樣當(dāng)子元素調(diào)用request方法的時候父元素才能繼續(xù)攔截所需的事件,。

其他的
如果覺得上面兩個方式太復(fù)雜,看暈了,,其實(shí)也可以自己根據(jù)項(xiàng)目的實(shí)際需要來指定自己的策略實(shí)現(xiàn),。例如根據(jù)你手指按的點(diǎn)的位置來判斷你當(dāng)前觸碰的是哪個控件,以此來猜測用戶是否是要對這個控件進(jìn)行操作,。如果點(diǎn)擊的是空白的地方,,就操作外部控件即可。

【等有時間了就把ViewPager的處理總結(jié)一下,,挺重要的】

繼續(xù)斷點(diǎn)3

  • 當(dāng)我們點(diǎn)擊桌面的APP圖標(biāo)時,,Launcher進(jìn)程會采用Binder的方式向AMS發(fā)出startActivity請求
  • AMS在接收到請求之后,就會通過Socket向Zygote進(jìn)程發(fā)送創(chuàng)建進(jìn)程的請求
  • Zygote進(jìn)程會fork出新的子進(jìn)程(APP進(jìn)程)
  • 之后APP進(jìn)程會再向AMS發(fā)起一次請求,,AMS收到之后經(jīng)過一系列的準(zhǔn)備工作再回傳請求,。
  • APP進(jìn)程收到AMS返回的請求后,會利用Handler向主線程發(fā)送LAUNCH_ACTIVITY消息
  • 主線程在收到消息之后,,就創(chuàng)建目標(biāo)Activity,,并回調(diào)onCreate()/onStart()/onResume()等方法,UI渲染結(jié)束后便可以看到App主界面
    【斷點(diǎn)4】

Handler/Looper/Message Queue/ThreadLocal機(jī)制

Android的消息機(jī)制主要是指Handler的運(yùn)行機(jī)制,,Handler的運(yùn)行需要底層的MessageQueue和Looper的支撐

雖然MessageQueue叫做消息隊(duì)列,,但是實(shí)際上它內(nèi)部的存儲結(jié)構(gòu)是單鏈表的方式。由于Message只是一個消息的存儲單元,,它不能去處理消息,,這個時候Looper就彌補(bǔ)了這個功能,Looper會以無限循環(huán)的形式去查找是否有新消息,,如果有的話就處理消息,,否則就一直等待(機(jī)制等會介紹)。而對于Looper來說,,存在著另外的一個很重要的概念,,就是ThreadLocal。

ThreadLocal

ThreadLocal它并不是一個線程,,而是一個可以在每個線程中存儲數(shù)據(jù)的數(shù)據(jù)存儲類,,通過它可以在指定的線程中存儲數(shù)據(jù),數(shù)據(jù)存儲之后,,只有在指定線程中可以獲取到存儲的數(shù)據(jù),,對于其他線程來說則無法獲取到該線程的數(shù)據(jù),。
舉個例子,多個線程通過同一個ThreadLocal獲取到的東西是不一樣的,,就算有的時候出現(xiàn)的結(jié)果是一樣的(偶然性,,兩個線程里分別存了兩份相同的東西),但他們獲取的本質(zhì)是不同的,。

那為什么有這種區(qū)別呢,?為什么要這樣設(shè)計(jì)呢?
先來研究一下為什么會出現(xiàn)這個結(jié)果,。
在ThreadLocal中存在著兩個很重要的方法,,get和set方法,一個讀取一個設(shè)置,。

 /**
     * Returns the value of this variable for the current thread. If an entry
     * doesn't yet exist for this variable on this thread, this method will
     * create an entry, populating the value with the result of
     * {@link #initialValue()}.
     *
     * @return the current value of the variable for the calling thread.
     */
    @SuppressWarnings("unchecked")
    public T get() {
        // Optimized for the fast path.
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values != null) {
            Object[] table = values.table;
            int index = hash & values.mask;
            if (this.reference == table[index]) {
                return (T) table[index + 1];
            }
        } else {
            values = initializeValues(currentThread);
        }

        return (T) values.getAfterMiss(this);
    }

 /**
     * Sets the value of this variable for the current thread. If set to
     * {@code null}, the value will be set to null and the underlying entry will
     * still be present.
     *
     * @param value the new value of the variable for the caller thread.
     */
    public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

摘自源碼
首先研究它的get方法吧,,從注釋上可以看出,get方法會返回一個當(dāng)前線程的變量值,,如果數(shù)組不存在就會創(chuàng)建一個新的,。
這里有幾個很重要的詞,就是“當(dāng)前線程”和“數(shù)組”,。
這里提到的數(shù)組對于每個線程來說都是不同的,,values.table,而values是通過當(dāng)前線程獲取到的一個Values對象,,因此這個數(shù)組是每個線程唯一的,,不能共用,而下面的幾句話也更直接了,,獲取一個索引,,再返回通過這個索引找到數(shù)組中對應(yīng)的值。這也就解釋了為什么多個線程通過同一個ThreadLocal返回的是不同的東西,。

那這里為什么要這么設(shè)置呢,?翻了一下書,搜了一下資料:

  • ThreadLocal在日常開發(fā)中使用到的地方較少,,但是在某些特殊的場景下,,通過ThreadLocal可以輕松實(shí)現(xiàn)一些看起來很復(fù)雜的功能,。一般來說,,當(dāng)某些數(shù)據(jù)是以線程為作用域并且不同線程具有不同的數(shù)據(jù)副本的時候,就可以考慮使用ThreadLocal,。例如在Handler和Looper中,。對于Handler來說,它需要獲取當(dāng)前線程的Looper,,很顯然Looper的作用域就是線程并且不同的線程具有不同的Looper,,這個時候通過ThreadLocal就可以輕松的實(shí)現(xiàn)Looper在線程中的存取,。如果不采用ThreadLocal,那么系統(tǒng)就必須提供一個全局的哈希表供Handler查找指定的Looper,,這樣就比較麻煩了,,還需要一個管理類。
  • ThreadLocal的另一個使用場景是復(fù)雜邏輯下的對象傳遞,,比如監(jiān)聽器的傳遞,,有些時候一個線程中的任務(wù)過于復(fù)雜,就可能表現(xiàn)為函數(shù)調(diào)用棧比較深以及代碼入口的多樣性,,這種情況下,,我們又需要監(jiān)聽器能夠貫穿整個線程的執(zhí)行過程。這個時候就可以使用到ThreadLocal,,通過ThreadLocal可以讓監(jiān)聽器作為線程內(nèi)的全局對象存在,,在線程內(nèi)通過get方法就可以獲取到監(jiān)聽器。如果不采用的話,,可以使用參數(shù)傳遞,,但是這種方式在設(shè)計(jì)上不是特別好,當(dāng)調(diào)用棧很深的時候,,通過參數(shù)來傳遞監(jiān)聽器這個設(shè)計(jì)太糟糕,。而另外一種方式就是使用static靜態(tài)變量的方式,但是這種方式存在一定的局限性,,拓展性并不是特別的強(qiáng),。比如有10個線程在執(zhí)行,就需要提供10個監(jiān)聽器對象,。

消息機(jī)制

上面提到了Handler/Looper/Message Queue,,它們實(shí)際上是一個整體,只不過我們在開發(fā)中接觸更多的是Handler而已,,Handler的主要作用是將一個任務(wù)切換到某個指定的線程中去執(zhí)行,,而Android之所以提供這個機(jī)制是因?yàn)锳ndroid規(guī)定UI只能在主線程中進(jìn)程,如果在子線程中訪問UI就會拋出異常,。

為什么Android不允許在子線程訪問UI
其實(shí)這一點(diǎn)不僅僅是對于Android,對于其他的所有圖形界面現(xiàn)在都采用的是單線程模式,。
因?yàn)閷τ谝粋€多線程來說,如果子線程更改了UI,,那么它的相關(guān)操作就必須對其他子線程可見,,也就是Java并發(fā)中很重要的一個概念,線程可見性,,Happen-before原則【下篇博客總結(jié)一下自己對Java并發(fā)的理解吧,,挺重要的,總結(jié)完后再把傳送門貼過來】而一般來說,,對于這種并發(fā)訪問,,一般都是采用加鎖的機(jī)制,,但是加鎖的機(jī)制存在很明顯的問題:讓UI訪問間的邏輯變得復(fù)雜,同時效率也會降低,。甚至有的時候還會造成死鎖的情況,,這個時候就麻煩了。
而至于究竟能不能夠?qū)崿F(xiàn)這種UI界面的多線程呢,?SUN公司的某個大牛(忘了是誰,,很久之前看的,好像是前副總裁)說:“行肯定是沒問題,,但是非??技夹g(shù),因?yàn)楸仨氁紤]到很多種情況,,這個時候就需要技術(shù)專家來設(shè)計(jì),。而這種設(shè)計(jì)出來的東西對于廣大普通程序員來說又是異常頭疼的,就算是實(shí)現(xiàn)了多線程,,普通人用起來也是怨聲載道的,。所以建議還是單線程”。

死鎖

順帶著BB一下死鎖,。

死鎖的四個必要條件

  1. 互斥條件:資源不能被共享,,只能被同一個進(jìn)程使用
  2. 請求與保持條件:已經(jīng)得到資源的進(jìn)程可以申請新的資源
  3. 非剝奪條件:已經(jīng)分配的資源不能從相應(yīng)的進(jìn)程中被強(qiáng)制剝奪
  4. 循環(huán)等待條件:系統(tǒng)中若干進(jìn)程組成環(huán)路,該環(huán)路中每個進(jìn)程都在等待相鄰進(jìn)程占用的資源

舉個常見的死鎖例子:進(jìn)程A中包含資源A,進(jìn)程B中包含資源B,,A的下一步需要資源B,,B的下一步需要資源A,所以它們就互相等待對方占有的資源釋放,,所以也就產(chǎn)生了一個循環(huán)等待死鎖,。

處理死鎖的方法

  1. 忽略該問題,也就是鴕鳥算法,。當(dāng)發(fā)生了什么問題時,,不管他,直接跳過,,無視它,。
  2. 檢測死鎖并恢復(fù)
  3. 資源進(jìn)行動態(tài)分配
  4. 破除上面的四種死鎖條件之一

繼續(xù)消息機(jī)制

MessageQueue主要包含兩個操作:插入和讀取,讀取操作本身會伴隨著刪除操作,,插入和讀取對應(yīng)的方法分別為enqueueMessage和next,,其中enqueueMessage的作用是往消息隊(duì)列中插入一條消息,而next的作用是從消息隊(duì)列中取出一條消息并將其從消息隊(duì)列中移除,。這也就是為什么使用的是一個單鏈表的數(shù)據(jù)結(jié)構(gòu)來維護(hù)消息列表,,因?yàn)樗诓迦牒蛣h除上比較有優(yōu)勢(把下一個連接的點(diǎn)切換一下就完成了),。

而對于MessageQueue的插入操作來說,,沒什么可以看的,,也就這樣吧,主要需要注意的是它的讀取方法next,。

 Message next() {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105

源碼有點(diǎn)長,,總結(jié)一下就是:
next方法它是一個死循環(huán),如果消息隊(duì)列中沒有消息,,那么next方法就會一直阻塞在這里,,當(dāng)有新的消息來的時候,next方法就會返回這條信息并將其從單鏈表中移除,。

而這個時候勒Looper就等著的,,它也是一直循環(huán)循環(huán),不停地從MessageQueue中查看是否有新消息,,如果有新消息就會立刻處理,,否則就會一直阻塞在那里。而對于Looper來說,,它是只能創(chuàng)建一個的,,這個要?dú)w功與它的prepare方法。

 /** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
    public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

從這里我們就可以看出該prepare方法會首先檢測是否已經(jīng)存在looper了,,如果不存在,,就創(chuàng)建一個新的;如果存在,,就拋出異常,。
而之后使用Looper.loop()就可以開啟消息循環(huán)了。

  /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            msg.target.dispatchMessage(msg);

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

從這里面我們可以看到它也是個死循環(huán),,會不停的調(diào)用queue.next()方法來獲取信息,,如果沒有,就return,如果有就處理,。

注意
當(dāng)然了,,這里有一個很重要的點(diǎn),一般可能會忘,,那就是在子線程中如果手動為其創(chuàng)建了Looper,,那么在所有的事情完成以后應(yīng)該調(diào)用quit方法來終止消息循環(huán),否則這個子線程就會一直處于等待狀態(tài),,而如果退出Looper之后,,這個線程就會立刻終止,所以建議不需要使用的時候終止Looper,。

Handler
上面總結(jié)了Looper和MessageQueue,,這里就對Handler進(jìn)行一個總結(jié)吧。它的工作主要包含消息的發(fā)送和接受過程,消息的發(fā)送可以通過post的一系列方法以及send的一系列方法來實(shí)現(xiàn),,post的一系列方法最終是通過send的一系列方法來實(shí)現(xiàn)的,。
實(shí)際上它發(fā)送消息的過程僅僅是向消息隊(duì)列中插入了一條消息,MessageQueue的next方法就會返回這條消息給Looper,,Looper在收到消息之后就會開始處理了,。最后由Looper交給Handler處理(handleMessage()方法)。

IPC通信

上面總結(jié)完了Android的消息處理機(jī)制,,那么就順帶總結(jié)一下IPC通信吧,,畢竟上面提到過那么多次Binder和Socket。

資料:為什么Android要采用Binder作為IPC機(jī)制,?
知乎上面的回答相當(dāng)?shù)暮?,這個博主對系統(tǒng)底層也是頗有鉆研,學(xué)習(xí),。

這里就結(jié)合上面的知乎回答以及加上《Linux程序設(shè)計(jì)》還有一本Linux內(nèi)核剖析(書名忘了但是講得真的非常好),,摻雜一些個人的理解。

進(jìn)程的定義
UNIX標(biāo)準(zhǔn)把進(jìn)程定義為:“一個其中運(yùn)行著一個或多個進(jìn)程的地址控件和這些線程所需要的系統(tǒng)資源”,。目前,,可以簡單的把進(jìn)程看做正在運(yùn)行的程序。

進(jìn)程都會被分配一個唯一的數(shù)字編號,,我們成為PID(也就是進(jìn)程標(biāo)識符),,它通常是一個取值范圍從2到32768的正整數(shù)。當(dāng)進(jìn)程被啟動時,,系統(tǒng)將按順序選擇下一個未被使用的數(shù)字作為PID,,當(dāng)數(shù)字已經(jīng)回繞一圈時,新的PID重新從2開始,,數(shù)字1一般是為init保留的,。在進(jìn)程中,存在一個自己的??臻g,,用于保存函數(shù)中的局部變量和控制函數(shù)的調(diào)用與返回。進(jìn)程還有自己的環(huán)境空間,,包含專門為這個進(jìn)程建立的環(huán)境變量,,同時還必須要維護(hù)自己的程序計(jì)數(shù)器,這個計(jì)數(shù)器用來記錄它執(zhí)行到的位置,,即在執(zhí)行線程中的位置,。
在Linux中可以通過system函數(shù)來啟動一個進(jìn)程

守護(hù)進(jìn)程
這里就需要提到一個守護(hù)進(jìn)程了,這個在所有的底層中經(jīng)常都會被提到,。
在linux或者unix操作系統(tǒng)中在系統(tǒng)引導(dǎo)的時候會開啟很多服務(wù),,這些服務(wù)就叫做守護(hù)進(jìn)程。為了增加靈活性,root可以選擇系統(tǒng)開啟的模式,,這些模式叫做運(yùn)行級別,,每一種運(yùn)行級別以一定的方式配置系統(tǒng)。 守護(hù)進(jìn)程是脫離于終端并且在后臺運(yùn)行的進(jìn)程,。守護(hù)進(jìn)程脫離于終端是為了避免進(jìn)程在執(zhí)行過程中的信息在任何終端上顯示并且進(jìn)程也不會被任何終端所產(chǎn)生的終端信息所打斷。
守護(hù)進(jìn)程常常在系統(tǒng)引導(dǎo)裝入時啟動,,在系統(tǒng)關(guān)閉時終止,。如果想要某個進(jìn)程不因?yàn)橛脩艋蚪K端或其他的變化而受到影響,那么就必須把這個進(jìn)程變成一個守護(hù)進(jìn)程

防止手機(jī)服務(wù)后臺被殺死
是不是在手機(jī)的設(shè)置界面看當(dāng)前正在運(yùn)行的服務(wù)時會發(fā)現(xiàn)有的APP不止存在一個服務(wù),?有的APP后臺存在兩個,,有的存在三個?有的流氓軟件也會這么設(shè)置,,這樣的話就可以一直運(yùn)行在后臺,,用戶你關(guān)也關(guān)不了(倒不是說所有這么設(shè)置的都是流氓軟件,因?yàn)橛械能浖枰3忠粋€長期的后臺在線,,這是由功能決定的),。

這里有兩種方法(可能還有更多,這里只總結(jié)我了解的):

  • 第一種方法就是利用android中service的特性來設(shè)置,,防止手機(jī)服務(wù)后臺被殺死,。通過更改onStartCommand方法的返回值,將service設(shè)置為粘性service,,那么當(dāng)service被kill的時候就會將服務(wù)的狀態(tài)返回到最開始啟動的狀態(tài),,也就是運(yùn)行的狀態(tài),所以這個時候也就會再次重新運(yùn)行,。但是需要注意一點(diǎn),,這個時候的intent值就為空了,獲取的話需要注意一下這一點(diǎn),。
  • 第二種就是fork出一個C的進(jìn)程,,因?yàn)樵贚inux中,子類進(jìn)程在父類被殺死銷毀的時候不會隨之殺死,,它會被init進(jìn)程領(lǐng)養(yǎng),。所以也就可以使用這一個方法,利用主進(jìn)程fork出一個C進(jìn)程在后臺運(yùn)行,,一旦檢測到服務(wù)被殺死(檢測的方式多種,,可使用觀察者模式,廣播,,輪詢等等),,就重啟服務(wù)即可

IPC通信
上面總結(jié)了進(jìn)程的相關(guān)基礎(chǔ),這里就開始總結(jié)一下進(jìn)程間通信(IPC
)的問題了。
現(xiàn)在Linux現(xiàn)有的所有IPC方式:

  1. 管道:在創(chuàng)建時分配一個page大小的內(nèi)存,,緩存區(qū)大小有限
  2. 消息隊(duì)列:信息復(fù)制兩次,,額外的cpu消耗,不適合頻繁或信息量大的通信
  3. 共享內(nèi)存:無需復(fù)制,,共享緩沖區(qū)直接附加到進(jìn)程虛擬地址控件,,速度是在所有IPC通信中最快的。但是進(jìn)程間的同步問題操作系統(tǒng)無法實(shí)現(xiàn),,必須由各進(jìn)程利用同步工具解決,。
  4. Socket:作為更通用的接口,傳輸效率低,,主要用于不通機(jī)器或跨網(wǎng)絡(luò)的通信
  5. 信號量:常作為一種鎖機(jī)制,。
  6. 信號:不適用于信息交換,更適用于進(jìn)程件中斷控制,,例如kill process

到了這里,,就有了問題,為什么在Linux已經(jīng)存在這么多優(yōu)良的IPC方案時,,Android還要采取一種新的Binder機(jī)制呢,?
猜測:我覺得Android采用這種新的方式(當(dāng)然也大面積的同時使用Linux的IPC通信方式),最多兩個原因:

  1. 推廣時手機(jī)廠商自定義ROM底層的保密性或者公司之間的關(guān)系,。
  2. 在某些情況下更適合手機(jī)這種低配置,,對效率要求極高,用戶體驗(yàn)極其重要的設(shè)備

資料

對于Binder來說,,存在著以下的優(yōu)勢:

  • 性能角度:Binder的數(shù)據(jù)拷貝只需要一次,,而管道、消息隊(duì)列,、Socket都需要2次,,而共享內(nèi)存是一次都不需要拷貝,因此Binder的性能僅次于共享內(nèi)存
  • 穩(wěn)定性來說:Binder是基于C/S架構(gòu)的,,也就是Client和Server組成的架構(gòu),,Client端有什么需求,直接發(fā)送給Server端去完成,,架構(gòu)清晰,,分工明確。而共享內(nèi)存的實(shí)現(xiàn)方式復(fù)雜,,需要充分考慮訪問臨界資源的并發(fā)同步問題,,否則可能會出現(xiàn)死鎖等問題。從穩(wěn)定性來說,,Binder的架構(gòu)優(yōu)于共享內(nèi)存,。
  • 從安全的角度:Linux的傳統(tǒng)IPC方式的接收方無法獲得對方進(jìn)程可靠的UID(用戶身份證明)/PID(進(jìn)程身份證明),,從而無法鑒別對方身份,而Android是一個對安全性能要求特別高的操作系統(tǒng),,在系統(tǒng)層面需要對每一個APP的權(quán)限進(jìn)行管控或者監(jiān)視,,對于普通用戶來說,絕對不希望從App商店下載偷窺隱射數(shù)據(jù),、后臺造成手機(jī)耗電等問題,。傳統(tǒng)的Linux IPC無任何保護(hù)措施,完全由上層協(xié)議來確保,。而在Android中,,操作系統(tǒng)為每個安裝好的應(yīng)用程序分配了自己的UID,通過這個UID可以鑒別進(jìn)程身份,。同時Android系統(tǒng)對外只暴露Client端,,Client端將任務(wù)發(fā)送給Server端,,Server端會根據(jù)權(quán)限控制策略判斷UID/PID是否滿足訪問權(quán)限,。也就是說Binder機(jī)制對于通信雙方的身份是內(nèi)核進(jìn)行校驗(yàn)支持的。例如Socket方式只需要指導(dǎo)地址就可以連接,,他們的安全機(jī)制需要上層協(xié)議來假設(shè)
  • 從語言角度:Linux是基于C的,,而Android是基于Java的,而Binder是符合面向?qū)ο笏枷氲?。它的?shí)體位于一個進(jìn)程中,,而它的引用遍布與系統(tǒng)的各個進(jìn)程之中,它是一個跨進(jìn)程引用的對象,,模糊了進(jìn)程邊界,,淡化了進(jìn)程通信的過程,整個系統(tǒng)仿佛運(yùn)行于同一個面向?qū)ο蟮某绦蛑小?/li>
  • 從公司角度:Linux內(nèi)核是開源的,,GPL協(xié)議保護(hù),,受它保護(hù)的Linux Kernel是運(yùn)行在內(nèi)核控件,對于上層的任何類庫,、服務(wù)等只要進(jìn)行系統(tǒng)調(diào)用,,調(diào)用到底層Kernel,那么也必須遵循GPL協(xié)議,。而對于Android來說,,Google巧妙地將GPL協(xié)議控制在內(nèi)核控件,將用戶控件的協(xié)議采用Apache-2.0協(xié)議(允許基于Android的開發(fā)商不向社區(qū)反饋源碼),。

反射

剛才談到Binder的時候提了一下效率的問題,,那這里就不得不講到反射了。

反射它允許一個類在運(yùn)行過程中獲得任意類的任意方法,,這個是Java語言的一個很重要的特性,。它方便了程序員的編寫,,但是降低了效率。

實(shí)際上,,對于只要不是特別大的項(xiàng)目(非Android),,反射對于效率的影響微乎其微,而與之對比的開發(fā)成本來說就更劃算了,。
但是,,Android是一個用于手機(jī)的,它的硬件設(shè)施有限,,我們必須要考慮到它的這個因素,,用戶體驗(yàn)是最重要的。以前看到過國外的一項(xiàng)統(tǒng)計(jì),。在一個APP中的Splash中使用了反射,,結(jié)果運(yùn)行時間增加了一秒,這個已經(jīng)算是很嚴(yán)重的效率影響了,。

為什么反射影響效率呢
這里就需要提到一個東西,,JIT編譯器。JIT編譯器它可以把字節(jié)碼文件轉(zhuǎn)換為機(jī)器碼,,這個是可以直接讓處理器使用的,,經(jīng)過它處理的字節(jié)碼效率提升非常大,但是它有一個缺點(diǎn),,就是把字節(jié)碼轉(zhuǎn)換成機(jī)器碼的過程很慢,,有的時候甚至還超過了不轉(zhuǎn)換的代碼效率(轉(zhuǎn)換之后存在一個復(fù)用的問題,對于轉(zhuǎn)換了的機(jī)器碼,,使用的次數(shù)越多就越值的),。因此,在JVM虛擬機(jī)中,,也就產(chǎn)生了一個機(jī)制,,把常用的、使用頻率高的字節(jié)碼通過JIT編譯器轉(zhuǎn)換,,而頻率低的就不管它,。而反射的話則是直接越過了JIT編譯器,不管是常用的還是非常用的字節(jié)碼一律沒有經(jīng)過JIT編譯器的轉(zhuǎn)化,,所以效率就會低,。
而在Android里面,5.0之前使用的是Davlik虛擬機(jī),,它就是上面的機(jī)制,,而在Android5.0之后Google使用了一個全新的ART虛擬機(jī)全面代替Davlik虛擬機(jī)。
ART虛擬機(jī)會在程序安裝時直接把所有的字節(jié)碼全部轉(zhuǎn)化為機(jī)器碼,,雖然這樣會導(dǎo)致安裝時間邊長,,但是程序運(yùn)行的效率提升非常大,。
【疑問:那在Android5.0之后的系統(tǒng)上,反射會不會沒影響了,?由于現(xiàn)在做項(xiàng)目的時候更多考慮的是向下兼容,,單獨(dú)考慮5.0的情況還沒有,等以后有需求或者是有機(jī)會的時候再深入了解一下,,以后更新】

繼續(xù)斷點(diǎn)4

剛才總結(jié)了Android的消息處理機(jī)制和IPC通信,,那么我們主線程的消息處理機(jī)制是什么時候開始的呢?因?yàn)槲覀冎涝谥骶€程中我們是不需要手動調(diào)用Looper.prepare()和Looper.loop()的,。

Android的主線程就是ActivityThread,,主線程的入口方法是main方法,在main方法中系統(tǒng)會通過Looper.prepareMainLooper()來創(chuàng)建主線程的Looper以及MessageQueue,,并通過Looper.loop來開啟消息循環(huán),,所以這一步實(shí)際上是系統(tǒng)已經(jīng)為我們做了,我們就不再需要自己來做,。
ActivityThread通過AppplicationThread和AMS進(jìn)行進(jìn)程件通信,,AMS以進(jìn)程間通信的方式完成ActivityThread的請求后會回調(diào)ApplicationThread中的Binder方法,然后ApplicationThread會向Handler發(fā)送消息,,Handler收到消息后會將ApplicationThread中的邏輯切換到主線程中去執(zhí)行,,這個過程就是主線程的消息循環(huán)模型,。

上面總結(jié)到了APP開始運(yùn)行,,依次調(diào)用onCreate/onStart/onResume等方法,那么在onCreate方法中我們經(jīng)常使用的setContentView和findViewById做了什么事呢,?

Activity界面顯示

首先,,就考慮到第一個問題,也就是setContentView這個東西做了什么事,,這里就要對你當(dāng)前繼承的Activity分類了,,如果是繼承的Activity,那么setContentView源碼是這樣的:


    /**
     * Set the activity content from a layout resource.  The resource will be
     * inflated, adding all top-level views to the activity.
     *
     * @param layoutResID Resource ID to be inflated.
     *
     * @see #setContentView(android.view.View)
     * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
     */
    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

    /**
     * Set the activity content to an explicit view.  This view is placed
     * directly into the activity's view hierarchy.  It can itself be a complex
     * view hierarchy.  When calling this method, the layout parameters of the
     * specified view are ignored.  Both the width and the height of the view are
     * set by default to {@link ViewGroup.LayoutParams#MATCH_PARENT}. To use
     * your own layout parameters, invoke
     * {@link #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)}
     * instead.
     *
     * @param view The desired content to display.
     *
     * @see #setContentView(int)
     * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
     */
    public void setContentView(View view) {
        getWindow().setContentView(view);
        initWindowDecorActionBar();
    }

    /**
     * Set the activity content to an explicit view.  This view is placed
     * directly into the activity's view hierarchy.  It can itself be a complex
     * view hierarchy.
     *
     * @param view The desired content to display.
     * @param params Layout parameters for the view.
     *
     * @see #setContentView(android.view.View)
     * @see #setContentView(int)
     */
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        getWindow().setContentView(view, params);
        initWindowDecorActionBar();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

這里面存在著3個重載函數(shù),,而不管你調(diào)用哪一個,,最后都會調(diào)用到initWindowDecorActionBar()這個方法。
而對于新的一個AppcompatActivity,,這個Activity里面包含了一些新特性,,現(xiàn)在我做的項(xiàng)目里基本都是使用AppcompatActivity代替掉原來的Activity,當(dāng)然也并不是一定的,,還是要根據(jù)項(xiàng)目的實(shí)際情況來選擇,。
在AppcompatActivity中,setContentView是這樣的:

 @Override
    public void setContentView(@LayoutRes int layoutResID) {
        getDelegate().setContentView(layoutResID);
    }

    @Override
    public void setContentView(View view) {
        getDelegate().setContentView(view);
    }

    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        getDelegate().setContentView(view, params);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

一樣的3個重載函數(shù),,只是里面沒有了上面的那個init方法,,取而代之的是一個getDelegate().setContentView,,這個delegate從字面上可以了解到它是一個委托的對象,源碼是這樣的:

 /**
     * @return The {@link AppCompatDelegate} being used by this Activity.
     */
    @NonNull
    public AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, this);
        }
        return mDelegate;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

而在AppCompatDelegate.Create方法中,,則會返回一個很有意思的東西:

/**
     * Create a {@link android.support.v7.app.AppCompatDelegate} to use with {@code activity}.
     *
     * @param callback An optional callback for AppCompat specific events
     */
    public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
        return create(activity, activity.getWindow(), callback);
    }

private static AppCompatDelegate create(Context context, Window window,
            AppCompatCallback callback) {
        final int sdk = Build.VERSION.SDK_INT;
        if (sdk >= 23) {
            return new AppCompatDelegateImplV23(context, window, callback);
        } else if (sdk >= 14) {
            return new AppCompatDelegateImplV14(context, window, callback);
        } else if (sdk >= 11) {
            return new AppCompatDelegateImplV11(context, window, callback);
        } else {
            return new AppCompatDelegateImplV7(context, window, callback);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

這里會根據(jù)SDK的等級來返回不同的東西,,這樣的話就不深究了,底層的話我撇了一下,,應(yīng)該原理和Activity是一樣的,,可能存在一些區(qū)別。這里就用Activity來談?wù)勊膕etContentView方法做了什么事,。

在setContentView上面有段注釋:

Set the activity content from a layout resource. The resource will be inflated, adding all top-level views to the activity.

這里就介紹了它的功能,,它會按照一個布局資源去設(shè)置Activity的內(nèi)容,而這個布局資源將會被引入然后添加所有頂級的Views到這個Activity當(dāng)中,。
這是個啥意思勒,。
下面從網(wǎng)上扒了一張圖:
這里寫圖片描述
這里是整個Activity的層級,最外面一層是我們的Activity,,它包含里面的所有東西,。
再上一層是一個PhoneWindow,這個PhoneWindow是由Window類派生出來的,,每一個PhoneWindow中都含有一個DecorView對象,,Window是一個抽象類。
再上面一層就是一個DecorView,,我理解這個DecorView就是一個ViewGroup,,就是裝View的。
而在DecoreView中,,最上面的View就是我們的TitleActionBar,,下面就是我們要設(shè)置的content。所以在上面的initWindowDecorActionBar就能猜到是什么意思了吧,。

而在initWindowDecorActionBar方法中,,有一段代碼:

 /**
     * Creates a new ActionBar, locates the inflated ActionBarView,
     * initializes the ActionBar with the view, and sets mActionBar.
     */
    private void initWindowDecorActionBar() {
        Window window = getWindow();

        // Initializing the window decor can change window feature flags.
        // Make sure that we have the correct set before performing the test below.
        window.getDecorView();

        if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) {
            return;
        }

        mActionBar = new WindowDecorActionBar(this);
        mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);

        mWindow.setDefaultIcon(mActivityInfo.getIconResource());
        mWindow.setDefaultLogo(mActivityInfo.getLogoResource());
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

注意上面的window.getDecoreView()方法的注釋,該方法會設(shè)置一些window的標(biāo)志位,,而當(dāng)這個方法執(zhí)行完之后,,就再也不能更改了,這也就是為什么很多第三方SDK設(shè)置window的標(biāo)志位時一定要求要在setContentView方法前調(diào)用,。

findViewById

我們通過一個findViewById方法可以實(shí)現(xiàn)對象的綁定,,那它底層究竟是怎么實(shí)現(xiàn)的呢?

findViewById根據(jù)繼承的Activity類型的不同也存在著區(qū)別,,老規(guī)矩,,還是以Activity的來。

/**
     * Finds a view that was identified by the id attribute from the XML that
     * was processed in {@link #onCreate}.
     *
     * @return The view if found or null otherwise.
     */
    @Nullable
    public View findViewById(@IdRes int id) {
        return getWindow().findViewById(id);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

從源碼來看,,findViewById也是經(jīng)過了一層層的調(diào)用,,它的功能如同它上面的注釋一樣,,通過一個view的id屬性查找view,這里也可以看到一個熟悉的getWindow方法,,說明findViewById()實(shí)際上Activity把它也是交給了自己的window來做

 /**
     * Finds a view that was identified by the id attribute from the XML that
     * was processed in {@link android.app.Activity#onCreate}.  This will
     * implicitly call {@link #getDecorView} for you, with all of the
     * associated side-effects.
     *
     * @return The view if found or null otherwise.
     */
    @Nullable
    public View findViewById(@IdRes int id) {
        return getDecorView().findViewById(id);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

而在這里面,,又調(diào)用了getDecorView的findViewById()方法,這也相當(dāng)于是一個層層傳遞的過程,,因?yàn)镈ecorView我理解為就是一個ViewGroup,,而當(dāng)運(yùn)行g(shù)etDecorView().findViewById()方法時,就會運(yùn)行View里面的findViewById方法,。它會使用這個被給予的id匹配子View的Id,,如果匹配,就返回這個View,,完成View的綁定

/**
     * Look for a child view with the given id.  If this view has the given
     * id, return this view.
     *
     * @param id The id to search for.
     * @return The view that has the given id in the hierarchy or null
     */
    @Nullable
    public final View findViewById(@IdRes int id) {
        if (id < 0) {
            return null;
        }
        return findViewTraversal(id);
    }

    /**
     * {@hide}
     * @param id the id of the view to be found
     * @return the view of the specified id, null if cannot be found
     */
    protected View findViewTraversal(@IdRes int id) {
        if (id == mID) {
            return this;
        }
        return null;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

最后總結(jié)一下(Activity中),,findViewById的過程是這樣的:
Activity -> Window -> DecorView -> View

參考

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購買等信息,,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請點(diǎn)擊一鍵舉報,。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多