Nothing seek,Nothing find

美圖欣賞

picture-01.jpg
picture-02.jpg
picture-03.jpg
picture-04.jpg
picture-05.jpg
picture-06.jpg
picture-07.jpg

Java、Android知識點匯集

Java集合類

** Java集合相關的博客**


Java線程相關

1.線程

synchronized的作用域:
synchronized的用法有兩種,同步方法和同步代碼塊。

一、同步方法。

  • 1、非靜態同步方法的鎖是當前類對象this,即非靜態的synchronized方法的作用域為對象本身。例如,一個對象有多個同步方法,只要有一個線程訪問了其中的一個synchronized方法,其他線程就不能同時訪問這個對象中的任何一個synchronized方法。但是不同對象實例之間的synchronized方法是互不干擾的。

  • 2、靜態同步方法的鎖是當前類的字節碼文件,作用域為當前類,即作用于該類的所有對象實例。

二、同步代碼塊。對該代碼塊的資源實行互斥訪問。

注:
0、同步方法和同步代碼塊的區別,同步代碼塊不會鎖住整個對象(當然你也可以讓它鎖住整個對象)。同步方法會鎖住整個對象,哪怕這個類中有多個不相關聯的同步塊,這通常會導致他們停止執行并需要等待獲得這個對象上的鎖。Java的synchronized的同步代碼塊和同步方法的區別
1、wait(),notify(),notifyAll()方法只能在synchronized中調用。
2、synchronized中調用wait()方法時立即阻塞讓出CPU執行權,代碼塊中剩下的代碼不會繼續執行,只能等待喚醒繼續執行。
3、synchronized中調用notify()或者notifyAll()后,synchronized中的剩余代碼會繼續執行。并且只有等待該同步方法或者同步代碼塊中剩余代碼執行完成后,其他正在等待獲取該對象鎖的同步方法或者同步代碼塊才會繼續執行。
4、死鎖

public class Test {
 public static Object object = new Object();
 /**
  * @param args
  * @throws ClassNotFoundException 
  */
 public static void main(String[] args) throws ClassNotFoundException {
        
  Thread1 thread1 = new Thread1();
        Thread2 thread2 = new Thread2();
         
        thread1.start();
         
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
         
        thread2.start();
 }

  static class Thread1 extends Thread{
         @Override
         public void run() {
             synchronized (object) {
                 try {
                  System.out.println("開始Thread1");
                  Thread.sleep(2000);
                     object.wait();
                 } catch (InterruptedException e) {
                 }
                 System.out.println("線程"+Thread.currentThread().getName()+"獲取到了鎖");
             }
         }
     }
      
     static class Thread2 extends Thread{
         @Override
         public void run() {
             synchronized (object) {
                 object.notifyAll();
                 System.out.println("線程"+Thread.currentThread().getName()+"調用了object.notify()");
                 try {
      sleep(10000);
     } catch (InterruptedException e) {
      e.printStackTrace();
     }
                 
                 System.out.println("sleep end");
             }
             System.out.println("線程"+Thread.currentThread().getName()+"釋放了鎖");
         }
     }
}

控制臺輸出結果如下:

1 開始Thread1
2 線程Thread-1調用了object.notify()
3 sleep end
4 線程Thread-1釋放了鎖
5 線程Thread-0獲取到了鎖

需要注意的是1,2,3是先執行的而且是順序執行。4,5執行順序不確定。

2.ThreadLocal(線程本地存儲)
3.線程池

對比new Thread()和線程池的優劣
new Thread()弊端:
1、new Thread()每次創建新的對象,性能差;
2、缺乏統一管理,資源開銷大。無限制的創建可能會因為資源占用過高導致OOM或者死機;
3、功能單一,沒有辦法定時,定期執行任務等;
相比于new Thread()
1、重用線程,減少對象的創建,回收空閑的線程,提升性能;
2、可以有效控制最大并發數,提高系統資源利用率,同時避免過多的資源競爭,避免堵塞;
3、提供可定時,定期,單線程,并發控制等功能

4.volatile關鍵字

** volatile特性**

當一個共享變量被volatile修飾的時候,它能保證修改的值能夠立即被更新到主存。
內存可見性:線程A對一個volatile修飾的共享變量的修改,對于其他線程來說是可見的,即線程每次獲取volatile變量的值都是最新的。

volatile的使用條件

