Android在橫豎屏切換時到底發生了什么?

jopen 9年前發布 | 31K 次閱讀 Android Android開發 移動開發

原文出處: Square技術博客   譯文出處:Android Cool Posts   

之前的一篇文章我們深入loopers和handler進行分析,看它們是如何同Android主線程相關聯的。

今天,我們將繼續深入Android主線程同Android組件生命周期的交互。

Activity同orientation changes之間的關系

首先來看看Activity的生命周期和它處理configuration changes 的神奇之處。

這篇文章主要來自于一段類似下面的代碼:

public class MyActivity extends Activity {
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Handler handler = new Handler(Looper.getMainLooper());
    handler.post(new Runnable() {
      public void run() {
        doSomething();
      }
    });
  }

  void doSomething() {
    // Uses the activity instance
  }
}

通過上面的代碼我們知道,doSomething()方法會在Activity因為一個configuration change導致onDestroy()方法會被調用之后執行。之后,你也不能再使用Activity這個實例了。

基于orientation changes的refresher

設備的 orientation 回來任意時刻發生改變。我們會在一個Activity被創建的時候通過Activity#setRequestedOrientation(int)方法來模擬一個orientation change。

你能預測當這個Activity處于portrait模式時log輸出嗎?

public class MyActivity extends Activity {
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d("Square", "onCreate()");
    if (savedInstanceState == null) {
      Log.d("Square", "Requesting orientation change");
      setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
    }
  }

  protected void onResume() {
    super.onResume();
    Log.d("Square", "onResume()");
  }

  protected void onPause() {
    super.onPause();
    Log.d("Square", "onPause()");
  }

  protected void onDestroy() {
    super.onDestroy();
    Log.d("Square", "onDestroy()");
  }
}

如果你了解 Android 生命周期, 你的預測結果可能是下面的答案:

public class MyActivity extends Activity {
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d("Square", "onCreate()");
    if (savedInstanceState == null) {
      Log.d("Square", "Requesting orientation change");
      setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
    }
  }

  protected void onResume() {
    super.onResume();
    Log.d("Square", "onResume()");
  }

  protected void onPause() {
    super.onPause();
    Log.d("Square", "onPause()");
  }

  protected void onDestroy() {
    super.onDestroy();
    Log.d("Square", "onDestroy()");
  }
}

Android的生命周期正常運轉,Activity被created,resumed,然后這個時候orientation change 發生了,Activity被 paused, destroyed,接著一個新的Activity被created 和 resumed。

Orientation changes 和Android主線程

此處有一個重要的細節:一個orientation change 導致Activity 被重新創建是通過向Android主線程的消息隊列發送了一個簡單的消息。

請看下面通過反射來讀取主線程消息隊列里面內容的spy代碼:

public class MyActivity extends Activity {
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d("Square", "onCreate()");
    if (savedInstanceState == null) {
      Log.d("Square", "Requesting orientation change");
      setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
    }
  }

  protected void onResume() {
    super.onResume();
    Log.d("Square", "onResume()");
  }

  protected void onPause() {
    super.onPause();
    Log.d("Square", "onPause()");
  }

  protected void onDestroy() {
    super.onDestroy();
    Log.d("Square", "onDestroy()");
  }
}

從上面可以看出,一個消息隊列僅僅只是一個鏈表,每一個消息都有一個指向下一個消息的引用。

我們可以通過上面的代碼來記錄orientation change發生之后消息隊列里面的內容:

public class MyActivity extends Activity {
  private final MainLooperSpy mainLooperSpy = new MainLooperSpy();

  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d("Square", "onCreate()");
    if (savedInstanceState == null) {
      Log.d("Square", "Requesting orientation change");
      setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
      mainLooperSpy.dumpQueue();
    }
  }
}

最后的輸出是:

public class MyActivity extends Activity {
  private final MainLooperSpy mainLooperSpy = new MainLooperSpy();

  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d("Square", "onCreate()");
    if (savedInstanceState == null) {
      Log.d("Square", "Requesting orientation change");
      setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
      mainLooperSpy.dumpQueue();
    }
  }
}

我們可以通過ActivityThread 類的源碼知道數字 118 和 126 代表的消息是:

public class MyActivity extends Activity {
  private final MainLooperSpy mainLooperSpy = new MainLooperSpy();

  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d("Square", "onCreate()");
    if (savedInstanceState == null) {
      Log.d("Square", "Requesting orientation change");
      setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
      mainLooperSpy.dumpQueue();
    }
  }
}

一個orientation change 會往Android主線程的消息隊列里面添加一個CONFIGURATION_CHANGED和一個RELAUNCH_ACTIVITY消息。

讓我們會退一步來看看到底發生了什么:

當Activity第一次啟動的時候,主線程的消息隊列是空的。當前要執行的消息就是LAUNCH_ACTIVITY,即創建一個Activity實例,先后調用onCreate()方法和onResume()方法。接下來只有主線程的looper才能處理消息隊列里面下一個消息。

當設備檢測到有一個 orientation change 時,會有一個RELAUNCH_ACTIVITY消息被推送到主線程的消息隊列。

當這個消息被處理的時候它會:

  • 在老的Activity實例上調用onSaveInstanceState(),onPause(),onDestroy()方法,
  • 創建一個新的Activity實例,
  • 在新的Activity實例上調用onCreate()和onResume()方法。

上面這些都是一個消息要處理的。與此同時發生所有消息都會在新的Activity執行完onResume()方法后才被調用的。

全面測試

如果你在一個orientation change發生期間在onCreate()方法中向主線程消息隊列發送消息會怎么樣?這會有兩種情況,在orientation change發生之前和之后:

