1. 程式人生 > >2018面試知識點整理

2018面試知識點整理

目錄(手機瀏覽可以進行跳轉,PC使用者請使用CSDN自動生成的目錄)

ListView優化方案

1. 複用convertView
2. 快取item條目的引用,減少findViewbyId—>ViewHolder
3. 資料的 分頁/分批 載入:對大量的資料進行分頁展示,對不同的滾動狀態進行分別處理,在快速滑動狀態不載入資料
4. 圖片的快取,需要解決圖片錯位問題—>推薦使用成熟框架Glide或Picasso
5. 根據列表的滑動狀態來控制任務的執行頻率(在快速滑動時不要載入圖片)
6. 可以開啟硬體加速使ListView更加流暢(android:hardwareAccelerated=”true”)
7. 將ListView的scrollingCache和animateCache這兩個屬性設定為false(預設是true);
8. 避免GC(可以從LOGCAT檢視有無GC的LOG);
9. 儘可能減少List Item的Layout層次(如可以使用RelativeLayout替換LinearLayout,或使用自定的View代替組合巢狀使用的Layout);

android中的動畫有哪幾類,它們的特點和區別是什麼

1、FrameAnimation(逐幀動畫):將多張圖片組合起來進行播放,類似於早期電影的工作原理,很多靜態圖片不斷切換的效果類似gif圖
        //在res/drawable/資料夾下定義好XML檔案
        imageView.setBackgroundResource(R.drawable.frame);// 設定圖片控制元件的背景資源
        Drawable drawable = imageView.getBackground();// 獲取圖片控制元件的靜態背景資源得到一個drawable物件
AnimationDrawable animationDrawable = (AnimationDrawable) drawable;// 強制轉變成動態圖 animationDrawable.start();// 啟動動態圖片
2、TweenAnimation(補間動畫):知道開始和結束,將中間的過程給補充起來,元件從初始狀態變成結束狀態,為了讓改變看起來更平滑【平移動畫:TranslateAnimation、縮放動畫:ScaleAnimation、透明動畫:AlphaAnimation、旋轉動畫:RotateAnimation】。

使用XML:在res/anim/xxx.xml書寫好動畫,在程式碼中使用 AnimationUtils.loadAnimation()(動畫集使用AnimationSet),設定動畫時間,在讓控制元件啟動動畫就可以了;

        Animation animation = AnimationUtils.loadAnimation(this, R.anim.mytranslate);//把一個動畫的資原始檔載入成一個動畫類( Animation是所有動畫的超類  )
        TranslateAnimation animation2=(TranslateAnimation) animation;
        imageView.startAnimation(animation);//讓控制元件啟動動畫(每一個控制元件都有)

使用程式碼建立:直接使用對應的動畫類建立動畫物件構造傳參,設定好動畫的相關屬性,使用控制元件啟動動畫即可

        TranslateAnimation animation=new TranslateAnimation(0, 100, 0, 0);
        animation.setDuration(2000);//設定動畫時間
        animation.setFillAfter(true);//保持動畫的最後效果
        animation.setRepeatCount(Animation.INFINITE);//重複的次數
        animation.setRepeatMode(Animation.REVERSE);//重複的模式
        imageView.startAnimation(animation);
        //使用動畫集
        AnimationSet set=new AnimationSet(false);//ture為使用每個動畫各自的效果
        set.addAnimation(a);//新增一個已經建立好的動畫,可以新增多個動畫
        set.addAnimation(b);//新增一個已經建立好的動畫,可以新增多個動畫
        imageView.startAnimation(set);

檢視動畫監聽器

        //繫結補間動畫的監聽器
        animation.setAnimationListener(new AnimationListener() {
            public void onAnimationStart(Animation animation) {//動畫開始的時候呼叫   animation為事件源
            }
            public void onAnimationRepeat(Animation animation) {//動畫重複的時候呼叫   animation為事件源
            }
            public void onAnimationEnd(Animation animation) {//動畫結束的時候呼叫   animation為事件源
            }
        });
3、PropertyAnimation(屬性動畫):屬性動畫不再僅僅是一種視覺效果了,而是一種不斷地對值進行操作的機制,並將值賦到指定物件的指定屬性上,可以是任意物件的任意屬性。控制元件位置也發生了改變,屬性動畫只對Android 3.0(API 11)以上版本的Android系統才有效,這種動畫可以設定給任何Object,包括那些還沒有渲染到螢幕上的物件。這種動畫是可擴充套件的,可以讓你自定義任何型別和屬性的動畫。
        /*ObjectAnimator.ofFloat(target,    :動畫的執行者
         *              propertyName,   :動畫的名字:scaleX/scaleY、alpha、rotation、translationX/translationY
         *               values)    :執行動畫的可變引數(中間可以有多個引數)*/
        ObjectAnimator animator=ObjectAnimator.ofFloat(imageView, "translationX", 0,100,0,100);
        animator.setDuration(2000);
        animator.setRepeatCount(2);
        animator.setRepeatMode(Animation.REVERSE);
        animator.start();
        //屬性動畫的動畫集
        AnimatorSet set=new AnimatorSet();
        //set.playTogether(animator1,animator2);//  讓多個動畫一起執行
        set.playSequentially(animator1,animator2);//讓多個動畫按順序執行
        set.start();

android中有哪幾種解析xml的類?官方推薦哪種?以及它們的原理和區別

更多的參考
回到目錄