volatile相當于一個輕量級的synchronize。但是volatile必須滿足兩個條件:
1、對變量的寫操作不能依賴于本身(即不能依賴于當前值)。如多線程下執行a++,這樣是無法通過volatile保證結果的準確性的。
2、該變量沒有包含在具有其他變量的不變式中

public class NumberRange {
    private volatile int lower = 0;
     private volatile int upper = 10;

    public int getLower() { return lower; }
    public int getUpper() { return upper; }

    public void setLower(int value) { 
        if (value > upper) 
            throw new IllegalArgumentException(...);
        lower = value;
    }

    public void setUpper(int value) { 
        if (value < lower) 
            throw new IllegalArgumentException(...);
        upper = value;
    }
}

上述代碼中,上下界初始化分別為0和10,假設線程A和B在某一時刻同時執行了setLower(8)和setUpper(5),且都通過了不變式的檢查,設置了一個無效范圍(8, 5),所以在這種場景下,需要通過sychronize保證方法setLower和setUpper在每一時刻只有一個線程能夠執行。

volatile的使用場景

1、狀態標記
2、double check(雙重檢查)

    private volatile static Singleton instance;
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized(Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    } 
}

volatile關鍵字

Java代理模式

組合與繼承

-深入理解Java中的組合和繼承

泛型

Java反射

Java內存相關

注意點


java異常

java異??蚣軋D.jpg

所有的異常都繼承自一個共同的父類Throwable,而Throwable有兩個重要的子類:Exception(異常)和Error(錯誤)

  • ** Error**(錯誤)
    是程序無法處理的錯誤,表示運行應用程序中較嚴重問題。大多數錯誤與代碼編寫者執行的操作無關,而表示代碼運行時 JVM(Java 虛擬機)出現的問題。例如,Java虛擬機運行錯誤(Virtual MachineError),當 JVM 不再有繼續執行操作所需的內存資源時,將出現 OutOfMemoryError。這些異常發生時,Java虛擬機(JVM)一般會選擇線程終止。
    這些錯誤表示故障發生于虛擬機自身、或者發生在虛擬機試圖執行應用時,如Java虛擬機運行錯誤(Virtual MachineError)、類定義錯誤(NoClassDefFoundError)等。這些錯誤是不可查的,因為它們在應用程序的控制和處理能力之 外,而且絕大多數是程序運行時不允許出現的狀況。對于設計合理的應用程序來說,即使確實發生了錯誤,本質上也不應該試圖去處理它所引起的異常狀況。在 Java中,錯誤通過Error的子類描述。

  • ** Exception**(異常)
    是程序本身可以處理的異常。主要包含RuntimeException等運行時異常和IOException,SQLException等非運行時異常。

** 運行時異常包括:**都是RuntimeException類及其子類異常,如NullPointerException(空指針異常)、IndexOutOfBoundsException(下標越界異常)等,這些異常是不檢查異常,程序中可以選擇捕獲處理,也可以不處理。這些異常一般是由程序邏輯錯誤引起的,程序應該從邏輯角度盡可能避免這類異常的發生。
運行時異常的特點是Java編譯器不會檢查它,也就是說,當程序中可能出現這類異常,即使沒有用try-catch語句捕獲它,也沒有用throws子句聲明拋出它,也會編譯通過。

** 非運行時異常**(編譯異常)包括:RuntimeException以外的異常,類型上都屬于Exception類及其子類。從程序語法角度講是必須進行處理的異常,如果不處理,程序就不能編譯通過。如IOException、SQLException等以及用戶自定義的Exception異常,一般情況下不自定義檢查異常。

從** 編譯器是否要求強制處理**的角度分類,異常類別又可分為:

** 可查異常**
正確的程序在運行中,很容易出現的、情理可容的異常狀況。可查異常雖然是異常狀況,但在一定程度上它的發生是可以預計的,而且一旦發生這種異常狀況,就必須采取某種方式進行處理。
除了RuntimeException及其子類以外,其他的Exception類及其子類都屬于可查異常。這種異常的特點是Java編譯器會檢查它,也就是說,當程序中可能出現這類異常,要么用try-catch語句捕獲它,要么用throws子句聲明拋出它,否則編譯不會通過。

** 不可查異常**
包括運行時異常(RuntimeException與其子類)和錯誤(Error)。

面試常見問題

