參考另一篇文章[進程和線程](http://note.youdao.com/noteshare?id=de140840a1da668b7f2f7f3facb34620)
原文出處——>[進程和線程](https://developer.android.com/guide/components/processes-and-threads.html#Processes)
當某個應用組件啟動且該應用沒有運行其他任何組件時,Android 系統會使用單個執行線程為應用啟動新的 Linux 進程。默認情況下,同一應用的所有組件在相同的進程和線程(稱為“主”線程)中運行。 如果某個應用組件啟動且該應用已存在進程(因為存在該應用的其他組件),則該組件會在此進程內啟動并使用相同的執行線程。 但是,您可以安排應用中的其他組件在單獨的進程中運行,并為任何進程創建額外的線程。
本文檔介紹進程和線程在 Android 應用中的工作方式。
#### **進程**
默認情況下,同一應用的所有組件均在相同的進程中運行,且大多數應用都不會改變這一點。 但是,如果您發現需要控制某個組件所屬的進程,則可在清單文件中執行此操作。
各類組件元素的清單文件條目—[`<activity>`](https://developer.android.com/guide/topics/manifest/activity-element.html)、[`<service>`](https://developer.android.com/guide/topics/manifest/service-element.html)、[`<receiver>`](https://developer.android.com/guide/topics/manifest/receiver-element.html) 和 `<provider>`—均支持 android:process 屬性,此屬性可以指定該組件應在哪個進程運行。您可以設置此屬性,使每個組件均在各自的進程中運行,或者使一些組件共享一個進程,而其他組件則不共享。 此外,您還可以設置 android:process,使不同應用的組件在相同的進程中運行,但前提是這些應用共享相同的 Linux 用戶 ID 并使用相同的證書進行簽署。
此外,`<application>` 元素還支持 android:process 屬性,以設置適用于所有組件的默認值。
如果內存不足,而其他為用戶提供更緊急服務的進程又需要內存時,Android 可能會決定在某一時刻關閉某一進程。在被終止進程中運行的應用組件也會隨之銷毀。 當這些組件需要再次運行時,系統將為它們重啟進程。
決定終止哪個進程時,Android 系統將權衡它們對用戶的相對重要程度。例如,相對于托管可見 Activity 的進程而言,它更有可能關閉托管屏幕上不再可見的 Activity 的進程。 因此,是否終止某個進程的決定取決于該進程中所運行組件的狀態。 下面,我們介紹決定終止進程所用的規則。
**進程生命周期**
Android 系統將盡量長時間地保持應用進程,但為了新建進程或運行更重要的進程,最終需要移除舊進程來回收內存。 為了確定保留或終止哪些進程,系統會根據進程中正在運行的組件以及這些組件的狀態,將每個進程放入“重要性層次結構”中。 必要時,系統會首先消除重要性最低的進程,然后是重要性略遜的進程,依此類推,以回收系統資源。
重要性層次結構一共有 5 級。以下列表按照重要程度列出了各類進程(第一個進程最重要,將是最后一個被終止的進程):
1. **前臺進程**
用戶當前操作所必需的進程。如果一個進程滿足以下任一條件,即視為前臺進程:
* 托管用戶正在交互的 [Activity](https://developer.android.com/reference/android/app/Activity.html)(已調用 Activity 的 onResume() 方法)
* 托管某個 [Service](https://developer.android.com/reference/android/app/Service.html),后者綁定到用戶正在交互的 Activity
* 托管正在“前臺”運行的 Service(服務已調用 startForeground())
* 托管正執行一個生命周期回調的 Service(onCreate()、onStart() 或 onDestroy())
* 托管正執行其 onReceive() 方法的 [BroadcastReceiver](https://developer.android.com/reference/android/content/BroadcastReceiver.html)
通常,在任意給定時間前臺進程都為數不多。只有在內存不足以支持它們同時繼續運行這一萬不得已的情況下,系統才會終止它們。 此時,設備往往已達到內存分頁狀態,因此需要終止一些前臺進程來確保用戶界面正常響應。
2. **可見進程**
沒有任何前臺組件、但仍會影響用戶在屏幕上所見內容的進程。 如果一個進程滿足以下任一條件,即視為可見進程:
* 托管不在前臺、但仍對用戶可見的 Activity(已調用其 onPause() 方法)。例如,如果前臺 Activity 啟動了一個對話框,允許在其后顯示上一 Activity,則有可能會發生這種情況。
* 托管綁定到可見(或前臺)Activity 的 Service。
可見進程被視為是極其重要的進程,除非為了維持所有前臺進程同時運行而必須終止,否則系統不會終止這些進程。
3. **服務進程**
正在運行已使用 startService() 方法啟動的服務且不屬于上述兩個更高類別進程的進程。盡管服務進程與用戶所見內容沒有直接關聯,但是它們通常在執行一些用戶關心的操作(例如,在后臺播放音樂或從網絡下載數據)。因此,除非內存不足以維持所有前臺進程和可見進程同時運行,否則系統會讓服務進程保持運行狀態。
4. **后臺進程**
包含目前對用戶不可見的 Activity 的進程(已調用 Activity 的 onStop() 方法)。這些進程對用戶體驗沒有直接影響,系統可能隨時終止它們,以回收內存供前臺進程、可見進程或服務進程使用。 通常會有很多后臺進程在運行,因此它們會保存在 LRU (最近最少使用)列表中,以確保包含用戶最近查看的 Activity 的進程最后一個被終止。如果某個 Activity 正確實現了生命周期方法,并保存了其當前狀態,則終止其進程不會對用戶體驗產生明顯影響,因為當用戶導航回該 Activity 時,Activity 會恢復其所有可見狀態。 有關保存和恢復狀態的信息,請參閱 [Activity](https://developer.android.com/guide/components/activities.html#SavingActivityState)文檔。
5. **空進程**
不含任何活動應用組件的進程。保留這種進程的的唯一目的是用作緩存,以縮短下次在其中運行組件所需的啟動時間。 為使總體系統資源在進程緩存和底層內核緩存之間保持平衡,系統往往會終止這些進程。
根據進程中當前活動組件的重要程度,Android 會將進程評定為它可能達到的最高級別。例如,如果某進程托管著服務和可見 Activity,則會將此進程評定為可見進程,而不是服務進程。
此外,一個進程的級別可能會因其他進程對它的依賴而有所提高,即服務于另一進程的進程其級別永遠不會低于其所服務的進程。 例如,如果進程 A 中的內容提供程序為進程 B 中的客戶端提供服務,或者如果進程 A 中的服務綁定到進程 B 中的組件,則進程 A 始終被視為至少與進程 B 同樣重要。
由于運行服務的進程其級別高于托管后臺 Activity 的進程,因此啟動長時間運行操作的 Activity 最好為該操作啟動[服務](https://developer.android.com/guide/components/services.html),而不是簡單地創建工作線程,當操作有可能比 Activity 更加持久時尤要如此。例如,正在將圖片上傳到網站的 Activity 應該啟動服務來執行上傳,這樣一來,即使用戶退出 Activity,仍可在后臺繼續執行上傳操作。使用服務可以保證,無論 Activity 發生什么情況,該操作至少具備“服務進程”優先級。 同理,廣播接收器也應使用服務,而不是簡單地將耗時冗長的操作放入線程中。
#### **線程**
應用啟動時,系統會為應用創建一個名為“主線程”的執行線程。 此線程非常重要,因為它負責將事件分派給相應的用戶界面小部件,其中包括繪圖事件。 此外,它也是應用與 Android UI 工具包組件(來自 [android.widget](https://developer.android.com/reference/android/widget/package-summary.html) 和 [android.view](https://developer.android.com/reference/android/view/package-summary.html) 軟件包的組件)進行交互的線程。因此,主線程有時也稱為 UI 線程。
系統不會為每個組件實例創建單獨的線程。運行于同一進程的所有組件均在 UI 線程中實例化,并且對每個組件的系統調用均由該線程進行分派。 因此,響應系統回調的方法(例如,報告用戶操作的 onKeyDown() 或生命周期回調方法)始終在進程的 UI 線程中運行。
例如,當用戶觸摸屏幕上的按鈕時,應用的 UI 線程會將觸摸事件分派給小部件,而小部件反過來又設置其按下狀態,并將失效請求發布到事件隊列中。 UI 線程從隊列中取消該請求并通知小部件應該重繪自身。
在應用執行繁重的任務以響應用戶交互時,除非正確實現應用,否則這種單線程模式可能會導致性能低下。 具體地講,如果 UI 線程需要處理所有任務,則執行耗時很長的操作(例如,網絡訪問或數據庫查詢)將會阻塞整個 UI。 一旦線程被阻塞,將無法分派任何事件,包括繪圖事件。 從用戶的角度來看,應用顯示為掛起。 更糟糕的是,如果 UI 線程被阻塞超過幾秒鐘時間(目前大約是 5 秒鐘),用戶就會看到一個讓人厭煩的“[應用無響應](http://developer.android.com/guide/practices/responsiveness.html)”(ANR) 對話框。如果引起用戶不滿,他們可能就會決定退出并卸載此應用。
此外,Android UI 工具包并非線程安全工具包。因此,您不得通過工作線程操縱 UI,而只能通過 UI 線程操縱用戶界面。 因此,Android 的單線程模式必須遵守兩條規則:
1. 不要阻塞 UI 線程
2. 不要在 UI 線程之外訪問 Android UI 工具包
**工作線程**
根據上述單線程模式,要保證應用 UI 的響應能力,關鍵是不能阻塞 UI 線程。 如果執行的操作不能很快完成,則應確保它們在單獨的線程(“后臺”或“工作”線程)中運行。
例如,以下代碼演示了一個點擊偵聽器從單獨的線程下載圖像并將其顯示在 [ImageView](https://developer.android.com/reference/android/widget/ImageView.html) 中:
~~~
public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
Bitmap b = loadImageFromNetwork("http://example.com/image.png");
mImageView.setImageBitmap(b);
}
}).start();
}
~~~
乍看起來,這段代碼似乎運行良好,因為它創建了一個新線程來處理網絡操作。 但是,它違反了單線程模式的第二條規則:不要在 UI 線程之外訪問 Android UI 工具包 — 此示例從工作線程(而不是 UI 線程)修改了 ImageView。 這可能導致出現不明確、不可預見的行為,但要跟蹤此行為困難而又費時。
為解決此問題,Android 提供了幾種途徑來從其他線程訪問 UI 線程。 以下列出了幾種有用的方法:
* [Activity.runOnUiThread(Runnable)](https://developer.android.com/reference/android/app/Activity.html#runOnUiThread(java.lang.Runnable))
* [View.post(Runnable)](https://developer.android.com/reference/android/view/View.html#post(java.lang.Runnable))
* [View.postDelayed(Runnable, long)](https://developer.android.com/reference/android/view/View.html#postDelayed(java.lang.Runnable, long))
例如,您可以通過使用 View.post(Runnable) 方法修復上述代碼:
~~~
public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
final Bitmap bitmap =
loadImageFromNetwork("http://example.com/image.png");
mImageView.post(new Runnable() {
public void run() {
mImageView.setImageBitmap(bitmap);
}
});
}
}).start();
}
~~~
現在,上述實現屬于線程安全型:在單獨的線程中完成網絡操作,而在 UI 線程中操縱 ImageView。
但是,隨著操作日趨復雜,這類代碼也會變得復雜且難以維護。 要通過工作線程處理更復雜的交互,可以考慮在工作線程中使用 [Handler ](https://developer.android.com/reference/android/os/Handler.html)處理來自 UI 線程的消息。當然,最好的解決方案或許是擴展 [AsyncTask](https://developer.android.com/reference/android/os/AsyncTask.html) 類,此類簡化了與 UI 進行交互所需執行的工作線程任務。
**使用 AsyncTask**
[AsyncTask](https://developer.android.com/reference/android/os/AsyncTask.html) 允許對用戶界面執行異步操作。 它會先阻塞工作線程中的操作,然后在 UI 線程中發布結果,而無需您親自處理線程和/或處理程序。
要使用它,必須創建 AsyncTask 的子類并實現 [doInBackground()](https://developer.android.com/reference/android/os/AsyncTask.html#doInBackground(Params...)) 回調方法,該方法將在后臺線程池中運行。 要更新 UI,應該實現 [onPostExecute()](https://developer.android.com/reference/android/os/AsyncTask.html#onPostExecute(Result)) 以傳遞 doInBackground() 返回的結果并在 UI 線程中運行,以便您安全地更新 UI。 稍后,您可以通過從 UI 線程調用 [execute()](https://developer.android.com/reference/android/os/AsyncTask.html#execute(Params...)) 來運行任務。
例如,您可以通過以下方式使用 AsyncTask 來實現上述示例:
~~~
public void onClick(View v) {
new DownloadImageTask().execute("http://example.com/image.png");
}
private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
/** The system calls this to perform work in a worker thread and
* delivers it the parameters given to AsyncTask.execute() */
protected Bitmap doInBackground(String... urls) {
return loadImageFromNetwork(urls[0]);
}
/** The system calls this to perform work in the UI thread and delivers
* the result from doInBackground() */
protected void onPostExecute(Bitmap result) {
mImageView.setImageBitmap(result);
}
}
~~~
現在 UI 是安全的,代碼也得到簡化,因為任務分解成了兩部分:一部分應在工作線程內完成,另一部分應在 UI 線程內完成。
下面簡要概述了 AsyncTask 的工作方法,但要全面了解如何使用此類,您應閱讀 AsyncTask 參考文檔:
* 可以使用泛型指定參數類型、進度值和任務最終值
* 方法 doInBackground() 會在工作線程上自動執行
* onPreExecute()、onPostExecute() 和 onProgressUpdate() 均在 UI 線程中調用
* doInBackground() 返回的值將發送到 onPostExecute()
* 您可以隨時在 doInBackground() 中調用[publishProgress()](https://developer.android.com/reference/android/os/AsyncTask.html#publishProgress(Progress...)),以在 UI 線程中執行 onProgressUpdate()
* 您可以隨時取消任何線程中的任務
> 注意:使用工作線程時可能會遇到另一個問題,即:[運行時配置變更](https://developer.android.com/guide/topics/resources/runtime-changes.html)(例如,用戶更改了屏幕方向)導致 Activity 意外重啟,這可能會銷毀工作線程。 要了解如何在這種重啟情況下堅持執行任務,以及如何在 Activity 被銷毀時正確地取消任務,請參閱[書架](http://code.google.com/p/shelves/)示例應用的源代碼。
**線程安全方法**
在某些情況下,您實現的方法可能會從多個線程調用,因此編寫這些方法時必須確保其滿足線程安全的要求。
這一點主要適用于可以遠程調用的方法,如[綁定服務](https://developer.android.com/guide/components/bound-services.html)中的方法。如果對 [IBinder](https://developer.android.com/reference/android/os/IBinder.html) 中所實現方法的調用源自運行 IBinder 的同一進程,則該方法在調用方的線程中執行。但是,如果調用源自其他進程,則該方法將在從線程池選擇的某個線程中執行(而不是在進程的 UI 線程中執行),線程池由系統在與 IBinder 相同的進程中維護。 例如,即使服務的 onBind() 方法將從服務進程的 UI 線程調用,在 [onBind()](https://developer.android.com/reference/android/app/Service.html#onBind(android.content.Intent)) 返回的對象中實現的方法(例如,實現 RPC 方法的子類)仍會從線程池中的線程調用。 由于一個服務可以有多個客戶端,因此可能會有多個池線程在同一時間使用同一 IBinder 方法。因此,IBinder 方法必須實現為線程安全方法。
同樣,內容提供程序也可接收來自其他進程的數據請求。盡管 [ContentResolver](https://developer.android.com/reference/android/content/ContentResolver.html) 和 [ContentProvider](https://developer.android.com/reference/android/content/ContentProvider.html) 類隱藏了如何管理進程間通信的細節,但響應這些請求的 ContentProvider 方法(query()、insert()、delete()、update() 和 getType() 方法)將從內容提供程序所在進程的線程池中調用,而不是從進程的 UI 線程調用。 由于這些方法可能會同時從任意數量的線程調用,因此它們也必須實現為線程安全方法。
#### **進程間通信**
Android 利用遠程過程調用 (RPC) 提供了一種進程間通信 (IPC) 機制,通過這種機制,由 Activity 或其他應用組件調用的方法將(在其他進程中)遠程執行,而所有結果將返回給調用方。 這就要求把方法調用及其數據分解至操作系統可以識別的程度,并將其從本地進程和地址空間傳輸至遠程進程和地址空間,然后在遠程進程中重新組裝并執行該調用。 然后,返回值將沿相反方向傳輸回來。 Android 提供了執行這些 IPC 事務所需的全部代碼,因此您只需集中精力定義和實現 RPC 編程接口即可。
要執行 IPC,必須使用 bindService() 將應用綁定到服務上。如需了解詳細信息,請參閱服務開發者指南。
- 前言
- Google官網對Android API各版本的介紹
- jelly Bean(果凍豆)Android 4.1、4.2、4.3
- Android 4.1
- Android 4.2
- Android 4.3
- KitKat(Android 4.4.*)巧克力
- Android 4.4 APIS
- Lollipop(棒棒糖)Android 5.*
- Android 5.0 APIs
- Android 5.0 Changes(變更)
- Android 5.1APIs
- Marshmallow(棉花糖)Android 6.0
- Android 6.0 APIs
- Android 6.0 Changes(變更)
- Android 6.0 Samples
- Android 6.0 Testing
- Nougat(牛扎塘)Android 7.*
- Android 7.0
- API
- 行為變更
- 示例
- Android 7.1
- 開發者API
- 示例Sample
- Oreo(奧利奧)8.*
- Android 8.0
- 功能和 API
- Android 8.0 行為變更
- 向 Android 8.0 遷移應用
- Android 8.0 示例
- Android 8.1
- 后臺執行限制
- 后臺位置限制
- API指南
- Android 簡介
- 應用基礎知識
- 設備兼容性
- 系統權限
- 請求權限
- 定義權限
- 平臺架構
- Java8 概覽
- 在ART上驗證應用行為
- 應用組件
- Intent 和 Intent 過濾器(Google官網介紹)
- 通用intent
- Activity
- 任務和返回棧(官網譯文)
- 概覽屏幕
- 活動簡介
- 活動生命周期
- 活動狀態更改
- 進程和應用程序生命周期
- 包裹和捆綁
- 最近的屏幕
- 片段
- 加載器
- 服務Service
- 綁定服務
- AIDL
- 內容提供程序
- 內容提供程序基礎知識
- 創建內容提供程序
- 日歷提供程序
- 聯系人提供程序
- 存儲訪問框架
- 使用存儲訪問框架打開文件
- 創建自定義文檔提供程序
- 應用小部件
- 應用小部件主機
- 進程和線程
- 應用資源
- 概覽
- 提供資源
- 訪問資源
- 處理運行時變更
- 本地化
- ICU4J Android框架API
- Android上的國際化
- 語言和語言區域
- 復雜的XML資源
- 資源類型
- 動畫
- 顏色狀態列表
- 可繪制對象
- 布局
- 菜單
- 字符串
- 樣式
- 其他類型
- 應用清單
- <action>
- <activity>
- <activity-alias>
- <application>
- <category>
- <compatiable-screens>
- <data>
- <grant-uri-permission>
- <intent-filter>
- <manifest>
- <meta-data>
- <path-permission>
- <permission>
- <permission-group>
- <permission-tree>
- <provider>
- <receiver>
- <service>
- <supporte-gl-texture>
- <supports-screens>
- <uses-configuration>
- <uses-feature>
- <uses-library>
- <uses-permission>
- <uses-permission-sdk-23>
- <uses-sdk>
- 用戶界面
- 界面概覽
- 界面布局
- 線性布局
- 相對布局
- 列表視圖
- 網格視圖
- 回收站視圖
- 外觀和感覺
- 可下載的字體
- XML中的字體
- 表情符號兼容性
- 自動調整TextView
- 樣式和主題-
- 輸入控件
- 按鈕
- 文本字段
- 復選框
- 單選按鈕
- 切換按鈕
- 微調框
- 選取器
- 輸入事件
- 菜單Menu
- 設置
- 對話框
- 通知
- Toast
- 自適應圖標
- 應用快捷方式
- 搜索
- 創建搜索界面
- 添加近期查詢建議
- 添加自定義建議
- 可搜索配置
- 多窗口支持
- 拖放
- 無障礙功能
- 為應用設置無障礙功能
- 無障礙功能開發者檢查單
- 構建無障礙服務
- 讓應用更容易訪問
- 使用節點樹調試
- 構建可訪問自定義視圖
- 樣式和主題
- 自定義組件
- 動畫和圖形
- 概覽介紹
- 屬性動畫
- 視圖動畫
- 可繪制動畫
- 畫布和可繪制對象
- 基于物理的動畫
- Spring Animation
- Fling Animation
- OpenGL ES
- 硬件加速
- 計算
- RenderScript
- 高級RenderScript
- Runtime API Reference(參考)
- Numerical Types(數字類型)
- Object Types(對象類型)
- Conversion Functions(轉換函數)
- Mathematical Constants and Functions(數學常量和函數)
- Vector Math Functions(矢量數學函數)
- Matrix Functions(矩陣函數)
- Quaternion Functions(四元數函數)
- Atomic Update Functions(原子更新函數)
- Time Functions and Types(時間函數和類型)
- Allocation Data Access Functions(分配數據訪問函數)
- Object Characteristics Functions(對象特性函數)
- Kernel Invocation Functions and Types(內核調用函數和類型)
- Input/Output Functions(輸入輸出函數)
- Debugging Functions(調試函數)
- Graphics Functions and Types(圖形函數和類型)
- Index(索引)
- Media Apps(媒體應用)
- Media Apps Overview(媒體應用程序概述)
- Working with a Media Session(使用媒體會話)
- Building an Audio App(建立一個音頻應用)
- Building a Media Browser Service(構建媒體瀏覽器服務)
- Building a Media Browser Client(構建媒體瀏覽器客戶端)
- Media Session Callbacks(媒體會話回調)
- Building a Video App(建立一個視頻應用)
- Building a Video Player Activity(建立一個視頻播放器Activity)
- Media Session Callbacks-(媒體會話回調)
- Responding to Media Buttons(響應媒體按鈕)
- Handling Changes in Audio Output(處理音頻輸出的變化)
- Managing Audio Focus(管理音頻焦點)
- The Google Assistant and Media Apps(Google智能助理和媒體應用)
- 媒體和相機
- Supported Media Formats(支持的媒體格式)
- MediaPlayer(媒體播放器)
- MediaRecorder
- ExoPlayer
- Controller Amplitude with VolumeShaper(VolumeShaper控制器振幅)
- Media Routing(媒體路由)
- MediaRouter API
- MediaRouteProvider API
- Camera API(相機API)
- 位置和傳感器
- Location and Maps(位置和地圖)
- Location Strategies(位置策略)
- Sensors Overview(傳感器概覽)
- Motion Sensors(運動傳感器)
- Position Sensors(位置傳感器)
- Environment Sensors(環境傳感器)
- Raw GNSS Measurements(原始的GNSS測量)
- 連接
- Bluetooth
- Bluetooth Low Energy(藍牙低功耗)
- NFC
- NFC Basics(NFC基礎知識)
- Advanced NFC(高級NFC)
- Host-based Card Emulation(基于主機的卡模擬)
- Telecom(電信)
- Self-Managed ConnectionServices(自我管理的連接服務)
- Wi-Fi P2P
- Wi-Fi Aware
- Companion Device Pairing
- USB
- Accessory(配件)
- Host(主機)
- SIP
- 文本和輸入
- Autofill Framework(自動填充框架)
- Test your app with autofill(使用自動填充測試你的應用)
- Building autofill services(構建自動填充服務)
- Copy and Paste(復制和粘貼)
- Creating an IME(創建IME)
- Image Keyboard(圖像鍵盤)
- Spelling Checker(拼寫檢查程序)
- 數據存儲
- Storage Options(存儲選項)
- Data Backup(數據備份)
- Account Transfer API(賬戶轉移API)
- Auto Backup(自動備份)
- Key/Value Backup(鍵值備份)
- Testing Backup and Restore(測試備份和還原)
- App Install Location(應用安裝位置)
- 庫
- 支持庫
- 功能
- 修訂歷史記錄
- 庫設置
- 數據綁定庫
- 測試支持庫
- 管理
- 設備策略
- 網絡應用
- Supporting Different Screens in Web Apps(在網絡應用中支持不同屏幕)
- Building Web Apps in WebView(在WebView中構建網絡應用)
- Managing WebViews
- Migrating to WebView in Android 4.4(遷移到Android4.4中的WebView)
- Debugging Web Apps(調試網絡應用)
- Best Practices for Web Apps(網絡應用最佳做法)
- 最佳實踐
- Supporting Multiple Screens(支持多種屏幕)
- Distributing to Specific Screens(分配到特定屏幕)
- Screen Compatibility Mode(屏幕兼容性模式)
- Designing for Seamlessness
- Supporting Tablets and Handsets
- 培訓