XML解析主要有三種方式,PULL、SAX、DOM。Pull解析是基於事件常量的方式,SAX解析是基於事件通知的方式,DOM解析是基於DOM樹結構的方式,通常來說:在PC開發中使用DOM解析快速方便,在效能低的裝置上採用Pull解析,如果已知XML檔案不大還是可以使用DOM解析的

XML解析方式 原理 優點 缺點
PULL 基於事件常量 解析完成之後,記憶體中只會保留我們想要的資料.資源佔用極低 沒有儲存完整的文件結構.所以只能進行查詢,不能增刪改,比sax要靈活. 可以自己控制是否需要繼續向下解析
Sax 基於事件通知 解析完成之後,記憶體中只會保留我們想要的資料.資源佔用極低 沒有儲存完整的文件結構.所以只能進行查詢,不能增刪改,一旦開始一定要從頭解析到尾
DOM 基於DOM樹結構 操作方便,可以完成增刪該查操作 因為所有內容都會被封裝成物件儲存在記憶體中,所以資源佔用較大

1.Pull解析 (以事件常量的方式解析)(遊標一個一個事件的往下移)

      XmlPullParserFactory factory=XmlPullParserFactory.newInstance();  // 建立解析工廠
      XmlPullParser xmlParser=factory.newPullParser();   // 生成解析物件
      xmlParser.setInput(new StringReader(content));   // 設定要讀取的內容
      int type=xmlParser.getEventType();  // 獲取當前解析的事件型別常量
      String tagName=xmlParser.getName();    // 獲取當前的標籤名稱
      xmlParser.next();    // 將遊標下移,獲取下一個事件常量
      String userName=xmlParser.getText();  // 獲取文字

2.SAX解析 (Simple Api for Xml) 針對XML的簡單解析API,SAX以事件通知的方式解析XML,自定義一個繼承自DefaultHandler的類,複寫其中的相關方法。(處理類:複寫三個方法,當遇到開始標籤、結束標籤、文字內容的時候分別做什麼),優點:不佔記憶體空間、解析屬性方便,但缺點就是對於套嵌多個分支來說處理不是很方便

      SAXParserFactory factory=SAXParserFactory.newInstance();   // 建立解析工廠
      SAXParser parser=factory.newSAXParser();    // 建立解析物件
      File f=new File("xxx");
      例項化DefaultHandler物件handler
      parser.parse(f,handler);    // 解析XML

3.DOM解析(瞭解)(Document Object Model),將XML結構以DOM樹結構解析到記憶體中,DOM解析可以隨機訪問DOM樹中的節點。記憶體開銷大

      DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance();
      DocumentBuilder builder=factory.newDocumentBuilder();
      Document doc = builder.parse("file物件");   // 解析XML到記憶體中
      NodeList nodeList=doc.getElementsByTagName("標籤名");
      Node node = nodeList.item(index);
      String nodeValue=node.getFirstChild().getNodeValue();

Android的資料儲存方式

更多的參考
回到目錄

1.SharedPreferences:採用XML檔案的方式進行儲存,以鍵值對的方式儲存,可以很方便的讀寫,常用於儲存設定和登入資訊

2.檔案儲存:分為內部儲存和外部儲存(SD卡),其操作與 Java中實現I/O的程式是完全一樣的,另Context提供了openFileInput()和openFileOutput()讀寫檔案

3.SQLite資料庫儲存:集成了SQLite資料庫,提供了很多操作的API,它具有以下優點: a. 效率出眾, b. 十分適合儲存結構化資料 c. 方便在不同的Activity,甚至不同的應用之間傳遞資料。

4.ContentProvider 儲存:能實現所有應用程式共享的一種資料儲存方式,由於資料通常在各應用間的是互相私密的,所以此儲存方式較少使用,但是其又是必不可少的一種儲存方式。例如音訊,視訊,圖片和通訊錄,一般都可以採用此種方式進行儲存。每個ContentProvider都會對外提供一個公共的URI(包裝成Uri物件),如果應用程式有資料需要共享時,就需要使用ContentProvider為這些資料定義一個URI,然後其他的應用程式就通過Content Provider傳入這個URI來對資料進行操作

5.網路儲存:網路儲存方式,需要與Android 網路資料包打交道

總結:SharedPreferences適用於儲存一些鍵值對,檔案適用於儲存一些簡單的文字資料或者二進位制資料,資料庫則適用於那些複雜的關係型資料,ContentProvider使用於多個應用間的資料共享

activity的啟動模式有哪些?是什麼含義?

更多的參考
回到目錄

1、standard 模式: 這是預設模式,每次啟用Activity時都會建立Activity例項,並放入任務棧中。使用場景:大多數Activity。

2、singleTop 模式: 如果在任務的棧頂正好存在該Activity的例項,就重用該例項( 會呼叫例項的 onNewIntent() ),否則就會建立新的例項並放入棧頂,即使棧中已經存在該Activity的例項,只要不在棧頂,都會建立新的例項。使用場景如新聞類或者閱讀類App的內容頁面。

3、singleTask 模式: 如果在棧中已經有該Activity的例項,就重用該例項(會呼叫例項的 onNewIntent() )。重用時,會讓該例項回到棧頂,因此在它上面的例項將會被移出棧。如果棧中不存在該例項,將會建立新的例項放入棧中。使用場景如瀏覽器的主介面。不管從多少個應用啟動瀏覽器,只會啟動主介面一次,其餘情況都會走onNewIntent,並且會清空主介面上面的其他頁面。