1.描述Java 7 ARM(Automatic Resource Management,自動資源管理)特征和多個catch塊的使用如果一個try塊中有多個異常要被捕獲,catch塊中的代碼會變丑陋的同時還要用多余的代碼來記錄異常。有鑒于此,Java 7的一個新特征是:一個catch子句中可以捕獲多個異常。示例代碼如下:

catch(IOException | SQLException | Exception ex){
     logger.error(ex);
     throw new MyException(ex.getMessage());
}

大多數情況下,當忘記關閉資源或因資源耗盡出現運行時異常時,我們只是用finally子句來關閉資源。這些異常很難調試,我們需要深入到資源使用的每一步來確定是否已關閉。因此,Java 7用try-with-resources進行了改進:在try子句中能創建一個資源對象,當程序的執行完try-catch之后,運行環境自動關閉資源。下面是這方面改進的示例代碼:

try (MyResource mr = new MyResource()) {
     System.out.println("MyResource created in try-with-resources");
} catch (Exception e) {
     e.printStackTrace();
}

2.在Java中throw與throws關鍵字之間的區別?
throws用于在方法簽名中聲明此方法可能拋出的異常,而throw關鍵字則是中斷程序的執行并移交異常對象到運行時進行處理。

3.** 被檢查的異常 不受檢查的異常**有什么區別?

  • 被檢查的異常應該用try-catch塊代碼處理,或者在main方法中用throws關鍵字讓JRE了解程序可能拋出哪些異常。不受檢查的異常在程序中不要求被處理或用throws語句告知。

  • Exception是所有被檢查異常的基類,然而,RuntimeException是所有不受檢查異常的基類。

  • 被檢查的異常適用于那些不是因程序引起的錯誤情況,比如:讀取文件時文件不存在引發的FileNotFoundException。然而,不被檢查的異常通常都是由于糟糕的編程引起的,比如:在對象引用時沒有確保對象非空而引起的NullPointerException。

4.Java中** final, finally, finalize**的區別?

** final finally在Java中是關鍵字,而 finalize則是一個方法。
** final
關鍵字使得類變量不可變,避免類被其它類繼承或方法被重寫。
** finally try-catch塊一起使用,即使是出現了異常,其子句總會被執行,通常, finally子句用來關閉相關資源。
** finalize
方法中的對象被銷毀之前會被垃圾回收。

異常相關的博客

JVM模型


Java常用算法

Android基本架構

Android基本架構圖.gif

android基本架構
Android基本框架結構

Mac OS 下Android環境搭建

Android環境搭建

四大組件之 Activity

1. Activity生命周期
20160229214006125.png

正常情況下Activity會經歷如下生命周期:

  1. onCreate(),表示Activity的創建。在這個方法中我們可以做一些初始化的工作。比如調用setContentView去加載layout布局資源,初始化Activity所需的數據等。

  2. onRestart(),重新啟動Activity。一般情況下,當當前的Activity從不可見變為可見的時候,該方法會被調用。這中情況一般是用戶按Home健回到桌面或者啟動了一個新的Activity,這個時候onPause和onStop方法就會被調用。上面的操作完成后用戶如果點擊桌面應用的圖標或者按back鍵回到之前的Activity,這時候onRestart就會被調用。

  3. onStart(),表示開始啟動Activity。這個時候Activity就變為可見了,但是前臺還是看不到的,還沒有辦法和用戶進行交互。只有當onResume方法被調用的時候,才會真正的出現在前臺。

  4. onResume(),表示Activity已經變得可見,可以和用戶進行交互了。需要注意的是,雖然onStart和onResume雖然都表示Activity變得可見。但是onStart的時候,Activity還在后臺,onResume的時候才真正的出現在前臺。

  5. onPause(),此時的Activity正準備停止,正常情況下,接著就會調用onStop方法。特殊情況下,如果這個時候用戶又回到這個Activity,那么onResume就會被調用。這是一種非常極端的情況,很難重現。onPause中不要做太多耗時的操作,因為會影響新的Activity的展現,onPause必須先執行完成,新的Activity的onResume方法才會被執行。

  6. onStop(),表示Activity即將停止??梢宰鲆恍┥晕⒅亓考壍幕厥展ぷ?。同樣不能做太多耗時的操作。

  7. onDestroy(),這是Activity生命周期中最后一個回調,表示Activity即將被銷毀??梢宰鲆恍┗厥展ぷ鳎尫刨Y源等等。