public class MyActivity extends Activity {
  private final MainLooperSpy mainLooperSpy = new MainLooperSpy();

  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d("Square", "onCreate()");
    if (savedInstanceState == null) {
      Log.d("Square", "Requesting orientation change");
      setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
      mainLooperSpy.dumpQueue();
    }
  }
}

輸出結果為:

onCreate<span class="o">()</span>
Requesting orientation change
Begin dumping queue
<span class="o">{</span> <span class="nv">what</span><span class="o">=</span>0 <span class="nv">when</span><span class="o">=</span>-129ms <span class="o">}</span>
<span class="o">{</span> <span class="nv">what</span><span class="o">=</span>118 <span class="nv">when</span><span class="o">=</span>-96ms <span class="nv">obj</span><span class="o">={</span>1.0 208mcc15mnc en_US ldltr sw360dp w598dp h335dp 320dpi nrml land finger -keyb/v/h -nav/h s.46?spn<span class="o">}</span> <span class="o">}</span>
<span class="o">{</span> <span class="nv">what</span><span class="o">=</span>126 <span class="nv">when</span><span class="o">=</span>-69ms <span class="nv">obj</span><span class="o">=</span>ActivityRecord<span class="o">{</span>41fd6b68 <span class="nv">token</span><span class="o">=</span>android.os.BinderProxy@41fd0ae0 no component name<span class="o">}</span> <span class="o">}</span>
<span class="o">{</span> <span class="nv">what</span><span class="o">=</span>0 <span class="nv">when</span><span class="o">=</span>-6ms <span class="o">}</span>
End dumping queue
onResume<span class="o">()</span>
Posted before requesting orientation change
onPause<span class="o">()</span>
onDestroy<span class="o">()</span>
onCreate<span class="o">()</span>
onResume<span class="o">()</span>
Posted after requesting orientation change

有上面的結果我們可得知:在執行到onCreate()方法最后時,消息隊列里面包含四個消息。第一個就是orientation change 發生之前發送的消息,接著兩個消息為orientation change相關的,最后一個消息就是orientation change發生之后要發送的消息。最后的logs 表明這些消息都是按順序依次被執行的。

而且,任何在orientation change 之前發送的消息都會在Activity離開時調用的 onPause() 之前被處理,任何在orientation change 之后發送的消息都會在新的Activity調用的 onResume() 之后被處理。

這個實驗告訴我們當你發送一個消息的時候,你不能保證當時存在的Activity實例在消息處理完之后還會存在(即使你是在onCreate()或者onResume()方法里面發送的)。如過你在Activity A發送的消息B里面還持有一個view C或者一個 Activity D的引用,那么這個Activity A在消息 B被處理之前是不會被垃圾回收的。

那么我們可以如何解決這樣的問題呢?What could you do?

The real fix解決方案

當你處于主線程的時候不要調用handler.post()方法。大部分情況下,handler.post()被用來處理順序問題。為了保證你的應用架構穩定請慎用handler.post()。

如果你真的需要使用handler.post()

保證你的消息不要持有一個Activity實例的引用?Make sure your message does not hold a reference to an activity, as you would do for a background operation.

如果你真的需要使用handler.post()還要保持Activity引用

在Activity的onPause()方法里面通過 handler.removeCallbacks() 方法從消息隊列移除這個消息。

如果你真的需要讓你的消息被及時處理

使用handler.postAtFrontOfQueue()方法保證消息在onPause()發送,那么這個消息會在onPause()之前被處理。但是你的代碼將會變得很難懂。說真的,別這樣干。

關于runOnUiThread()的一些話

你注意到我們創建一個handler然后使用handler.post()方法而不是直接調用Activity.runOnUiThread()方法嗎?

原因是這樣的:

public class Activity {
  public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
      mHandler.post(action);
    } else {
      action.run();
    }
  }
}

不像handler.post(),runOnUiThread()在當前線程為主線程的情況下是不會發送這個runnable的,它會同步地調用run()方法。

Services

有一個誤解一直需要解除:service 不是 運行在一個后臺線程。

所有的 service 生命周期方法 (onCreate(),onStartCommand(), 等等) 都是運行在主線程上面 (就是那個執行你的Activities各種有趣動畫的線程)。

無論你是出于一個 service 還是 activity,長時間任務必須要在后臺線程進行處理。這個后臺線程可以和你的應用生命周期一樣長,即使你的Activities已經銷毀了。

然而,Android可以在任意時刻決定 kill 一個應用進程。一個 service 是一種請求系統讓我們可以存活更久,同時在系統要kill 進程的時候可以讓這個service知道。

注意: 當onBind() 接受到一個其他進程的調用并返回一個IBinder,那么這個方法會在后臺線程里面執行。

你可以花時間讀讀 Service documentation –相當不錯。

IntentService

IntentService 為在后臺線程依次執行一個Intent 隊列里面所有Intent提供了一個簡單的方式。

public class MyService extends IntentService {
  public MyService() {
    super("MyService");
  }

  protected void onHandleIntent(Intent intent) {
    // This is called on a background thread.
  }
}

在它內部,使用了一個Looper在一個專用HandlerThread的來處理這些Intents。當這個service 被銷毀的時候,這個Looper會讓你結束當前intent的處理,然后終結這個后臺線程。

總結

大部分的Android 生命周期方法都是在主線程被調用的。你可以把這些回調函數當成發送給一個looper 隊列的簡單消息。

在此文結束之前還是要像大多數的Android開發博客那樣強調:不要阻塞Android主線程。

你是不是想知道阻塞了主線程到底意味著什么?哈哈,這是下一個主題了。

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!