4、singleInstance 模式: 在一個新棧中建立該Activity的例項,並讓多個應用共享該棧中的該Activity例項。一旦該模式的Activity例項已經存在於某個棧中,任何應用再啟用該Activity時都會重用該棧中的例項( 會呼叫例項的 onNewIntent() )。其效果相當於多個應用共享一個應用,不管誰啟用該 Activity 都會進入同一個應用中。使用場景如鬧鈴提醒,將鬧鈴提醒與鬧鈴設定分離。singleInstance不要用於中間頁面,如果用於中間頁面,跳轉會有問題,比如:A -> B (singleInstance) -> C,完全退出後,在此啟動,首先開啟的是B。

Activity的生命週期

更多的參考
回到目錄

在Activity的生命週期中,如下的方法會被系統回撥:

方法名 方法備註
onCreate(Bundle savedInstanceState) Activity被建立時呼叫。
onStart() Activity已經啟動,未獲取到焦點,還不可以與使用者進行互動。
onResume() 當Activity可見,已獲取到焦點可以與使用者互動。
onPause() 暫停Activity時被呼叫,呼叫了該方法後,Activity變得不可互動即失去焦點但仍然可見,可恢復至onResume()。
onStop() 停止Activity時被呼叫,Activity變得不可見,可恢復至onRestart()。
onDestroy() 銷燬Activity時被呼叫。
onRestart() 重啟Activity時被呼叫,當Activity從不可見重新變為可見時,就會呼叫該方法。

image.png

image.png

activity在螢幕旋轉時的生命週期

更多的參考
回到目錄

- 不設定Activity的android:configChanges時,切屏會重新呼叫各個生命週期,切橫屏時會執行一次,切豎屏時會執行兩次
- 設定Activity的android:configChanges=”orientation”時,切屏還是會重新呼叫各個生命週期,切橫、豎屏時只會執行一次
- 設定Activity的android:configChanges=”orientation|keyboardHidden”時,切屏不會重新呼叫各個生命週期,只會執行 onConfigurationChanged 方法。

Service的介紹

更多的參考
回到目錄

建立自定義Service需要重寫父類的如下方法:

  • void onCreate():該方法在該Service第一次被建立時呼叫。
  • int onStartCommand(Intent intent,int flags,intstartId):當應用程式通過startService()的方式啟動Service時,會呼叫該方法。
  • IBinder onBind(Intent intent):當Service通過繫結的方式啟動時,會呼叫該onBind()方法,該方法返回一個IBinder物件,應用程式可以通過IBinder物件與Service通訊。
  • boolean onUnbind(Intent intent):當該Service上繫結的所有客戶端都斷開連線時,會觸發該方法。
  • void onDestroy(Intent intent):當Service被銷燬時觸發該方法。

Service有兩類:

1:本地服務: Local Service 用於應用程式內部。在Service可以呼叫Context.startService()啟動,呼叫Context.stopService()結束。 在內部可以呼叫Service.stopSelf() 或 Service.stopSelfResult()來自己停止。無論呼叫了多少次startService(),都只需呼叫一次 stopService()來停止。

2:遠端服務: Remote Service 用於android系統內部的應用程式之間。可以定義介面並把介面暴露出來,以便其他應用進行操作。客戶端建立到服務物件的連線,並通過那個連線來呼叫服務。呼叫Context.bindService()方法建立連線,並啟動,以呼叫 Context.unbindService()關閉連線。多個客戶端可以繫結至同一個服務。如果服務此時還沒有載入,bindService()會先加 載它。提供給可被其他應用複用,比如定義一個天氣預報服務,提供與其他應用呼叫即可。使用遠端服務需要藉助AIDL來進行跨程序通訊。

Service生命週期圖一:

image.png

  • 通過start方式啟動Service,則生命週期函式呼叫為:context.startService() —> onCreate() —> onStartCommand() —> Service running —> 呼叫context.stopService() —> onDestroy(),第一次 啟動服務時,執行 onCreate –>onStartCommand,後面在啟動服務時,服務只執行onStartCommand,不再執行OnCreate

  • 通過bind方式啟動Service:context.bindService() —> onCreate() —> onBind() —> Service running —> 所有客戶端被銷燬或客戶端呼叫了unbindService() —> onUnbind() —> onDestroy(),第一次繫結時會呼叫onCreate->onBind()。隨後無論哪個元件再繫結幾次該Service。服務A的onCreate()和onBind()只調用一次。

Service生命週期圖二:

image.png

遠端Service生命週期圖:

image.png

註冊廣播有幾種方式,這些方式有何優缺點?

第一種:在清單檔案中宣告:常駐型,也就是說當應用程式關閉後,如果有資訊廣播來,程式也會被系統呼叫自動執行。

  <receiver
        android:name=".MyReceiver"
        android:enabled="true"
        android:exported="true">
        <!-- 靜態註冊廣播 -->
        <!-- intent過濾器,指定可以匹配哪些intent, 一般需要定義action 可以是自定義的也可是系統的 -->
        <intent-filter>
        <!--action-->
            <action android:name="com.broadcast.test" />
       </intent-filter>
 </receiver>

第二種:使用程式碼進行註冊:不是常駐型廣播,也就是說廣播跟隨程式的生命週期。

IntentFilter filter =  new IntentFilter("android.provider.Telephony.SMS_RECEIVED");
IncomingSMSReceiver receiver = new IncomgSMSReceiver();
registerReceiver(receiver.filter);