Activity啟動模式

Android中Activity的啟動模式有四種,分別是standard、singleTop、singleTask和singleInstance。
1、standard:標準模式。這也是Activity默認的啟動模式。每次啟動一個Activity都會重新創建新的Activity實例。不管這個Activity實例在任務棧中是否已經存在。被創建的Activity實例的生命周期符合典型情況下的Activity生命周期。
事例說明:此時有兩個Activity,SecondActivity和ThirdActivity。當我們當我們從SecondActivity跳轉到ThirdActivity再從ThirdActivity跳轉到SecondActivity。我們可以發現SecondActivity被創建了兩次。這就說明標準模式下的Activity不會復用,每次啟動都會創建新的Activity實例。

2、singleTop:棧頂復用模式。這種模式下,如果要被啟動的Activity實例已經位于任務棧的棧頂,則不再創建新的Activity實例。同時它的onNewIntent方法會被調用。通過這個方法我們可以取出當前請求的信息。需要注意的是,這個Activity的onCreate。onStart不會被調用。因為它并沒有發生改變。如果新的Activity不是位于棧頂,那么新的Activity任然會被創建。例如現在任務棧中有ABCD,ABCD啟動模式均為singleTop模式,A位于棧底,D位于棧頂,如果此時要啟動C,那么任務棧的情況就會變成ABCDC。

3、singleTask:這是站內復用模式。這種模式下,只要Activity實例在任務棧中存在,啟動的時候就不會去再次創建新的Activity實例,而是沿用已經存在的Activity實例。不論啟動多少次,任務棧都只會存在一個Activity實例。這就是站內復用模式。和singleTop一樣,系統啟動Activity的時候,如果任務棧中存在該Activity實例,就不會調用onCreate和onStart方法,而是會調用onNewIntent方法。
singleTask站內復用模式的幾種情況:
1)如果目前任務棧T1中存在ABC三個activity實例,這個時候Activity D以singleTask模式請求啟動,其所需要的任務棧未T2.由于T2和D的實例都不存在,所以系統會首先創建T2,然后將D壓入到T2中。
2)第二種情況,如果目前任務棧T1中存在ABCD三個activity實例,這個時候Activity D以singleTask模式請求啟動,由于D已經存在T1的任務棧,所以D就不會被再次創建,而是直接沿用已經存在的D的實例。
3)第三種情況,如果目前任務棧T1中存在ADBC三個activity實例,這個時候Activity D以singleTask模式請求啟動,由于D已經在T1中已經存在,同樣不會再次創建,也是沿用已經存在的D的實例。同時會將位于D上面的其他Activity實例移出棧,使自己位于棧頂位置。

4、singleInstance:單實例模式。這種模式可以看成是singleTask的加強模式,它除了具有singleTask的模式所具有的一些列特性之外,還增加了一點特殊的特性。具有這種模式的activity只能單獨的運行在獨立的任務棧中。什么叫運行在獨立的任務棧,其實就是如果Activity A是singleInstance模式,當A啟動后,系統會為A創建一個新的任務棧使其獨立運行在其中,這個棧中只有A自己不存在其他Activity實例。由于它也具有singleTask的復用的特性,在后續啟動的時候,也會復用這個Activity。

Fragment

四大組件之 Service

  1. Service詳解
  2. Service學習筆記
  3. IntentService
  4. Android 5.0之后隱式聲明Intent 啟動Service引發的問題

四大組件之 Broadcast Receiver


四大組件之 ContentProvider


Notification


SharedPreferences

SharedPreferences類是一個接口類,真正的實現類是SharedPreferencesImpl。修改SharedPreferences需要獲取它的Editor,在對Editor進行put操作后,最后通過commit或者apply提交修改到內存和文件。當然有了兩種都可以提交的方法,肯定要區別一下的。從實現類SharedPreferencesImpl的源碼上看也很容易看出兩者的區別:

commit這種方式很常用,在比較早的SDK版本中就有了,這種提交修改的方式是同步的,會阻塞調用它的線程,并且這個方法會返回boolean值告知保存是否成功(如果不成功,可以做一些補救措施)。
而apply是異步的提交方式,目前Android Studio也會提示大家使用這種方式。