知識點
- 1、不在廣播中做耗時操作,不在廣播中建立子執行緒,
- 2、廣播是四大元件中唯一可以動態註冊的元件;
- 3、本地廣播只在本應用程式內傳播,只能由程式碼進行註冊,有良好的安全性,可以使用EventBus來替代本地BroadcastReceiver,
- 4、廣播使用了觀察者模式,基於訊息的釋出/訂閱事件模型

使用場景
- 1.同一app內部的同一組件內的訊息通訊(單個或多個執行緒之間);----無意義 可以採用Handler 沒必要
- 2.同一app內部的不同元件之間的訊息通訊(單個程序);————應用場景較少。
- 3.同一app具有多個程序的不同元件之間的訊息通訊;
- 4.不同app之間的元件之間訊息通訊;
- 5.Android系統在特定情況下與App之間的訊息通訊。

請解釋下在單執行緒模型中Message、Handler、MessageQueue、Looper之間的關係

答:簡單的說,Handler獲取當前執行緒中的looper物件,looper用來從存放Message的MessageQueue中取出Message,再有Handler進行Message的分發和處理.

Message Queue(訊息佇列):用來存放通過Handler釋出的訊息,通常附屬於某一個建立它的執行緒,可以通過Looper.myQueue()得到當前執行緒的訊息佇列

Handler:可以釋出或者處理一個訊息或者操作一個Runnable,通過Handler釋出訊息,訊息將只會傳送到與它關聯的訊息佇列,然也只能處理該訊息佇列中的訊息

Looper:是Handler和訊息佇列之間通訊橋樑,程式元件首先通過Handler把訊息傳遞給Looper,Looper把訊息放入佇列。Looper也把訊息佇列裡的訊息廣播給所有的

Handler:Handler接受到訊息後呼叫handleMessage進行處理

Message:訊息的型別,在Handler類中的handleMessage方法中得到單個的訊息進行處理

在單執行緒模型下,為了執行緒通訊問題,Android設計了一個Message Queue(訊息佇列), 執行緒間可以通過該Message Queue並結合Handler和Looper元件進行資訊交換。下面將對它們進行分別介紹:

1. Message

    Message訊息,理解為執行緒間交流的資訊,處理資料後臺執行緒需要更新UI,則傳送Message內含一些資料給UI執行緒。

2. Handler

    Handler處理者,是Message的主要處理者,負責Message的傳送,Message內容的執行處理。後臺執行緒就是通過傳進來的 Handler物件引用來sendMessage(Message)。而使用Handler,需要implement 該類的 handleMessage(Message)方法,它是處理這些Message的操作內容,例如Update UI。通常需要子類化Handler來實現handleMessage方法。

3. Message Queue

    Message Queue訊息佇列,用來存放通過Handler釋出的訊息,按照先進先出執行。

    每個message queue都會有一個對應的Handler。Handler會向message queue通過兩種方法傳送訊息:sendMessage或post。這兩種訊息都會插在message queue隊尾並按先進先出執行。但通過這兩種方法傳送的訊息執行的方式略有不同:通過sendMessage傳送的是一個message物件,會被 Handler的handleMessage()函式處理;而通過post方法傳送的是一個runnable物件,則會自己執行。

4. Looper

    Looper是每條執行緒裡的Message Queue的管家。Android沒有Global的Message Queue,而Android會自動替主執行緒(UI執行緒)建立Message Queue,但在子執行緒裡並沒有建立Message Queue。所以呼叫Looper.getMainLooper()得到的主執行緒的Looper不為NULL,但呼叫Looper.myLooper() 得到當前執行緒的Looper就有可能為NULL。對於子執行緒使用Looper,API Doc提供了正確的使用方法:這個Message機制的大概流程:

    1. 在Looper.loop()方法執行開始後,迴圈地按照接收順序取出Message Queue裡面的非NULL的Message2. 一開始Message Queue裡面的Message都是NULL的。當Handler.sendMessage(Message)到Message Queue,該函式裡面設定了那個Message物件的target屬性是當前的Handler物件。隨後Looper取出了那個Message,則呼叫 該Message的target指向的Hander的dispatchMessage函式對Message進行處理。在dispatchMessage方法裡,如何處理Message則由使用者指定,三個判斷,優先順序從高到低:

        1) Message裡面的Callback,一個實現了Runnable介面的物件,其中run函式做處理工作;

        2) Handler裡面的mCallback指向的一個實現了Callback介面的物件,由其handleMessage進行處理;

        3) 處理訊息Handler物件對應的類繼承並實現了其中handleMessage函式,通過這個實現的handleMessage函式處理訊息。

    由此可見,我們實現的handleMessage方法是優先順序最低的!

    3. Handler處理完該Message (update UI) 後,Looper則設定該Message為NULL,以便回收!

    在網上有很多文章講述主執行緒和其他子執行緒如何互動,傳送資訊,最終誰來執行處理資訊之類的,個人理解是最簡單的方法——判斷Handler物件裡面的Looper物件是屬於哪條執行緒的,則由該執行緒來執行!

    1. 當Handler物件的建構函式的引數為空,則為當前所線上程的Looper;

    2. Looper.getMainLooper()得到的是主執行緒的Looper物件,Looper.myLooper()得到的是當前執行緒的Looper物件。

Handler的總結歸納

Handler的作用:將一個任務切換到某個指定的執行緒中去執行

  1. MessageQueue:儲存訊息,包含插入和讀取訊息的操作:

    1) enqueueMessage():新增訊息,實際上是單鏈表的操作,若訊息佇列滿則新增訊息的執行緒阻塞等待被喚醒;

    2) next():讀取訊息伴隨著訊息的刪除(相當於出佇列),是一個無限迴圈,若無訊息則一直阻塞等待,若有新訊息到來則返回該訊息並從連結串列中移除;

  2. Looper: 一個執行緒只能建立一個Looper物件,一個Looper物件只有一個MessageQueue(在Looper的構造方法中例項化),建立Looper物件使用Looper.prepare(),使用Looper.loop()啟動訊息迴圈,不斷的從MessageQueue中獲取訊息,在交給Message的target屬性所對應的Handler去處理(由於通常Looper物件會在主執行緒中建立並呼叫Looper.loop()去輪詢訊息,因此該方法執行在主執行緒中),若取到空訊息則loop()退出迴圈(呼叫了Looper的quit()或quitSafely()才會取到空訊息);

  3. Handler:訊息的傳送和處理,

    1) 傳送訊息:通過post()或sendMessage(),再呼叫MessageQueue的enqueueMessage()插入訊息佇列

    2) 處理訊息:當Looper的loop()方法中的迴圈呼叫MessageQueue的next()取到訊息後,呼叫 msg.target.dispatchMessage(msg)進行分發,其Handler中事件處理的優先順序順序:Message.callback(Runnable) -> mCallBack(CallBack介面的子類) ->Handler或子類的handleMessage()【平時使用的是優先順序最低的】

  4. Handler的呼叫流程: Looper會呼叫prepare()和loop()方法,在當前執行的執行緒中儲存一個Looper例項,這個例項會儲存一個MessageQueue物件,然後當前執行緒進入一個無限迴圈中去,不斷從MessageQueue中讀取Handler發來的訊息。然後再回調建立這個訊息的handler中的dispathMessage方法

    1) 首先Looper.prepare()在本執行緒中儲存一個Looper例項,然後該例項中儲存一個MessageQueue物件;因為Looper.prepare()在一個執行緒中只能呼叫一次,所以MessageQueue在一個執行緒中只會存在一個。

    2) Looper.loop()會讓當前執行緒進入一個無限迴圈,不端從MessageQueue的例項中讀取訊息,然後回撥msg.target.dispatchMessage(msg)方法。

    3) Handler的構造方法,會首先得到當前執行緒中儲存的Looper例項,進而與Looper例項中的MessageQueue想關聯。

    4) Handler的sendMessage方法,會給msg的target賦值為handler自身,然後加入MessageQueue中。

    5) 在構造Handler例項時,我們會重寫handleMessage方法,也就是msg.target.dispatchMessage(msg)最終呼叫的方法。

Handler的原理

更多的參考
回到目錄

ThreadLocal

ThreadLocal是一個執行緒內部的資料儲存類 ,實質上是一個泛型類,定義為:public class ThreadLocal。通過它可以在某個指定執行緒中儲存資料,資料儲存以後,只有在指定執行緒(儲存資料的執行緒) 中可以獲取到它儲存的資料,對於其他的執行緒來說無法獲取到它的資料。

通過使用ThreadLocal,能夠讓同一個資料物件在不同的執行緒中存在多個副本,而這些副本互不影響。Looper的實現中便使用到了ThreadLocal。通過使用ThreadLocal,每個執行緒都有自己的Looper,它們是同一個資料物件的不同副本,並且不會相互影響。

ThreadLocal中有一個內部類ThreadLocalMap,ThreadLocal中有一個內部類Entry,Entry中的Object value這個value實際上就是每一個執行緒中的資料副本。ThreadLocalMap中有一個存放Entry的陣列:Entry[] table。 ThreadLocal類的部分程式碼如下:

image.png

ThreadLocal的set 方法:實際上就是往ThreadLocalMap物件(map)維護的物件陣列table中插入資料。

image.png

ThreadLocal的get 方法,呼叫了ThreadLocalMap的getEntry()方法:

image.png

ThreadLocalMap的getEntry() 方法:

image.png

i的值是由執行緒的雜湊碼和(table的長度-1)進行“按位與”運算,所有每個執行緒得到的i是不一樣的,因此最終資料副本在table中的位置也不一樣。

MessageQueue

MessageQueue主要包含兩個操作,插入和讀取。讀取操作的函式是next() ,該操作同時也會伴隨著刪除操作(相當於出佇列),插入操作對應的函式是enqueueMessage()enqueueMessage() 實際上就是單鏈表的插入操作。next() 方法是一個無限迴圈的方法,如果訊息佇列中沒有訊息,那麼next()方法會一直阻塞。當有新訊息到來時,next()方法會返回這條訊息並將其從單鏈表中移除。

Looper

Looper在Android的訊息機制中扮演著訊息迴圈的角色,它會不停地從MessageQueue中檢視是否有新的Message到來,如果有新訊息就會立刻處理,否則就一直阻塞在那裡。一個執行緒只能有一個Looper物件,從而也只有一個MessageQueue(在Looper的構造方法初始化)。

Looper中的幾個重要的成員變數:

image.png

Looper的構造方法,在構造方法中,建立了一個MessageQueue 例項:

image.png

當需要為一個執行緒建立Looper物件時,需要呼叫Looper的prepare() 方法(該方法在一個執行緒中只能呼叫一次,否則會丟擲異常):