還有一點用得比較少的,就是SharedPreferences還提供一個監聽接口可以監聽SharedPreferences的鍵值變化,需要監控鍵值變化的可以用registerOnSharedPreferenceChangeListener添加監聽器。

public interface SharedPreferences { 
    /** 
    * Interface definition for a callback to be invoked when a shared 
    * preference is changed. 
    */ 
    public interface OnSharedPreferenceChangeListener { 
       void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key); 
    }
}

多進程操作和讀取SharedPreferences的問題
在SDK 3.0及以上版本,可以通過Context.MODE_MULTI_PROCESS屬性來實現SharedPreferences多進程共享。如下設置:

public static SharedPreferences getSharedPreferences(String name) { 
     if (null != context) {
          if (Build.VERSION.SDK_INT >= 11) { 
             return context.getSharedPreferences(name, Context.MODE_MULTI_PROCESS); 
          } else { 
             return context.getSharedPreferences(name, Context.MODE_PRIVATE); } 
          } 

      return null; 
 }

MODE_MULTI_PROCESS屬性使用SharedPreferences也不能完全保證進程間的共享數據不會出問題,真正使用中發現有會有一定概率出現這個取值出錯(變為初始值)問題。
Google也在SDK 6.0的版本將這個MODE_MULTI_PROCESS標識為deprecated(不贊成使用)。目前來說,越來越多的項目在不斷的膨脹,為了降低單個進程的內存占用率,使用"android:process"配置一些組件在單獨的進程中運行已經是司空見慣了,所以大家在遇到自己的項目有多進程時,要注意一下SharedPreferences的問題。

注意: 在一個進程中,SharedPreference往往建單個實例就可以了,一般不會出現并發沖突,如果對提交的結果不關心的話,建議使用apply,當然需要確保提交成功且有后續操作的話,還是需要用commit的。關于SharedPreferences多進程數據共享問題,可以借鑒開源?替代方案如Github上的tray。

Android消息處理機制(Looper、Handler、MessageQueue、Message)

** 概述**:Android應用程序是通過消息來驅動的,Android某種意義上也可以說成是一個以消息驅動的系統,UI、事件、生命周期都和消息處理機制息息相關,并且消息處理機制在整個Android知識體系中也是尤其重要。