image.png

loop() 的訊息迴圈中,實際上是呼叫了MessageQueue的next() 方法。

image.png

image.png

Looper主要作用:
1、 與當前執行緒繫結,保證一個執行緒只會有一個Looper例項,同時一個Looper例項也只有一個MessageQueue。
2、 loop()方法,不斷從MessageQueue中去取訊息,交給訊息的target屬性的dispatchMessage去處理。

Handler

Handler的工作主要是訊息的傳送和訊息接收處理。訊息的傳送可以通過Handler的post() 方法或者sendMessage() 方法來實現,訊息的處理,需要我們重寫handleMessage()函式來進行處理。

Handler的sendMessage()函式:

image.png

image.png

image.png

最後呼叫了MessageQueue的enqueueMessage() 函式:

image.png

image.png

Message 的callback成員變數實際上是一個Runnable物件

Runnable callback;

經常使用的Handler的post(Runnable r) 方法,原始碼是這樣的:


    public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

其中,getPostMessage(r) 為:

private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

原來,Handler的post()方法實際上是把這個Runnable物件封裝到了一個Message中的。

因此,Handler中的事件處理優先順序順序是:

Message.callback(Runnable) – > mCallback(Callback介面實現類或Callback匿名內部類) —> Handler或其子類的handleMessage()。

Android中為什麼主執行緒不會因為Looper.loop()裡的死迴圈卡死?

更多的參考
來源連結
回到目錄

要完全徹底理解這個問題,需要準備以下4方面的知識:Process/Thread,Android Binder IPC,Handler/Looper/MessageQueue訊息機制,Linux pipe/epoll機制。

總結一下主要有3個疑惑:

1.Android中為什麼主執行緒不會因為Looper.loop()裡的死迴圈卡死?

2.沒看見哪裡有相關程式碼為這個死迴圈準備了一個新執行緒去運轉?

3.Activity的生命週期這些方法這些都是在主執行緒裡執行的吧,那這些生命週期方法是怎麼實現在死迴圈體外能夠執行起來的?

(1) Android中為什麼主執行緒不會因為Looper.loop()裡的死迴圈卡死?

這裡涉及執行緒,先說說說程序/執行緒,程序:每個app執行時前首先建立一個程序,該程序是由Zygote fork出來的,用於承載App上執行的各種Activity/Service等元件。程序對於上層應用來說是完全透明的,這也是google有意為之,讓App程式都是執行在Android Runtime。大多數情況一個App就執行在一個程序中,除非在AndroidManifest.xml中配置Android:process屬性,或通過native程式碼fork程序。

執行緒:執行緒對應用來說非常常見,比如每次new Thread().start都會建立一個新的執行緒。該執行緒與App所在程序之間資源共享,從Linux角度來說程序與執行緒除了是否共享資源外,並沒有本質的區別,都是一個task_struct結構體,在CPU看來程序或執行緒無非就是一段可執行的程式碼,CPU採用CFS排程演算法,保證每個task都儘可能公平的享有CPU時間片

有了這麼準備,再說說死迴圈問題:

對於執行緒既然是一段可執行的程式碼,當可執行程式碼執行完成後,執行緒生命週期便該終止了,執行緒退出。而對於主執行緒,我們是絕不希望會被執行一段時間,自己就退出,那麼如何保證能一直存活呢?簡單做法就是可執行程式碼是能一直執行下去的,死迴圈便能保證不會被退出,例如,binder執行緒也是採用死迴圈的方法,通過迴圈方式不同與Binder驅動進行讀寫操作,當然並非簡單地死迴圈,無訊息時會休眠。但這裡可能又引發了另一個問題,既然是死迴圈又如何去處理其他事務呢?通過建立新執行緒的方式。

真正會卡死主執行緒的操作是在回撥方法onCreate/onStart/onResume等操作時間過長,會導致掉幀,甚至發生ANR,looper.loop本身不會導致應用卡死。

(2) 沒看見哪裡有相關程式碼為這個死迴圈準備了一個新執行緒去運轉?

事實上,會在進入死迴圈之前便建立了新binder執行緒,在程式碼ActivityThread.main()中:

public static void main(String[] args) {
        ....

        //建立Looper和MessageQueue物件,用於處理主執行緒的訊息
        Looper.prepareMainLooper();

        //建立ActivityThread物件
        ActivityThread thread = new ActivityThread();

        //建立Binder通道 (建立新執行緒)
        thread.attach(false);

        Looper.loop(); //訊息迴圈執行
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

thread.attach(false);便會建立一個Binder執行緒(具體是指ApplicationThread,Binder的服務端,用於接收系統服務AMS傳送來的事件),該Binder執行緒通過Handler將Message傳送給主執行緒,具體過程可檢視 startService流程分析,這裡不展開說,簡單說Binder用於程序間通訊,採用C/S架構。關於binder感興趣的朋友,可檢視我回答的另一個知乎問題:
為什麼Android要採用Binder作為IPC機制? - Gityuan的回答

另外,ActivityThread實際上並非執行緒,不像HandlerThread類,ActivityThread並沒有真正繼承Thread類,只是往往執行在主執行緒,該人以執行緒的感覺,其實承載ActivityThread的主執行緒就是由Zygote fork而建立的程序。

主執行緒的死迴圈一直執行是不是特別消耗CPU資源呢? 其實不然,這裡就涉及到Linux pipe/e****poll機制,簡單說就是在主執行緒的MessageQueue沒有訊息時,便阻塞在loop的queue.next()中的nativePollOnce()方法裡,詳情見Android訊息機制1-Handler(Java層),此時主執行緒會釋放CPU資源進入休眠狀態,直到下個訊息到達或者有事務發生,通過往pipe管道寫端寫入資料來喚醒主執行緒工作。這裡採用的epoll機制,是一種IO多路複用機制,可以同時監控多個描述符,當某個描述符就緒(讀或寫就緒),則立刻通知相應程式進行讀或寫操作,本質同步I/O,即讀寫是阻塞的。 所以說,主執行緒大多數時候都是處於休眠狀態,並不會消耗大量CPU資源。

(3) Activity的生命週期是怎麼實現在死迴圈體外能夠執行起來的?

ActivityThread的內部類H繼承於Handler,通過handler訊息機制,簡單說Handler機制用於同一個程序的執行緒間通訊。

Activity的生命週期都是依靠主執行緒的Looper.loop,當收到不同Message時則採用相應措施:
在H.handleMessage(msg)方法中,根據接收到不同的msg,執行相應的生命週期。

比如收到msg=H.LAUNCH_ACTIVITY,則呼叫ActivityThread.handleLaunchActivity()方法,最終會通過反射機制,建立Activity例項,然後再執行Activity.onCreate()等方法;
再比如收到msg=H.PAUSE_ACTIVITY,則呼叫ActivityThread.handlePauseActivity()方法,最終會執行Activity.onPause()等方法。 上述過程,我只挑核心邏輯講,真正該過程遠比這複雜。

主執行緒的訊息又是哪來的呢?當然是App程序中的其他執行緒通過Handler傳送給主執行緒,請看接下來的內容:

最後,從程序與執行緒間通訊的角度,*通過一張圖*加深大家對App執行過程的理解:
image.png

system_server程序是系統程序,java framework框架的核心載體,裡面運行了大量的系統服務,比如這裡提供ApplicationThreadProxy(簡稱ATP),ActivityManagerService(簡稱AMS),這個兩個服務都執行在system_server程序的不同執行緒中,由於ATP和AMS都是基於IBinder介面,都是binder執行緒,binder執行緒的建立與銷燬都是由binder驅動來決定的。App程序則是我們常說的應用程式,主執行緒主要負責Activity/Service等元件的生命週期以及UI相關操作都執行在這個執行緒; 另外,每個App程序中至少會有兩個binder執行緒 ApplicationThread(簡稱AT)和ActivityManagerProxy(簡稱AMP),除了圖中畫的執行緒,其中還有很多執行緒,比如signal catcher執行緒等,這裡就不一一列舉。Binder用於不同程序之間通訊,由一個程序的Binder客戶端向另一個程序的服務端傳送事務,比如圖中執行緒2向執行緒4傳送事務;而handler用於同一個程序中不同執行緒的通訊,比如圖中執行緒4向主執行緒傳送訊息。結合圖說說Activity生命週期,比如暫停Activity,流程如下:執行緒1的AMS中呼叫執行緒2的ATP;(由於同一個程序的執行緒間資源共享,可以相互直接呼叫,但需要注意多執行緒併發問題)執行緒2通過binder傳輸到App程序的執行緒4;執行緒4通過handler訊息機制,將暫停Activity的訊息傳送給主執行緒;主執行緒在looper.loop()中迴圈遍歷訊息,當收到暫停Activity的訊息時,便將訊息分發給ActivityThread.H.handleMessage()方法,再經過方法的呼叫,最後便會呼叫到Activity.onPause(),當onPause()處理完後,繼續迴圈loop下去。

說說mvc模式的原理,它在android中的運用,android的官方建議應用程式的開發採用mvc模式。何謂mvc?

mvc是model,view,controller的縮寫,mvc包含三個部分:

  模型(model)物件:是應用程式的主體部分,所有的業務邏輯都應該寫在該層。

  檢視(view)物件:是應用程式中負責生成使用者介面的部分。也是在整個mvc架構中使用者唯一可以看到的一層,接收使用者的輸入,顯示處理結果。

  控制器(control)物件:是根據使用者的輸入,控制使用者介面資料顯示及更新model物件狀態的部分,控制器更重要的一種導航功能,響應使用者出發的相關事件,交給m層處理。

  android鼓勵弱耦合和元件的重用,在android中mvc的具體體現如下:

  1)檢視層(view):一般採用xml檔案進行介面的描述,使用的時候可以非常方便的引入,當然,如果你對android瞭解的比較的多了話,就一定可以想到在android中也可以使用JavaScript+html等的方式作為view層,當然這裡需要進行java和javascript之間的通訊,幸運的是,android提供了它們之間非常方便的通訊實現。

  2)控制層(controller):android的控制層的重任通常落在了眾多的acitvity的肩上,這句話也就暗含了不要在acitivity中寫程式碼,要通過activity交割model業務邏輯層處理,這樣做的另外一個原因是android中的acitivity的響應時間是5s,如果耗時的操作放在這裡,程式就很容易被回收掉。

  3)模型層(model):對資料庫的操作、對網路等的操作都應該在model裡面處理,當然對業務計算等操作也是必須放在的該層的。

什麼是ANR,如何避免它?

更多的參考
回到目錄