Handler的運行機制描述:談到Handler的運行機制一般情況下,都會涉及到幾個比較重要的類,Looper,MessageQueue,Message等等。Handler的實例必須在Looper線程中創建,否則就會拋出RuntimeException異常(Can't create handler inside thread that has not called Looper.prepare())提示handler實例的創建必須在looper線程中進行。handler實例的創建一般都是在UI線程,因為,一般情況下我們使用handler的目的是為了執行完后臺任務后,和UI線程進行交互。由于UI線程在創建之初,就被設置成了Looper線程(這個可以在ActivityThread源碼中看到,里面有一個main方法,在實例化ActivityThread的之前,調用了 Looper.prepareMainLooper()),所以我們在實例化Handler的時候不需要手動再次調用Looper.prepare()方法。Looper線程中會維護一個MessageQueue消息隊列。handler通過sendMessage()方法向Looper中的消息隊列插入一條Message消息。MessageQueue通過enqueueMessage方法將handler發送來的message消息放到消息隊列中。由于Looper線程是一個循環進程,里面有一個阻塞方法loop()。在該方法中looper會調用MessageQueue的next()不停的循環遍歷MessageQueue中的消息(可以在Looper源碼的loop()方法中看到)。當Looper發現有新的message來的時候,就會回調給Handler中handMessage方法進行處理。

備注:handler在主線程中實例化后就會拿到主線程的MessageQueue的引用(Looper中維護的MessageQueue其實就是主線程中MessageQueue)。handler在sendMessage的時候,發送的Message里面持有Handler的引用。這樣在整個消息流程中就就把Looper,MessageQueue,Message,Handler串聯連起來了,不會出現錯亂。

消息處理機制的本質:** 一個線程開啟循環模式持續監聽并依次處理各個線程發來的消息**

簡單的說:一個線程開啟一個無限循環模式,不斷遍歷自己的消息列表,如果有消息就挨個拿出來做處理,如果列表沒消息,自己就堵塞(相當于wait,讓出cpu資源給其他線程),其他線程如果想讓該線程做什么事,就往該線程的消息隊列插入消息,該線程會不斷從隊列里拿出消息做處理。

** 消息處理機制的描述:**
線程其實就是一段可執行代碼,當這段代碼執行完成后,該線程的生命周期就結束了,線程就會退出。既然如此,UI線程在執行完成后為什么沒有退出呢?因為UI線程在創建的時候就被變成了Looper線程(這個可以在ActivityThread的main方法中看到,在ActivityThread創建之前調用了Looper.prepareMainLooper();ActivityThread創建完成后接著就調用了Looper.loop();),Looper里面有一個loop()方法,這個方法里面有一段死循環的代碼。

主線程會一直處于這個死循環中,由于Looper里面維護一個MessageQueue消息隊列,這個消息隊列就用來維護其他線程發送來的Message消息(例如,Activity的啟動,生命周期,UI的更新,控件的事件等等),UI線程會根據消息隊列中的消息(例如,Activity的啟動,生命周期,UI的更新,控件的事件等等),依次做出處理。

那么其他線程是如何發送Message到UI線程的MessageQueue消息隊列中的?首先肯定是要拿到MessageQueue的實例,Google為了統一添加消息和消息的回調處理,又專門構建了Handler類,你只要在主線程構建Handler類,那么這個Handler實例就獲取主線程MessageQueue實例的引用(獲取方式mLooper = Looper.myLooper();mQueue = mLooper.getQueue();),Handler 在sendMessage的時候就通過這個引用往消息隊列里插入新消息。Handler 的另外一個作用,就是能統一處理消息的回調。這樣一個Handler發出消息又確保消息處理也是自己來做

** Looper、Handler、MessageQueue、Message作用和存在的意義?**

  • ** Looper** 循環線程,在主線程中調用Looper.prepare()...Looper.loop()就會變當前線程變成Looper線程(可以先簡單理解:無限循環不退出的線程)

  • ** Handler**簡單說Handler用于同一個進程的線程間通信。Google 為了統一添加消息和消息的回調處理,又專門構建了Handler類,你只要在主線程構建Handler類,那么這個Handler實例就獲取主線程MessageQueue實例的引用(獲取方式mLooper = Looper.myLooper();mQueue = mLooper.mQueue;),Handler 在sendMessage的時候就通過這個引用往消息隊列里插入新消息。Handler 的另外一個作用,就是能統一處理消息的回調。這樣一個Handler發出消息又確保消息處理也是自己來做,這樣的設計非常的贊。具體做法就是在隊列里面的Message持有Handler的引用(哪個handler 把它放到隊列里,message就持有了這個handler的引用),然后等到主線程輪詢到這個message的時候,就來回調我們經常重寫的Handler的handleMessage(Message msg)方法。

  • ** MessageQueue**MessageQueue 存在的原因很簡單,就是同一線程在同一時間只能處理一個消息,同一線程代碼執行是不具有并發性,所以需要隊列來保存消息和安排每個消息的處理順序。多個其他線程往UI線程發送消息,UI線程必須把這些消息保持到一個列表(它同一時間不能處理那么多任務),然后挨個拿出來處理,這種設計很簡單,我們平時寫代碼其實也經常這么做。每一個Looper線程都會維護這樣一個隊列,而且僅此一個,這個隊列的消息只能由該線程處理。
  • ** Message** UI線程和子線程通信的載體,主要存儲和傳遞一些子線程想讓UI線程處理的內容

** 關于Looper中loop()方法的疑問**
1.UI線程一直在這個循環里跳不出來,主線程不會因為Looper.loop()里的死循環卡死嗎,那還怎么執行其他的操作呢?

在looper啟動后,主線程上執行的任何代碼都是被looper從消息隊列里取出來執行的。也就是說主線程之后都是通過其他線程給它發消息來實現執行其他操作的。生命周期的回調也是如此的,系統服務ActivityManagerService通過Binder發送IPC調用給APP進程,App進程接到到調用后,通過App進程的Binder線程給主線程的消息隊列插入一條消息來實現的。

2.主線程是UI線程和用戶交互的線程,優先級應該很高,主線程的死循環一直運行是不是會特別消耗CPU資源嗎?App進程的其他線程怎么辦?

這基本是一個類似生產者消費者的模型,簡單說如果在主線程的MessageQueue沒有消息時,就會阻塞在loop的queue.next()方法里,這時候主線程會釋放CPU資源進入休眠狀態,直到有下個消息進來時候就會喚醒主線程,在2.2 版本以前,這套機制是用我們熟悉的線程的wait和notify 來實現的,之后的版本涉及到Linux pipe/epoll機制,通過往pipe管道寫端寫入數據來喚醒主線程工作。原理類似于I/O,讀寫是堵塞的,不占用CPU資源。

** 關于Handler實例的創建**

  • Handler實例的創建,是需要在創建之前調用Looper.prepare()將當前線程變成Looper線程的,同時調用Looper.loop()。那為什么我們平時在UI線程中創建Handler實例的時候為什么沒有調用Looper.prepare()和Looper.loop(),那時因為UI線程在創建的時候就變成了Looper線程,所以不再需要調用Looper.prepare()和Looper.loop()。如果再調用這兩個方法的話就會拋出Only one Looper may be created per thread異常。就是告訴你Looper實例已經存在了,并且只能有一個。

我們在Activity中創建的Handler實例IDE常常提示出一些警告,這些警告一半都是提示這種定義方式可能出現內存泄露。那么如何正確創建Handler實例??可以采用static方式,定義一個內部類繼承Handler,利用軟引用WeakReference,接收外部類的引用。具體可以參照下面這兩篇文章

** Android消息處理相關機制相關的博文**


AsyncTask

1.AsyncTask機制

AnsycTask執行任務時,內部會創建一個進程作用域的線程池來管理要運行的任務,也就就是說當你調用了AsyncTask.execute()后,AsyncTask會把任務交給線程池,由線程池來管理創建Thread和運行Therad。最后和UI打交道就交給Handler去處理了。

這是從API 23的AsyncTask源碼中截取的一部分源碼:

Paste_Image.png

從上面的代碼中我們可以看出,AsyncTask中的線程池,核心線程數(CORE_POOL_SIZE)由可獲得的CPU內核數決定,最大線程數量(MAXIMUM_POOL_SIZE)是可獲得CPU內核數的兩倍加1(CPU_COUNT*2+1)。keepAliveTime是30秒,工作隊列是128。

  • corePoolSize: 核心線程數目,即使線程池沒有任務,核心線程也不會終止(除非設置了allowCoreThreadTimeOut參數)可以理解為“常駐線程”
  • maximumPoolSize: 線程池中允許的最大線程數目;一般來說,線程越多,線程調度開銷越大;因此一般都有這個限制。
  • keepAliveTime: 當線程池中的線程數目比核心線程多的時候,如果超過這個keepAliveTime的時間,多余的線程會被回收;這些與核心線程相對的線程通常被稱為緩存線程
  • unit: keepAliveTime的時間單位
  • workQueue: 任務執行前保存任務的隊列;這個隊列僅保存由execute提交的Runnable任務
  • threadFactory: 用來構造線程池的工廠;一般都是使用默認的;
  • handler: 當線程池由于線程數目和隊列限制而導致后續任務阻塞的時候,線程池的處理方式。

2.多個AsyncTask任務是串行還是并行?

從Android 1.6到2.3(Gingerbread) AsyncTask是并行的,即上面我們提到的有5個核心線程的線程池(ThreadPoolExecutor)負責調度任務。從Android 3.0開始,Android團隊又把AsyncTask改成了串行,默認的Executor被指定為SERIAL_EXECUTOR。

3.AsyncTask容易引發的Activity內存泄露

如果AsyncTask被聲明為Activity的非靜態的內部類,那么AsyncTask會保留一個對創建了AsyncTask的Activity的引用。如果Activity已經被銷毀,AsyncTask的后臺線程還在執行,它將繼續在內存里保留這個引用,導致Activity無法被回收,引起內存泄露。

4.博文推薦

你真的了解AsyncTask?

總結

AsyncTask的運行機制
AsyncTask分為兩個部分,一部分和主線程交互,一部分負責線程調度。雖然可能會存在多個AsyncTask的子類實例,但是其內部的Handler和ThreadPoolExecutor都是靜態的,是進程范圍內共享的。所以AsyncTask控制著進程范圍內所有其子實例。

與主線程交互 AsyncTask和主線程交互是通過Handler來完成的;

?線程調度 關于AsyncTask內部的線程調度,其內部會創建一個進程作用域內的線程池;也就是說當你調用了AsyncTask的execute(...)方法后,AsyncTask就會把任務交給線程池,由線程池來管理創建和運行Thread。對于內部線程池,不同版本的Android內部實現方式是不一樣的。
Android2.3(API 10)之前,線程池限制數為5個,因為Android2.3之前的AsyncTask是并行的,所以同時只能有5個線程在運行,超過的只能等待,等待前面5個完成才能繼續執行。
這種情況在Android3.0(API 11)之后的版本得到了改善,Google工程師把AsyncTask的execute(...)方法由之前的并行方式,改成了串行方式,按先后順序每次只允許一個線程執行。除此之外,還添加了一個executeOnExecutor(...)方法,這個方法是并行執行方法,同時也允許開發者提供自定義的線程池來運行和調度Thread,如果你想讓所有的任務都能并發同時運行,那就創建一個沒有限制的線程池(Executors.newCachedThreadPool()),并提供給AsyncTask。這樣這個AsyncTask實例就有了自己的線程池而不必使用AsyncTask默認的。當然AsyncTask中還有兩個預設的線程池SERIAL_EXECUTORTHREAD_POOL_EXECUTOR。SERIAL_EXECUTOR表示串行執行;THREAD_POOL_EXECUTOR表示并行執行。SERIAL_EXECUTOR作用是保證任務執行的順序,也就是它可以保證提交的任務確實是按照先后順序執行的。它的內部有一個隊列用來保存所提交的任務,保證當前只運行一個,這樣就可以保證任務是完全按照順序執行的,默認的execute()使用的就是這個,也就是executeOnExecutor(AsyncTask.SERIAL_EXECUTOR)與execute()是一樣的。?對于THREAD_POOL_EXECUTOR,可以通過調用executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR),這樣起碼不用等到前面的都結束了再執行。