- ANR:Application Not Responding。在Android中,活動管理器和視窗管理器這兩個系統服務負責監視應用程式的響應,當用戶操作的在5s內應用程式沒能做出反應,BroadcastReceiver在10秒內沒有執行完畢,就會出現應用程式無響應對話方塊,這既是ANR。
- 避免方法:Activity應該在它的關鍵生命週期方法(如onCreate()和onResume())裡儘可能少的去做建立操作。潛在的耗時操作,例如網路或資料庫操作,或者高耗時的計算如改變點陣圖尺寸,應該在子執行緒裡(或者非同步方式)來完成。主執行緒應該為子執行緒提供一個Handler,以便完成時能夠提交給主執行緒。

什麼情況會導致ForceClose?如何避免?能否捕獲導致其的異常?

更多的參考
回到目錄

- forceclose,意為強行關閉,當前應用程式發生了衝突。NullPointExection(空指標),IndexOutOfBoundsException(下標越界),就連Android API使用的順序錯誤也可能導致(比如setContentView()之前進行了findViewById()操作)等等一系列未捕獲異常
- 避免:編寫程式時邏輯連貫,思維縝密。能捕獲異常,在logcat中能看到異常資訊
- 捕獲異常:可以實現Thread.UncaughtExceptionHandler介面的uncaughtException方法,想要哪個執行緒可以處理未捕獲異常,Thread.setDefaultUncaughtExceptionHandler( this); 這句程式碼就要在那個執行緒中執行一次,不僅可以在主執行緒中這麼做,還可以在子執行緒中進行,在uncaughtException方法中,第一個引數是發生異常的執行緒,第二個引數是異常。

描述一下android的系統架構

更多的參考
回到目錄

image.png

android系統架構分從下往上為linux 核心層、執行庫、應用程式框架層、和應用程式層。

  • 1、linuxkernel:負責硬體的驅動程式、網路、電源、系統安全以及記憶體管理等功能。
  • 2、libraries和 android runtime:libraries:即c/c++函式庫部分,大多數都是開放原始碼的函式庫,例如webkit(引擎),該函式庫負責 android網頁瀏覽器的執行,例如標準的c函式庫libc、openssl、sqlite等,當然也包括支援遊戲開發2dsgl和 3dopengles,在多媒體方面有mediaframework框架來支援各種影音和圖形檔案的播放與顯示,例如mpeg4、h.264、mp3、 aac、amr、jpg和png等眾多的多媒體檔案格式。android的runtime負責解釋和執行生成的dalvik格式的位元組碼。
  • 3、applicationframework(應用軟體架構),java應用程式開發人員主要是使用該層封裝好的api進行快速開發。
  • 4、applications:該層是java的應用程式層,android內建的googlemaps、e-mail、即時通訊工具、瀏覽器、mp3播放器等處於該層,java開發人員開發的程式也處於該層,而且和內建的應用程式具有平等的位置,可以呼叫內建的應用程式,也可以替換內建的應用程式。

上面的四個層次,下層為上層服務,上層需要下層的支援,呼叫下層的服務,這種嚴格分層的方式帶來的極大的穩定性、靈活性和可擴充套件性,使得不同層的開發人員可以按照規範專心特定層的開發。android應用程式使用框架的api並在框架下執行,這就帶來了程式開發的高度一致性,另一方面也告訴我們,要想寫出優質高效的程式就必須對整個 applicationframework進行非常深入的理解。精通applicationframework,你就可以真正的理解android的設計和執行機制,也就更能夠駕馭整個應用層的開發。

AIDL的全稱是什麼?如何工作?能處理哪些型別的資料?

更多的參考
回到目錄

全稱是:Android Interface Define Language
在Android中, 每個應用程式都可以有自己的程序. 在寫UI應用的時候, 經常要用到Service. 在不同的程序中, 怎樣傳遞物件呢?顯然, Java中不允許跨程序記憶體共享. 因此傳遞物件, 只能把物件拆分成作業系統能理解的簡單形式, 以達到跨界物件訪問的目的. 在J2EE中,採用RMI的方式, 可以通過序列化傳遞物件. 在Android中, 則採用AIDL的方式. 理論上AIDL可以傳遞Bundle,實際上做起來卻比較麻煩。
AIDL(AndRoid介面描述語言)是一種藉口描述語言; 編譯器可以通過aidl檔案生成一段程式碼,通過預先定義的介面達到兩個程序內部通訊程序的目的. 如果需要在一個Activity中, 訪問另一個Service中的某個物件, 需要先將物件轉化成AIDL可識別的引數(可能是多個引數), 然後使用AIDL來傳遞這些引數, 在訊息的接收端, 使用這些引數組裝成自己需要的物件.
AIDL的IPC的機制和COM或CORBA類似, 是基於介面的,但它是輕量級的。它使用代理類在客戶端和實現層間傳遞值. 如果要使用AIDL, 需要完成2件事情: 1. 引入AIDL的相關類.; 2. 呼叫aidl產生的class.
AIDL的建立方法:
AIDL語法很簡單,可以用來宣告一個帶一個或多個方法的介面,也可以傳遞引數和返回值。 由於遠端呼叫的需要, 這些引數和返回值並不是任何型別.下面是些AIDL支援的資料型別:
1. 不需要import宣告的簡單Java程式語言型別(int,boolean等)
2. String, CharSequence不需要特殊宣告
3. List, Map和Parcelables型別, 這些型別內所包含的資料成員也只能是簡單資料型別, String等其他比支援的型別.
(另外: 我沒嘗試Parcelables, 在Eclipse+ADT下