AsyncTask帶來的問題
1.生命周期,AsyncTask不會?隨著Activity的?銷毀而銷毀。正常情況下Activity銷毀了,此時如果有AsyncTask在執行,AsyncTask并不會隨著Activity的?銷毀而銷毀。而是會繼續執行,如果我們在onPostExecute()方法中做了更新UI的操作,就有可能出現crash現象。解決方法就是在Activity銷毀的時候,檢測當前是否存在正在運行的AsyncTask(通過isCancelled()),如果有的話,就調用AsyncTask的cancel(true)方法,取消任務,注意不要再AsyncTask的doBackground()方法中執行不可中斷的操作(如BitmapFactory.decodeStream()),否則無法立即cancel掉。除此,還可以在onPostExecute()方法中,在執行前判斷當前Activity是否已存活,如果是活著的,就繼續執行,反之則return取消掉。

2.內存泄露,如果AsyncTask被聲明為Activity的非靜態的內部類,那么AsyncTask會保留一個對創建了AsyncTask的Activity的引用。如果Activity已經被銷毀,AsyncTask的后臺線程還在執行,它將繼續在內存里保留這個引用,導致Activity無法被回收,引起內存泄露。解決辦法,就是在Activity銷毀的時候,檢查是否有還在運行的AsyncTask,如果有就cancel掉。

3.結果丟失,屏幕旋轉或Activity在后臺被系統殺掉等情況會導致Activity的重新創建,之前運行的AsyncTask會持有一個之前Activity的引用,這個引用已經無效,這時調用onPostExecute()再去更新界面將不再生效。解決方法和上面一樣,也是在銷毀之前cancel掉

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,615評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,606評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,044評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,826評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,227評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,447評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,992評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,807評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,001評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,243評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,667評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,930評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,709評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,996評論 2 374

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,665評論 25 708
  • Android Handler機制系列文章整體內容如下: Android Handler機制1之ThreadAnd...
    隔壁老李頭閱讀 20,360評論 13 54
  • 1.要做一個盡可能流暢的ListView,你平時在工作中如何進行優化的? ①Item布局,層級越少越好,使用hie...
    fozero閱讀 741評論 0 0
  • 我已經很久沒有耐心去認真的看完一篇文章或者用心的去想要寫一篇文章,我花了大量的時間在手機和電腦上,做著一些自己也不...
    煢人閱讀 220評論 0 1
  • 在電影《機器紀元》里,機器人學會了自我復制和創造能力,代替人類成為新紀元即“機器紀元”的主人。 從常識和邏輯上理解...
    威威專欄閱讀 675評論 0 0