diff options
Diffstat (limited to 'docs/html-intl/intl/zh-tw/guide/components')
11 files changed, 6216 insertions, 0 deletions
diff --git a/docs/html-intl/intl/zh-tw/guide/components/activities.jd b/docs/html-intl/intl/zh-tw/guide/components/activities.jd new file mode 100644 index 000000000000..ea0934964fed --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/components/activities.jd @@ -0,0 +1,756 @@ +page.title=Activity +page.tags=Activity,意圖 +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> +<h2>本文件內容</h2> +<ol> + <li><a href="#Creating">建立 Activity</a> + <ol> + <li><a href="#UI">實作使用者介面</a></li> + <li><a href="#Declaring">在宣示說明中宣告 Activity</a></li> + </ol> + </li> + <li><a href="#StartingAnActivity">啟動 Activity</a> + <ol> + <li><a href="#StartingAnActivityForResult">啟動 Activity 以取得結果</a></li> + </ol> + </li> + <li><a href="#ShuttingDown">關閉 Activity</a></li> + <li><a href="#Lifecycle">管理 Activity 生命週期</a> + <ol> + <li><a href="#ImplementingLifecycleCallbacks">實作生命週期回呼</a></li> + <li><a href="#SavingActivityState">儲存 Activity 狀態</a></li> + <li><a href="#ConfigurationChanges">處理設定變更</a></li> + <li><a href="#CoordinatingActivities">協調 Activity</a></li> + </ol> + </li> +</ol> + +<h2>重要類別</h2> +<ol> + <li>{@link android.app.Activity}</li> +</ol> + +<h2>另請參閱</h2> +<ol> + <li><a href="{@docRoot}guide/components/tasks-and-back-stack.html">工作和返回堆疊</a> +</li> +</ol> + +</div> +</div> + + + +<p>{@link android.app.Activity} 是提供畫面的應用程式元件,使用者可以與此畫面互動以執行動作,例如撥號、拍照、傳送電子郵件或檢視地圖。 + +每個 Activity 都會有專屬視窗,用於繪製其使用者介面。視窗一般會佔滿螢幕,但也可能小於螢幕或在其他視窗上方浮動。 + +</p> + +<p> 應用程式通常由多個 Activity 組成,這些 Activity 之間的繫結鬆散。 +一般來說,應用程式中的某個 Activity 會指定為「主要」Activity。使用者第一次啟動應用程式時,會將此 Activity 向使用者顯示。 +然後,每個 Activity 可以啟動另一個 Activity,以執行不同的動作。 +每次啟動新的 Activity 時,之前的 Activity 會停止,但系統將該 Activity 保留在堆疊中(即「返回堆疊」)。 + +新的 Activity 啟動時會推送至返回堆疊,然後取得使用者焦點。 +返回堆疊遵循基本的「後進先出」堆疊機制,因此,使用者完成目前的 Activity,按下 [返回] 按鈕<em></em>時,目前的 Activity 會從堆疊被推出 (並終止),然後繼續之前的 Activity。 + +(返回堆疊在<a href="{@docRoot}guide/components/tasks-and-back-stack.html">工作和返回堆疊</a>文件中,有更詳細的說明)。 + +</p> + +<p>Activity 因啟動新的 Activity 而停止時,Activity 的生命週期回呼方法會通知此狀態的變更。由於 Activity 狀態的變更 — 可能是系統建立、停止、繼續或終止 Activity 時 — Activity 會收到數個回呼方法,而且每個回呼都提供您執行適合該次狀態變更的特定工作機會。 + + + + +例如,停止時,您的 Activity 應釋放任何大型物件,例如網路或資料庫連線。 +Activity 繼續時,您可以重新取得必要資源,並繼續之前中斷的動作。 +這些狀態轉換都是 Activity 生命週期的一部分。 +</p> + +<p>本文件的其他部分會討論建置和使用 Activity 的基本概念,包括完整討論 Activity 生命週期的運作方式,讓您可以正確地管理各種 Activity 狀態之間的轉換。 + +</p> + + + +<h2 id="Creating">建立 Activity</h2> + +<p>如要建立 Activity,您必須建立 {@link android.app.Activity} 的子類別 (或它現有的子類別)。 +在您的子類別中,您需要實作回呼方法,當 Activity 在其生命週期的各種狀態之間轉換時,系統可以呼叫此回呼方法。例如,Activity 建立、停止、繼續或終止時。 + +最重要的兩個回呼方法如下: +</p> + +<dl> + <dt>{@link android.app.Activity#onCreate onCreate()}</dt> + <dd>您必須實作此方法。系統建立您的 Activity 時會呼叫此方法。 +在您的實作中,應該初始化 Activity 的基本元件。 + + 最重要的是,您必須在這裡呼叫 {@link android.app.Activity#setContentView + setContentView()},才能定義 Activity 使用者介面的版面配置。</dd> + <dt>{@link android.app.Activity#onPause onPause()}</dt> + <dd>系統呼叫此方法做為使用者離開您的 Activity 的第一個指標 (但並非一定表示該 Activity 遭到終止)。 +您通常需要透過這個方法提交要在目前的使用者工作階段以外保留的任何變更 (原因在於使用者可能不會返回)。 + +</dd> +</dl> + +<p>還有一些您應使用的其他生命週期回呼方法,以便提供 Activity 之間流暢的使用者體驗,並處理讓您的 Activity 停止、甚至終止的非預期中斷。 + +如需所有生命週期回呼方法的相關資訊,請參閱<a href="#Lifecycle">管理 Activity 生命週期</a>。 +</p> + + + +<h3 id="UI">實作使用者介面</h3> + +<p> Activity 的使用者介面是由階層的檢視所提供 — 衍生自 {@link android.view.View} 類別的物件。 +每個檢視都控制 Activity 視窗內特定的長方形空間,並且可以回應使用者的互動。 +例如,檢視可能是按鈕,使用者觸碰此按鈕時會初始化一個動作。 +</p> + +<p>Android 提供許多現成的檢視,您可以用於設計並組織您的版面配置。 +「小工具」是在畫面上提供視覺和互動元素的檢視,例如按鈕、文字欄位、核取方塊或只是一張影像。 +「版面配置」是衍生自 {@link +android.view.ViewGroup} 的檢視,讓子檢視可具有獨特的版面配置模型,例如線性版面配置、網格版面配置或相對版面配置。 +您也可以製作 {@link android.view.View} 和 +{@link android.view.ViewGroup} 類別 (或現有子類別) 的子類別,以建立您自己的小工具和版面配置,然後套用到您的 Activity 版面配置。 +</p> + +<p>使用檢視定義版面配置的最常見方式,是使用 XML 版面配置檔案 (儲存於您的應用程式資源)。 +這樣一來,您可以分別維護使用者介面的設計和定義 Activity 行為的原始程式碼。 +您可以使用 {@link android.app.Activity#setContentView(int) setContentView()} 傳送版面配置的資源 ID,將版面配置設為 Activity 的 UI。 + +不過,您也可以透過將新的 {@link +android.view.View} 插入 {@link android.view.ViewGroup},然後藉由將根 +{@link android.view.ViewGroup} 傳送給 {@link android.app.Activity#setContentView(View) +setContentView()} 來使用該版面配置,以便在您的 Activity 程式碼中建立新的 {@link android.view.View},並且建置視圖層次。 +</p> + +<p>如需關於建立使用者介面的詳細資訊,請參閱<a href="{@docRoot}guide/topics/ui/index.html">使用者介面</a>。</p> + + + +<h3 id="Declaring">在宣示說明中宣告 Activity</h3> + +<p>您必須在宣示說明檔案中宣告 Activity,系統才能加以存取。 +如要宣告您的 Activity,請開啟宣示說明檔案,然後新增 <a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a> 元素做為 <a href="{@docRoot}guide/topics/manifest/application-element.html">{@code <application>}</a> 元素的下層物件。 + +範例:</p> + +<pre> +<manifest ... > + <application ... > + <activity android:name=".ExampleActivity" /> + ... + </application ... > + ... +</manifest > +</pre> + +<p>您可以包括此元素中的其他屬性 (attribute) 來定義屬性 (property),例如 Activity 的標籤、Activity 的圖示或 Activity UI 的設計風格。<a href="{@docRoot}guide/topics/manifest/activity-element.html#nm">{@code android:name}</a> 屬性 (attribute) 是唯一必須的屬性 — 它會指定 Activity 的類別名稱。 + + +一旦發佈應用程式,您就不得變更此名稱,這是因為這樣做可能會破壞一些功能,例如應用程式捷徑 (閱讀部落格貼文:<a href="http://android-developers.blogspot.com/2011/06/things-that-cannot-change.html">不能變更的事項</a>)。 + + +</p> + +<p>請參閱 <a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a> 元素參考文件,進一步瞭解如何在宣示說明中宣告 Activity。 +</p> + + +<h4>使用意圖篩選器</h4> + +<p><a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code +<activity>}</a> 元素也可以指定各種意圖篩選器 — 使用 <a href="{@docRoot}guide/topics/manifest/intent-filter-element.html">{@code +<intent-filter>}</a> 元素 — 以便宣告其他應用程式元件啟動它的方式。 +</p> + +<p>使用 Android SDK 工具建立新的應用程式時,自動為您建立的 +虛設常式 Activity 會內含意圖篩選器。含意圖篩選器會宣告回應 +「主要」動作的 Activity,並且應放置在「啟動器」類別。意圖篩選器看起來會如下所示: +</p> + +<pre> +<activity android:name=".ExampleActivity" android:icon="@drawable/app_icon"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> +</activity> +</pre> + +<p><a href="{@docRoot}guide/topics/manifest/action-element.html">{@code +<action>}</a> 元素指出這是應用程式的「主要」進入點。<a href="{@docRoot}guide/topics/manifest/category-element.html">{@code +<category>}</a> 元素指出此 Activity 應列於系統的應用程式啟動器 (以允許使用者啟動此 Activity)。 +</p> + +<p>如果您本來就要讓應用程式獨自運作,不要讓其他應用程式啟動其 Activity,則不需要任何其他意圖篩選器。 +只有一個 Activity 可以有「主要」動作和「啟動器」類別,如同上一個範例所示。 +您不要讓其他應用程式使用的 Activity,則不應使用意圖篩選器,而且您可以使用明確的意圖自行加以啟動 (將於以下小節討論)。 + +</p> + +<p>不過,如果您的 Activity 需要回應從其他應用程式 (和您自己的應用程式) 傳送過來的隱含式意圖,則必須為您的 Activity 定義額外的意圖篩選器。 + +針對您要回應的意圖類型,您必須包括 <a href="{@docRoot}guide/topics/manifest/intent-filter-element.html">{@code +<intent-filter>}</a>,其中內含 +<a href="{@docRoot}guide/topics/manifest/action-element.html">{@code +<action>}</a> 元素和 <a href="{@docRoot}guide/topics/manifest/category-element.html">{@code +<category>}</a> 元素 (選用) 和/或 <a href="{@docRoot}guide/topics/manifest/data-element.html">{@code +<data>}</a> 元素。這些元素會指定您的 Activity 可以回應哪些類型的意圖。 +</p> + +<p>如需關於您的 Activity 如何回應意圖的詳細資訊,請參閱<a href="{@docRoot}guide/components/intents-filters.html">意圖和意圖篩選器</a>。 +</p> + + + +<h2 id="StartingAnActivity">啟動 Activity</h2> + +<p>透過呼叫 {@link android.app.Activity#startActivity + startActivity()}、將 {@link android.content.Intent} (描述要啟動的 Activity) 傳給它,您可以啟動另一個 Activity。 +意圖會指出您要啟動的那個 Activity,或描述您要執行的動作類型 (而讓系統為您選取適當的 Activity,甚至可以是來自不同應用程式的 Activity)。 + + +意圖也可以攜帶少量的資料給已啟動的 Activity 使用。 +</p> + +<p>在您自己的應用程式內運作時,通常只要啟動已知的 Activity。 + 建立意圖並明確定義您要啟動的 Activity (使用類別名稱),可以達成此目的。 +例如,以下示範 Activity 如何啟動另一個名為 {@code +SignInActivity} 的 Activity:</p> + +<pre> +Intent intent = new Intent(this, SignInActivity.class); +startActivity(intent); +</pre> + +<p>不過,您的應用程式也希望可以執行其他動作,例如使用您 Activity 中的資料以傳送電子郵件、文字訊息或狀態更新。 +在此情況下,您的應用程式可能就沒有專屬的 Activity 來執行這類動作。因此,您可以改為運用裝置上其他應用程式提供的 Activity。讓這些 Activity 為您執行所需的動作。 + +這正是意圖寶貴的地方 — 您可以建立意圖,在其中描述您要執行的動作,然後系統會從另一個應用程式啟動適當的 Activity。 + + +如果有多個 Activity 都可以處理意圖,則使用者可以選取要使用哪個 Activity。 +例如,如果要讓使用者傳送電子郵件訊息,您可以建立下列意圖: + +</p> + +<pre> +Intent intent = new Intent(Intent.ACTION_SEND); +intent.putExtra(Intent.EXTRA_EMAIL, recipientArray); +startActivity(intent); +</pre> + +<p>加入意圖的 {@link android.content.Intent#EXTRA_EMAIL} 額外值是一個字串陣列,由應該要寄送電子郵件的電子郵件地址所組成。 +電子郵件應用程式回應此意圖時,它會讀取額外值中提供的字串陣列,並將這些內容放置在編寫電子郵件表單的「收件人」欄位。 + +在此情況下,電子郵件應用程式的 Activity 會啟動,並且會在使用者完成後,繼續您的 Activity。 +</p> + + + + +<h3 id="StartingAnActivityForResult">啟動 Activity 以取得結果</h3> + +<p>有時候,您會想要收到由您啟動 Activity 的結果。如要接收結果,請透過呼叫 {@link android.app.Activity#startActivityForResult + startActivityForResult()} (而非 {@link android.app.Activity#startActivity + startActivity()}) 以啟動 Activity。 +如要接收後續 Activity 的結果,請實作 {@link android.app.Activity#onActivityResult onActivityResult()} 回呼方法。 + +後續 Activity 完成時,會將 {@link +android.content.Intent} 中的結果傳回到您的 {@link android.app.Activity#onActivityResult onActivityResult()} 方法。 +</p> + +<p>例如,您可能會讓使用者在聯絡人中挑選一位,讓您的 Activity 可以針對該聯絡人的資訊進行一些處理。 +以下示範如何建立這類意圖,並處理結果: +</p> + +<pre> +private void pickContact() { + // Create an intent to "pick" a contact, as defined by the content provider URI + Intent intent = new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI); + startActivityForResult(intent, PICK_CONTACT_REQUEST); +} + +@Override +protected void onActivityResult(int requestCode, int resultCode, Intent data) { + // If the request went well (OK) and the request was PICK_CONTACT_REQUEST + if (resultCode == Activity.RESULT_OK && requestCode == PICK_CONTACT_REQUEST) { + // Perform a query to the contact's content provider for the contact's name + Cursor cursor = getContentResolver().query(data.getData(), + new String[] {Contacts.DISPLAY_NAME}, null, null, null); + if (cursor.moveToFirst()) { // True if the cursor is not empty + int columnIndex = cursor.getColumnIndex(Contacts.DISPLAY_NAME); + String name = cursor.getString(columnIndex); + // Do something with the selected contact's name... + } + } +} +</pre> + +<p>此範例顯示您應該在您的 {@link +android.app.Activity#onActivityResult onActivityResult()} 方法中使用的基本邏輯,以便處理 Activity 結果。 +第一個條件會檢查要求是否成功 — 如果成功,則{@code resultCode} 會是 {@link android.app.Activity#RESULT_OK} —,以及此結果回應的要求是否為已知的要求 — 範例中的 {@code requestCode} 符合以 {@link android.app.Activity#startActivityForResult +startActivityForResult()} 傳送的第二個參數。 + + +程式碼在這裡透過查詢 {@link android.content.Intent} ({@code data} 參數) 中傳回的資料,來處理 Activity 結果。 +</p> + +<p>其中的過程是,{@link +android.content.ContentResolver} 針對內容供應程式執行查詢,所傳回的 +{@link android.database.Cursor} 可以讀取查詢到的資料。如需詳細資訊,請參閱<a href="{@docRoot}guide/topics/providers/content-providers.html">內容供應程式</a>。 +</p> + +<p>如需關於使用意圖的詳細資訊,請參閱<a href="{@docRoot}guide/components/intents-filters.html">意圖和意圖篩選器</a>。 +</p> + + +<h2 id="ShuttingDown">關閉 Activity</h2> + +<p>呼叫 Activity 的 {@link android.app.Activity#finish +finish()} 方法,可以關閉此 Activity。您也可以呼叫 +{@link android.app.Activity#finishActivity finishActivity()},關閉您之前啟動的個別 Activity。</p> + +<p class="note"><strong>注意:</strong>大多數情況,您不應使用這些方法明確地結束 Activity。 +如同下一節所討論的 Activity 生命週期,Android 系統會為您管理 Activity 的生命週期,所以您不需要結束您自己的 Activity。 + +呼叫這些方法對使用者體驗有負面的影響,只有在您十分確定不希望使用者返回 Activity 的此執行個體時,才加以呼叫。 + +</p> + + +<h2 id="Lifecycle">管理 Activity 生命週期</h2> + +<p>實作回呼方法來管理 Activity 的生命週期,對於開發強大且有彈性的應用程式來說,相當重要。 + +Activity 的生命週期受到相關其他 Activity、本身的工作以及返回堆疊的直接影響。 +</p> + +<p>Activity 基本上有以下三種狀態:</p> + +<dl> + <dt><i>已繼續</i></dt> + <dd>Activity 位於螢幕的前景,具有使用者焦點 (此狀態有時候也稱為「執行中」)。 +</dd> + + <dt><i>已暫停</i></dt> + <dd>前景中有其他具備焦點的 Activity,但系統仍然可以看到這個 Activity。也就是說,這個 Activity 上方有另一個,該 Activity 為半透明,或是未覆蓋整個螢幕。 + +已暫停的 Activity 是完全有效的 ({@link android.app.Activity} 物件會保留在記憶體中、維護所有狀態和成員資訊,以及繼續附加至視窗管理員),但在系統記憶體極低的情況下會遭到終止。 + +</dd> + + <dt><i>已停止</i></dt> + <dd>另一個 Activity 已完全遮蓋原本的 Activity (原本的 Activity 現在位於「背景」)。 +已停止的 Activity 仍然是有效的 ({@link android.app.Activity} 物件會保留在記憶體中、維護所有狀態和成員資訊,但「不會」<em></em>附加至視窗管理員)。 + +只是使用者不會再看到已停止的 Activity,而且其他地方需要記憶體時,系統會將它終止。 +</dd> +</dl> + +<p>如果 Activity 已暫停或已停止,系統可以透過要求它結束 (呼叫其 {@link android.app.Activity#finish finish()} 方法) 或直接終止其處理程序,將它從記憶體中刪除。 + +Activity 遭到結束或終止後,再次開啟時,必須全部重新建立。 +</p> + + + +<h3 id="ImplementingLifecycleCallbacks">實作生命週期回呼</h3> + +<p>Activity 在不同的狀態之間轉換時 (如上所述),會透過各種回呼方法進行通知。 +所有的回呼方法都是虛設,請加以覆寫,以便在 Activity 狀態變更時,執行適當的工作。 +下列主要 Activity 包括每個基礎生命週期方法: +</p> + + +<pre> +public class ExampleActivity extends Activity { + @Override + public void {@link android.app.Activity#onCreate onCreate}(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // The activity is being created. + } + @Override + protected void {@link android.app.Activity#onStart onStart()} { + super.onStart(); + // The activity is about to become visible. + } + @Override + protected void {@link android.app.Activity#onResume onResume()} { + super.onResume(); + // The activity has become visible (it is now "resumed"). + } + @Override + protected void {@link android.app.Activity#onPause onPause()} { + super.onPause(); + // Another activity is taking focus (this activity is about to be "paused"). + } + @Override + protected void {@link android.app.Activity#onStop onStop()} { + super.onStop(); + // The activity is no longer visible (it is now "stopped") + } + @Override + protected void {@link android.app.Activity#onDestroy onDestroy()} { + super.onDestroy(); + // The activity is about to be destroyed. + } +} +</pre> + +<p class="note"><strong>注意:</strong>實作這些生命週期方法時,一定要先呼叫超級類別實作,才能執行任何工作,如同以上範例所示。 +</p> + +<p>總而言之,這些方法定義了 Activity 的整個生命週期。透過實作這些方法,您可以監視 Activity 生命週期中的三個巢狀迴圈: + </p> + +<ul> +<li>Activity 的<b>整個生命週期</b>是介於 {@link +android.app.Activity#onCreate onCreate()} 呼叫和 {@link +android.app.Activity#onDestroy} 呼叫之間。您的 Activity 應在 {@link android.app.Activity#onCreate onCreate()} 中設定「全域」狀態 (例如定義版面配置),並且在 {@link android.app.Activity#onDestroy} 中釋放所有剩餘的資源。 + +例如,如果您的 Activity 有一個執行緒在背景執行,並從網路下載資料,那麼最好可以在 {@link android.app.Activity#onCreate onCreate()} 中建立該執行緒,然後在 {@link +android.app.Activity#onDestroy} 中將它停止。 + +</li> + +<li><p>Activity 的<b>可見生命週期</b>是介於 {@link +android.app.Activity#onStart onStart()} 呼叫和 {@link +android.app.Activity#onStop onStop()} 呼叫之間。在此期間,使用者可以在螢幕上看到該 Activity,並與之互動。 +例如,啟動新的 Activity,而這個 Activity 不再看得到時,會呼叫 {@link android.app.Activity#onStop onStop()}。 +在這兩個方法之間,您可以維護需要讓使用者看到的資源。 +例如,您可以在{@link +android.app.Activity#onStart onStart()} 中註冊 +{@link android.content.BroadcastReceiver},以監視影響到 UI 的變更,然後當使用者不再看到您顯示的內容時,在 {@link android.app.Activity#onStop onStop()} 中將它取消註冊。 + +系統可以在 Activity 的整個生命週期時,多次呼叫 {@link android.app.Activity#onStart onStart()} 和 {@link +android.app.Activity#onStop onStop()},因為 Activity 對使用者而言會一直在顯示和隱藏之間切換。 +</p></li> + +<li><p>Activity 的<b>前景生命週期</b>是介於 {@link +android.app.Activity#onResume onResume()} 呼叫和 {@link android.app.Activity#onPause +onPause()} 呼叫之間。在此期間,Activity 會在螢幕上所有其他 Activity 的前面,而且具有使用者輸入焦點。 +Activity 可以經常在前景和背景之間轉換 — 例如,裝置進入睡眠或顯示對話方塊時,會呼叫 {@link android.app.Activity#onPause onPause()}。 + +由於此狀態可能會經常轉換,因此這兩個方法中的程式碼應十分精簡,這樣可以避免使用者在轉換時等待。 +</p></li> +</ul> + +<p>圖 1 說明 Activity 在轉換狀態時的可能迴圈和路徑。長方形代表您可以實作的回呼方法,以便 Activity 在轉換狀態時執行操作。 + + <p> + +<img src="{@docRoot}images/activity_lifecycle.png" alt="" /> +<p class="img-caption"><strong>圖 1.</strong>Activity 生命週期。</p> + +<p>表 1 列出相同的生命週期回呼方法,其中詳細描述每個回呼方法,並且指出每個回呼方法在 Activity 整個生命週期中的位置,包括系統是否可以在回呼方法完成後終止 Activity。 + + +</p> + +<p class="table-caption"><strong>表 1.</strong>Activity 生命週期回呼方法摘要。 +</p> + +<table border="2" width="85%" frame="hsides" rules="rows"> +<colgroup align="left" span="3"></colgroup> +<colgroup align="left"></colgroup> +<colgroup align="center"></colgroup> +<colgroup align="center"></colgroup> + +<thead> +<tr><th colspan="3">方法</th> <th>描述</th> <th>完成後是否可終止?</th> <th>下一個方法</th></tr> +</thead> + +<tbody> +<tr> + <td colspan="3" align="left"><code>{@link android.app.Activity#onCreate onCreate()}</code></td> + <td>一開始建立 Activity 時呼叫。 + 您應該在這裡所有的一般靜態設定 — 建立檢視、將資料繫結至清單等等。 +「套件」物件會傳送給此方法,如果有擷取到狀態,此物件會內含 Activity 的上一個狀態 (請參閱下文的<a href="#actstate">儲存 Activity 狀態</a>)。 + + + + <p>後面一定會接著 {@code onStart()}。</p></td> + <td align="center">否</td> + <td align="center">{@code onStart()}</td> +</tr> + +<tr> + <td rowspan="5" style="border-left: none; border-right: none;"> </td> + <td colspan="2" align="left"><code>{@link android.app.Activity#onRestart +onRestart()}</code></td> + <td>Activity 已停止後,即將再次啟動之前呼叫。 + + <p>後面一定會接著 {@code onStart()}。</p></td> + <td align="center">否</td> + <td align="center">{@code onStart()}</td> +</tr> + +<tr> + <td colspan="2" align="left"><code>{@link android.app.Activity#onStart onStart()}</code></td> + <td>Activity 即將要讓使用者看到之前呼叫。 + <p>如果 Activity 移到前景,後面會接著 {@code onResume()},如果變成隱藏,後面會接著 {@code onStop()}。 +</p></td> + <td align="center">否</td> + <td align="center">{@code onResume()} <br/>或<br/> {@code onStop()}</td> +</tr> + +<tr> + <td rowspan="2" style="border-left: none;"> </td> + <td align="left"><code>{@link android.app.Activity#onResume onResume()}</code></td> + <td>Activity 即將與使用者開始互動之前呼叫。 +此時,Activity 位於 Activity 堆疊的最上方,接受使用的輸入。 + + <p>後面一定會接著 {@code onPause()}。</p></td> + <td align="center">否</td> + <td align="center">{@code onPause()}</td> +</tr> + +<tr> + <td align="left"><code>{@link android.app.Activity#onPause onPause()}</code></td> + <td>系統即將開始繼續另一個 Activity 時呼叫。 +認可對永久資料的未儲存變更、停止動畫,以及會使用 CPU 等等資源的其他操作時,一般會使用此方法。 + +不論此操作會執行什麼動作,都要快速完成,這是因為此方法傳回後,才會繼續下一個 Activity。 + + <p>如果 Activity 返回前景,後面會接著 {@code onResume()},如果變成使用者看不到它,後面會接著 {@code onStop()}。 + +</td> + <td align="center"><strong style="color:#800000">是</strong></td> + <td align="center">{@code onResume()} <br/>或<br/> {@code onStop()}</td> +</tr> + +<tr> + <td colspan="2" align="left"><code>{@link android.app.Activity#onStop onStop()}</code></td> + <td>使用者看不到 Activity 時呼叫。Activity 遭到終止或另一個 Activity (不論是現有 Activity 或新的 Activity) 已經繼續,而且將它覆蓋住,就會發生此情形。 + + + <p>如果 Activity 回來與使用者互動,後面會接著 {@code onRestart()},如果 Activity 離開,後面會接著 + {@code onDestroy()}。 +</p></td> + <td align="center"><strong style="color:#800000">是</strong></td> + <td align="center">{@code onRestart()} <br/>或<br/> {@code onDestroy()}</td> +</tr> + +<tr> + <td colspan="3" align="left"><code>{@link android.app.Activity#onDestroy +onDestroy()}</code></td> + <td>在 Activity 終止前呼叫。Activity 會接收到的最後呼叫。 +Activity 正在完成 (有人在 Activity 上呼叫 <code>{@link android.app.Activity#finish + finish()}</code>),或系統正在暫時終止 Activity 的這個執行個體以節省空間時,會呼叫此方法。 + +您可以使用 <code>{@link + android.app.Activity#isFinishing isFinishing()}</code> 方法分辨這兩種情況。 +</td> + <td align="center"><strong style="color:#800000">是</strong></td> + <td align="center"><em>無</em></td> +</tr> +</tbody> +</table> + +<p>此欄標示為「完成後是否可終止?」表示系統是否可以「在方法傳回後」隨時終止代管 Activity 的處理程序<em></em>,而不需要執行另一行 Activity 的程式碼。 + +有三個方法標示為「是」:({@link +android.app.Activity#onPause +onPause()}、{@link android.app.Activity#onStop onStop()} 以及 {@link android.app.Activity#onDestroy +onDestroy()})。由於 {@link android.app.Activity#onPause onPause()} 是這三個方法中的第一個方法,Activity 建立後,{@link android.app.Activity#onPause onPause()} 是一定會呼叫的最後一個方法,之後處理程序才能加以終止<em></em> — 如果系統必須緊急收回記憶體,可能就不會呼叫 {@link +android.app.Activity#onStop onStop()} 和 {@link android.app.Activity#onDestroy onDestroy()}。 + + + +因此,您應該使用 {@link android.app.Activity#onPause onPause()} 將極為重要的永久資料 (例如使用者的編輯內容) 寫入儲存空間。 +不過,您應選擇哪些資訊必須在 {@link android.app.Activity#onPause onPause()} 期間加以保留,這是因為此方法中的任何程序遭到封鎖,都無法轉換到下一個 Activity,而且會讓使用者體驗變慢。 + + +</p> + +<p> 在「是否可終止」<b></b>欄標示為「否」的方法,從呼叫這些方法的那一刻起,會保護代管 Activity 的處理程序不會遭到終止。 +因此,Activity 從 {@link android.app.Activity#onPause onPause()} 傳回到呼叫 + {@link android.app.Activity#onResume onResume()} 的期間為可終止。 +再次呼叫和傳回 +{@link android.app.Activity#onPause onPause()} 之前,Activity 為不可終止。 </p> + +<p class="note"><strong>注意:</strong>表 1 中定義為不是「可終止」的 Activity 仍可遭到系統終止 — 但只會發生在無技可施的情況下。 + +Activity 可加以終止的時機,於<a href="{@docRoot}guide/components/processes-and-threads.html">處理和執行緒</a>文件中有更詳細的討論。 + +</p> + + +<h3 id="SavingActivityState">儲存 Activity 狀態</h3> + +<p><a href="#Lifecycle">管理 Activity 生命週期</a>的簡介中提到,當 Activity 暫停或停止時,會保留 Activity 的狀態。 + +這點是成立的,原因在於當 {@link android.app.Activity} 物件暫停或停止時,它仍然保留在記憶體中 — 關於它的成員和目前狀態的所有資訊,仍然為有效的。 + +因此,使用者在 Activity 內所做的任何變更,都會保留下來。所以,當 Activity 返回前景 (當它「繼續」時),那些變更仍然會在原地。 + +</p> + +<p>不過,當系統終止 Activity 以收回記憶體時,{@link +android.app.Activity} 物件會遭到終止,所以系統就無法將它及其狀態完好無缺地繼續。 +如果使用者瀏覽回 {@link android.app.Activity} 物件,系統必須加以重新建立。 +但是,使用者不會注意到系統已終止該 Activity 並加以重新建立,可能因此期待 Activity 就跟之前的狀態一樣。 + +如果是這樣,您可以實作額外的回呼方法,以確認 Activity 狀態相關的重要資訊會保留下來。此回呼方法讓您儲存關於 Activity 狀態的資訊:{@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()}。 + +</p> + +<p>系統會在終止 Activity 之前呼叫 {@link android.app.Activity#onSaveInstanceState onSaveInstanceState()}。 +系統會將 {@link android.os.Bundle} 傳送給此方法,您可以使用 {@link +android.os.Bundle#putString putString()} 和 {@link +android.os.Bundle#putInt putInt()} 之類的方法,將 Activity 相關的狀態資訊以名稱-值組的方式儲存。 + +然後,如果系統終止應用程式處理程序,並且使用者瀏覽回您的 Activity,則系統會重新建立 Activity,然後將 {@link android.os.Bundle} 傳送給 {@link android.app.Activity#onCreate onCreate()} 和 {@link +android.app.Activity#onRestoreInstanceState onRestoreInstanceState()}。 + +您可以使用以上其中一種方法,從 {@link android.os.Bundle} 擷取已儲存的狀態,然後還原 Activity 狀態。 + +如果沒有狀態資訊可供還原,則傳送過來的 {@link +android.os.Bundle} 為空值 (null) (第一次建立 Activity 時,就是這種情況)。 +</p> + +<img src="{@docRoot}images/fundamentals/restore_instance.png" alt="" /> +<p class="img-caption"><strong>圖 2.</strong>Activity 返回使用者焦點,同時具備完整狀態的兩種方式:Activity 遭到終止,然後重新建立,Activity 必須還原之前儲存的狀態;或者Activity 已停止,然後繼續,Activity 狀態維持完整。 + + +</p> + +<p class="note"><strong>注意:</strong>不保證在您的 Activity 遭到終止之前,一定會呼叫 {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()},這是因為有時會發生不需要儲存狀態的情形 (例如,使用者使用「返回」按鈕離開您的 Activity 時<em></em>,原因在於使用者明確地關閉 Activity)。 + + + +如果系統要呼叫 {@link android.app.Activity#onSaveInstanceState +onSaveInstanceState()},會在 {@link +android.app.Activity#onStop onStop()} 之前呼叫,可能會在 {@link android.app.Activity#onPause +onPause()} 之前呼叫。</p> + +<p>不過,即使您沒有做任何事,沒有實作 {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()},有些 Activity 狀態會經由{@link android.app.Activity} 類別的預設實作 {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} 而還原。 +具體來說,預設實作會針對版面配置中的每一個 {@link +android.view.View} 呼叫對應的 {@link +android.view.View#onSaveInstanceState onSaveInstanceState()} 方法,這樣可以讓每個檢視提供本身應該要儲存的相關資訊。 + +在 Android 架構中,幾乎每個小工具都適當地實作此方法,因此 UI 中可見的變更都會自動儲存,並於 Activity 重新建立時加以還原。 + +例如,{@link android.widget.EditText} 小工具會儲存使用者輸入的任何文字,而 {@link android.widget.CheckBox} 小工具則會儲存是否勾選。 + +您只要針對需要儲存狀態的每個小工具,提供唯一的 ID (使用 <a href="{@docRoot}guide/topics/resources/layout-resource.html#idvalue">{@code android:id}</a> 屬性) 即可。 +如果小工具沒有 ID,則系統無法儲存其狀態。 +</p> + +<div class="sidebox-wrapper"> +<div class="sidebox"> +<p>您也可以明確地讓版面配置中的檢視停止儲存其狀態,只要將 +{@link android.R.attr#saveEnabled android:saveEnabled} 屬性設為 {@code "false"},或呼叫 {@link android.view.View#setSaveEnabled setSaveEnabled()} 方法即可。 +您通常不應停用儲存狀態的功能,不過,如果您想要還原不同的 Activity UI 狀態,則另當別論。 +</p> +</div> +</div> + +<p>儘管 {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} 的預設實作會儲存 Activity UI 相關的實用資訊,您仍然需要加以覆寫,以儲存額外的資訊,例如,您需要儲存 Activity 生命期間變更的成員值 (此真可能與 UI 中要還原的值有關,但保留那些 UI 值的成員預設不會加以還原)。 + + + +</p> + +<p>由於 {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} 的預設實作可協助儲存 UI 的狀態,因此,如果您覆寫此方法以儲存額外的狀態資訊,一定要再執行任何動作之前,呼叫 {@link android.app.Activity#onSaveInstanceState onSaveInstanceState()} 的超級類別實作。 + + +同樣的情況,您也要呼叫 {@link +android.app.Activity#onRestoreInstanceState onRestoreInstanceState()} 的超級類別實作 (如果您將它覆寫),讓預設實作可以還原檢視狀態。 +</p> + +<p class="note"><strong>注意:</strong>由於不保證一定會呼叫 {@link android.app.Activity#onSaveInstanceState +onSaveInstanceState()},您只能用它來記錄 Activity 的短暫狀態 (UI 的狀態) — 不應該用它儲存永久資料。 + +而是要在使用者離開 Activity 時,利用 {@link +android.app.Activity#onPause onPause()} 來儲存永內資料 (例如要儲存到資料庫的資料)。 +</p> + +<p>測試應用程式是否能夠還原其狀態的好方式,只要旋轉裝置,改變螢幕方向即可。 +螢幕方向改變時,系統會終止 Activity 並重新建立,以便套用針對新的螢幕設定而提供使用的替代資源。 + +單單就這一點而言,您的 Activity 在重新建立時可以完整還原,就非常重要了,這是因為使用者操作應用程式時會經常旋轉螢幕。 + +</p> + + +<h3 id="ConfigurationChanges">處理設定變更</h3> + +<p>有些裝置設定可以在執行階段期間進行變更 (例如,螢幕方向、鍵盤可用性和語言)。 +發生這類變更時,Android 會重新建立執行中的 Activity (系統呼叫 {@link android.app.Activity#onDestroy},然後立即呼叫 {@link +android.app.Activity#onCreate onCreate()})。 +此行為的設計透過自動重新載入應用程式與提供的替代資源 (例如針對不同的螢幕方向和大小的不同版面配置),可協助應用程式適應新的設定。 + + +</p> + +<p>如果您如上所述正確地設計 Activity,處理由於螢幕方向變更的重新啟動,然後還原 Activity 的狀態,您的應用程式對於 Activity 生命週期中的不可預期事件,會更具有抗性。 + +</p> + +<p>處理這類重新啟動的最佳方式,是使用 {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} 和 {@link +android.app.Activity#onRestoreInstanceState onRestoreInstanceState()} (或 {@link +android.app.Activity#onCreate onCreate()}) 儲存並還原 Activity 的狀態,如同上一節所討論。 +</p> + +<p>如需關於執行階段發生的設定變更,以及如何加以處理的詳細資訊,請參閱<a href="{@docRoot}guide/topics/resources/runtime-changes.html">處理執行階段變更</a>指南。 + +</p> + + + +<h3 id="CoordinatingActivities">協調 Activity</h3> + + <p>Activity 啟動另一個 Activity 時,它們兩者都經歷生命週期轉換。第一個 Activity 暫停並停止 (雖然如果仍然可以在背景看到它,表示它並未真的「停止」),而另一個 Activity 建立起來。 + +若這些 Activity 共用儲存到磁碟或其他地方的資料,第一個 Activity 在第二個 Activity 已建立之前,不會完全停止,瞭解這件事很重要。然而,啟動第二個 Activity 的處理程序與停止第一個 Activity 的處理程序會重疊。 + + +</p> + +<p>生命週期回呼的順序定義的很好,尤其是當兩個 Activity 位於相同的處理程序,而其中一個 Activity 啟動另一個 Activity 時。 +Activity A 啟動 Activity B 時所發生的操作順利如下: + </p> + +<ol> +<li>Activity A 的 {@link android.app.Activity#onPause onPause()} 方法會執行。</li> + +<li>Activity B 按順序執行 {@link android.app.Activity#onCreate onCreate()}、{@link +android.app.Activity#onStart onStart()} 以及 {@link android.app.Activity#onResume onResume()} 方法。 +(Activity B 現在擁有使用者焦點)。</li> + +<li>然後,如果螢幕上已經看不到 Activity A,就會執行 Activity A 的 {@link +android.app.Activity#onStop onStop()} 方法。</li> +</ol> + + <p>這一段可預測的生命週期回呼,可以讓您管理 Activity 之間資訊的轉換。 +例如,如果第一個 Activity 停止時,您必須寫入資料庫,讓接下來的 Activity 可以讀取,那麼您應該在 {@link android.app.Activity#onPause onPause()} 期間寫入,而不是在 {@link +android.app.Activity#onStop onStop()} 期間寫入。 + +</p> + +<!-- +<h2>Beginner's Path</h2> + +<p>For more information about how Android maintains a history of activities and +enables user multitasking, continue with the <b><a +href="{@docRoot}guide/components/tasks-and-back-stack.html">Tasks and Back +Stack</a></b> document.</p> +--> diff --git a/docs/html-intl/intl/zh-tw/guide/components/bound-services.jd b/docs/html-intl/intl/zh-tw/guide/components/bound-services.jd new file mode 100644 index 000000000000..da47634b894f --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/components/bound-services.jd @@ -0,0 +1,658 @@ +page.title=繫結服務 +parent.title=服務 +parent.link=services.html +@jd:body + + +<div id="qv-wrapper"> +<ol id="qv"> +<h2>本文件內容</h2> +<ol> + <li><a href="#Basics">基本概念</a></li> + <li><a href="#Creating">建立繫結服務</a> + <ol> + <li><a href="#Binder">延伸 Binder 類別</a></li> + <li><a href="#Messenger">使用 Messenger</a></li> + </ol> + </li> + <li><a href="#Binding">繫結至服務</a></li> + <li><a href="#Lifecycle">管理繫結服務的週期</a></li> +</ol> + +<h2>重要類別</h2> +<ol> + <li>{@link android.app.Service}</li> + <li>{@link android.content.ServiceConnection}</li> + <li>{@link android.os.IBinder}</li> +</ol> + +<h2>範例</h2> +<ol> + <li><a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/RemoteService.html">{@code + RemoteService}</a></li> + <li><a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/LocalService.html">{@code + LocalService}</a></li> +</ol> + +<h2>另請參閱</h2> +<ol> + <li><a href="{@docRoot}guide/components/services.html">服務</a></li> +</ol> +</div> + + +<p>繫結服務是主從介面中的伺服器。繫結服務讓元件 (例如 Activity) 可以繫結至服務、傳送要求、接收回應,甚至執行處理程序間通訊 (IPC)。 + +繫結服務通常只會在服務另一個應用程式元件時才存在,而且不會在背景中一直執行。 +</p> + +<p>本文會告訴您如何建立繫結服務,包括如何從其他應用程式元件繫結至服務。 +不過,如需關於服務的一般其他資訊,可參閱<a href="{@docRoot}guide/components/services.html">服務</a>文件,例如如何從服務傳遞通知、設定服務在前景執行等等。 + +</p> + + +<h2 id="Basics">基本概念</h2> + +<p>繫結服務是 {@link android.app.Service} 類別的實作,允許其他應用程式繫結至此實作,並與之互動。 +若服務要要提供繫結功能,您必須實作 {@link android.app.Service#onBind onBind()} 回呼方法。 +此方法會傳回 {@link android.os.IBinder} 物件,其中定義程式設計介面。用戶端可以使用此介面與服務互動。 + +</p> + +<div class="sidebox-wrapper"> +<div class="sidebox"> + <h3>繫結至已啟動的服務</h3> + +<p>我們在<a href="{@docRoot}guide/components/services.html">服務</a>文件中討論過,您可以建立已啟動且繫結的服務。 +也就是說,此服務可以透過呼叫 {@link android.content.Context#startService startService()} 加以啟動,讓此服務一直無限執行,也可以透過呼叫 {@link +android.content.Context#bindService bindService()},讓用戶端繫結至此服務。 + + + <p>如果您允許服務被啟動且繫結,當服務啟動後,系統「不會」<em></em>在所有用戶端都解除繫結時將此服務終結。 +您必須透過呼叫 {@link android.app.Service#stopSelf stopSelf()} 或 {@link +android.content.Context#stopService stopService()} 明確地停止服務。 +</p> + +<p>雖然您通常只會實作 {@link android.app.Service#onBind onBind()} 或<em></em> {@link android.app.Service#onStartCommand onStartCommand()},但有時候需要實作兩者。 + +例如,此功能對於音樂播放器就很實用,除了可以讓服務無限期執行,也可以提供繫結功能。 +這樣一來,Activity 就可以啟用服務並播放音樂,然後即使使用者離開應用程式後,音樂仍然繼續播放。 +使用者後續回到此應用程式時,Activity 可以繫結至服務,以重新取得播放的控制權。 +</p> + +<p>務必閱讀<a href="#Lifecycle">管理繫結服務的週期</a>,以取得關於新增繫結至已啟動的服務時,此服務週期的詳細資訊。 + +</p> +</div> +</div> + +<p>用戶端可以透過呼叫 {@link android.content.Context#bindService +bindService()} 繫結至服務。這麼做時,用戶端必須實作 {@link +android.content.ServiceConnection} 以監視與服務之間的連線狀況。{@link +android.content.Context#bindService bindService()} 方法會立即傳回 (不含值),當 Android 系統在用戶端和服務之間建立連線時,會呼叫 {@link +android.content.ServiceConnection#onServiceConnected onServiceConnected()} (位於 {@link +android.content.ServiceConnection}) 以傳遞 {@link android.os.IBinder} (用戶端可以用來與服務溝通)。 + + +</p> + +<p>多個用戶端可以同時連線至該服務。不過,系統只會在用戶端第一次繫結時,呼叫服務的 +{@link android.app.Service#onBind onBind()} 方法,以擷取 {@link android.os.IBinder}。 +然後,系統會將同一個 {@link android.os.IBinder} 傳遞給其他任何繫結的用戶端,不會再次呼叫 {@link android.app.Service#onBind onBind()}。 +</p> + +<p>最後一個用戶端從服務解除繫結時,系統會終結服務 (除非服務也是由 {@link android.content.Context#startService startService()} 所啟動)。 +</p> + +<p>實作繫結服務時,最重要的部分是定義您的 {@link android.app.Service#onBind onBind()} 回呼方法傳回的介面。 +您可以使用幾種不同的方式來定義服務的 {@link android.os.IBinder} 介面,以下小節將討論其中的每一種技術。 + +</p> + + + +<h2 id="Creating">建立已繫結的服務</h2> + +<p>建立提供繫結功能的服務時,您必須提供 {@link android.os.IBinder} 程式設計介面。用戶端可以使用此介面與服務互動。 +定義介面有三種方式: +</p> + +<dl> + <dt><a href="#Binder">延伸 Binder 類別</a></dt> + <dd>如果您的服務只讓您自己的應用程式使用,而且跟用戶端執行在同一個處理程序 (此作法很常見),則應該要透過延伸 {@link android.os.Binder} 類別,並從 +{@link android.app.Service#onBind onBind()} 傳回此類別的執行個體來建立介面。 + +用戶端接收 {@link android.os.Binder} 後,可以用它直接存取 {@link android.os.Binder} 實作或 {@link android.app.Service} 提供的公用方法。 + + + <p>若您的服務只是您自己應用程式的背景工作者,建議使用此方式。 +除非您的服務是由其他應用程式或跨個別處理程序使用,才不用以此方式建立自己的介面。 +</dd> + + <dt><a href="#Messenger">使用 Messenger</a></dt> + <dd>如果您的介面需要跨不同處理程序運作,則可以建立內含 {@link android.os.Messenger} 服務的介面。 +此服務定義了回應不同類型 {@link +android.os.Message} 物件的 {@link android.os.Handler}。 +這個 {@link android.os.Handler} 是 {@link android.os.Messenger} 的基礎,之後可以與用戶端分享 {@link android.os.IBinder},讓用戶端使用 {@link +android.os.Message} 物件傳送命令給此服務。 + +此外,用戶端可以定義專屬的 {@link android.os.Messenger},服務就可以傳回訊息。 + + <p>這是處理程序間通訊 (IPC) 最簡單的執行方式,因為 {@link +android.os.Messenger} 會將所有要求都排列到單一個執行緒,因此,就不用將服務設計成執行緒安全的形式。 +</p> + </dd> + + <dt>使用 AIDL</dt> + <dd>AIDL (Android 介面定義語言) 的工作是將物件分解為作業系統瞭解的始類型,然後將這些原始類型在各個處理程序間進行封送,以執行 IPC。先前使用 {@link android.os.Messenger} 的技術,實際上就是以 AIDL 作為底層結構。 + + +如上所述,{@link android.os.Messenger} 會在單一執行緒中建立所有用戶端要求的佇列,所以服務一次會接收一個要求。 +不過,如果您要讓服務可以同時處理多個要求,則可以直接使用 AIDL。 + +在此情況下,您的服務必須具備多執行緒的功能,而且是以執行緒安全的形式建置。 + <p>如要直接使用 AIDL,您必須建立 {@code .aidl} 檔案,並在其中定義程式設計介面。 +Android SDK 工具會使用此檔案產生一個抽象類別,以便實作介面並處理 IPC。您就可以在服務內加以延伸。 + +</p> + </dd> +</dl> + + <p class="note"><strong>注意:</strong>大部分應用程式<strong>不應</strong>使用 AIDL 建立繫結服務,若自行建立的話,就需要實作多執行緒功能,可能會導致更複雜的實作。 + +因此,AIDL 不適用於大部分應用程式,而且本文不會討論如何在您的服務中使用 AIDL。 +如果您確定需要直接使用 AIDL,請參閱 <a href="{@docRoot}guide/components/aidl.html">AIDL</a> 文件。 + +</p> + + + + +<h3 id="Binder">延伸 Binder 類別</h3> + +<p>如果您的服務只會在本機應用程式使用,而且不需要跨處理程序運作,則可以實作您自己的 {@link android.os.Binder} 類別,讓用戶端直接存取服務中的公用方法。 + +</p> + +<p class="note"><strong>注意:</strong>用戶端和服務都位於相同應用程式和處理程序時才適用,這也是最常見的情況。 +例如,需要將 Activity 繫結到其專屬服務 (在背景播放音樂)的音樂應用程式,就很適合。 + +</p> + +<p>設定的方式如下:</p> +<ol> + <li>在您的服務中建立 {@link android.os.Binder} 的執行個體,以具備以下其中一種功用: + <ul> + <li>包含用戶端可以呼叫的公用方法</li> + <li>傳回目前的 {@link android.app.Service} 執行個體,其中含有用戶端可以呼叫的公用方法 +</li> + <li>傳回由服務所裝載另一個類別的執行個體,而此服務含有用戶端可以呼叫的公用方法 +</li> + </ul> + <li>從 {@link +android.app.Service#onBind onBind()} 回呼方法傳回此 {@link android.os.Binder} 的執行個體。</li> + <li>在用戶端方面,從 {@link +android.content.ServiceConnection#onServiceConnected onServiceConnected()} 回呼方法接收 {@link android.os.Binder},然後使用提供的方法呼叫繫結服務。 +</li> +</ol> + +<p class="note"><strong>注意:</strong>服務和用戶端必須位於相同的應用程式,是因為用戶端才可以轉換傳回的物件,然後正確地呼叫其 API。 +服務和用戶端也必須位於相同的處理程序,因為此技術不會執行任何跨處理程序間旳封送。 + +</p> + +<p>例如,以下的服務可以讓用戶端透過實作的 {@link android.os.Binder} 存取服務中的方法: +</p> + +<pre> +public class LocalService extends Service { + // Binder given to clients + private final IBinder mBinder = new LocalBinder(); + // Random number generator + private final Random mGenerator = new Random(); + + /** + * Class used for the client Binder. Because we know this service always + * runs in the same process as its clients, we don't need to deal with IPC. + */ + public class LocalBinder extends Binder { + LocalService getService() { + // Return this instance of LocalService so clients can call public methods + return LocalService.this; + } + } + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + /** method for clients */ + public int getRandomNumber() { + return mGenerator.nextInt(100); + } +} +</pre> + +<p>{@code LocalBinder} 提供 {@code getService()} 方法,讓用戶端擷取 {@code LocalService} 目前的執行個體。 +這樣可以讓用戶端呼叫服務中的公用方法。 +例如,用戶端可以從服務呼叫 {@code getRandomNumber()}。</p> + +<p>當按一下按鈕時,會發生繫結至 {@code LocalService} 並呼叫 {@code getRandomNumber()}的 Activity: +</p> + +<pre> +public class BindingActivity extends Activity { + LocalService mService; + boolean mBound = false; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + } + + @Override + protected void onStart() { + super.onStart(); + // Bind to LocalService + Intent intent = new Intent(this, LocalService.class); + bindService(intent, mConnection, Context.BIND_AUTO_CREATE); + } + + @Override + protected void onStop() { + super.onStop(); + // Unbind from the service + if (mBound) { + unbindService(mConnection); + mBound = false; + } + } + + /** Called when a button is clicked (the button in the layout file attaches to + * this method with the android:onClick attribute) */ + public void onButtonClick(View v) { + if (mBound) { + // Call a method from the LocalService. + // However, if this call were something that might hang, then this request should + // occur in a separate thread to avoid slowing down the activity performance. + int num = mService.getRandomNumber(); + Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show(); + } + } + + /** Defines callbacks for service binding, passed to bindService() */ + private ServiceConnection mConnection = new ServiceConnection() { + + @Override + public void onServiceConnected(ComponentName className, + IBinder service) { + // We've bound to LocalService, cast the IBinder and get LocalService instance + LocalBinder binder = (LocalBinder) service; + mService = binder.getService(); + mBound = true; + } + + @Override + public void onServiceDisconnected(ComponentName arg0) { + mBound = false; + } + }; +} +</pre> + +<p>上述範例顯示:用戶端如何使用 +{@link android.content.ServiceConnection} 的實作和 {@link +android.content.ServiceConnection#onServiceConnected onServiceConnected()} 回呼,繫結至服務。下一節提供關於繫結至服務處理程序的詳細資訊。 +</p> + +<p class="note"><strong>注意:</strong>上述範例未明確從服務解除繫結,但所有用戶端都應該在適當時間解除繫結 (例如,Activity 暫停時)。 +</p> + +<p>如要取得更多範例程式碼,請參閱 <a href="{@docRoot}resources/samples/ApiDemos/index.html">ApiDemos</a> 中的 <a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/LocalService.html">{@code +LocalService.java}</a> 類別和 <a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/LocalServiceActivities.html">{@code +LocalServiceActivities.java}</a> 類別。</p> + + + + + +<h3 id="Messenger">使用 Messenger</h3> + +<div class="sidebox-wrapper"> +<div class="sidebox"> + <h4>與 AIDL 的比較</h4> + <p>需要執行 IPC 時,使用 {@link android.os.Messenger} 作為介面較簡單 (與使用 AIDL 實作介面相比),因為 {@link android.os.Messenger} 佇列都會呼叫服務,但是單純的 AIDL 介面會同時將要求傳送給服務。因此,服務必須具備處理多執行緒功能。 + + +</p> + <p>對於大部分應用程式而言,服務不需要執行多執行緒,所以使用 {@link +android.os.Messenger} 讓服務一次處理一個呼叫。如果您的服務必須處理多執行緒,則要使用 <a href="{@docRoot}guide/components/aidl.html">AIDL</a> 定義您的介面。 +</p> +</div> +</div> + +<p>如果服務要和遠端處理程序溝通,則可以使用 +{@link android.os.Messenger} 為您的服務提供介面。此技術讓您不需要使用 AIDL,就可以執行處理程序間通訊 (IPC)。 +</p> + +<p>以下是使用 {@link android.os.Messenger} 的摘要:</p> + +<ul> + <li>此服務實作 {@link android.os.Handler},可以從用戶端接收每個呼叫的回呼。 +</li> + <li>{@link android.os.Handler} 用於建立 {@link android.os.Messenger} 物件 (這是 {@link android.os.Handler} 的參照)。 +</li> + <li>{@link android.os.Messenger} 會建立 {@link android.os.IBinder},服務會從 {@link android.app.Service#onBind onBind()} 傳回給用戶端。 +</li> + <li>用戶端使用 {@link android.os.IBinder} 將 {@link android.os.Messenger} (參照服務的 {@link android.os.Handler}) 具現化,用戶端就可以用來將 +{@link android.os.Message} 物件傳送給服務。 +</li> + <li>服務會在其 {@link +android.os.Handler} 中接收每個 {@link android.os.Message} — 更明確地說,就是在 {@link android.os.Handler#handleMessage +handleMessage()} 方法中加以接收。</li> +</ul> + + +<p>這樣一來,用戶端就不需要呼叫服務的任何「方法」。用戶端只要傳遞「訊息」({@link android.os.Message} 物件),服務就會在其 {@link android.os.Handler} 中接收。 + +</p> + +<p>以下是使用 {@link android.os.Messenger} 介面的簡單範例服務:</p> + +<pre> +public class MessengerService extends Service { + /** Command to the service to display a message */ + static final int MSG_SAY_HELLO = 1; + + /** + * Handler of incoming messages from clients. + */ + class IncomingHandler extends Handler { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_SAY_HELLO: + Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show(); + break; + default: + super.handleMessage(msg); + } + } + } + + /** + * Target we publish for clients to send messages to IncomingHandler. + */ + final Messenger mMessenger = new Messenger(new IncomingHandler()); + + /** + * When binding to the service, we return an interface to our messenger + * for sending messages to the service. + */ + @Override + public IBinder onBind(Intent intent) { + Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show(); + return mMessenger.getBinder(); + } +} +</pre> + +<p>請注意, +{@link android.os.Handler} 中的 {@link android.os.Handler#handleMessage handleMessage()} 方法是服務接收傳入 {@link android.os.Message} 的位置,也是根據 {@link android.os.Message#what} 成員,決定後續執行動作的位置。 +</p> + +<p>用戶端只需要根據服務傳回的 {@link +android.os.IBinder},建立 {@link android.os.Messenger},然後使用 {@link +android.os.Messenger#send send()} 傳送訊息。例如,以下的簡單 Activity 會繫結至服務,然後將 {@code MSG_SAY_HELLO} 訊息傳遞給服務: +</p> + +<pre> +public class ActivityMessenger extends Activity { + /** Messenger for communicating with the service. */ + Messenger mService = null; + + /** Flag indicating whether we have called bind on the service. */ + boolean mBound; + + /** + * Class for interacting with the main interface of the service. + */ + private ServiceConnection mConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + // This is called when the connection with the service has been + // established, giving us the object we can use to + // interact with the service. We are communicating with the + // service using a Messenger, so here we get a client-side + // representation of that from the raw IBinder object. + mService = new Messenger(service); + mBound = true; + } + + public void onServiceDisconnected(ComponentName className) { + // This is called when the connection with the service has been + // unexpectedly disconnected -- that is, its process crashed. + mService = null; + mBound = false; + } + }; + + public void sayHello(View v) { + if (!mBound) return; + // Create and send a message to the service, using a supported 'what' value + Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0); + try { + mService.send(msg); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + } + + @Override + protected void onStart() { + super.onStart(); + // Bind to the service + bindService(new Intent(this, MessengerService.class), mConnection, + Context.BIND_AUTO_CREATE); + } + + @Override + protected void onStop() { + super.onStop(); + // Unbind from the service + if (mBound) { + unbindService(mConnection); + mBound = false; + } + } +} +</pre> + +<p>注意,此範例並未顯示服務回應用戶端的方式。如果您希望 +服務有所回應,則同時需要在用戶端建立 {@link android.os.Messenger}。之後,用戶端接收 {@link android.content.ServiceConnection#onServiceConnected +onServiceConnected()} 回呼時,會傳送 {@link android.os.Message} 給服務,其中包括用戶端的 {@link android.os.Messenger} (位於 {@link android.os.Messenger#send send()} 方法的 {@link android.os.Message#replyTo} 參數中)。 + + +</p> + +<p>您可以在 <a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/MessengerService.html">{@code +MessengerService.java}</a> (服務) 和 <a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/MessengerServiceActivities.html">{@code +MessengerServiceActivities.java}</a> (用戶端) 的範例中看到如何進行雙向傳訊的例子。</p> + + + + + +<h2 id="Binding">繫結至服務</h2> + +<p>應用程式元件 (用戶端) 可以透過呼叫 +{@link android.content.Context#bindService bindService()},繫結至服務。然後,Android 系統會呼叫服務的 {@link android.app.Service#onBind +onBind()} 方法 (此方法傳回 {@link android.os.IBinder} 以便與服務互動)。 +</p> + +<p>繫結為非同步。{@link android.content.Context#bindService +bindService()} 會立即傳回,但「不會」將<em></em> {@link android.os.IBinder} 傳回給用戶端。 +如要接收 {@link android.os.IBinder},用戶端必須建立 {@link +android.content.ServiceConnection} 的執行個體,然後將此執行個體傳送給 {@link android.content.Context#bindService +bindService()}。{@link android.content.ServiceConnection} 內含回呼方法,系統會呼叫此方法以傳遞 {@link android.os.IBinder}。 +</p> + +<p class="note"><strong>注意:</strong>只有 Activity、服務以及內容提供者可以繫結至服務 — 您<strong>不能</strong>從廣播接收者繫結至服務。 +</p> + +<p>因此,如要從用戶端繫結至服務,必須符合下列條件: </p> +<ol> + <li>實作 {@link android.content.ServiceConnection}。 + <p>實作中必須覆寫兩個回呼方法:</p> + <dl> + <dt>{@link android.content.ServiceConnection#onServiceConnected onServiceConnected()}</dt> + <dd>系統會呼叫此方法來傳遞 {@link android.os.IBinder} (由服務的 {@link android.app.Service#onBind onBind()} 方法所傳回)。 +</dd> + <dt>{@link android.content.ServiceConnection#onServiceDisconnected +onServiceDisconnected()}</dt> + <dd>與服務之間的連線突然遺失時 (例如服務當機或遭到終止時),Android 系統會呼收此方法。 +用戶端解除繫結時,「不會」<em></em>呼叫此方法。 +</dd> + </dl> + </li> + <li>呼叫會傳送 {@link +android.content.ServiceConnection} 實作的 {@link +android.content.Context#bindService bindService()}。 </li> + <li>系統呼叫 {@link android.content.ServiceConnection#onServiceConnected +onServiceConnected()} 回呼方法時,您就可以使用介面定義的方法,開始呼叫服務。 +</li> + <li>如要與服務中斷連線,請呼叫 {@link +android.content.Context#unbindService unbindService()}。 + <p>用戶端遭到終結時,會與服務解除繫結。不過,您應該要在與服務完成互動時,或者 Activity 暫停,要讓服務未使用時可以加以關閉的情況下,一定要解除繫結。 + +(以下將有繫結和解除繫結適當時機的詳細討論)。 +</p> + </li> +</ol> + +<p>例如,下列程式碼片段會透過<a href="#Binder">延伸 Binder 類別</a>,將用戶端連線到上述建立的服務,因此用戶端只要將傳回的 +{@link android.os.IBinder} 轉換為 {@code LocalService} 類別,然後要求 {@code +LocalService} 執行個體: +</p> + +<pre> +LocalService mService; +private ServiceConnection mConnection = new ServiceConnection() { + // Called when the connection with the service is established + public void onServiceConnected(ComponentName className, IBinder service) { + // Because we have bound to an explicit + // service that is running in our own process, we can + // cast its IBinder to a concrete class and directly access it. + LocalBinder binder = (LocalBinder) service; + mService = binder.getService(); + mBound = true; + } + + // Called when the connection with the service disconnects unexpectedly + public void onServiceDisconnected(ComponentName className) { + Log.e(TAG, "onServiceDisconnected"); + mBound = false; + } +}; +</pre> + +<p>使用此 {@link android.content.ServiceConnection},用戶端可以透過將它傳送給 {@link android.content.Context#bindService bindService()} 而繫結至服務。 +例如:</p> + +<pre> +Intent intent = new Intent(this, LocalService.class); +bindService(intent, mConnection, Context.BIND_AUTO_CREATE); +</pre> + +<ul> + <li>{@link android.content.Context#bindService bindService()} 的第一個參數是 +{@link android.content.Intent},明確地指定服務進行繫結 (儘管意圖是隱含的)。 +</li> +<li>第二個參數是 {@link android.content.ServiceConnection} 物件。</li> +<li>第三個參數是旗標,用來指出繫結的選項。如果服務尚未存在,通常是使用 {@link +android.content.Context#BIND_AUTO_CREATE} 以建立服務。其他可能的值為 {@link android.content.Context#BIND_DEBUG_UNBIND}和 {@link android.content.Context#BIND_NOT_FOREGROUND},或者 {@code 0} 代表無。 + +</li> +</ul> + + +<h3>其他注意事項</h3> + +<p>以下是關於繫結至服務的一些重要注意事項:</p> +<ul> + <li>您一定要設陷 {@link android.os.DeadObjectException} 例外狀況 (連線中斷時會擲回此例外狀況)。 +遠端方法只會擲回這個例外狀況。</li> + <li>物件會跨處理程序計算參照。 </li> + <li>繫結和解除繫結通常應成對使用,以符合用戶端的開始和結束週期。 +例如: + <ul> + <li>如果您只要在 Activity 可見時與服務互動,則要在 {@link android.app.Activity#onStart onStart()} 時繫結,並於 {@link +android.app.Activity#onStop onStop()} 時解除繫結。 +</li> + <li>如果您希望 Activity 即使在背景中停止時,仍然會接收回應,則可以在 {@link android.app.Activity#onCreate onCreate()} 時繫結,並於{@link android.app.Activity#onDestroy onDestroy()} 時解除繫結。 + +請注意,這表示您的 Activity 在整個執行期間 (即使是在背景執行也一樣) 都需要使用服務,因此,如果服務位於另一個處理程序,您要增加該處理程序的權重。但系統很可能因而將它終止。 + + +</li> + </ul> + <p class="note"><strong>注意:</strong>Activity 的 {@link android.app.Activity#onResume onResume()} 和 {@link +android.app.Activity#onPause onPause()} 期間,通常<strong>不要</strong>繫結和解除繫結,因為這些回呼會在每個週期轉換時發生,而且您應該要讓這些轉換期間所發生的處理動作保持在最少狀態。 + +另外,如果您的應用程式中有多個 Activity 繫結至同一個服務,而這兩個 Activity 之間會進行轉換,則服務會在目前的 Activity 解除繫結(暫停) 時,而下一個 Activity 繫結之前 (繼續時),先終結後再重新建立 + + +(此 Activity 轉換如何在 Activity 之間協調其週期的資訊,於 <a href="{@docRoot}guide/components/activities.html#CoordinatingActivities">Activity</a> 文件中說明)。 + +</p> +</ul> + +<p>如要取得如何繫結至服務的更多範例程式碼,請參閱 <a href="{@docRoot}resources/samples/ApiDemos/index.html">ApiDemos</a> 中的 <a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/RemoteService.html">{@code +RemoteService.java}</a> 類別。</p> + + + + + +<h2 id="Lifecycle">管理繫結服務的週期</h2> + +<p>服務與所有用戶端解除繫結時,Android 系統會將服務終結 (除非服務是和 {@link android.app.Service#onStartCommand onStartCommand()} 一起啟動的)。 +如果您的服務純粹是繫結服務,就不用管理它的週期— Android 系統會根據服務是否繫結至任何用戶端,為您管理服務。 + +</p> + +<p>不過,如果您選擇實作 {@link android.app.Service#onStartCommand +onStartCommand()} 回呼方法,則必須明確停止服務,因為服務現在會視為「已啟動」<em></em>。 +如果是此情形,除非服務本身使用 {@link android.app.Service#stopSelf()} 自行停止,或另一個元件呼叫 {@link +android.content.Context#stopService stopService()} 加以停止,否則服務會持續執行,不論它是否繫結至任何用戶端。 + +</p> + +<p>此外,如果您的服務已啟動並且接受繫結,當系統呼叫您的 {@link android.app.Service#onUnbind onUnbind()} 方法時,可以選擇傳回 +{@code true} (如果您希望用戶端下次繫結至服務時,可以接收 {@link android.app.Service#onRebind +onRebind()} 呼叫,而不是接收 {@link +android.app.Service#onBind onBind()} 的呼叫)。{@link android.app.Service#onRebind +onRebind()} 會傳回空值,但用戶端仍然會在其 +{@link android.content.ServiceConnection#onServiceConnected onServiceConnected()} 回呼中接收到 {@link android.os.IBinder}。以下「圖 1」說明這類週期的邏輯。 + +</p> + + +<img src="{@docRoot}images/fundamentals/service_binding_tree_lifecycle.png" alt="" /> +<p class="img-caption"><strong>圖 1.</strong>服務的週期開始後,就允許繫結行為。 +</p> + + +<p>如需關於已啟動服務週期的詳細資訊,請參閱<a href="{@docRoot}guide/components/services.html#Lifecycle">服務</a>文件。</p> + + + + diff --git a/docs/html-intl/intl/zh-tw/guide/components/fragments.jd b/docs/html-intl/intl/zh-tw/guide/components/fragments.jd new file mode 100644 index 000000000000..e54769b0c88b --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/components/fragments.jd @@ -0,0 +1,812 @@ +page.title=片段 +parent.title=Activity +parent.link=activities.html +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> + <h2>本文件內容</h2> + <ol> + <li><a href="#Design">設計概念</a></li> + <li><a href="#Creating">建立片段</a> + <ol> + <li><a href="#UI">新增使用者介面</a></li> + <li><a href="#Adding">將片段新增到 Activity 中</a></li> + </ol> + </li> + <li><a href="#Managing">管理片段</a></li> + <li><a href="#Transactions">進行片段交易</a></li> + <li><a href="#CommunicatingWithActivity">與 Activity 通訊</a> + <ol> + <li><a href="#EventCallbacks">為 Activity 建立事件回呼</a></li> + <li><a href="#ActionBar">將項目新增到動作列中</a></li> + </ol> + </li> + <li><a href="#Lifecycle">處理片段生命週期</a> + <ol> + <li><a href="#CoordinatingWithActivity">調整 Activity 生命週期</a></li> + </ol> + </li> + <li><a href="#Example">範例說明</a></li> + </ol> + + <h2>重要類別</h2> + <ol> + <li>{@link android.app.Fragment}</li> + <li>{@link android.app.FragmentManager}</li> + <li>{@link android.app.FragmentTransaction}</li> + </ol> + + <h2>另請參閱</h2> + <ol> + <li><a href="{@docRoot}training/basics/fragments/index.html">使用片段建置動態 UI</a></li> + <li><a href="{@docRoot}guide/practices/tablets-and-handsets.html">支援平板電腦和手機</a> +</li> + </ol> +</div> +</div> + +<p>{@link android.app.Fragment} 代表一種行為或 +{@link android.app.Activity} 中的一部分使用者介面。您可以合併單一 Activity 中的多個片段,藉此建置 +多窗格 UI 以及在多個 Activity 中重複使用片段。您可以將片段想成是 Activity 的模組化區段,片段擁有自己的生命週期、接收自己的輸入事件,而且您可以在 Activity 執行時新增或移除片段 (有點像是您可以在不同 Activity 中重複使用的「子 Activity」)。 + + +</p> + +<p>片段必須一律嵌入 Activity 中,而主要 Activity 的生命週期會直接影響片段的生命週期。 +例如,當 Activity 暫停時,其中的所有片段也會一併暫停;而當 Activity 遭到刪除時,所有片段也會一併刪除。 +不過,當 Activity 執行時 (該 Activity 會處於繼續進行<em></em><a href="{@docRoot}guide/components/activities.html#Lifecycle">生命週期狀態</a>),您可以個別操縱所有片段,例如新增或移除片段。 + +當您進行片段交易這類操作時,您也可以將片段加到 Activity 所管理的返回堆疊中 — Activity 中的所有返回堆疊項目均為所發生片段交易的記錄。 + + +返回堆疊可讓使用者復原片段交易 (往回瀏覽),只要按下 [返回]<em></em> 按鈕即可。 +</p> + +<p>當您將片段新增為 Activity 版面配置的一部分後,片段就會位於 Activity 檢視階層中的 {@link +android.view.ViewGroup},而且片段會自行定義專屬的版面配置。您可以宣告 Activity 版面配置檔案中的片段,或是在應用程式的程式碼中將片段加到現有的 {@link android.view.ViewGroup} 中,藉此在 Activity 版面配置中將片段插入為 {@code <fragment>} 元素。 + + + +不過,片段未必要成為 Activity 版面配置的一部分;您也可以選擇不透過其 UI,以隱形工作人員的身分使用 Activity 的片段。 + +</p> + +<p>本文說明如何建置應用程式以使用片段,包括片段如何在加到 Activity 返回堆疊時保持自身狀態、如何與 Activity 和 Activity 中的其他片段共用活動、如何製作 Activity 欄等等。 + + +</p> + + +<h2 id="Design">設計概念</h2> + +<p>我們在 Android 3.0 (API 級別 11) 中導入了片段,主要目的是為了在大型螢幕 (例如平板電腦) 上支援更多動態和彈性 UI 設計。 +由於平板電腦的螢幕比手機大上許多,因此有更多空間可結合及交換 UI 元件。 + +片段可實現這種介面設計,而不必讓您管理複雜的檢視階層變更。 +將 Activity 的版面配置劃分成片段後,您就可以修改 Activity 在執行階段的外觀,以及保留 Activity 所管理返回堆疊的相關變更。 + +</p> + +<p>例如,某個新聞應用程式可使用單一片段在畫面左側顯示文章清單,並且使用另一個片段在畫面右側顯示某篇文章 — 這兩個片段是以並排方式出現在某個 Activity 中,而每個片段都有自己的一組生命週期回呼方法,可自行處理其使用者輸入事件。 + + +因此,使用者可以在相同 Activity 中選取並閱讀某篇文章 (如圖 1 中的平板電腦版面配置所示),而不必使用不同 Activity 選取及閱讀文章。 + +</p> + +<p>請務必將每個片段設計成模組化和可重複使用的 Activity 元件。這是因為每個片段會根據其生命週期回呼,定義專屬版面配置和行為,而您可將單一片段加到多個 Activity 中,故請將其設計成可重複使用的元件,同時避免直接操縱個別片段。 + + +由於模組化片段可讓您針對不同螢幕大小變更片段組合,因此請務必這麼做。 +設計您的應用程式以支援平板電腦和手機時,您可以在不同版面配置設定中重複使用片段,藉此根據可用的螢幕空間提供最佳的使用者體驗。 + +以手機為例說明,如果相同 Activity 中有多個片段不相符,則只要分割片段即可提供單一面板式的 UI。 + +</p> + +<img src="{@docRoot}images/fundamentals/fragments.png" alt="" /> +<p class="img-caption"><strong>圖 1.</strong>片段所定義的兩個 UI 模組如何針對平板電腦設計合併成單一 Activity、如何針對手機設計分割成個別 Activity。 + +</p> + +<p>例如 — 延續新聞應用程式範例加以說明 — 在平板電腦大小的裝置上執行的應用程式可將兩個片段嵌入「Activity A」<em></em>。 +不過,在手機大小的螢幕上,由於螢幕空間不足以容納兩個片段,因此「Activity A」<em></em>只會包含文章清單的片段,而當使用者選取文章後,系統就會啟動內含第二個片段的「Activity B」<em></em>,讓使用者閱讀文章。 + + +因此,應用程式可透過重複使用不同片段組合的方式,同時支援平板電腦和手機 (如圖 1 所示)。 + +</p> + +<p>如要進一步瞭解如何使用不同片段組合針對各種螢幕設定設計應用程式,請參閱<a href="{@docRoot}guide/practices/tablets-and-handsets.html">支援平板電腦和手機</a>指南。 +</p> + + + +<h2 id="Creating">建立片段</h2> + +<div class="figure" style="width:327px"> +<img src="{@docRoot}images/fragment_lifecycle.png" alt="" /> +<p class="img-caption"><strong>圖 2.</strong>片段的生命週期 (當其中的 Activity 處於執行狀態時)。 +</p> +</div> + +<p>如要建立片段,您必須建立 {@link android.app.Fragment} 的子類別 (或是其現有的子類別)。 +{@link android.app.Fragment} 類別內含與{@link android.app.Activity} 十分雷同的程式碼。 +該程式碼包括與 Activity 類似的回呼方法,例如 {@link android.app.Fragment#onCreate onCreate()}、{@link android.app.Fragment#onStart onStart()}、 +{@link android.app.Fragment#onPause onPause()} 和 {@link android.app.Fragment#onStop onStop()}。 +事實上,如果您是設定現有 Android 應用程式改用片段,只要將 Activity 的回呼方法中的程式碼移到片段的個別回呼方法即可。 + + +</p> + +<p>一般來說,您至少必須實作下列生命週期方法:</p> + +<dl> + <dt>{@link android.app.Fragment#onCreate onCreate()}</dt> + <dd>系統會在建立片段時呼叫這個方法。在實作這個方法時,您必須初始化您想保留的必要片段元件,以便恢復已暫停或停止的片段。 + +</dd> + <dt>{@link android.app.Fragment#onCreateView onCreateView()}</dt> + <dd>系統會在片段初次顯示其使用者介面時呼叫這個方法。 +您必須透過這個方法傳回 {@link android.view.View} (片段版面配置的根目錄),才能顯示片段的 UI。 +如果片段並未提供 UI 的話,則可以傳回空值。 +</dd> + <dt>{@link android.app.Activity#onPause onPause()}</dt> + <dd>系統會在使用者初次離開片段時呼叫這個方法 (即使使用者這麼做未必會刪除片段)。 +您通常需要透過這個方法提交要在目前的使用者工作階段以外保留的任何變更 (原因在於使用者可能不會返回)。 + +</dd> +</dl> + +<p>大多數應用程式都至少必須針對每個片段實作這三個方法,不過您也必須使用幾個其他回呼方法來控制片段生命週期的各種狀態。 + +如要進一步瞭解所有回呼方法,請參閱<a href="#Lifecycle">處理片段生命週期</a>。 +</p> + + +<p>以下列出幾個您可能會想擴充的子類別 (基礎 {@link +android.app.Fragment} 類別除外):</p> + +<dl> + <dt>{@link android.app.DialogFragment}</dt> + <dd>顯示浮動對話方塊。使用這個類別建立對話方塊是使用 {@link android.app.Activity} 類別中的對話方塊協助程式方法的推薦替代方法,這是因為使用此類別可將片段對話方塊納入 Activity 所管理的片段堆疊,讓使用者得已返回已關閉的片段。 + + +</dd> + + <dt>{@link android.app.ListFragment}</dt> + <dd>顯示配接器 (例如 {@link +android.widget.SimpleCursorAdapter}) 所管理的項目清單;與 {@link android.app.ListActivity} 方法相似。這個方法可提供數種管理清單檢視畫面的方法,例如可處理點擊事件的 {@link +android.app.ListFragment#onListItemClick(ListView,View,int,long) onListItemClick()}回呼。 + +</dd> + + <dt>{@link android.preference.PreferenceFragment}</dt> + <dd>列出 {@link android.preference.Preference} 物件的階層;與 +{@link android.preference.PreferenceActivity} 方法相似。為應用程式建立「設定」Activity 時,這個方法就非常實用。 +</dd> +</dl> + + +<h3 id="UI">新增使用者介面</h3> + +<p>片段通常是當作某 Activity 的使用者介面使用,而且可將自身的版面配置提供給 Activity。 +</p> + +<p>如要提供片段的版面配置,您必須實作 {@link +android.app.Fragment#onCreateView onCreateView()} 回呼方法,讓 Android 系統呼叫片段顯示其版面配置。 +實作這個方法時,您必須傳回 +{@link android.view.View} (片段版面配置的根目錄)。</p> + +<p class="note"><strong>注意:</strong>如果您的片段是 {@link +android.app.ListFragment} 的子類別,則實作完畢後系統預設會傳回 {@link android.app.Fragment#onCreateView onCreateView()} 的 +{@link android.widget.ListView},因此您不必加以實作。</p> + +<p>如要從 {@link +android.app.Fragment#onCreateView onCreateView()} 傳回版面配置,您可以從 XML 中定義的<a href="{@docRoot}guide/topics/resources/layout-resource.html">l版面配置資源</a>擴大它。為協助您完成這項作業,{@link android.app.Fragment#onCreateView onCreateView()} 提供了 +{@link android.view.LayoutInflater} 物件。 +</p> + +<p>例如,以下是 {@link android.app.Fragment} 的子類別,可從 +{@code example_fragment.xml} 檔案載入版面配置:</p> + +<pre> +public static class ExampleFragment extends Fragment { + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.example_fragment, container, false); + } +} +</pre> + +<div class="sidebox-wrapper"> +<div class="sidebox"> + <h3>建立版面配置</h3> + <p>在上方範例中,{@code R.layout.example_fragment} 是應用程式中儲存的 +「{@code example_fragment.xml}」版面配置資源的參照資料。如要瞭解如何在 XML 中建立版面配置,請參閱<a href="{@docRoot}guide/topics/ui/index.html">使用者介面</a>。 + +</p> +</div> +</div> + +<p>傳入 {@link android.app.Fragment#onCreateView +onCreateView()} 的 {@code container} 參數是上層 {@link android.view.ViewGroup} (來自 Activity 的版面配置),系統會將您的片段版面配置插入其中。 + +{@code savedInstanceState} 參數是 {@link android.os.Bundle},當片段即將恢復時 (如要進一步瞭解還原狀態,請參閱<a href="#Lifecycle">處理片段生命週期</a>),這個參數就會提供先前的片段執行個體的相關資料。 + + +</p> + +<p>{@link android.view.LayoutInflater#inflate(int,ViewGroup,boolean) inflate()} 方法採用三種引數: +</p> +<ul> + <li>您想要擴大的版面配置的資源 ID。</li> + <li>要設為擴大過後版面配置的上層檢視的 {@link android.view.ViewGroup}。請務必傳遞 {@code +container},以便讓系統將版面配置參數套用至擴大過後版面配置的根檢視 (由將做為其目標的父檢視所指定)。 +</li> + <li>用於指示系統是否要在擴大期間將擴大過後的版面配置附加到 {@link +android.view.ViewGroup} (第二個參數) 的布林值 (由於系統已將擴大過後的版面配置插入 {@code +container},因此布林值應為 false — 如果您傳送 true,會導致系統在最終版面配置中建立多餘的檢視群組)。 +</li> +</ul> + +<p>您現在已瞭解如何建立可提供版面配置的片段了。接著,請將建立好的片段新增至 Activity。 +</p> + + + +<h3 id="Adding">將片段新增到 Activity 中</h3> + +<p>片段通常會將一部分 UI 嵌入主要 Activity 的整體檢視階層中。 +您有兩種方式可將片段新增到 Activity 版面配置: +</p> + +<ul> + <li><b>宣告 Activity 版面配置檔案內含的片段。</b> +<p>選用這種方式時,您可以將片段視為檢視,為其指定版面配置屬性。 +例如,以下是內含兩個片段的 Activity 版面配置檔案: +</p> +<pre> +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <fragment android:name="com.example.news.ArticleListFragment" + android:id="@+id/list" + android:layout_weight="1" + android:layout_width="0dp" + android:layout_height="match_parent" /> + <fragment android:name="com.example.news.ArticleReaderFragment" + android:id="@+id/viewer" + android:layout_weight="2" + android:layout_width="0dp" + android:layout_height="match_parent" /> +</LinearLayout> +</pre> + <p>{@code <fragment>} 中的 {@code android:name} 屬性可指定系統呼叫版面配置中的 {@link +android.app.Fragment} 類別。</p> + +<p>系統建立這個 Activity 版面配置後,就會呼叫版面配置中指定的任何片段,並為每個片段呼叫 {@link android.app.Fragment#onCreateView onCreateView()} 方法,藉此擷取所有片段的版面配置。 + +系統會插入片段所傳回的 {@link android.view.View} 來取代 {@code <fragment>} 元素。 +</p> + +<div class="note"> + <p><strong>注意:</strong>您必須為每個片段提供專屬識別碼,以便系統在 Activity 重新開始時復原片段 (您也可以使用此識別碼擷取要交易的片段,例如移除片段)。 + +您有三種方式可提供片段的 ID: +</p> + <ul> + <li>提供內含專屬 ID 的 {@code android:id} 屬性。</li> + <li>提供內含不重複字串的 {@code android:tag} 屬性。</li> + <li>如果您未提供上述兩項屬性,系統會採用容器檢視的 ID。 +</li> + </ul> +</div> + </li> + + <li><b>或者,利用程式將片段新增至現有的 {@link android.view.ViewGroup}。</b> +<p>只要 Activity 處於執行狀態,您都可以將片段新增至 Activity 版面配置。方法很簡單,只要指定您想在其中加入片段的 {@link +android.view.ViewGroup} 即可。 +</p> + <p>如要在 Activity 中進行片段交易 (例如新增、移除或替換片段),請使用 {@link android.app.FragmentTransaction} 中的 API 進行。 +您可以透過以下方式取得 {@link android.app.Activity} 的 {@link android.app.FragmentTransaction} 執行個體: +</p> + +<pre> +FragmentManager fragmentManager = {@link android.app.Activity#getFragmentManager()} +FragmentTransaction fragmentTransaction = fragmentManager.{@link android.app.FragmentManager#beginTransaction()}; +</pre> + +<p>接著,您就可以使用 {@link +android.app.FragmentTransaction#add(int,Fragment) add()} 方法指定要新增的片段,以及要插入片段的目標檢視。 +例如:</p> + +<pre> +ExampleFragment fragment = new ExampleFragment(); +fragmentTransaction.add(R.id.fragment_container, fragment); +fragmentTransaction.commit(); +</pre> + + <p>第一個傳入 {@link android.app.FragmentTransaction#add(int,Fragment) add()} 的引數是要在其中插入片段的 {@link android.view.ViewGroup} (使用資源 ID 加以指定),而第二個參數則是要新增的引數。 + +</p> + <p>透過 +{@link android.app.FragmentTransaction} 完成變更後,請呼叫 {@link android.app.FragmentTransaction#commit} 以便讓變更生效。 +</p> + </li> +</ul> + + +<h4 id="AddingWithoutUI">新增不顯示 UI 的片段</h4> + +<p>上述範例說明如何將片段新增至 Activity,以提供 UI。但事實上,您也可以使用片段為 Activity 提供背景行為,避免顯示額外的 UI。 + +</p> + +<p>如要新增不顯示使 UI 的片段,請使用 {@link +android.app.FragmentTransaction#add(Fragment,String)} 從 Activity 新增片段 (請提供片段的不重複字串「標記」,而不是檢視 ID)。 +這樣即可新增片段,但由於該片段並未與 Activity 版面配置中的檢視相關聯,因此不會接收 {@link +android.app.Fragment#onCreateView onCreateView()} 的呼叫。 +如此一來,您就不必實作該方法。</p> + +<p>提供片段的字串標記並不是採用非 UI 片段時的必要步驟 — 您也可以提供沒有 UI 的片段的字串標記 — 不過,如果片段沒有 UI,則字串標記將成為識別片段的唯一途徑。 + +如果您想之後再從 Activity 中取得片段,請使用 {@link android.app.FragmentManager#findFragmentByTag +findFragmentByTag()}。 +</p> + +<p>如需使用沒有 UI 的片段做為背景工作者的 Activity 範例,請參閱 SDK 範例中位於以下路徑的 {@code +FragmentRetainInstance.java} 範例 (可透過 Android SDK Manager 存取)<code><sdk_root>/APIDemos/app/src/main/java/com/example/android/apis/app/FragmentRetainInstance.java</code>。 + +</p> + + + +<h2 id="Managing">管理片段</h2> + +<p>如要管理 Activity 中的片段,請使用 {@link android.app.FragmentManager}。如要取得這些片段,請呼叫 Activity 中的 {@link android.app.Activity#getFragmentManager()}。 +</p> + +<p>您可透過 {@link android.app.FragmentManager} 執行下列操作:</p> + +<ul> + <li>使用 {@link +android.app.FragmentManager#findFragmentById findFragmentById()} (針對在 Activity 版面配置中提供 UI 的片段) 或 {@link android.app.FragmentManager#findFragmentByTag +findFragmentByTag()} (針對未提供 UI 的片段) 取得 Activity 中的現有片段。 +</li> + <li>使用 {@link +android.app.FragmentManager#popBackStack()} (模擬使用者的「返回」<em></em>命令) 將片段從返回堆疊中推出。</li> + <li>使用 {@link +android.app.FragmentManager#addOnBackStackChangedListener addOnBackStackChangedListener()} 針對返回堆疊的變更項目註冊監聽器。</li> +</ul> + +<p>如要進一步瞭解上述方法以及其他方法,請參閱 {@link +android.app.FragmentManager} 類別說明文件。</p> + +<p>如上一節所述,您也可以使用 {@link android.app.FragmentManager} 開啟 {@link android.app.FragmentTransaction},以便進行片段交易 (例如新增及移除片段)。 + +</p> + + +<h2 id="Transactions">進行片段交易</h2> + +<p>使用 Activity 中片段的一項實用功能,就是新增、移除、替換片段以及對它們執行其他動作,藉此反映使用者互動。 +您針對 Activity 提交的每組變更稱為交易,而您可以使用 {@link +android.app.FragmentTransaction} 中的進行這種交易。 +此外,您也可以儲存對 Activity 所管理的返回堆疊進行的交易,讓使用者能夠往回瀏覽片段變更 (如同往回瀏覽 Activity)。 + +</p> + +<p>您可以從 {@link +android.app.FragmentManager} 中取得如下所示的 {@link android.app.FragmentTransaction}:</p> + +<pre> +FragmentManager fragmentManager = {@link android.app.Activity#getFragmentManager()}; +FragmentTransaction fragmentTransaction = fragmentManager.{@link android.app.FragmentManager#beginTransaction()}; +</pre> + +<p>每次交易都是您想同時進行的一組變更。您可以使用 {@link +android.app.FragmentTransaction#add add()}、{@link android.app.FragmentTransaction#remove remove()}和 {@link android.app.FragmentTransaction#replace replace()} 等方法設定您想針對特定交易進行的變更。 + +接著,只要呼叫 {@link android.app.FragmentTransaction#commit()},就能將該交易套用至 Activity。 +</p> +</dl> + +<p>不過,您可能會為了新增交易至片段交易返回堆疊,先呼叫 {@link +android.app.FragmentTransaction#addToBackStack addToBackStack()},然後再呼叫 {@link +android.app.FragmentTransaction#commit()}。 +返回堆疊是由 Activity 所管理,可讓使用者透過按下 [返回]<em></em> 按鈕的方式,返回先前的片段狀態。 +</p> + +<p>以下範例可讓您替換片段,並且保留先前的返回堆疊狀態: +</p> + +<pre> +// Create new fragment and transaction +Fragment newFragment = new ExampleFragment(); +FragmentTransaction transaction = getFragmentManager().beginTransaction(); + +// Replace whatever is in the fragment_container view with this fragment, +// and add the transaction to the back stack +transaction.replace(R.id.fragment_container, newFragment); +transaction.addToBackStack(null); + +// Commit the transaction +transaction.commit(); +</pre> + +<p>在這個範例中,{@code newFragment} 會針對依據 {@code R.id.fragment_container} ID 識別的版面配置容器, +替換其中的任何現有片段 (如果有的話)。系統會呼叫 {@link +android.app.FragmentTransaction#addToBackStack addToBackStack()},將替換交易儲存到返回堆疊,以便使用者按下 [返回]<em></em> 按鈕來復原交易以及返回上一個片段。 + +</p> + +<p>如果您將多項變更新增至交易 (例如新增另一個 {@link +android.app.FragmentTransaction#add add()} 或 {@link android.app.FragmentTransaction#remove +remove()}),並且呼叫 {@link +android.app.FragmentTransaction#addToBackStack addToBackStack()},那麼您在呼叫 {@link android.app.FragmentTransaction#commit commit()} 之前套用的所有變更都會新增至返回堆疊做為單次交易,在這種情況下,按下 [返回]<em></em> 按鈕就能一次復原所有變更。 + +</p> + +<p>您將變更新增至 {@link android.app.FragmentTransaction} 的順序並不會造成任何影響,但請注意以下例外狀況: +</p> +<ul> + <li>務必最後才呼叫 {@link android.app.FragmentTransaction#commit()}</li> + <li>如果您是將多個片段新增至同一容器,那麼您新增片段的順序將決定這些片段在檢視階層中的顯示順序 +</li> +</ul> + +<p>如果您並未在進行移除片段的交易時呼叫 {@link android.app.FragmentTransaction#addToBackStack(String) +addToBackStack()},該片段會在您提交交易後遭到刪除,而且使用者無法往回瀏覽至該片段。 +不過,如果您在移除片段時呼叫 {@link android.app.FragmentTransaction#addToBackStack(String) addToBackStack()},則該片段將會遭到「停止」<em></em>,而且會在使用者往回瀏覽時繼續進行。 + + +</p> + +<p class="note"><strong>提示:</strong>您可以在進行每次片段交易時套用交易動畫,方法是在提交交易前呼叫 {@link android.app.FragmentTransaction#setTransition setTransition()}。 + +</p> + +<p>呼叫 {@link android.app.FragmentTransaction#commit()} 並不會立即進行交易, +而是會讓系統排定 UI 執行緒 (「主要」執行緒) 可執行這個方法時,立即加以執行。 +不過,您可以視需要透過 UI 執行緒呼叫 {@link +android.app.FragmentManager#executePendingTransactions()},立即執行 {@link android.app.FragmentTransaction#commit()} 所提交的交易。 +您通常不必這樣做,除非該交易是其他執行緒的工作的必要元件。 +</p> + +<p class="caution"><strong>注意:</strong>您可以使用 {@link +android.app.FragmentTransaction#commit commit()} 來提交交易,但僅限於 Activity <a href="{@docRoot}guide/components/activities.html#SavingActivityState">儲存其狀態</a>之前 (也就是使用者離開 Activity 之前)。 +如果您在這個時間點之後嘗試提交交易,就會發生例外狀況, +這是因為狀態會在交易提交後遺失 (如果需要復原 Activity 的話)。 +如果想確保狀態遺失不會造成任何影響,請使用 {@link +android.app.FragmentTransaction#commitAllowingStateLoss()} 提交交易。</p> + + + + +<h2 id="CommunicatingWithActivity">與 Activity 通訊</h2> + +<p>雖然 {@link android.app.Fragment} 是實作成不同於 +{@link android.app.Activity} 的物件,而且可在多個 Activity 中使用,特定片段執行個體仍會直接與含有該物件的 Activity 建立關聯。 +</p> + +<p>因此,片段可存取內含{@link +android.app.Fragment#getActivity()} 的 {@link android.app.Activity} 執行個體,以及輕鬆進行在 Activity 版面配置中尋找檢視等工作: +</p> + +<pre> +View listView = {@link android.app.Fragment#getActivity()}.{@link android.app.Activity#findViewById findViewById}(R.id.list); +</pre> + +<p>相同地,您的 Activity 可利用 {@link +android.app.FragmentManager#findFragmentById findFragmentById()} 或 {@link +android.app.FragmentManager#findFragmentByTag findFragmentByTag()} 從 {@link android.app.FragmentManager} 中取得 {@link android.app.Fragment} 參照資料,以呼叫片段中的方法。 +例如:</p> + +<pre> +ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment); +</pre> + + +<h3 id="EventCallbacks">為 Activity 建立事件回呼</h3> + +<p>在某些情況下,您可能需要可用來與 Activity 分享事件的片段。如果您需要這種片段,建議您在片段內定義回呼介面,然後要求主要 Activity 實作該片段。 + +當 Activity 透過介面接收回呼後,即可視需要與版面配置中的其他片段分享這項資訊。 +</p> + +<p>例如,假設新聞應用程式的 Activity 中有兩個片段 — 一個用於顯示文章清單 (片段 A),另一個用於顯示文章 (片段 B) — 其中的片段 A 必須告知 Activity 使用者選取清單項目的時間點,以便通知片段 B 顯示文章。 + +在這個範例中,{@code OnArticleSelectedListener} 介面是在片段 A 中完成宣告: +</p> + +<pre> +public static class FragmentA extends ListFragment { + ... + // Container Activity must implement this interface + public interface OnArticleSelectedListener { + public void onArticleSelected(Uri articleUri); + } + ... +} +</pre> + +<p>接著,代管片段的 Activity 會實作 {@code OnArticleSelectedListener} 介面,並且覆寫 {@code onArticleSelected()} 以便將片段 A 的事件告知片段 B。為了確保主要 Activity 可實作該介面,片段 A 的 {@link +android.app.Fragment#onAttach onAttach()} 回呼方法 (系統將片段新增至 Activity 時會呼叫這種方法) 轉換傳入 {@link android.app.Fragment#onAttach +onAttach()} 的 {@link android.app.Activity},藉此呼叫 {@code OnArticleSelectedListener} 執行個體: + + + + +</p> + +<pre> +public static class FragmentA extends ListFragment { + OnArticleSelectedListener mListener; + ... + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + try { + mListener = (OnArticleSelectedListener) activity; + } catch (ClassCastException e) { + throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener"); + } + } + ... +} +</pre> + +<p>如果 Activity 並未實作介面,那麼片段會擲回 +{@link java.lang.ClassCastException}。成功擲回時,{@code mListener} 成員會保留 Activity 所實作 +{@code OnArticleSelectedListener} 的參照資料,以便片段 A 呼叫 {@code OnArticleSelectedListener} 介面定義的方法與 Activity 分享事件。 + +例如,假設片段 A 是 +{@link android.app.ListFragment} 的延伸,則每當使用者點擊清單項目時,系統就會呼叫片段中的 {@link android.app.ListFragment#onListItemClick +onListItemClick()},讓該方法呼叫 {@code onArticleSelected()} 以便與 Activity 分享事件: + +</p> + +<pre> +public static class FragmentA extends ListFragment { + OnArticleSelectedListener mListener; + ... + @Override + public void onListItemClick(ListView l, View v, int position, long id) { + // Append the clicked item's row ID with the content provider Uri + Uri noteUri = ContentUris.{@link android.content.ContentUris#withAppendedId withAppendedId}(ArticleColumns.CONTENT_URI, id); + // Send the event and Uri to the host activity + mListener.onArticleSelected(noteUri); + } + ... +} +</pre> + +<p>傳入 {@link +android.app.ListFragment#onListItemClick onListItemClick()} 的 {@code id} 參數是使用者所點擊項目的資料列 ID,可讓 Activity (或其他片段) 用來從應用程式的 {@link +android.content.ContentProvider} 擷取文章。 +</p> + +<p><!--To see a complete implementation of this kind of callback interface, see the <a +href="{@docRoot}resources/samples/NotePad/index.html">NotePad sample</a>. -->如要進一步瞭解如何使用內容供應程式,請參閱<a href="{@docRoot}guide/topics/providers/content-providers.html">內容供應程式</a>。 +</p> + + + +<h3 id="ActionBar">將項目新增到動作列中</h3> + +<p>片段可實作 +{@link android.app.Fragment#onCreateOptionsMenu(Menu,MenuInflater) onCreateOptionsMenu()} 來為 Activity 的<a href="{@docRoot}guide/topics/ui/menus.html#options-menu">選項選單</a> (以及<a href="{@docRoot}guide/topics/ui/actionbar.html">動作列</a>) 提供選單項目。不過,您必須在呼叫 {@link +android.app.Fragment#onCreate(Bundle) onCreate()} 時呼叫 {@link +android.app.Fragment#setHasOptionsMenu(boolean) setHasOptionsMenu()},告知系統該片段會新增項目到「選項選單」,這個方法才能接收呼叫 (否則該片段將無法接收 +{@link android.app.Fragment#onCreateOptionsMenu onCreateOptionsMenu()} 的呼叫)。 + +</p> + +<p>您之後透過片段新增到「選項選單」的任何物件都會附加到現有的選單項目。 +該片段也會在使用者選取選單項目時接收 {@link +android.app.Fragment#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} 的回呼。 +</p> + +<p>此外,您也可以在片段版面配置中註冊檢視來提供內容選單,方法是呼叫 {@link +android.app.Fragment#registerForContextMenu(View) registerForContextMenu()}。當使用者開啟內容選單時,片段就會接收 {@link +android.app.Fragment#onCreateContextMenu(ContextMenu,View,ContextMenu.ContextMenuInfo) +onCreateContextMenu()} 的呼叫。 +而當使用者選取某個項目時,片段則會接收 {@link +android.app.Fragment#onContextItemSelected(MenuItem) onContextItemSelected()} 的呼叫。</p> + +<p class="note"><strong>注意:</strong>雖然片段會在使用者選取項目時,針對所有新增的選單項目接收回呼,不過最先在使用者選取選單項目時接收個別回呼的是 Activity。 + +如果 Activity 在使用者選取項目時所實作的回呼無法處理所選項目,則系統會將該事件傳送到片段的回呼中。 +這種情況會發生在「選項選單」和內容選單。 +</p> + +<p>如要進一步瞭解選單,請參閱<a href="{@docRoot}guide/topics/ui/menus.html">選單</a>和<a href="{@docRoot}guide/topics/ui/actionbar.html">動作列</a>開發人員指南。</p> + + + + +<h2 id="Lifecycle">處理片段生命週期</h2> + +<div class="figure" style="width:350px"> +<img src="{@docRoot}images/activity_fragment_lifecycle.png" alt="" /> +<p class="img-caption"><strong>圖 3.</strong>Activity 生命週期對片段生命週期造成的影響。 +</p> +</div> + +<p>管理片段生命週期的方式與管理 Activity 生命週期十分雷同。與 Activity 相同,片段有以下三種狀態: +</p> + +<dl> + <dt><i>已繼續</i></dt> + <dd>系統會在執行中的 Activity 內顯示片段。</dd> + + <dt><i>已暫停</i></dt> + <dd>前景中有其他具備焦點的 Activity,但系統仍會顯示含有這個片段的 Activity (前景 Activity 處於半透明狀態,或是未覆蓋整個螢幕)。 + +</dd> + + <dt><i>已停止</i></dt> + <dd>系統不會顯示片段。這可能是因為主要 Activity 已停止,或是加到返回堆疊的片段已從 Activity 中移除。 +已停止的片段仍處於有效狀態 (系統會保留所有狀態和成員資訊), +但使用者無法看到這類片段,而且當 Activity 遭到刪除時,這些片段也會一併刪除。 +</dd> +</dl> + +<p>與 Activity 相同,您可以使用 {@link +android.os.Bundle} 保留片段的狀態,以便在 Activity 的處理程序遭到刪除後想重新建立 Activity 時,還原片段狀態。 +您可以在片段的 {@link +android.app.Fragment#onSaveInstanceState onSaveInstanceState()} 回呼期間儲存狀態,並且在 +{@link android.app.Fragment#onCreate onCreate()}、{@link +android.app.Fragment#onCreateView onCreateView()} 或 {@link +android.app.Fragment#onActivityCreated onActivityCreated()} 時還原狀態。如要進一步瞭解如何儲存狀態,請參閱 <a href="{@docRoot}guide/components/activities.html#SavingActivityState">Activity</a>。 + +</p> + +<p>Activity 與片段生命週期之間最明顯的差異是,生命週期儲存在個別返回堆疊的方式。 +在預設情況下,Activity 停止後會插入系統所管理的 Activity 堆疊,方便使用者按下 [返回]<em></em> 按鈕來返回該 Activity (如<a href="{@docRoot}guide/components/tasks-and-back-stack.html">工作和返回堆疊</a>所述)。不過,片段只會在您進行移除片段的交易期間,呼叫 {@link +android.app.FragmentTransaction#addToBackStack(String) addToBackStack()} 來要求系統儲存執行個體時,插入主要 Activity 所管理的返回堆疊。 + + + + +</p> + +<p>在其他情況下,管理片段生命週期的方式與管理 Activity 生命週期十分雷同。 +因此,<a href="{@docRoot}guide/components/activities.html#Lifecycle">管理 Activity 生命週期</a>的做法同樣適用於片段。 +不過,建議您參考相關資源,瞭解 Activity 生命週期對片段生命週期造成的影響。 +</p> + +<p class="caution"><strong>注意:</strong>如果您需要 {@link android.app.Fragment} 的 +{@link android.content.Context} 物件,請呼叫 {@link android.app.Fragment#getActivity()}。不過,請務必只在確定片段是附加到 Activity 的情況下,再呼叫 {@link android.app.Fragment#getActivity()}。 + +如果片段未附加到 Activity,或是片段因超過生命週期而遭到卸除,則 {@link android.app.Fragment#getActivity()} 將傳回空值。 +</p> + + +<h3 id="CoordinatingWithActivity">調整 Activity 生命週期</h3> + +<p>內含片段的 Activity 的生命週期會直接影響片段的生命週期,這樣一來,Activity 的每次生命週期回呼會針對每個片段產生相似的回呼。 + +例如,當 Activity 收到 {@link android.app.Activity#onPause} 後,Activity 中的每個片段都會收到 {@link android.app.Fragment#onPause}。 +</p> + +<p>不過,片段有幾個額外的生命週期回呼,可用於處理與 Activity 之間的特殊互動,以執行建置或刪除片段 UI 等動作。以下是這些額外的回呼方法: + +</p> + +<dl> + <dt>{@link android.app.Fragment#onAttach onAttach()}</dt> + <dd>當片段與 Activity 建立關聯時,系統就會呼叫這個方法 ({@link +android.app.Activity} 會傳入與片段相關聯的 Activity)。</dd> + <dt>{@link android.app.Fragment#onCreateView onCreateView()}</dt> + <dd>系統會呼叫這個方法來建立與片段相關聯的檢視階層。</dd> + <dt>{@link android.app.Fragment#onActivityCreated onActivityCreated()}</dt> + <dd>當 Activity 的 {@link android.app.Activity#onCreate +onCreate()} 方法成功傳回時,系統就會呼叫這個方法。</dd> + <dt>{@link android.app.Fragment#onDestroyView onDestroyView()}</dt> + <dd>當使用者移除與片段相關聯的檢視階層時,系統就會呼叫這個方法。</dd> + <dt>{@link android.app.Fragment#onDetach onDetach()}</dt> + <dd>當使用者將片段與 Activity 解除關聯時,系統就會呼叫這個方法。</dd> +</dl> + +<p>如圖 3 所述,片段生命週期的流程會受到主要 Activity 的影響。 +您可以透過該圖片瞭解 Activity 的連續狀態如何決定片段要接收的回呼方法。 +例如,當 Activity 收到 {@link +android.app.Activity#onCreate onCreate()} 回呼後,Activity 中的片段就不會收到 +{@link android.app.Fragment#onActivityCreated onActivityCreated()} 以外的回呼。</p> + +<p>Activity 一旦進入已恢復狀態,您便可視需要在 Activity 中新增或移除片段。 +因此,只有處於已恢復狀態的 Activity 會影響片段的生命週期。 +</p> + +<p>不過,當 Activity 不再處於已恢復狀態後,Activity 就會再次推送片段的生命週期。 +</p> + + + + +<h2 id="Example">範例說明</h2> + +<p>以下提供使用兩個片段建立兩個面板的版面配置的 Activity 範例,藉此綜合說明本文所述內容。 +下方 Activity 包含一個用於顯示莎士比亞劇作清單的片段,以及另一個用於在使用者選取清單項目時顯示劇作摘要的片段。 + +這個 Activity 範例同時示範了如何根據螢幕設定提供不同的片段設定。 +</p> + +<p class="note"><strong>注意:</strong>如需這個 Activity 的完整原始碼,請查閱 +<a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.html">{@code +FragmentLayout.java}</a>。</p> + +<p>主要 Activity 會在 {@link +android.app.Activity#onCreate onCreate()} 時以常見的方式套用版面配置:</p> + +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java main} + +<p>Activity 套用的版面配置為 {@code fragment_layout.xml}:</p> + +{@sample development/samples/ApiDemos/res/layout-land/fragment_layout.xml layout} + +<p>使用這個版面配置可讓系統在 Activity 載入版面配置時,呼叫 {@code TitlesFragment} (這個類別會列出劇本名稱),而 {@link android.widget.FrameLayout} (顯示劇本摘要的片段將納入的目標類別) 則會佔用螢幕右側的空間,但在一開始會保持空白狀態。 + + +如下方所示,系統只會在使用者從清單中選取項目後,才將片段插入 {@link android.widget.FrameLayout}。 +</p> + +<p>不過,並非所有螢幕設定都有足夠的空間同時並排顯示劇作清單以及劇作摘要。 +因此,上方版面配置只適用於橫向螢幕設定,系統會將它儲存在 {@code res/layout-land/fragment_layout.xml} 中。 +</p> + +<p>而在螢幕為直向的情況下,系統會套用儲存在 {@code res/layout/fragment_layout.xml} 中的以下版面配置: +</p> + +{@sample development/samples/ApiDemos/res/layout/fragment_layout.xml layout} + +<p>這個版面配置只包含 {@code TitlesFragment}。也就是說,如果裝置採用直向螢幕設定,系統只會顯示劇作名稱清單。 +因此,當使用者在採用這種設定的裝置上點擊清單項目後,應用程式就會啟動新 Activity 來顯示劇作摘要,而不是載入第二個片段。 + +</p> + +<p>接著,請看看片段類別如何達到以上目標。首先是用於顯示莎士比亞劇作名稱清單的 {@code +TitlesFragment}。這個片段會延伸 {@link +android.app.ListFragment},並且依據該類別控制大多數的清單檢視工作。</p> + +<p>如果您檢查這個程式碼,將會發現使用者點擊清單項目後會觸發兩種行為:視採用的版面配置而定,系統會建立並呈現新的片段,以便在同一 Activity 中顯示詳細資料 (將片段加到 {@link +android.widget.FrameLayout}),或是啟動新的 Activity (藉此顯示片段)。 + +</p> + +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java titles} + +<p>第二個片段 {@code DetailsFragment} 則會針對 +{@code TitlesFragment} 中,使用者所選清單項目的劇本摘要:</p> + +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java details} + +<p>針對 {@code TitlesFragment} 類別發出的回呼,如果使用者點擊清單項目,而且目前的版面配置「並未」<em></em>包含 {@code R.id.details} 檢視 ({@code DetailsFragment} 所屬的檢視),則應用程式會執行 {@code DetailsActivity} Activity 來顯示項目內容。 + + +</p> + +<p>以下是會在螢幕採用橫向版面設定時,嵌入 {@code DetailsFragment} 以顯示所選劇本摘要的 {@code DetailsActivity}: +</p> + +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java +details_activity} + +<p>請注意,這個 Activity 會在螢幕採用橫向版面配置的情況下自行結束,因此主要 Activity 會接續顯示 {@code TitlesFragment} 旁的 {@code DetailsFragment}。如果使用者在採用直向版面配置的裝置上執行 {@code DetailsActivity},然後將該裝置旋轉成橫向 (這會重新執行目前的 Activity),就可能會發生這種情況。 + + +</p> + + +<p>如需更多使用片段的範例 (以及本範例的原始檔案),請參閱<a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/index.html#Fragment">ApiDemos</a> 所提供的 API Demos 範例應用程式 (可透過 <a href="{@docRoot}resources/samples/get.html">SDK 元件範本</a>下載)。 + +</p> + + diff --git a/docs/html-intl/intl/zh-tw/guide/components/fundamentals.jd b/docs/html-intl/intl/zh-tw/guide/components/fundamentals.jd new file mode 100644 index 000000000000..d3b3c2899614 --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/components/fundamentals.jd @@ -0,0 +1,480 @@ +page.title=應用程式基礎知識 +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> + +<h2>本文件內容</h2> +<ol> +<li><a href="#Components">應用程式元件</a> + <ol> + <li><a href="#ActivatingComponents">啟用元件</a></li> + </ol> +</li> +<li><a href="#Manifest">宣示說明檔案</a> + <ol> + <li><a href="#DeclaringComponents">宣告元件</a></li> + <li><a href="#DeclaringRequirements">宣告應用程式需求</a></li> + </ol> +</li> +<li><a href="#Resources">應用程式資源</a></li> +</ol> +</div> +</div> + +<p>Android 應用程式是以 Java 程式語言編寫而成。Android SDK 工具可將您的程式碼 — 連同任何相關資料和資源檔案 — 編入 APK ( + <i>Android 套件,</i>使用 {@code .apk} 後綴字詞的封存檔)。 +APK 檔案包含 Android 應用程式的所有內容,搭載 Android 作業系統的裝置會使用這種檔案來安裝應用程式。 +</p> + +<p>Android 應用程式安裝到裝置之後,便可在專屬的安全性沙箱中執行: </p> + +<ul> + <li>Android 作業系統是一種支援多位使用者的 Linux 系統;在這種系統中,每款應用程式即代表不同的使用者。 +</li> + +<li>在預設情況下,系統會為每款應用程式指派一個不重複 Linux 使用者 ID (只有系統可使用這個 ID,應用程式無法取得這項資訊)。 +系統會為應用程式中的所有檔案設定權限,因此只有該應用程式指派的使用者 ID 可存取這些檔案。 + </li> + +<li>所有處理程序都有專屬的虛擬機器 (VM),供系統在獨立環境中執行應用程式的程式碼。 +</li> + +<li>在預設情況下,每款應用程式會在專屬的 Linux 處理程序中執行。Android 會在需要執行應用程式的任何元件時啟動處理程序,並且在不必執行應用程式元件,或系統必須復原記憶體供其他應用程式使用時關閉處理程序。 + +</li> +</ul> + +<p>如此一來,Android 系統就會實作「最低權限原則」<em></em>。換句話說,在預設情況下,所有應用程式只能存取執行工作時所需的元件。 + +這樣一來,應用程式便無法存取系統的某些部分,藉此建立十分安全的執行環境。 +</p> + +<p>不過,應用程式可透過一些方式與其他應用程式分享資料,以及存取系統服務: +</p> + +<ul> + <li>兩款應用程式可共用相同的 Linux 使用者 ID,以便存取彼此的檔案。 +為了節省系統資源,共用相同使用者 ID 的應用程式可在相同 Linux 處理程序中執行,以及共用相同的 VM (前提是應用程式必須使用相同的憑證進行簽署)。 + +</li> + <li>應用程式可要求存取裝置資料 (例如使用者的聯絡人資料、簡訊、掛載式儲存空間 (SD 卡)、相機、藍牙等)。 +使用者必須在安裝期間授予所有應用程式權限。 +</li> +</ul> + +<p>本文提供有關 Android 應用程式如何在系統中運作的基本概念,其餘部分則說明以下幾點: +</p> +<ul> + <li>定義應用程式的核心架構元件。</li> + <li>用於宣告應用程式所需元件和裝置功能的宣示說明檔案。 +</li> + <li>應用程式的程式碼以外的資源;這些資源可讓您的應用程式針對各種裝置設定最佳化本身的行為。 +</li> +</ul> + + + +<h2 id="Components">應用程式元件</h2> + +<p>應用程式元件是 Android 應用程式的重要設計模組。每個元件 都是系統進入您應用程式的不同要點。並非所有元件都是使用者的實際進入點;某些元件的定義取決於其他元件,但所有元件都是獨立的個體,扮演著特定角色 — 換句話說,每個元件都是獨特的設計模組,可協助定義您應用程式的整體行為。 + + + +</p> + +<p>應用程式元件可分為 4 種不同類型。每種類型的用途和生命週期均不相同,可定義元件的建立及刪除方式。 +</p> + +<p>以下是應用程式元件的 4 種類型:</p> + +<dl> + +<dt><b>Activity</b></dt> + +<dd>單一 <i>Activity</i> 代表顯示使用者介面的一個畫面。例如,電子郵件應用程式可包含一個用於顯示新郵件清單的 Activity、一個用於撰寫郵件的 Activity,以及一個用於閱讀郵件的 Activity。 + +雖然電子郵件應用程式的 Activity 會共同運作,以提供豐富的使用者體驗,不過每個 Activity 都是不同的個體。 + +因此,其他應用程式可執行其中任何一項 Activity (如果電子郵件應用程式允許的話)。 +例如,相機應用程式可在電子郵件應用程式中,執行用於撰寫新郵件的 Activity,以便讓使用者分享相片。 + + +<p>Activity 是實作成 {@link android.app.Activity} 的子類別;詳情請參閱 <a href="{@docRoot}guide/components/activities.html">Activity</a> 開發人員指南。 + +</p> +</dd> + + +<dt><b>服務</b></dt> + +<dd>單一 <i>服務</i> 是在背景執行的元件,用於進行長期作業或遠端處理程序工作。 +服務並不會提供使用者介面。 +例如,服務可在使用者位於其他應用程式時在背景撥放音樂,或是透過網路擷取資料,同時允許使用者與 Activity 進行互動。 + +其他元件 (例如 Activity) 可啟動並讓服務執行,或是繫結至 Activity 以便與其進行互動。 + + +<p>服務是實作成 {@link android.app.Service} 的子類別;詳情請參閱<a href="{@docRoot}guide/components/services.html">服務</a>開發人員指南。 + +</p> +</dd> + + +<dt><b>內容供應程式</b></dt> + +<dd>單一 <i>內容供應程式</i> 可管理一組已分享的應用程式資料。您可以將資料儲存在檔案系統、SQLite 資料庫、網路上,或是您應用程式可存取的任何其他永久儲存空間。 + +其他應用程式可透過內容供應程式查詢或甚至修改資料 (如果內容供應程式允許這麼做的話)。 +例如,Android 系統會提供用於管理使用者聯絡資訊的內容供應程式。 +因此,任何具備適當權限的應用程式均可查詢內容供應程式的一部分 (例如 {@link +android.provider.ContactsContract.Data}),以便讀取及寫入有關特定使用者的相關資訊。 + + +<p>此外,內容供應程式也可用於讀取及寫入只有您應用程式能存取的不公開資料。 +例如,<a href="{@docRoot}resources/samples/NotePad/index.html">Note Pad</a> 範例應用程式可使用內容供應程式儲存記事。 +</p> + +<p>內容供應程式是實作成 {@link android.content.ContentProvider} 的子類別,而且必須實作一組標準 API 以便讓其他應用程式執行交易。 + +如需詳細資訊,請參閱<a href="{@docRoot}guide/topics/providers/content-providers.html">內容供應程式</a>開發人員指南。 +</p> +</dd> + + +<dt><b>廣播接收器</b></dt> + +<dd>單一 <i>廣播接收器</i> 是一種元件,可回應整個系統的廣播通知。 +大多數廣播都是由系統所發出 — 例如,系統會發出廣播來通知使用者螢幕已關閉、電池電量不足,或相片已拍攝完成。此外,應用程式也可發出廣播 — 例如說發出廣播來通知其他應用程式特定資料已下載到裝置,可供它們使用。 + + +雖然廣播接收器無法顯示使用者介面,但它們可<a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">建立狀態列通知</a>,告訴使用者發生了廣播事件。 + +具體而言,廣播接收器只是其他元件的「閘道」,用於執行極少量的工作。 +例如,廣播接收器可啟動服務依據事件執行特定工作。 + + +<p>廣播接收器是實作為成 {@link android.content.BroadcastReceiver} 的子類別,而每個廣播都是由 {@link android.content.Intent} 物件所發出。 +如需詳細資訊,請參閱 {@link android.content.BroadcastReceiver} 類別。 +</p> +</dd> + +</dl> + + + +<p>Android 系統設計的一項特色是,任何應用程式都可啟動其他應用程式的元件。 +例如,假設您想讓使用者透過裝置相機拍攝相片,您的應用程式可利用其他具備相關功能的應用程式 (如果有的話) 以達到這個目標,這樣您就不必自行建立用於拍攝相片的 Activity。 + +您不需要納入或連結相機應用程式的程式碼,只要啟動相機應用程式中用於拍攝像片的 Activity 即可。 + + +啟動相關 Activity 後,系統就會將相片傳回您的應用程式供您使用。對於使用者而言,相機就宛如是您應用程式的一部分。 +</p> + +<p>當系統啟動某個元件後,就會啟動該應用程式的處理程序 (如果該應用程式目前並非處於執行中狀態) 並且呼叫該元件所需的類別。 +例如,假設您的應用程式啟動相機應用程式中用於拍攝相片的 Activity,則該 Activity 會在隸屬於相機應用程式的處理程序中執行,而不是您應用程式的處理程序。因此,與大多數其他系統的應用程式不同,Android 應用程式沒有單一進入點 (例如沒有 {@code main()} 函式)。 + + + +</p> + +<p>系統是在個別處理程序 (具備檔案權限,可限制其他應用程式存取) 中執行每款應用程式,因此您的應用程式無法直接啟動其他應用程式的元件,不過 Android 系統可以。 + +基於這個原因,如要啟動其他應用程式的元件,您必須向指定「意圖」<em></em>的系統發送訊息,以啟動特定元件。 + +系統隨後會為您啟用所需的元件。</p> + + +<h3 id="ActivatingComponents">啟用元件</h3> + +<p>4 種元件類型的其中 3 種 — Activity、服務和廣播接收器 — 是透過「意圖」<em></em>這種非同步訊息啟用。意圖會在執行階段將元件與彼此繫結 (您可以意圖想成要求其他元件進行動作的傳令員),不論元件是否屬於您的應用程式。 + + + +</p> + +<p>意圖是使用 {@link android.content.Intent} 物件建立而成,該物件可定義訊息來啟用特定元件或特定「類型」<em></em>的元件 — 意圖可以採取明確或隱含設定。 + +</p> + +<p>針對 Activity 和服務,意圖會定義要執行的動作 (例如「查看」或「傳送」某項目),並且可能會指定執行動作的目標資料 URI (以及通知要啟用的元件)。 + +例如,意圖可傳達某 Activity 的要求,顯示圖片或開啟網頁。 +在某些情況下,您可以啟動 Activity 來接收結果,此時該 Activity 也會傳回{@link android.content.Intent} 的結果 (例如,您可以發出意圖讓使用者挑選聯絡人資料,並將該資訊傳回給您 — 傳回意圖會包含指向所選聯絡人的 URI )。 + + + +</p> + +<p>針對廣播接收器,意圖只會定義要廣播的通知 (例如,用於通知裝置電量不足的廣播只會包含指出「電池電量不足」的已知動作字串)。 + +</p> + +<p>其他元件類型和內容供應程式並非由意圖所啟用,而是在受 {@link android.content.ContentResolver} 發出的要求所指定時由系統啟用。 +內容解析程式可處理內容供應程式的所有直接交易,因此與供應程式進行交易的元件不必呼叫 {@link +android.content.ContentResolver} 物件的方法。 + +這樣會在內容供應程式與要求資訊 (基於安全目的) 之間保留抽象層。 +</p> + +<p>用於啟用各種元件的方法有以下幾種:</p> +<ul> + <li>將 {@link android.content.Intent} 傳送到 {@link android.content.Context#startActivity +startActivity()} 或 {@link android.app.Activity#startActivityForResult startActivityForResult()} (如果您想讓 Activity 傳回結果的話) 即可啟動 Activity (或是指派新工作給 Activity)。 + +</li> + <li>將 {@link android.content.Intent} 傳送到 {@link android.content.Context#startService +startService()} 即可啟動服務 (或是指派新指示給正在執行的服務)。 +或者,您也可以將 {@link android.content.Intent} 傳送到 +{@link android.content.Context#bindService bindService()} 來繫結至服務。</li> + <li>將 {@link android.content.Intent} 傳送到 +{@link android.content.Context#sendBroadcast(Intent) sendBroadcast()}、{@link +android.content.Context#sendOrderedBroadcast(Intent, String) sendOrderedBroadcast()} 或 {@link +android.content.Context#sendStickyBroadcast sendStickyBroadcast()} 等方法即可啟用廣播。</li> + <li>對 {@link android.content.ContentResolver} 呼叫{@link +android.content.ContentProvider#query query()} 即可查詢內容供應程式。</li> +</ul> + +<p>如要進一步瞭解如何使用意圖,請參閱<a href="{@docRoot}guide/components/intents-filters.html">意圖和意圖篩選器</a>。 +如要進一步瞭解如何啟用特定元件,請參閱下列說明文件: +<a href="{@docRoot}guide/components/activities.html">Activity</a>、<a href="{@docRoot}guide/components/services.html">服務</a>、{@link +android.content.BroadcastReceiver} 和<a href="{@docRoot}guide/topics/providers/content-providers.html">內容供應程式</a>。</p> + + +<h2 id="Manifest">宣示說明檔案</h2> + +<p>Android 系統必須先讀取應用程式的 {@code AndroidManifest.xml} 檔案 (「宣示說明」檔案) 確認應用程式元件確實存在,才能啟動該元件。 + +您的應用程式必須在這個檔案中宣告本身的所有元件,而該檔案必須位於應用程式專案目錄的根目錄。 +</p> + +<p>除了宣告應用程式的元件以外,宣示說明還可以進行許多工作,包括: +</p> +<ul> + <li>識別應用程式所需的任何使用者權限,例如網際網路存取權或使用者合約的讀取存取權。 +</li> + <li>根據應用程式使用的 API,宣告應用程式所需的最低 <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#ApiLevels">API 級別</a>。 +</li> + <li>宣告應用程式所使用或所需的硬體和軟體,例如相機、藍牙服務或多點觸控螢幕。 +</li> + <li>應用程式需要連結的 API 程式庫 (除了 Android 架構 API 以外),例如 <a href="http://code.google.com/android/add-ons/google-apis/maps-overview.html">Google 地圖程式庫</a>。 + +</li> + <li>還有其他工作</li> +</ul> + + +<h3 id="DeclaringComponents">宣告元件</h3> + +<p>宣告說明的主要工作是將應用程式的元件告知系統。例如,宣告說明檔案可用如下方式宣告 Activity: + </p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<manifest ... > + <application android:icon="@drawable/app_icon.png" ... > + <activity android:name="com.example.project.ExampleActivity" + android:label="@string/example_label" ... > + </activity> + ... + </application> +</manifest></pre> + +<p>在 <code><a +href="{@docRoot}guide/topics/manifest/application-element.html"><application></a></code>元素中,{@code android:icon} 屬性會指向識別應用程式的圖示資源。 + +</p> + +<p>而在 <code><a +href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code> 元素中, +{@code android:name} 屬性會指定 {@link +android.app.Activity} 子類別的完整類別名稱,{@code android:label} 屬性則會指定要當作 Activity 的使用者可見標籤使用的字串。 +</p> + +<p>您必須用以下方式宣告所有應用程式元件:</p> +<ul> + <li><code><a +href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code>:Activity 適用的元素 +</li> + <li><code><a +href="{@docRoot}guide/topics/manifest/service-element.html"><service></a></code>:服務適用的元素 +</li> + <li><code><a +href="{@docRoot}guide/topics/manifest/receiver-element.html"><receiver></a></code>:廣播接收器適用的元素 +</li> + <li><code><a +href="{@docRoot}guide/topics/manifest/provider-element.html"><provider></a></code>:內容供應程式適用的元素 +</li> +</ul> + +<p>系統看不到您納入來源但未在宣示說明中宣告的 Activity、服務和內容供應程式,因此系統無法執行這些項目。 +不過,您可在宣示說明宣告廣播接收器,或是透過程式碼以動態方式建立廣播接收器 (將廣播接收器建立為 +{@link android.content.BroadcastReceiver} 物件),然後呼叫 {@link android.content.Context#registerReceiver registerReceiver()} +向系統註冊廣播接收器。 + +</p> + +<p>如要進一步瞭解如何為應用程式建立宣示說明檔案,請參閱 <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">AndroidManifest.xml 檔案</a>。 + </p> + + + +<h3 id="DeclaringComponentCapabilities">宣告元件功能</h3> + +<p>如<a href="#ActivatingComponents">啟用元件</a>所述,您可以使用 +{@link android.content.Intent} 來啟動 Activity、服務和廣播接收器。如要這麼做,請在意圖中明確指定目標元件 (使用元件類別名稱)。 +不過,意圖最大的功能在於「隱含意圖」<em></em>的概念。 +隱含意圖可簡單描述要執行的動作類型 (或是執行動作的資料依據) 以及讓系統在裝置中找出可執行動作的元件,然後加以啟動。 + + +如果意圖指出有多個元件可執行動作,則使用者可選取要使用的元件。 +</p> + +<p>系統會比對接受到的意圖與裝置上其他應用程式的宣示說明檔案中提供的意圖篩選器,藉此識別可回應意圖的元件。 +<i></i> +</p> + +<p>在應用程式的宣示說明中宣告 Activity 時,您可視需要納入宣告 Activity 功能的意圖篩選器,以便讓 Activity 可回應其他應用程式的意圖。 + +您可以為元件宣告意圖篩選器,方法是將 <a href="{@docRoot}guide/topics/manifest/intent-filter-element.html">{@code +<intent-filter>}</a> 元素新增為元件宣告元素的子元素。 +</p> + +<p>例如,假設您以用於撰寫新郵件的 Activity 建置電子郵件應用程式,您可以下列方式宣告意圖篩選器來回應「傳送」意圖 (藉此傳送新郵件): +</p> +<pre> +<manifest ... > + ... + <application ... > + <activity android:name="com.example.project.ComposeEmailActivity"> + <intent-filter> + <action android:name="android.intent.action.SEND" /> + <data android:type="*/*" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + </activity> + </application> +</manifest> +</pre> + +<p>接著,如果其他應用程式透過 {@link +android.content.Intent#ACTION_SEND} 動作建立了意圖並傳送到 {@link android.app.Activity#startActivity +startActivity()},系統就可能會啟動您的 Activity 讓使用者撰寫及傳送郵件。 +</p> + +<p>如要進一不瞭解如何建立意圖篩選器,請參閱<a href="{@docRoot}guide/components/intents-filters.html">意圖和意圖篩選器</a>。 +</p> + + + +<h3 id="DeclaringRequirements">宣告應用程式需求</h3> + +<p>並非所有搭載 Android 作業系統的裝置都能提供完整功能。 +為了避免使用者在缺少應用程式所需功能的裝置上安裝您的應用程式,請務必在宣示說明檔案中宣告裝置和軟體需求,清楚定義您的應用程式支援的裝置類型。 + + +大多數宣告僅供使用者參考,系統無法讀取,但 Google Play 等外部服務可讀取這些宣示,以便在使用者透過自己的裝置搜尋應用程式時提供篩選功能。 + +</p> + +<p>例如,假設您的應用程式需要相機且採用 Android 2.1 (<a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#ApiLevels">API 級別</a> 7) 導入的 API,建議您用下列方式在宣示說明檔案中宣告這些需求: +</p> + +<pre> +<manifest ... > + <uses-feature android:name="android.hardware.camera.any" + android:required="true" /> + <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="19" /> + ... +</manifest> +</pre> + +<p>如此一來,「沒有」<em></em>相機且搭載 Android 2.1「以下版本」<em></em>的裝置就無法從 Google Play 安裝您的應用程式。 +</p> + +<p>不過,您也可以宣告您的應用程式會使用相機,但相機並非應用程式的「必要」<em></em>配備。 +在這種情況下,應用程式的 <a href="{@docRoot}guide/topics/manifest/uses-feature-element.html#required">{@code required}</a>屬性必須設為 {@code "false"},而且應用程式必須在執行階段檢查裝置是否具備相機,並且視需要停用任何相機功能。 + +</p> + +<p>如要進一步瞭解如何管理應用程式與不同裝置的相容性,請參閱<a href="{@docRoot}guide/practices/compatibility.html">裝置相容性</a>。 + +</p> + + + +<h2 id="Resources">應用程式資源</h2> + +<p>Android 應用程式是以程式碼等其他要素開發而成 — 例如圖片、音訊檔案以及與應用程式視覺效果相關的其他資源。例如,您必須使用 XML 檔案定義 Activity 使用者介面的動畫、選單、樣式、顏色以及版面配置。 + + +使用應用程式資源可協助更新應用程式的各種特性,而不必修改程式碼 — 或是提供多組替代資源 — 藉此針對各種裝置設定 (例如不同的語言和螢幕大小) 最佳化您的應用程式。 + + +</p> + +<p>針對您在 Android 專案中加入的所有資源,SDK 建置工具會定義一個整數 ID,讓您用於從應用程式的程式碼或 XML 中定義的其他資源參照特定資源。 + +例如,假設您的應用程式含有名為 {@code +logo.png} 的圖片檔案 (儲存在 {@code res/drawable/} 目錄中),SDK 工具會產生名為 {@code R.drawable.logo} 的資源 ID,讓您用於參照圖片並將其插入使用者介面。 + +</p> + +<p>提供原始碼以外資源的一個重點是針對不同的裝置設定提供替代資源。 + +例如,您可以在 XML 中定義使用者介面字串,藉此將字串翻譯成其他語言,以及將這些字串儲存成個別檔案。 +接著,視您附加到資源目錄名稱的語言「修飾語」<em></em> (例如代表法文字串值的 {@code res/values-fr/}),以及使用者的語言設定而定,Android 系統會為您的 UI 套用適當的語言字串。 + + +</p> + +<p>Android 針對替代資源支援各種「修飾語」<em></em>。修飾語是一個簡短字串;您可在資源目錄名稱中加入修飾語,藉此定義應使用這些資源的裝置設定。 + +例如,您通常需要為 Activity 建立多種版面配置 (視裝置螢幕的方向和大小而定)。 + +例如,假設裝置螢幕的方向為縱向 (直版),版面配置的按鈕就必須以垂直方向排列;假設裝置螢幕的方向為橫向 (寬版),則版面配置的按鈕就必須以水平方向排列。 + +如要根據螢幕方向變更版面配置,請建立兩種版面配置,然後為每個版面配置目錄名稱套用適當的修飾語。 + +如此系統就會根據目前的裝置方向,自動套用適當的版面配置。 +</p> + +<p>如要進一步瞭解您可在應用程式中加入的資源類型,以及如何針對不同的裝置設定建立替代資源,請詳閱<a href="{@docRoot}guide/topics/resources/providing-resources.html">提供資源</a>。 +</p> + + + +<div class="next-docs"> +<div class="col-6"> + <h2 class="norule">繼續閱讀有關下列主題的說明文章:</h2> + <dl> + <dt><a href="{@docRoot}guide/components/intents-filters.html">意圖和意圖篩選器</a> + </dt> + <dd>說明如何使用 {@link android.content.Intent} API 來啟用應用程式元件 (例如 Activity 和服務),以及如何將應用程式元件提供給其他應用程式使用。 + +</dd> + <dt><a href="{@docRoot}guide/components/activities.html">Activity</a></dt> + <dd>說明如何建立 {@link android.app.Activity} 類別執行個體,以便讓應用程式的使用者介面提供不同內容。 +</dd> + <dt><a href="{@docRoot}guide/topics/resources/providing-resources.html">提供資源</a></dt> + <dd>說明 Android 應用程式如何區別應用程式資源與程式碼,包括如何針對特定裝置設定供替代資源。 + + + </dd> + </dl> +</div> +<div class="col-6"> + <h2 class="norule">您可能也會想瞭解下列主題:</h2> + <dl> + <dt><a href="{@docRoot}guide/practices/compatibility.html">裝置相容性</a></dt> + <dd>說明 Android 如何在各種裝置上運作,以及如何針對各個裝置最佳化您的應用程式,或針對不同裝置限制應用程式提供的功能。 + +</dd> + <dt><a href="{@docRoot}guide/topics/security/permissions.html">系統權限</a></dt> + <dd>說明 Android 如何運用系統權限規定應用程式必須取得使用者同意才能使用特定 API。 +</dd> + </dl> +</div> +</div> + diff --git a/docs/html-intl/intl/zh-tw/guide/components/index.jd b/docs/html-intl/intl/zh-tw/guide/components/index.jd new file mode 100644 index 000000000000..f34c7120e8ad --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/components/index.jd @@ -0,0 +1,57 @@ +page.title=應用程式元件 +page.landing=true +page.landing.intro=Android 的應用程式架構可讓您使用一系列可重複使用的元件,建立內容豐富的新穎應用程式。本節說明如何建置元件來定義應用程式的設計模組,以及如何使用意圖連結這些元件。 +page.metaDescription=Android 的應用程式架構可讓您使用一系列可重複使用的元件,建立內容豐富的新穎應用程式。本節說明如何建置元件來定義應用程式的設計模組,以及如何使用意圖連結這些元件。 +page.landing.image=images/develop/app_components.png +page.image=images/develop/app_components.png + +@jd:body + +<div class="landing-docs"> + + <div class="col-6"> + <h3>部落格文章</h3> + + <a href="http://android-developers.blogspot.com/2012/05/using-dialogfragments.html"> + <h4>使用 DialogFragments</h4> + <p>本文示範如何搭配 v4 支援程式庫 (可針對 Honeycomb 以下版本裝置提供向下相容性支援) 使用 DialogFragments 來顯示簡易的編輯對話方塊,以及透過介面將結果回傳給呼叫「Activity」。</p> + </a> + + <a href="http://android-developers.blogspot.com/2011/03/fragments-for-all.html"> + <h4>適用於各種裝置的片段</h4> + <p>我們於今日推出的靜態程式庫可列出相同的 Fragments API (以及新的 LoaderManager 和些許其他類別),讓與 Android 1.6 以下版本相容的應用程式能夠使用片段來建立相容於平板電腦的使用者介面。 </p> + </a> + + <a href="http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html"> + <h4>運用多個執行緒來提升效能</h4> + <p>建立回應式應用程式的建議做法是,最小化您的主要 UI 執行緒所執行的工作數。 +方法是讓應用程式透過其他執行緒處理任何所需執行時間偏長的工作。 +</p> + </a> + </div> + + <div class="col-6"> + <h3>培訓</h3> + + <a href="http://developer.android.com/training/basics/activity-lifecycle/index.html"> + <h4>管理 Activity 生命週期</h4> + <p>本課程說明每個「Activity」執行個體都會接收的生命週期重要回呼方法,並且說明如何使用這些方法,讓 Activity 的運作能符合使用者的預期,以及讓 Activity 在不需要系統資源時不耗用這類資源。 + +</p> + </a> + + <a href="http://developer.android.com/training/basics/fragments/index.html"> + <h4>使用片段建置動態 UI</h4> + <p>本課程示範如何使用片段來設計動態使用者體驗,以及如何針對各種螢幕大小的裝置最佳化您的應用程式使用者體驗,同時讓應用程式繼續支援搭載 Android 1.6 等舊版作業系統的裝置。 + +</p> + </a> + + <a href="http://developer.android.com/training/sharing/index.html"> + <h4>分享內容</h4> + <p>本課程說明幾個使用 Intent API 和 Action Provider 物件在不同應用程式間傳送及接收內容的常見方法。 +</p> + </a> + </div> + +</div> diff --git a/docs/html-intl/intl/zh-tw/guide/components/intents-filters.jd b/docs/html-intl/intl/zh-tw/guide/components/intents-filters.jd new file mode 100644 index 000000000000..8e19048fc74e --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/components/intents-filters.jd @@ -0,0 +1,901 @@ +page.title=意圖和意圖篩選器 +page.tags="IntentFilter" +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> + +<h2>本文件內容</h2> +<ol> + <li><a href="#Types">意圖類型</a></li> + <li><a href="#Building">建置意圖</a> + <ol> + <li><a href="#ExampleExplicit">明確意圖範例</a></li> + <li><a href="#ExampleSend">隱含意圖範例</a></li> + <li><a href="#ForceChooser">強制顯示應用程式選擇器</a></li> + </ol> + </li> + <li><a href="#Receiving">接收隱含意圖</a> + <ol> + <li><a href="#ExampleFilters">篩選器範例</a></li> + </ol> + </li> + <li><a href="#PendingIntent">使用待處理意圖</a></li> + <li><a href="#Resolution">意圖解析</a> + <ol> + <li><a href="#ActionTest">動作測試</a></li> + <li><a href="#CategoryTest">類別測試</a></li> + <li><a href="#DataTest">資料測試</a></li> + <li><a href="#imatch">意圖比對</a></li> + </ol> + </li> +</ol> + +<h2>另請參閱</h2> +<ol> +<li><a href="{@docRoot}training/basics/intents/index.html">與其他應用程式互動</a></li> +<li><a href="{@docRoot}training/sharing/index.html">分享內容</a></li> +</ol> + +</div> +</div> + + + + +<p>{@link android.content.Intent} 是可用來向另一個<a href="{@docRoot}guide/components/fundamentals.html#Components">應用程式元件</a>要求動作的傳訊物件。 + +雖然意圖有幾種方式可加速元件間的通訊,但共有三種基本使用案例: +</p> + +<ul> +<li><b>如何啟動 Activity:</b> +<p>{@link android.app.Activity} 代表應用程式中的單一畫面。您可以將 {@link android.content.Intent} 傳送至 {@link android.content.Context#startActivity startActivity()} 來啟動 +{@link android.app.Activity} 的新執行個體。 +{@link android.content.Intent} 可描述要啟動的 Activity 並攜帶任何必要資料。 +</p> + +<p>如果您想要在 Activity 完成時收到結果, +請呼叫 {@link android.app.Activity#startActivityForResult +startActivityForResult()}。Activity 的 {@link android.app.Activity#onActivityResult onActivityResult()}回呼中的個別 {@link android.content.Intent} 物件,就是 Activity 收到的結果。 + +如需詳細資訊,請參閱 <a href="{@docRoot}guide/components/activities.html">Activity</a> 指南。 +</p></li> + +<li><b>如何啟動服務:</b> +<p>{@link android.app.Service} 是可以在背景中執行操作的元件,但沒有使用者介面。 +您可以將 {@link android.content.Intent} 傳送至 +{@link android.content.Context#startService startService()} 來啟動服務以執行一次性操作 (例如下載檔案)。 +{@link android.content.Intent} 可描述要啟動的服務並攜帶任何必要資料。 +</p> + +<p>如果服務是採用主從介面設計,您可以將 {@link android.content.Intent} 傳送至 {@link +android.content.Context#bindService bindService()} 來繫結至另一個元件的服務。</code> +如需詳細資訊,請參閱<a href="{@docRoot}guide/components/services.html">服務</a>指南。</p></li> + +<li><b>如何傳送廣播:</b> +<p>廣播是指任何應用程式都可接收的訊息。系統會傳送各種系統事件廣播,例如系統開機或裝置開始充電。 +您可以將 {@link android.content.Intent} 傳送至 {@link android.content.Context#sendBroadcast(Intent) sendBroadcast()}、 +{@link android.content.Context#sendOrderedBroadcast(Intent, String) 或{@link +android.content.Context#sendStickyBroadcast sendStickyBroadcast()},以向其他應用程式傳送廣播。 + + +</p> +</li> +</ul> + + + + +<h2 id="Types">意圖類型</h2> + +<p>意圖類型分為兩種:</p> + +<ul> +<li><b>明確意圖</b>:可依名稱 (完整類別名稱) 指定要啟動的元件。 +一般情況下,您會使用明確意圖啟動您應用程式中的元件,這是因為您知道 Activity 的類別名稱或您想要啟動的服務。 +例如,為回應使用者動作而啟動新的 Activity,或啟動服務以在背景下載檔案。 + +</li> + +<li><b>隱含意圖</b>:不會指定特定元件,而會宣告要執行的一般動作,讓另一個應用程式的元件來處理它。 +例如,如果您想要向使用者顯示地圖上的某個位置,可以使用隱含意圖,要求另一個支援應用程式在地圖上顯示指定的位置。 + +</li> +</ul> + +<p>當您建立明確意圖以啟動 Activity 或服務時,系統會立即啟動 +{@link android.content.Intent} 物件中指定的應用程式元件。</p> + +<div class="figure" style="width:446px"> +<img src="{@docRoot}images/components/intent-filters@2x.png" width="446" alt="" /> +<p class="img-caption"><strong>圖 1.</strong>說明如何透過系統傳送隱含意圖以啟動另一個 Activity:<b>[1]</b> Activity A <em></em> 會建立含有動作描述的{@link android.content.Intent} 並傳送至 {@link +android.content.Context#startActivity startActivity()}。<b>[2]</b> Android 系統會搜尋所有應用程式,以找出符合該意圖的意圖篩選器。 + + +找到相符項目時,<b>[3]</b> 系統會呼叫其 {@link android.app.Activity#onCreate onCreate()} 方法,並將 {@link android.content.Intent} 傳送給它來啟動相符的 Activity (Activity B<em></em>)。 + + +</p> +</div> + +<p>當您建立隱含意圖時,Android 系統會比較意圖內容和裝置上其他應用程式的<a href="{@docRoot}guide/topics/manifest/manifest-intro.html">宣示說明檔案</a>中宣告的「意圖篩選器」<em></em>,以找出要啟動的適當元件。 + +如果意圖和意圖篩選器相符,系統會啟動該元件,並將 {@link android.content.Intent} 物件傳送給它。 +如果有多個意圖篩選器符合意圖,系統會顯示對話方塊,供使用者挑選要使用的應用程式。 +</p> + +<p>意圖篩選器是應用程式宣示說明檔案中的運算式,可指定元件要接收的意圖類型。 + +例如,藉由宣告 Activity 的意圖篩選器,可讓其他應用程式使用特定意圖類型直接啟動您的 Activity。 +同樣地,如果您「不」<em></em>為 Activity 宣告任何意圖篩選器,就只能以明確意圖啟動它。 + +</p> + +<p class="caution"><strong>注意:</strong>為了確保您的應用程式安全,請一律使用明確意圖啟動 {@link android.app.Service},並且不要宣告服務的意圖篩選器。 + +使用隱含意圖啟動服務會危害安全性,原因在於您無法確定哪個服務會回應意圖,而且使用者無法得知系統會啟動哪項服務。 + +從 Android 5.0 (API 級別 21) 開始,如果您使用隱含意圖呼叫 {@link android.content.Context#bindService bindService()},系統都會擲回例外狀況。 + +</p> + + + + + +<h2 id="Building">建置意圖</h2> + +<p>{@link android.content.Intent} 物件攜帶 Android 系統用來判斷要啟動哪個元件的資訊 (例如應接收意圖的確切元件名稱或元件類別),再加上接收者元件用以適當執行動作的資訊 (例如要執行的動作和據以執行的資料)。 + + +</p> + + +<p>{@link android.content.Intent} 包含的主要資訊如下:</p> + +<dl> + +<dt><b>元件名稱</b></dt> +<dd>要啟動元件的名稱。 + +<p>雖可選擇是否使用,但這卻是讓意圖「明確」<b></b>的重要資訊,表示意圖只能傳送至元件名稱所定義的應用程式元件。 + +如果不使用元件名稱,意圖會是「隱含」<b></b>的,因此系統會根據其他意圖資訊來決定哪個元件應接收意圖 (例如動作、資料及類別 — 如下所述)。 + +如果您需要啟動應用程式中的特定元件,就應該指定元件名稱。 +</p> + +<p class="note"><strong>注意:</strong>啟動 {@link android.app.Service} 時,請務必指定元件名稱。 +<strong></strong>否則,您無法確定哪個服務會回應意圖,而且使用者無法得知系統會啟動哪項服務。 +</p> + +<p>{@link android.content.Intent} 的這個欄位是 +{@link android.content.ComponentName} 物件,您可以使用目標元件的完整類別名稱加以指定,包括應用程式的封裝名稱。例如, + +{@code com.example.ExampleActivity}。您可以使用 {@link +android.content.Intent#setComponent setComponent()}、{@link android.content.Intent#setClass +setClass()}、{@link android.content.Intent#setClassName(String, String) setClassName()} 或 +{@link android.content.Intent} 建構函式來設定元件名稱。</p> + +</dd> + +<p><dt><b>動作</b></dt> +<dd>以字串指定要執行的一般動作 (例如「檢視」<em></em>或「挑選」<em></em>)。 + +<p>就廣播意圖而言,這是指已發生且系統回報的動作。動作大半決定其餘意圖的建構方式 — 特別是資料與額外資料中包含的項目。 + + + +<p>您可以在應用程式內指定自己的動作以供意圖使用 (或由其他應用程式用來呼叫應用程式中的元件),但您應使用 {@link android.content.Intent} 類別或其他架構類別定義的動作常數。 + +以下是可啟動 Activity 的一些常見動作: +</p> + +<dl> +<dt>{@link android.content.Intent#ACTION_VIEW}</dt> + <dd>當您有一些資訊可讓 Activity 向使用者顯示時,例如要在圖庫應用程式檢視的相片或要在地圖應用程式檢視的地址,就可以透過 {@link android.content.Context#startActivity startActivity()} 使用意圖中的這個動作。 + + +</dd> + +<dt>{@link android.content.Intent#ACTION_SEND}</dt> + <dd>也稱為「分享」意圖,當您有一些資料可供使用者透過其他應用程式分享時,例如電子郵件應用程式或社交分享應用程式,才應透過 {@link android.content.Context#startActivity startActivity()} 使用意圖中的這個動作。 + +</dd> +</dl> + +<p>如要進一步瞭解定義一般動作的常數,請參閱 {@link android.content.Intent} 類別參考文件。 +其他動作則是在 Android 架構的其他位置完成定義,例如可在系統設定應用程式中開啟特定畫面的動作位在 {@link android.provider.Settings} 中。 + +</p> + +<p>您可以透過 {@link android.content.Intent#setAction +setAction()} 或 {@link android.content.Intent} 建構函式來指定意圖的動作。</p> + +<p>如果您定義自己的動作,請務必加入您應用程式的封裝名稱做為前置詞。 +例如:</p> +<pre>static final String ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL";</pre> +</dd> + +<dt><b>資料</b></dt> +<dd>URI ({@link android.net.Uri} 物件) 可參考據以執行的資料和/或該資料的 MIME 類型。 +提供的資料類型通常是由意圖的動作控制。例如,如果動作是 {@link android.content.Intent#ACTION_EDIT},資料就應包含欲編輯文件的 URI。 + + + +<p>建立意圖時,除了意圖的 URI 以外,請務必指定資料類型 (其 MIME 類型)。 + + +例如,能夠顯示影像的 Activity 可能無法播放音訊檔案,即使有類似的 URI 格式。 +因此,指定資料的 MIME 格式可協助 Android 系統找出最適合接收意圖的元件。 + +不過 — 尤其是當資料指出資料位在裝置何處且受 +{@link android.content.ContentProvider} 控制讓系統看見資料 MIME 類型的 +{@code content:} URI 時,有時能夠從 URI 推論出 MIME 類型。</p> + +<p>如果您只想設定資料 URI,請呼叫 {@link android.content.Intent#setData setData()}。 +如要設定 MIME 類型,請呼叫 {@link android.content.Intent#setType setType()}。您還可以視需要利用 +{@link +android.content.Intent#setDataAndType setDataAndType()} 明確設定兩者。</p> + +<p class="caution"><strong>注意:</strong>如果您想同時設定 URI 與 MIME 類型,「請勿」<strong></strong>呼叫 {@link android.content.Intent#setData setData()} 和 +{@link android.content.Intent#setType setType()},原因是這兩者會抵銷彼此的值。 +請務必使用 {@link android.content.Intent#setDataAndType setDataAndType()} 來設定 URI 與 MIME 類型。 + +</p> +</dd> + +<p><dt><b>類別</b></dt> +<dd>字串當中包含哪種元件應處理意圖的其他相關資訊。 +意圖中可放置的類別描述數目並沒有限制,但大多數意圖都不需要類別。 +以下是一些常見類別: + + +<dl> +<dt>{@link android.content.Intent#CATEGORY_BROWSABLE}</dt> + <dd>目標 Activity 允許自己由網頁瀏覽器啟動,以顯示連結所參照的資料 — 例如影像或電子郵件訊息。 + + </dd> +<dt>{@link android.content.Intent#CATEGORY_LAUNCHER}</dt> + <dd>Activity 是工作的初始 Activity,而且列在系統的應用程式啟動器中。 + + </dd> +</dl> + +<p>如需類別的完整清單,請參閱 {@link android.content.Intent} 類別描述。 +</p> + +<p>您可以使用 {@link android.content.Intent#addCategory addCategory()} 來指定類別。</p> +</dd> +</dl> + + +<p>以上列出的屬性 (元件名稱、動作、資料及類別) 代表意圖的定義特性。 +藉由讀取這些屬性,Android 系統就能分析出應啟動的應用程式元件。 +</p> + +<p>不過,意圖還能攜帶其他不影響如何將它解析成應用程式元件的資訊。 +意圖還能提供:</p> + +<dl> +<dt><b>額外資料</b></dt> +<dd>鍵值對當中攜帶完成要求動作所需的其他資訊。 +和有些動作會使用特定種類的資料 URI 一樣,有些動作也會使用特定的額外資料。 + +<p>您可以使用各種 {@link android.content.Intent#putExtra putExtra()}方法來新增額外資料,每種方法都接受兩個參數:索引鍵名稱與值。 + + +您也可以使用所有的額外資料建立 {@link android.os.Bundle} 物件,再透過 {@link +android.content.Intent#putExtras putExtras()} 將 {@link android.content.Intent} 插入 {@link android.os.Bundle}。</p> + +<p>例如,建立意圖以使用 +{@link android.content.Intent#ACTION_SEND} 來傳送電子郵件時,您可以利用 +{@link android.content.Intent#EXTRA_EMAIL} 索引鍵指定「收件者」,並使用 +{@link android.content.Intent#EXTRA_SUBJECT} 索引鍵指定「主旨」。</p> + +<p>{@link android.content.Intent} 類別指定許多用於標準化資料類型的 +{@code EXTRA_*} 常數。如果您需要宣告自己的額外資料索引鍵 (用於您應用程式接收的意圖),請務必加入您應用程式的封裝名稱做為前置詞。 + +例如:</p> +<pre>static final String EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS";</pre> +</dd> + +<dt><b>旗標</b></dt> +<dd>{@link android.content.Intent} 類別中定義的旗標,可當成意圖的中繼資料使用。 +旗標可指示 Android 系統如何啟動 Activity (例如,Activity 屬於哪個 +<a href="{@docRoot}guide/components/tasks-and-back-stack.html">工作</a>) 以及如何處理已啟動的 Activity (例如,它是否屬於最近 Activity 清單)。 + + + +<p>如需詳細資訊,請參閱 {@link android.content.Intent#setFlags setFlags()} 方法。</p> +</dd> + +</dl> + + + + +<h3 id="ExampleExplicit">明確意圖範例</h3> + +<p>您用來啟動特定應用程式元件的就是明確意圖,例如應用程式中的特定 Activity 或服務。如要建立明確意圖,請定義 + +{@link android.content.Intent}物件的元件名稱 — 其他意圖屬性均為選用性質。 +</p> + +<p>例如,您在應用程式中建置稱為 {@code DownloadService} 的服務,其設計為從網頁下載檔案,您可以使用下列程式碼來啟動該服務: +</p> + +<pre> +// Executed in an Activity, so 'this' is the {@link android.content.Context} +// The fileUrl is a string URL, such as "http://www.example.com/image.png" +Intent downloadIntent = new Intent(this, DownloadService.class); +downloadIntent.setData({@link android.net.Uri#parse Uri.parse}(fileUrl)); +startService(downloadIntent); +</pre> + +<p>{@link android.content.Intent#Intent(Context,Class)} 建構函式提供應用程式 {@link android.content.Context} 與元件 ({@link java.lang.Class} 物件)。 + +因此,這個意圖會明確啟動應用程式中的 +{@code DownloadService} 類別。</p> + +<p>如要進一步瞭解如何建置及啟動服務,請參閱<a href="{@docRoot}guide/components/services.html">服務</a>指南。 +</p> + + + + +<h3 id="ExampleSend">隱含意圖範例</h3> + +<p>隱藏意圖指定的動作會呼叫裝置上任何能執行該動作的應用程式。 +當您的應用程式無法執行該動作,但其他應用程式或許能執行,而您想讓使用者挑選要使用的應用程式時,使用隱含意圖相當有用。 +</p> + +<p>例如,您有想讓使用者與其他人分享的內容,可使用 +{@link android.content.Intent#ACTION_SEND} 動作來建立意圖,以及新增可指定要分享內容的額外資料。 +當您使用該意圖呼叫 +{@link android.content.Context#startActivity startActivity()} 時,使用者就能挑選要用以分享內容的應用程式。 +</p> + +<p class="caution"><strong>注意:</strong>使用者可能會沒有「任何」<em></em> +應用程式可處理您傳送至 {@link android.content.Context#startActivity +startActivity()} 的隱含意圖。如果發生這種情況,呼叫會失敗且應用程式將會當機。如要確認 Activity 將可收到意圖,請在 +{@link android.content.Intent} 物件上呼叫 {@link android.content.Intent#resolveActivity +resolveActivity()}。如果結果不是 null,表示至少有一個應用程式能夠處理該意圖,可以放心呼叫 + +{@link android.content.Context#startActivity startActivity()}。如果結果為 null,表示您不應使用該意圖,可以的話您還必須將發出該意圖的功能停用。 + +</p> + + +<pre> +// Create the text message with a string +Intent sendIntent = new Intent(); +sendIntent.setAction(Intent.ACTION_SEND); +sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage); +sendIntent.setType({@link + org.apache.http.protocol.HTTP#PLAIN_TEXT_TYPE + HTTP.PLAIN_TEXT_TYPE}); // "text/plain" MIME type + +// Verify that the intent will resolve to an activity +if (sendIntent.resolveActivity(getPackageManager()) != null) { + startActivity(sendIntent); +} +</pre> + +<p class="note"><strong>注意:</strong>在這種情況下,不會使用 URI,但會宣告意圖的資料類型,以指定額外資料所攜帶的內容。 +</p> + + +<p>呼叫 {@link android.content.Context#startActivity startActivity()} 時,系統會檢查所有安裝的應用程式,判斷哪些可以處理這種意圖 (含有 {@link android.content.Intent#ACTION_SEND}動作和攜帶「純文字」資料的意圖)。 + + +如果只有一個應用程式能夠處理,該應用程式會立即開啟並獲派該意圖。 +如果有多個 Activity 接受該意圖,系統會顯示對話方塊,供使用者挑選要使用的應用程式。 +</p> + + +<div class="figure" style="width:200px"> + <img src="{@docRoot}images/training/basics/intent-chooser.png" alt=""> + <p class="img-caption"><strong>圖 2.</strong>選擇器對話方塊。</p> +</div> + +<h3 id="ForceChooser">強制顯示應用程式選擇器</h3> + +<p>有多個應用程式均回應您的隱含意圖時,使用者可以選擇要使用的應用程式,並將該應用程式當成動作的預設選擇。 + +如果在執行動作時使用者希望之後都使用同一應用程式 (例如使用者偏好使用某個特定網頁瀏覽器開啟網頁,這項功能就非常實用。 + +</p> + +<p>不過,如果有多個應用程式能回應該意圖,而使用者每次都想使用不同的應用程式,您應該明確顯示選擇器對話方塊。 +選擇器對話方塊每次都會要求使用者選取要用於該動作的應用程式 (使用者無法為該動作選取預設應用程式)。 + +例如,當您的應用程式使用 {@link +android.content.Intent#ACTION_SEND} 動作執行「分享」時,使用者可能希望根據當下的情況使用不同的應用程式來分享,此時您應該一律使用選擇器對話方塊,如圖 2 所示。 +</p> + + + + +<p>如要顯示選擇器,請使用 {@link +android.content.Intent#createChooser createChooser()} 建立 {@link android.content.Intent},並將其傳送至 {@link +android.app.Activity#startActivity startActivity()}。例如:</p> + +<pre> +Intent sendIntent = new Intent(Intent.ACTION_SEND); +... + +// Always use string resources for UI text. +// This says something like "Share this photo with" +String title = getResources().getString(R.string.chooser_title); +// Create intent to show the chooser dialog +Intent chooser = Intent.createChooser(sendIntent, title); + +// Verify the original intent will resolve to at least one activity +if (sendIntent.resolveActivity(getPackageManager()) != null) { + startActivity(chooser); +} +</pre> + +<p>以上範例會顯示對話方塊 (將回應意圖的應用程式清單傳送至 {@link +android.content.Intent#createChooser createChooser()} 方法),並使用提供的文字做為對話方塊的標題。 +</p> + + + + + + + + + +<h2 id="Receiving">接收隱含意圖</h2> + +<p>如要通知應用程式可接收的隱含內容,請在<a href="{@docRoot}guide/topics/manifest/manifest-intro.html">宣告說明檔案</a>中利用 <a href="{@docRoot}guide/topics/manifest/intent-filter-element.html">{@code <intent-filter>}</a> 元素,針對您的每個應用程式元件宣告一或多個意圖篩選器。每個意圖篩選器都會根據意圖的動作、資料和類別,指定其接受的意圖類型。 + + + +只有在意圖通過您的其中一個意圖篩選器時,系統才會將隱含意圖傳送至您的應用程式元件。 +</p> + +<p class="note"><strong>注意:</strong>不論元件宣告的意圖篩選器為何,明確意圖一律會傳送至其目標。 +</p> + +<p>應用程式元件應為其所能執行的各個獨特工作宣告不同的篩選器。例如,圖片庫應用程式中的一個 Activity 可能會有兩個篩選器:一個用於檢視圖片的篩選器,以及另一個用於編輯圖片的篩選器。 + +當 Activity 啟動時,它會檢查 +{@link android.content.Intent} 並根據 +{@link android.content.Intent} 中的資訊決定如何運作 (例如,是否要檢視編輯器控制項)。</p> + +<p>每個意圖篩選器都是由 <a href="{@docRoot}guide/topics/manifest/intent-filter-element.html">{@code <intent-filter>}</a> 元素定義在應用程式的宣示說明檔案中,以巢狀方式置於對應的應用程式元件 (例如,<a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a> 元素)。 + + +在 <a href="{@docRoot}guide/topics/manifest/intent-filter-element.html">{@code <intent-filter>}</a> 內,您可以使用以下三個元素當中的一或多個元素,指定要接受的意圖類型: + +</p> + +<dl> +<dt><a href="{@docRoot}guide/topics/manifest/action-element.html">{@code <action>}</a></dt> + <dd>在 {@code name} 屬性中,宣告接受的意圖動作。值必須是動作的常值字串值,不得為類別常數。 +</dd> +<dt><a href="{@docRoot}guide/topics/manifest/data-element.html">{@code <data>}</a></dt> + <dd>使用一或多項屬性指定資料 URI ( +<code>scheme</code>、<code>host</code>、<code>port</code>、 +<code>path</code> 等) 與 MIME 類型的各層面,以宣告接受的資料類型。</dd> +<dt><a href="{@docRoot}guide/topics/manifest/category-element.html">{@code <category>}</a></dt> + <dd>在 {@code name} 屬性中,宣告接受的意圖類別。值必須是動作的常值字串值,不得為類別常數。 + + + <p class="note"><strong>注意:</strong>如要接收隱含意圖,您「必須」<strong></strong>在意圖篩選器中納入 {@link android.content.Intent#CATEGORY_DEFAULT} 類別。 + + +{@link android.app.Activity#startActivity startActivity()} 與 +{@link android.app.Activity#startActivityForResult startActivityForResult()} 方法對所有意圖進行處理時,就像已宣告 +{@link android.content.Intent#CATEGORY_DEFAULT} 類別一樣。 + 如果您未在意圖篩選器中宣告此類別,任何隱含意圖都不會解析為 Activity。 +</p> + </dd> +</dl> + +<p>例如,假設資料類型為文字,以下 Activity 宣告的意圖篩選器都可接受 +{@link android.content.Intent#ACTION_SEND} 意圖:</p> + +<pre> +<activity android:name="ShareActivity"> + <intent-filter> + <action android:name="android.intent.action.SEND"/> + <category android:name="android.intent.category.DEFAULT"/> + <data android:mimeType="text/plain"/> + </intent-filter> +</activity> +</pre> + +<p>想要建立包含多個 +<a href="{@docRoot}guide/topics/manifest/action-element.html">{@code <action>}</a>、 +<a href="{@docRoot}guide/topics/manifest/data-element.html">{@code <data>}</a> 或 +<a href="{@docRoot}guide/topics/manifest/category-element.html">{@code <category>}</a> 執行個體的篩選器也沒關係。 +如果您這樣做,只需要確定該元件能處理這些篩選器元素的任何組合。 +</p> + +<p>當您想要處理多種意圖,但只限特定的動作、資料及類別類型組合時,您必須建立多個意圖篩選器。 +</p> + + +<div class="sidebox-wrapper"> +<div class="sidebox"> +<h2>限制存取元件</h2> +<p>使用意圖篩選器並無法安全地防止其他應用程式啟動您的元件。 +雖然意圖篩選器限制元件只能回應特定的隱含意圖,如果開發人員決定您的元件名稱,另一款應用程式還是有可能使用明確意圖來啟動您的應用程式元件。 + + +如果必須「限定只有您自己的應用程式」<em></em>才能啟動您的元件,請將該元件的 +<a href="{@docRoot}guide/topics/manifest/activity-element.html#exported">{@code +exported}</a> 屬性設定為 {@code "false"}。 +</p> +</div> +</div> + +<p>藉由將意圖與三個元素個別比較,針對篩選器來測試隱含意圖。 +如要傳送至元件,意圖必須通過共三個測試。 + +如果無一相符,Android 系統就不會將意圖傳送至該元件。不過,由於元件可能會有多個意圖篩選器,未通過其中一個元件篩選器的意圖可能會通過另一個篩選器。 + + +如要進一步瞭解系統如何解析意圖,請參閱下文的<a href="#Resolution">意圖解析</a>。</p> + +<p class="caution"><strong>注意:</strong>如要避免一時疏忽而執行不同應用程式的 +{@link android.app.Service},請一律使用明確意圖啟動您自己的服務,同時不要宣告服務的意圖篩選器。 +</p> + +<p class="note"><strong>注意:</strong>對於所有 Activity,您都必須在宣示說明檔案中宣告意圖篩選器。 + +不過,廣播接收器的篩選器可以藉由呼叫 +{@link android.content.Context#registerReceiver(BroadcastReceiver, IntentFilter, String, +Handler) registerReceiver()} 進行動態註冊。您之後可以利用 {@link +android.content.Context#unregisterReceiver unregisterReceiver()} 來取消註冊接收器。這樣做可在您的應用程式執行時,讓應用程式只能在指定期間內接聽特定廣播。 + +</p> + + + + + + + +<h3 id="ExampleFilters">篩選器範例</h3> + +<p>如要進一步瞭解意圖篩選器行為,可以看看社交分享應用程式宣示說明檔案中的以下程式碼片段。 +</p> + +<pre> +<activity android:name="MainActivity"> + <!-- This activity is the main entry, should appear in app launcher --> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> +</activity> + +<activity android:name="ShareActivity"> + <!-- This activity handles "SEND" actions with text data --> + <intent-filter> + <action android:name="android.intent.action.SEND"/> + <category android:name="android.intent.category.DEFAULT"/> + <data android:mimeType="text/plain"/> + </intent-filter> + <!-- This activity also handles "SEND" and "SEND_MULTIPLE" with media data --> + <intent-filter> + <action android:name="android.intent.action.SEND"/> + <action android:name="android.intent.action.SEND_MULTIPLE"/> + <category android:name="android.intent.category.DEFAULT"/> + <data android:mimeType="application/vnd.google.panorama360+jpg"/> + <data android:mimeType="image/*"/> + <data android:mimeType="video/*"/> + </intent-filter> +</activity> +</pre> + +<p>第一個 Activity {@code MainActivity} 是應用程式的主要進入點 — 這個 Activity 會在使用者使用啟動器圖示初次啟動應用程式時開啟: +</p> +<ul> + <li>{@link android.content.Intent#ACTION_MAIN} 動作可指出這是主要進入點且預期沒有任何意圖資料。 +</li> + <li>{@link android.content.Intent#CATEGORY_LAUNCHER} 類別可指出此 Activity 的圖示應該放在系統的應用程式啟動器中。 +如果 <a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a>元素未以 {@code icon} 指定圖示,系統會使用 <a href="{@docRoot}guide/topics/manifest/application-element.html">{@code <application>}</a> 元素中的圖示。 + +</li> +</ul> +<p>上述兩項必須成對,Activity 才會顯示在應用程式啟動器中。</p> + +<p>第二個 Activity {@code ShareActivity} 是用來加速分享文字與媒體內容。 +雖然使用者可能藉由從 {@code MainActivity} 來瀏覽它而進入此 Activity,但它們也能從發出隱含意圖 (符合兩個意圖篩選器之一) 的另一款應用程式直接進入 {@code ShareActivity}。 + +</p> + +<p class="note"><strong>注意:</strong> +<a href="https://developers.google.com/panorama/android/">{@code +application/vnd.google.panorama360+jpg}</a> MIME 類型是指定全景相片的特殊資料類型,您可以透過 +<a href="{@docRoot}reference/com/google/android/gms/panorama/package-summary.html">Google 全景</a> API 來處理。 +</p> + + + + + + + + + + + + + +<h2 id="PendingIntent">使用待處理意圖</h2> + +<p>{@link android.app.PendingIntent} 物件是環繞 {@link +android.content.Intent} 物件的包裝函式。{@link android.app.PendingIntent} 的主要用途是將權限授予外部應用程式,以便使用包含的 {@link android.content.Intent},有如從應用程式自己的程序執行一般。 + + +</p> + +<p>待處理意圖的主要使用案例包括:</p> +<ul> + <li>宣告當使用者透過您的<a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">通知</a>執行動作時要執行的意圖 (Android 系統的 {@link android.app.NotificationManager} 會執行 {@link android.content.Intent})。 + + + <li>宣告當使用者透過您的<a href="{@docRoot}guide/topics/appwidgets/index.html">應用程式小工具</a>執行動作時要執行的意圖 (主螢幕應用程式會執行 {@link android.content.Intent})。 + + + <li>宣告要在未來的指定時間內執行的意圖 (Android 系統的 {@link android.app.AlarmManager} 會執行 {@link android.content.Intent})。 + +</ul> + +<p>由於每個 {@link android.content.Intent} 物件都設計為要由特定的應用程式元件類型來處理 ({@link android.app.Activity}、{@link android.app.Service} 或 {@link android.content.BroadcastReceiver}),因此請務必以相同的考量建立 {@link android.app.PendingIntent}。 + + +使用待處理意圖時,您的應用程式將不會透過像是 +{@link android.content.Context#startActivity +startActivity()} 的呼叫來執行意圖。當您藉由呼叫以下的個別建立者方法建立 +{@link android.app.PendingIntent} 時,您必須改為宣告意圖元件類型:</p> + +<ul> + <li>{@link android.app.PendingIntent#getActivity PendingIntent.getActivity()} 適用於啟動 {@link android.app.Activity} 的 {@link android.content.Intent}。 +</li> + <li>{@link android.app.PendingIntent#getService PendingIntent.getService()} 適用於啟動 {@link android.app.Service} 的 {@link android.content.Intent}。 +</li> + <li>{@link android.app.PendingIntent#getBroadcast PendingIntent.getBroadcast()} 適用於啟動 {@link android.content.BroadcastReceiver} 的 {@link android.content.Intent}。 +</li> +</ul> + +<p>除非您的應用程式「收到」<em></em>來自其他應用程式的待處理意圖,否則您可能只需要上述建立 +{@link android.app.PendingIntent} 的 +{@link android.app.PendingIntent} 方法。</p> + +<p>每個方法會採用目前的應用程式 {@link android.content.Context}、您要包裝的 +{@link android.content.Intent} 以及一或多個指定應如何使用意圖的旗標 (例如該意圖是否能使用多次)。 +</p> + +<p>如要進一步瞭解如何使用待處理意圖,請參閱個別使用案例的參考文件,例如<a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">通知</a>以及<a href="{@docRoot}guide/topics/appwidgets/index.html">應用程式小工具</a> API 指南。 + +</p> + + + + + + + +<h2 id="Resolution">意圖解析</h2> + + +<p>當系統收到要啟動 Activity 的隱含意圖時,會根據三個層面來比較意圖與意圖篩選器,以便為該意圖搜尋最適合的 Activity。 +</p> + +<ul> + <li>意圖動作 + <li>意圖資料 (URI 與資料類型) + <li>意圖類別 +</ul> + +<p>下列各節就如何在應用程式宣示說明檔案中宣告意圖篩選器這點,說明如何比對意圖以找出適當的元件。 +</p> + + +<h3 id="ActionTest">動作測試</h3> + +<p>如要指定接受的意圖動作,意圖篩選器可以宣告零或多個 +<a href="{@docRoot}guide/topics/manifest/action-element.html">{@code +<action>}</a> 元素。例如:</p> + +<pre> +<intent-filter> + <action android:name="android.intent.action.EDIT" /> + <action android:name="android.intent.action.VIEW" /> + ... +</intent-filter> +</pre> + +<p>如要通過此篩選器,{@link android.content.Intent} 中指定的動作必須與篩選器中列出的其中一個動作相符。 +</p> + +<p>如果篩選器未列出任何可供意圖比對的動作,所有意圖都無法通過測試。 +不過,如果 {@link android.content.Intent} 未指定任何動作,它就會通過測試 (只要篩選器至少包含一個動作即可)。 + +</p> + + + +<h3 id="CategoryTest">類別測試</h3> + +<p>如要指定接受的意圖類別,意圖篩選器可以宣告零或多個 +<a href="{@docRoot}guide/topics/manifest/category-element.html">{@code +<category>}</a> 元素。例如:</p> + +<pre> +<intent-filter> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + ... +</intent-filter> +</pre> + +<p>{@link android.content.Intent} 中的每個類別都必須與篩選器中的類別相符,意圖才會通過類別測試。 +不需要反向進行 — 意圖篩選器宣告的類別可以比 +{@link android.content.Intent} 中指定的類別多,而 +{@link android.content.Intent} 仍可通過測試。因此,不論篩選器中宣告的類別為何,未包含類別的意圖一律可通過此測試。 +</p> + +<p class="note"><strong>注意:</strong>Android 會將 {@link android.content.Intent#CATEGORY_DEFAULT} 類別自動套用到所有傳送至 {@link +android.content.Context#startActivity startActivity()} 與 {@link +android.app.Activity#startActivityForResult startActivityForResult()} 的隱含意圖。因此,如果您希望 Activity 收到隱含意圖,Activity 的意圖篩選器就必須包含 +{@code "android.intent.category.DEFAULT"} 的類別 (如先前的 {@code <intent-filter>} 範例中所示)。 + + + +</p> + + + +<h3 id="DataTest">資料測試</h3> + +<p>如要指定接受的意圖資料,意圖篩選器可以宣告零或多個 +<a href="{@docRoot}guide/topics/manifest/data-element.html">{@code +<data>}</a> 元素。例如:</p> + +<pre> +<intent-filter> + <data android:mimeType="video/mpeg" android:scheme="http" ... /> + <data android:mimeType="audio/mpeg" android:scheme="http" ... /> + ... +</intent-filter> +</pre> + +<p>每個 <code><a href="{@docRoot}guide/topics/manifest/data-element.html"><data></a></code> + 元素都能指定 URI 結構與資料類型 (MIME 媒體類型)。URI 的每個部分都有個別的屬性: +{@code scheme}、{@code host}、{@code port} 和 {@code path}。 + +</p> + +<p style="margin-left: 2em">{@code <scheme>://<host>:<port>/<path>}</p> + +<p> +例如: +</p> + +<p style="margin-left: 2em">{@code content://com.example.project:200/folder/subfolder/etc}</p> + +<p>在這個 URI 中,配置為 {@code content}、主機為 {@code com.example.project}、連接埠為 {@code 200},而路徑為 {@code folder/subfolder/etc}。 + +</p> + +<p>在 <a href="{@docRoot}guide/topics/manifest/data-element.html">{@code <data>}</a> 元素中,上述所有屬性均為選用性質,但具有線性相依性: +</p> +<ul> + <li>如果未指定配置,就會忽略主機。</li> + <li>如果未指定主機,就會忽略連接埠。</li> + <li>如果配置與主機均未指定,就會忽略路徑。</li> +</ul> + +<p>將意圖中的 URI 與篩選器中的 URI 規格比較時,只會比較篩選器中所包含的 URI 部分。 +例如:</p> +<ul> + <li>如果篩選器只指定配置,含有該配置的所有 URI 都會與篩選器相符。 +</li> + <li>如果篩選器指定了配置與授權,但未指定路徑,不論其路徑為何,含有相同配置與授權的所有 URI 都會通過篩選器。 +</li> + <li>如果篩選器指定了配置、授權和路徑,則只有包含相同配置、授權和路徑的 URI 才會通過篩選器。 +</li> +</ul> + +<p class="note"><strong>注意:</strong>路徑規格可以包含萬用字元星號 (*),以要求僅部分相符的路徑名稱。 +</p> + +<p>資料測試會比較意圖中的 URI 與 MIME 類型,以及篩選器中指定的 URI 與 MIME 類型。 +以下說明規則: +</p> + +<ol type="a"> +<li>只有在篩選器未指定任何 URI 或 MIME 類型時,未包含 URI 或 MIME 類型的意圖才會通過測試。 +</li> + +<li>只有在其 URI 與篩選器的 URI 格式相符,而且篩選器同樣未指定 MIME 類型時,包含 URI 但沒有 MIME 類型 (既非明確也無法從 URI 推測得出) 的意圖才會通過測試。 + +</li> + +<li>只有在篩選器列出相同的 MIME 類型且未指定 URI 格式時,包含 MIME 類型但沒有 URI 的意圖才會通過測試。 +</li> + +<li>只有在 MIME 類型與篩選器中列出的類型相符時,包含 URI 與 MIME 類型 (明確或可從 URI 推測得出) 的意圖才會通過 MIME 類型部分的測試。 + +如果它的 URI 與篩選器中的 URI 相符,或如果它有 +{@code content:} 或 {@code file:} URI,而且篩選器未指定 URI 時,就會通過 URI 部分的測試。換句話說,如果它的篩選器「只」<em></em>列出 MIME 類型,就會假設元件支援 {@code content:} 與 {@code file:} 資料。 + + +</p></li> +</ol> + +<p> +最後一項規則 (規則 (d)) 可反映出希望元件能夠從檔案或內容供應程式取得本機資料的期望。 + +因此,其篩選器可以只列出資料類型,同時不需要明確命名 +{@code content:} 與 {@code file:} 配置。 +此為典型案例。例如,如下所示的 <a href="{@docRoot}guide/topics/manifest/data-element.html">{@code <data>}</a> 元素可以告訴 Android,元件能夠從內容供應程式取得影像資料並加以顯示: + + +</p> + +<pre> +<intent-filter> + <data android:mimeType="image/*" /> + ... +</intent-filter></pre> + +<p> +由於大部分可用資料都是由內容供應程式分配,因此指定資料類型但未指定 URI 的篩選器也許會最常見。 + +</p> + +<p> +另一個常見的設定是含有配置與資料類型的篩選器。例如,如下所示的 +<a href="{@docRoot}guide/topics/manifest/data-element.html">{@code <data>}</a>元素可以告訴 Android,元件能夠從網路擷取影片資料以執行動作: + + +</p> + +<pre> +<intent-filter> + <data android:scheme="http" android:type="video/*" /> + ... +</intent-filter></pre> + + + +<h3 id="imatch">意圖比對</h3> + +<p>和意圖篩選器比對的意圖不只可探尋要啟動的目標元件,還能探尋裝置上元件集合的相關資料。 + +例如,主畫面應用程式會利用指定 +{@link android.content.Intent#ACTION_MAIN} 動作與 +{@link android.content.Intent#CATEGORY_LAUNCHER} 類別的意圖篩選器來尋找所有 Activity,以填入應用程式啟動器。 +</p> + +<p>您的應用程式能以類似的方式使用意圖比對。 +{@link android.content.pm.PackageManager} 有一組 {@code query...()}方法可將接受特定意圖的所有元件傳回,還有一系列類似的 +{@code resolve...()} 方法可決定回應意圖的最佳元件。 + +例如, +{@link android.content.pm.PackageManager#queryIntentActivities +queryIntentActivities()} 會傳回所有 Activity 清單,上述的 Activity 都可以執行當成引數傳送的意圖,以及 +{@link +android.content.pm.PackageManager#queryIntentServices +queryIntentServices()} 可傳回類似的服務清單。 + +這些方法不會啟動元件,只會列出可以回應的元件。類似的方法 +{@link android.content.pm.PackageManager#queryBroadcastReceivers +queryBroadcastReceivers()} 可用於廣播接收器。 +</p> + + + + diff --git a/docs/html-intl/intl/zh-tw/guide/components/loaders.jd b/docs/html-intl/intl/zh-tw/guide/components/loaders.jd new file mode 100644 index 000000000000..89bfc80744d9 --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/components/loaders.jd @@ -0,0 +1,494 @@ +page.title=載入器 +parent.title=Activity +parent.link=activities.html +@jd:body +<div id="qv-wrapper"> +<div id="qv"> + <h2>本文件內容</h2> + <ol> + <li><a href="#summary">載入器 API 摘要</a></li> + <li><a href="#app">在應用程式中使用載入器</a> + <ol> + <li><a href="#requirements"></a></li> + <li><a href="#starting">啟動載入器</a></li> + <li><a href="#restarting">重新啟動載入器</a></li> + <li><a href="#callback">使用 LoaderManager 回呼</a></li> + </ol> + </li> + <li><a href="#example">範例說明</a> + <ol> + <li><a href="#more_examples">其他範例</a></li> + </ol> + </li> + </ol> + + <h2>重要類別</h2> + <ol> + <li>{@link android.app.LoaderManager}</li> + <li>{@link android.content.Loader}</li> + + </ol> + + <h2>相關範例</h2> + <ol> + <li> <a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/LoaderCursor.html">LoaderCursor +</a></li> + <li> <a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/LoaderThrottle.html">LoaderThrottle +</a></li> + </ol> + </div> +</div> + +<p>在 Android 3.0 導入的載入器可輕鬆在 Activity 或片段中,以非同步方式載入資料。 +載入器的特性如下:</p> + <ul> + <li>適用於所有 {@link android.app.Activity} 和 {@link +android.app.Fragment} 。</li> + <li>提供以非同步方式載入資料。</li> + <li>監視其資料來源,並在內容變更時傳送新的結果。 +</li> + <li>可在設定變更後重新建立時,自動重新連接到上次載入器的游標, +因此不需要重新查詢資料。 +</li> + </ul> + +<h2 id="summary">載入器 API 摘要</h2> + +<p>有多個類別和介面可能與在應用程式中使用載入器有關。 +請參閱下表的摘要說明:</p> + +<table> + <tr> + <th>類別/介面</th> + <th>說明</th> + </tr> + <tr> + <td>{@link android.app.LoaderManager}</td> + <td>與 {@link android.app.Activity} 或 +{@link android.app.Fragment} 相關聯的抽象類別,可用於管理一或多個 {@link +android.content.Loader} 執行個體。這可以協助應用程式管理所需執行時間較長的操作與 {@link android.app.Activity} 或 {@link android.app.Fragment} 生命週期搭配使用,這最常與 +{@link android.content.CursorLoader} 搭配使用,不過應用程式能夠撰寫自己的載入器來載入其他類型的資料。 + + + + <br /> + <br /> + 每個 Activity 或片段只能有一個 {@link android.app.LoaderManager},但 {@link android.app.LoaderManager} 可以有多個載入器。 +</td> + </tr> + <tr> + <td>{@link android.app.LoaderManager.LoaderCallbacks}</td> + <td>可供用戶端與 {@link +android.app.LoaderManager} 互動的回呼介面。例如,您使用 {@link +android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()} +回呼方法來建立新的載入器。</td> + </tr> + <tr> + <td>{@link android.content.Loader}</td> + <td>以非同步方式載入資料的抽象類別。這是載入器的基本類別。 +您通常會使用 {@link +android.content.CursorLoader},但您也可以實作自己的子類別。載入器處於使用中時,應會監視其資料來源,並在內容有變更時傳送新的結果。 + + </td> + </tr> + <tr> + <td>{@link android.content.AsyncTaskLoader}</td> + <td>提供 {@link android.os.AsyncTask} 以執行工作的抽象載入器。</td> + </tr> + <tr> + <td>{@link android.content.CursorLoader}</td> + <td>查詢 +{@link android.content.ContentResolver} 並傳回 {@link +android.database.Cursor} 的 {@link android.content.AsyncTaskLoader} 子類別。此類別會以標準方式實作 {@link +android.content.Loader} 通訊協定,用來查詢建置於 {@link android.content.AsyncTaskLoader} 的游標,以便在背景執行緒中執行游標查詢,藉此避免封鎖應用程式的 UI。 + + +以非同步方式從 {@link +android.content.ContentProvider} 載入資料,而不是透過片段或 Activity 的 API 來管理查詢,最好的方法就是使用此載入器。 +</td> + </tr> +</table> + +<p>上表中的類別和介面就是您將用來在應用程式中實作載入器的基本元件。 +上述元件不需要在您建立載入器時全部使用,但您必須一律參照到 {@link +android.app.LoaderManager},才能初始化載入器並實作 +{@link android.content.Loader} 類別,例如 {@link +android.content.CursorLoader}。 +以下各節說明如何在應用程式中使用這些類別和介面。 +</p> + +<h2 id ="app">在應用程式中使用載入器</h2> +<p>本節說明如何在 Android 應用程式中使用載入器。使用載入器的應用程式通常包括下列物件: +</p> +<ul> + <li>{@link android.app.Activity} 或 {@link android.app.Fragment}。</li> + <li>{@link android.app.LoaderManager} 的執行個體。</li> + <li>可載入 {@link +android.content.ContentProvider} 所備份資料的 {@link android.content.CursorLoader}。或者,您可以實作自己的 +{@link android.content.Loader} 子類別或 {@link android.content.AsyncTaskLoader},從其他來源載入資料。 +</li> + <li>{@link android.app.LoaderManager.LoaderCallbacks} 的實作。 +您可以在這裡建立新的載入器和管理對現有載入器的參照。 +</li> +<li>顯示載入器資料的一種方式,例如 {@link +android.widget.SimpleCursorAdapter}。</li> + <li>使用 {@link android.content.CursorLoader} 時的資料來源,例如 {@link android.content.ContentProvider}。 +</li> +</ul> +<h3 id="starting">啟動載入器</h3> + +<p>{@link android.app.LoaderManager} 可在 {@link android.app.Activity} 或 {@link android.app.Fragment} 內管理一或多個 {@link +android.content.Loader} 執行個體。 +每個 Activity 或片段只能有一個 {@link +android.app.LoaderManager}。</p> + +<p>您通常會在 Activity 的 {@link +android.app.Activity#onCreate onCreate()} 方法內或在片段的 {@link android.app.Fragment#onActivityCreated onActivityCreated()} 方法內,初始化 {@link android.content.Loader}, + +如下所示: +</p> + +<pre>// Prepare the loader. Either re-connect with an existing one, +// or start a new one. +getLoaderManager().initLoader(0, null, this);</pre> + +<p>{@link android.app.LoaderManager#initLoader initLoader()} 方法採用下列參數: +</p> +<ul> + <li>可識別載入器的不重複 ID。在本範例中,此 ID 為 0。</li> +<li>可在建構時提供給載入器的選用引數 (在本範例中為<code>null</code>)。 +</li> + +<li>{@link android.app.LoaderManager.LoaderCallbacks} 實作, +{@link android.app.LoaderManager} 會呼叫此實作來回報載入器事件。在本範例中,本機類別會實作 {@link +android.app.LoaderManager.LoaderCallbacks} 執行個體,以將參照傳送給它自己 ({@code this})。 + +</li> +</ul> +<p>{@link android.app.LoaderManager#initLoader initLoader()} 呼叫可確保載入器已初始化且處於有效狀態。 +可能會有兩種結果:</p> +<ul> + <li>如果 ID 所指定的載入器已經存在,就會重複使用上次建立的載入器。 +</li> + <li>如果 ID 所指定的載入器「不存在」<em></em>, +{@link android.app.LoaderManager#initLoader initLoader()} 會觸發 +{@link android.app.LoaderManager.LoaderCallbacks} 方法 {@link android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()}。 +您可以在這裡實作程式碼以具現化及傳回新的載入器。 +如需詳細資訊,請參閱 <a href="#onCreateLoader">onCreateLoader</a>。</li> +</ul> +<p>在任一情況下,指定的 {@link android.app.LoaderManager.LoaderCallbacks}實作會與載入器建立關聯且會在載入器狀態變更時呼叫。 + +如果進行此呼叫時,呼叫器處於已啟動狀態,而要求的載入器已經存在並產生自己的資料,那麼系統會立即呼叫 {@link +android.app.LoaderManager.LoaderCallbacks#onLoadFinished onLoadFinished()} (在 {@link android.app.LoaderManager#initLoader initLoader()} 期間),請務必做好發生這種情況的準備。 + + + +如要進一步瞭解此回呼,請參閱 <a href="#onLoadFinished">onLoadFinished +</a>。</p> + +<p>請注意,{@link android.app.LoaderManager#initLoader initLoader()}方法會傳回建立的 {@link android.content.Loader},但您不需要擷取它的參照。 + +{@link android.app.LoaderManager} 會自動管理載入器的生命週期。 +{@link android.app.LoaderManager} 會在必要時啟動及停止載入,並維護載入器的狀態與其相關內容。 + +顧名思義,您鮮少會直接與載入器互動 (但如需使用載入器方法微調載入器行為的範例,請參閱 <a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/LoaderThrottle.html"> LoaderThrottle</a> 範例)。 + +當發生特定事件時,您最常會使用 {@link +android.app.LoaderManager.LoaderCallbacks} 方法來介入載入程序。 + +如要進一步瞭解此主題,請參閱<a href="#callback">使用 LoaderManager 回呼</a>。</p> + +<h3 id="restarting">重新啟動載入器</h3> + +<p>當您使用 {@link android.app.LoaderManager#initLoader initLoader()} (如上所示),它會使用已指定 ID 的現有載入器 (如果有的話)。 + +如果沒有,就會自行建立載入器。不過,有時候您會想捨棄舊資料並從頭開始。 +</p> + +<p>如要捨棄舊資料,請使用 {@link +android.app.LoaderManager#restartLoader restartLoader()}。例如,當使用者的查詢改變時,實作 {@link android.widget.SearchView.OnQueryTextListener} 會重新啟動載入器。 + +您必須重新啟動載入器,才能使用修訂後的搜尋篩選器執行新的查詢: +</p> + +<pre> +public boolean onQueryTextChanged(String newText) { + // Called when the action bar search text has changed. Update + // the search filter, and restart the loader to do a new query + // with this filter. + mCurFilter = !TextUtils.isEmpty(newText) ? newText : null; + getLoaderManager().restartLoader(0, null, this); + return true; +}</pre> + +<h3 id="callback">使用 LoaderManager 回呼</h3> + +<p>{@link android.app.LoaderManager.LoaderCallbacks} 是回呼介面,可讓用戶端與 {@link android.app.LoaderManager} 互動。 + </p> +<p>載入器 (特別是 {@link android.content.CursorLoader}) 在停止之後,應該會保留它們的資料。 +這可讓應用程式保留其在 Activity 或片段的 {@link android.app.Activity#onStop +onStop()} 與 {@link android.app.Activity#onStart onStart()} 方法間的資料,好讓使用者回到應用程式時,不必枯等資料重新載入。 + + +您可以使用 {@link android.app.LoaderManager.LoaderCallbacks} 方法,藉以得知何時該建立新的載入器,以及指示應用程式何時該停止使用載入器的資料。 + +</p> + +<p>{@link android.app.LoaderManager.LoaderCallbacks} 包含以下方法: +</p> +<ul> + <li>{@link android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()} — 具現化及傳回指定 ID 的新 {@link android.content.Loader}。 + +</li></ul> +<ul> + <li> {@link android.app.LoaderManager.LoaderCallbacks#onLoadFinished onLoadFinished()} — 當先前建立的載入器已完成其載入工作時呼叫。 + +</li></ul> +<ul> + <li>{@link android.app.LoaderManager.LoaderCallbacks#onLoaderReset onLoaderReset()} — 正要重設先前建立的載入器時呼叫,使其資料無法使用。 + + +</li> +</ul> +<p>以上方法在下列幾節有更詳細的說明。</p> + +<h4 id ="onCreateLoader">onCreateLoader</h4> + +<p>當您嘗試存取載入器 (例如,透過 {@link +android.app.LoaderManager#initLoader initLoader()}) 時,系統會檢查根據 ID 指定的載入器是否已存在。 +如果不存在,就會觸發 {@link +android.app.LoaderManager.LoaderCallbacks} 方法 {@link +android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()}。您可以在這裡建立新的載入器。 +這通常會是 {@link +android.content.CursorLoader},但您也可以實作自己的 {@link +android.content.Loader} 子類別。 </p> + +<p>在此範例中,{@link +android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()} 回呼方法會建立 {@link android.content.CursorLoader}。 +您必須使用其建構函式方法來建置 +{@link android.content.CursorLoader},其需要一組完整的資訊才能對 {@link +android.content.ContentProvider} 執行查詢。 +具體來說,建構函式方法需要以下項目:</p> +<ul> + <li>uri<em></em> - 要擷取內容的 URI。 </li> + <li>projection<em></em> - 要傳回的欄清單。傳送 +<code>null</code> 將會傳回無效的所有欄。 </li> + <li>selection<em></em> - 篩選器會採用 SQL WHERE 子句的格式 (WHERE 本身除外) 宣告要傳回的列。 +傳送 +<code>null</code> 將會傳回指定 URI 的所有列。 </li> + <li>selectionArgs<em></em> - 您可能會在選取項目中包含 ?s,而其會由來自 selectionArgs<em></em> 的值按照其出現在選取項目中的順序所取代。 + +值將會繫結為字串。 </li> + <li>sortOrder<em></em> - 如何採用 SQL ORDER BY 子句的格式 (ORDER BY 本身除外) 來排列各列的順序。 +傳遞 <code>null</code> 將會使用預設的排序順序,其可能是無排序順序。 +</li> +</ul> +<p>例如:</p> +<pre> + // If non-null, this is the current filter the user has provided. +String mCurFilter; +... +public Loader<Cursor> onCreateLoader(int id, Bundle args) { + // This is called when a new Loader needs to be created. This + // sample only has one Loader, so we don't care about the ID. + // First, pick the base URI to use depending on whether we are + // currently filtering. + Uri baseUri; + if (mCurFilter != null) { + baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, + Uri.encode(mCurFilter)); + } else { + baseUri = Contacts.CONTENT_URI; + } + + // Now create and return a CursorLoader that will take care of + // creating a Cursor for the data being displayed. + String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND (" + + Contacts.HAS_PHONE_NUMBER + "=1) AND (" + + Contacts.DISPLAY_NAME + " != '' ))"; + return new CursorLoader(getActivity(), baseUri, + CONTACTS_SUMMARY_PROJECTION, select, null, + Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC"); +}</pre> +<h4 id="onLoadFinished">onLoadFinished</h4> + +<p>這個方法是在先前建立的載入器已完成其載入工作時呼叫。 +此方法一律是在提供給此載入器的最後資料發佈之前呼叫。 +此時,您應要移除所有使用的舊資料 (由於資料即將發佈),但不應自行發佈,因為載入器擁有該資料且將會負責處理。 + +</p> + + +<p>一旦應用程式不再使用資料時,載入器就會立即發佈資料。 +例如,如果資料是來自 {@link +android.content.CursorLoader} 的游標,您不應該自行對它呼叫 {@link +android.database.Cursor#close close()}。如果正要將該游標放入 +{@link android.widget.CursorAdapter},您應該使用 {@link +android.widget.SimpleCursorAdapter#swapCursor swapCursor()} 方法,如此才不會關閉舊的 +{@link android.database.Cursor}。例如:</p> + +<pre> +// This is the Adapter being used to display the list's data.<br +/>SimpleCursorAdapter mAdapter; +... + +public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + // Swap the new cursor in. (The framework will take care of closing the + // old cursor once we return.) + mAdapter.swapCursor(data); +}</pre> + +<h4 id="onLoaderReset">onLoaderReset</h4> + +<p>這個方法是在正要重設先前建立的載入器時呼叫,以便使其資料無法使用。 +此回呼方法可讓您知道即將要發佈資料,而能先行將其參照移除。 + </p> +<p>此實作會以 <code>null</code> 的值呼叫 +{@link android.widget.SimpleCursorAdapter#swapCursor swapCursor()}: +</p> + +<pre> +// This is the Adapter being used to display the list's data. +SimpleCursorAdapter mAdapter; +... + +public void onLoaderReset(Loader<Cursor> loader) { + // This is called when the last Cursor provided to onLoadFinished() + // above is about to be closed. We need to make sure we are no + // longer using it. + mAdapter.swapCursor(null); +}</pre> + + +<h2 id="example">範例說明</h2> + +<p>例如,以下是 {@link +android.app.Fragment} 的完整實作, +其顯示的 {@link android.widget.ListView} 包含聯絡人內容供應程式的查詢結果。它使用 {@link +android.content.CursorLoader} 管理對供應程式的查詢。</p> + +<p>如本範例所示,針對要存取使用者聯絡人的應用程式,它的宣示說明必須包含 {@link android.Manifest.permission#READ_CONTACTS READ_CONTACTS} 權限。 + +</p> + +<pre> +public static class CursorLoaderListFragment extends ListFragment + implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> { + + // This is the Adapter being used to display the list's data. + SimpleCursorAdapter mAdapter; + + // If non-null, this is the current filter the user has provided. + String mCurFilter; + + @Override public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + // Give some text to display if there is no data. In a real + // application this would come from a resource. + setEmptyText("No phone numbers"); + + // We have a menu item to show in action bar. + setHasOptionsMenu(true); + + // Create an empty adapter we will use to display the loaded data. + mAdapter = new SimpleCursorAdapter(getActivity(), + android.R.layout.simple_list_item_2, null, + new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS }, + new int[] { android.R.id.text1, android.R.id.text2 }, 0); + setListAdapter(mAdapter); + + // Prepare the loader. Either re-connect with an existing one, + // or start a new one. + getLoaderManager().initLoader(0, null, this); + } + + @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + // Place an action bar item for searching. + MenuItem item = menu.add("Search"); + item.setIcon(android.R.drawable.ic_menu_search); + item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + SearchView sv = new SearchView(getActivity()); + sv.setOnQueryTextListener(this); + item.setActionView(sv); + } + + public boolean onQueryTextChange(String newText) { + // Called when the action bar search text has changed. Update + // the search filter, and restart the loader to do a new query + // with this filter. + mCurFilter = !TextUtils.isEmpty(newText) ? newText : null; + getLoaderManager().restartLoader(0, null, this); + return true; + } + + @Override public boolean onQueryTextSubmit(String query) { + // Don't care about this. + return true; + } + + @Override public void onListItemClick(ListView l, View v, int position, long id) { + // Insert desired behavior here. + Log.i("FragmentComplexList", "Item clicked: " + id); + } + + // These are the Contacts rows that we will retrieve. + static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] { + Contacts._ID, + Contacts.DISPLAY_NAME, + Contacts.CONTACT_STATUS, + Contacts.CONTACT_PRESENCE, + Contacts.PHOTO_ID, + Contacts.LOOKUP_KEY, + }; + public Loader<Cursor> onCreateLoader(int id, Bundle args) { + // This is called when a new Loader needs to be created. This + // sample only has one Loader, so we don't care about the ID. + // First, pick the base URI to use depending on whether we are + // currently filtering. + Uri baseUri; + if (mCurFilter != null) { + baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, + Uri.encode(mCurFilter)); + } else { + baseUri = Contacts.CONTENT_URI; + } + + // Now create and return a CursorLoader that will take care of + // creating a Cursor for the data being displayed. + String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND (" + + Contacts.HAS_PHONE_NUMBER + "=1) AND (" + + Contacts.DISPLAY_NAME + " != '' ))"; + return new CursorLoader(getActivity(), baseUri, + CONTACTS_SUMMARY_PROJECTION, select, null, + Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC"); + } + + public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + // Swap the new cursor in. (The framework will take care of closing the + // old cursor once we return.) + mAdapter.swapCursor(data); + } + + public void onLoaderReset(Loader<Cursor> loader) { + // This is called when the last Cursor provided to onLoadFinished() + // above is about to be closed. We need to make sure we are no + // longer using it. + mAdapter.swapCursor(null); + } +}</pre> +<h3 id="more_examples">其他範例</h3> + +<p><strong>ApiDemos</strong> 中有數個不同的範例,示範如何使用載入器: +</p> +<ul> + <li><a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/LoaderCursor.html">LoaderCursor</a> - 上述程式碼片段的完整版本在此。 + +</li> + <li><a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/LoaderThrottle.html"> LoaderThrottle</a> - 以範例說明如何使用節流功能在其資料變更時降低內容供應程式執行的查詢數目。 +</li> +</ul> + +<p>如要進一步瞭解如何下載及安裝 SDK 範例,請參閱<a href="http://developer.android.com/resources/samples/get.html">取得範例</a>。 + </p> + diff --git a/docs/html-intl/intl/zh-tw/guide/components/processes-and-threads.jd b/docs/html-intl/intl/zh-tw/guide/components/processes-and-threads.jd new file mode 100644 index 000000000000..74dbb8ebb3f1 --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/components/processes-and-threads.jd @@ -0,0 +1,411 @@ +page.title=處理程序和執行緒 +page.tags=生命週期、背景 + +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> + +<h2>本文件內容</h2> +<ol> +<li><a href="#Processes">處理程序</a> + <ol> + <li><a href="#Lifecycle">處理程序生命週期</a></li> + </ol> +</li> +<li><a href="#Threads">執行緒</a> + <ol> + <li><a href="#WorkerThreads">工作者執行緒</a></li> + <li><a href="#ThreadSafe">安全執行緒方法</a></li> + </ol> +</li> +<li><a href="#IPC">處理程序間通訊</a></li> +</ol> + +</div> +</div> + +<p>當應用程式元件啟動且該應用程式未執行任何其他元件時,Android 系統會以執行單一執行緒的方式,為該應用程式啟動新的 Linux 處理程序。 + +預設會以相同的處理程序和執行緒 (稱為「主要」執行緒) 執行相同應用程式的所有元件。 +如果應用程式元件啟動且已有該應用程式的處理程序存在 (由於應用程式還有另一個元件存在),那麼元件會在該處理程序中啟動,並使用相同的執行緒執行。 + +不過,您可以安排應用程式中的不同元件以個別處理程序執行,還可以為任何處理程序建立額外的執行緒。 + +</p> + +<p>本文件說明處理程序和執行緒如何在 Android 應用程式中運作。</p> + + +<h2 id="Processes">處理程序</h2> + +<p>在預設情況下,系統會以相同的處理程序執行相同應用程式的所有元件,而且大部分應用程式都是如此。 +不過,如果您需要控制特定元件所屬的處理程序,可以在宣示說明檔案中這麼做。 +</p> + +<p>每種元件元素 — <a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code +<activity>}</a>、<a href="{@docRoot}guide/topics/manifest/service-element.html">{@code +<service>}</a>、<a href="{@docRoot}guide/topics/manifest/receiver-element.html">{@code +<receiver>}</a> 和 <a href="{@docRoot}guide/topics/manifest/provider-element.html">{@code +<provider>}</a> — 的宣示說明項目都支援 {@code android:process} 屬性,這項屬性能指定元件應在哪個處理程序執行。 +您可以設定此屬性讓每個元件都以自己的處理程序執行,或只讓當中的部分元件共用同一處理程序。 +您也可以設定 +{@code android:process},讓不同應用程式的元件以相同的處理程序執行,只要這些應用程式分享相同的 Linux 使用者 ID 並以相同的憑證簽署。 + +</p> + +<p><a href="{@docRoot}guide/topics/manifest/application-element.html">{@code +<application>}</a> 元素也支援 {@code android:process} 屬性,以設定要套用到所有元件的預設值。 +</p> + +<p>Android 可能會在記憶體不足且需要其他處理程序更立即為使用者提供服務時,決定關閉處理程序。 +使用已終止的處理程序執行的應用程式元件會因此而終結。 +當再次有工作需要執行時,就會為這些元件再次啟動處理程序。 +</p> + +<p>Android 系統會將處理程序對使用者的相對重要性加權,以決定要終止的處理程序。 +例如,相較於代管可見 Activity 的處理程序,系統較容易關閉代管已不在螢幕上顯示的 Activity 的處理程序。 +因此,是否要終止處理程序,取決於以該處理程序執行中的元件狀態。 +如要瞭解用於決定是否終止處理程序的規則,請參閱下文。 + </p> + + +<h3 id="Lifecycle">處理程序生命週期</h3> + +<p>Android 系統會儘可能持續維護處理程序,但最終仍必須移除舊的處理程序,以便回收記憶體供新的或更重要的處理程序使用。 +系統會根據以該處理程序執行的元件和那些元件的狀態,將每個處理程序放入「重要性階層」,藉此決定要保留以及要終止的處理程序。 + + +重要性最低的處理程序會最先遭到終止,接著是重要性次低的處理程序,依此類推,視需要收回系統資源。 + +</p> + +<p>重要性階層共有五個層級。下方清單依照重要性的順序列出不同類型的處理程序 (第一個處理程序為「最重要」<em></em>且會「最後終止」<em></em>): + +</p> + +<ol> + <li><b>前景處理程序</b> + <p>這種處理程序是指使用者目前執行的工作所需的處理程序。針對下列任一情況,系統或將處理程序視為位於前景中: +</p> + + <ul> + <li>使用者正與其代管的 {@link android.app.Activity} 互動 (已呼叫 {@link +android.app.Activity} 的 {@link android.app.Activity#onResume onResume()} 方法)。 +</li> + + <li>其代管的 {@link android.app.Service} 已繫結至正在與使用者互動的 Activity。 +</li> + + <li>其代管的 {@link android.app.Service} 正「在前景」執行中 (服務已呼叫 {@link android.app.Service#startForeground startForeground()})。 + + + <li>其代管的 {@link android.app.Service} 正在執行本身的其中一個生命週期回呼 ({@link android.app.Service#onCreate onCreate()}、{@link android.app.Service#onStart onStart()} 或 {@link android.app.Service#onDestroy onDestroy()})。 + +</li> + + <li>其代管的 {@link android.content.BroadcastReceiver} 正在執行本身的 {@link +android.content.BroadcastReceiver#onReceive onReceive()} 方法。</li> + </ul> + + <p>一般來說,在任何指定時間內只會有幾個前景處理程序存在。只有在記憶體過低而全都無法繼續執行時,才會採取這最後的手段來終止它們。 +在這種情況下,裝置通常已達到記憶體分頁處理狀態,因此必須終止一些前景處理程序,才能讓使用者介面保持回應。 + +</p></li> + + <li><b>可見處理程序</b> + <p>這種處理程序是指沒有任何前景元件的處理程序,但仍會影響使用者在螢幕上看見的內容。 +針對下列任一情況,系統會將處理程序視為可見: +</p> + + <ul> + <li>其代管的 {@link android.app.Activity} 不在前景中,但使用者仍可看見 (已呼叫它的 {@link android.app.Activity#onPause onPause()} 方法)。 +例如,如果前景 Activity 啟動的對話方塊允許在它身後看見先前的 Activity,就會發生這種情況。 + +</li> + + <li>其代管的 {@link android.app.Service} 已繫結至可見 (或前景) Activity。 +</li> + </ul> + + <p>可見處理程序相當重要而且不會遭到終止,除非系統為了讓所有前景處理程序維持執行而必須終止這類處理程序。 + </p> + </li> + + <li><b>服務處理程序</b> + <p>這種處理程序是指正在執行已使用 {@link +android.content.Context#startService startService()} 方法啟動的服務的處理程序;此處理程序不屬於上述兩種較重要的類別。 +雖然服務處理程序是間接繫結至使用者所見內容,但通常會執行使用者重視的工作 (例如,在背景中播放音樂,或下載網路上的資料),因此除非記憶體不足,無法讓所有前景與可見處理程序保持執行,否則系統會讓這類處理程序繼續執行。 + + + </p> + </li> + + <li><b>背景處理程序</b> + <p>這種處理程序會保留使用者目前看不見的 Activity (已呼叫 Activity 的 +{@link android.app.Activity#onStop onStop()} 方法)。這些處理程序會間接影響使用者體驗,且系統能隨時將其終止,藉此回收記憶體以供前景、可見或服務處理程序使用。 + + +通常會有許多背景處理程序處於執行中,因此會將它們放在 LRU (最近最少使用) 清單中,以確保在最後才將包含使用者最近最常見 Activity 的處理程序終止。 + +如果 Activity 正確實作其生命週期方法並儲存其目前狀態,終止其處理程序不會對使用者體驗造成任何可察覺的影響,原因是當使用者瀏覽回 Activity 時,該 Activity 會還原它的所有可見狀態。 + + +如要進一步瞭解如何儲存及還原狀態,請參閱 <a href="{@docRoot}guide/components/activities.html#SavingActivityState">Activity</a>。 +</p> + </li> + + <li><b>空白處理程序</b> + <p>這種處理程序是指未保留任何使用中應用程式元件的處理程序。讓這類處理程序保持有效的唯一目的是將其用於快取,以改善元件下次執行時所需的啟動時間。 + +系統通常會終止這些處理程序,以平衡處理程序快取與底層核心快取之間的整體系統資源。 +</p> + </li> +</ol> + + + <p>Android 會根據在處理程序中目前處於使用中的元件重要性,將處理程序盡量排在最高層級。 +例如,如果處理程序代管一項服務和一個可見 Activity,此處理程序的會排為可見處理程序,而不是服務處理程序。 +</p> + + <p>此外,還可能因為它有其他相依處理程序,而導致處理程序的排名提升:為另一個處理程序提供服務的處理程序,其排名絕不能低於為其提供服務的處理程序。 + +例如,如果處理程序 A 的內容供應程式為處理程序 B 中的用戶端提供服務,或處理程序 A 中的服務繫結至處理程序 B 中的元件,則系統至少會將處理程序 A 視為和處理程序 B 一樣重要。 + +</p> + + <p>由於執行服務的處理程序排名會比包含背景 Activity 的處理程序排名高,因此初始化長時間執行操作的 Activity 可能適合啟動該操作的<a href="{@docRoot}guide/components/services.html">服務</a>,而不只是建立工作者執行緒 — 特別是該操作可能會比 Activity 持久。例如,將圖片上傳至網站 Activity 應該啟動要執行上傳的服務,如果使用者離開 Activity,上傳處理程序也能在背景中繼續進行。如果使用服務,不論 Activity 發生什麼情況,都可保證該操作的優先順序至少會是「服務處理程序」。 + + + + + +廣播接收器應該採用服務,而不是只在執行緒中放置時間耗用操作,也是相同的理由。 +</p> + + + + +<h2 id="Threads">執行緒</h2> + +<p>當應用程式啟動時,系統會為執行該應用程式建立一個稱為「主要」的執行緒。 +這個執行緒非常重要,原因是它負責將事件分配給適當的使用者介面小工具,包括繪製事件。 +您的應用程式與 Android UI 工具組中的元件 ({@link +android.widget} 和 {@link android.view} 中的元件) 互動時,也需要使用這個執行緒。 +因此,主要執行緒有時也稱為 UI 執行緒。 +</p> + +<p>系統「不會」<em></em>為每個元件執行個體建立個別的執行緒。以相同處理程序執行的所有元件都是利用 UI 執行緒來具現化,而且都是由該執行緒分配系統呼叫的每個元件。 + +因此,回應系統回呼的方法 (例如報告使用者動作的 {@link android.view.View#onKeyDown onKeyDown()} 或生命週期回呼方法) 一律會以該處理程序的 UI 執行緒執行。 + +</p> + +<p>例如,當使用者輕觸畫面上的按鈕時,您應用程式的 UI 執行緒會將輕觸事件分配給小工具,接著設定其按下狀態並對事件佇列張貼失效要求。 + +UI 執行緒會將要求從佇列中移除,然後通知小工具應重新繪製本身。 +</p> + +<p>當您的應用程式密集執行作業以回應使用者互動時,除非您適當實作應用程式,否則這種單一執行緒模型會降低效能。 +具體來說,假設一切都在 UI 執行緒中進行,如果執行像是網路存取或資料庫查詢的長時間操作,將會封鎖整個 UI。當執行緒遭到封鎖時,會無法分配任何事件 (包括繪製事件)。 + + +從使用者的觀點來看,應用程式似乎閒置不動。 +更糟的是,如果 UI 執行緒遭到封鎖長達數秒 (目前約為 5 秒),就會向使用者顯示的「<a href="http://developer.android.com/guide/practices/responsiveness.html">應用程式沒有回應</a>」(ANR) 對話方塊。 + +使用者接著會決定結束您的應用程式,並可能因感到不滿而將其解除安裝。 +</p> + +<p>此外,Andoid UI 工具組「並非」<em></em>安全執行緒,因此請勿透過工作者執行緒操縱 UI — 使用者介面的所有操縱作業都必須從 UI 執行緒來執行。 + +基於上述原因,Android 的單一執行緒模型只有兩項簡單規則:</p> + +<ol> +<li>不要封鎖 UI 執行緒 +<li>不要從 UI 執行緒以外的位置存取 Android UI 工具組 +</ol> + +<h3 id="WorkerThreads">工作者執行緒</h3> + +<p>因為上述的單一執行緒模型,所以不封鎖 UI 執行緒對於應用程式 UI 的回應能力至關重要。 +如果您有不會立即執行的操作,請務必以不同的執行緒 (「背景」或「工作者」執行緒) 執行這類操作。 + +</p> + +<p>例如,以下是從個別執行緒下載圖片並顯示在 {@link android.widget.ImageView} 的部分點擊接聽器程式碼: +</p> + +<pre> +public void onClick(View v) { + new Thread(new Runnable() { + public void run() { + Bitmap b = loadImageFromNetwork("http://example.com/image.png"); + mImageView.setImageBitmap(b); + } + }).start(); +} +</pre> + +<p>首先,由於這會建立新的執行緒來處理網路操作,所以看起來似乎可以正常運作。 +不過,它違反單一執行緒模型的第二項規則:「不要從 UI 執行緒以外的位置存取 Android UI 工具組」<em></em>— 這個範例修改工作者執行緒中的 {@link +android.widget.ImageView},而不是 UI 執行緒。 +這樣會產生未定義且預期外的行為,不但難以追蹤且耗費時間。 +</p> + +<p>為修正這個問題,Android 提供數種可從其他執行緒存取 UI 執行緒的方法。 +以下是可協助修正此問題的方法清單:</p> + +<ul> +<li>{@link android.app.Activity#runOnUiThread(java.lang.Runnable) +Activity.runOnUiThread(Runnable)}</li> +<li>{@link android.view.View#post(java.lang.Runnable) View.post(Runnable)}</li> +<li>{@link android.view.View#postDelayed(java.lang.Runnable, long) View.postDelayed(Runnable, +long)}</li> +</ul> + +<p>例如,您可以使用 {@link +android.view.View#post(java.lang.Runnable) View.post(Runnable)} 方法來修正上述程式碼:</p> + +<pre> +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(); +} +</pre> + +<p>現在這個實作才是安全執行緒:雖然從不同的執行緒完成網路操作,但卻是從 UI 執行緒操縱 {@link android.widget.ImageView}。 +</p> + +<p>不過,隨著操作複雜度日益增加,這種程式碼也會變得複雜且難以維護。 +如要利用工作者執行緒處理更複雜的互動,您可能要考慮在工作者執行緒中使用 {@link android.os.Handler},以處理從 UI 執行緒傳送的訊息。 + +最佳解決方案也許是擴充 {@link android.os.AsyncTask} 類別,將必須與 UI 互動的工作者執行緒工作執行簡化。 +</p> + + +<h4 id="AsyncTask">使用 AsyncTask</h4> + +<p>{@link android.os.AsyncTask} 可讓您透過使用者介面執行非同步工作。 +它會以工作者執行緒執行封鎖操作,然後將結果發行在 UI 執行緒,完全不需要您自行處理執行緒和/或處理常式。 +</p> + +<p>使用方法是您必須要有子類別 {@link android.os.AsyncTask},並實作以背景執行緒集區執行的 {@link +android.os.AsyncTask#doInBackground doInBackground()} 回呼方法。 +如要更新您的 UI,請實作 {@link +android.os.AsyncTask#onPostExecute onPostExecute()} 來傳送 {@link +android.os.AsyncTask#doInBackground doInBackground()} 的結果,然後以 UI 執行緒執行,如此您才能安全地更新 UI。 +接著,您可以從 UI 執行緒呼叫 {@link android.os.AsyncTask#execute execute()} 來執行該工作。 +</p> + +<p>例如,您可以使用 {@link android.os.AsyncTask} 這種方法實作先前的範例: +</p> + +<pre> +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); + } +} +</pre> + +<p>現在 UI 很安全,而且程式碼變得更簡單,這是因為它將工作分成兩部分,一部分應在工作者執行緒上完成,而另一部分應在 UI 執行緒上完成。 +</p> + +<p>建議您參閱 {@link android.os.AsyncTask} 參考資料,以全面瞭解如何使用此類別;以下是其如何運作的快速總覽: +</p> + +<ul> +<li>您可以使用泛型來指定參數類型、進度值以及工作的最終值 +</li> +<li>{@link android.os.AsyncTask#doInBackground doInBackground()} 方法會在工作者執行緒中自動執行 +</li> +<li>{@link android.os.AsyncTask#onPreExecute onPreExecute()}、{@link +android.os.AsyncTask#onPostExecute onPostExecute()} 和 {@link +android.os.AsyncTask#onProgressUpdate onProgressUpdate()} 全都是透過 UI 執行緒呼叫。</li> +<li>{@link android.os.AsyncTask#doInBackground doInBackground()} 傳回的值會傳送至 +{@link android.os.AsyncTask#onPostExecute onPostExecute()}</li> +<li>您隨時都可用 {@link +android.os.AsyncTask#doInBackground doInBackground()} 呼叫 {@link android.os.AsyncTask#publishProgress publishProgress()},以便透過 UI 執行緒執行 {@link +android.os.AsyncTask#onProgressUpdate onProgressUpdate()}。</li> +<li>您可以從任何執行緒隨時取消工作</li> +</ul> + +<p class="caution"><strong>注意:</strong>使用工作者執行緒可能會遇到的另一個問題是,由於<a href="{@docRoot}guide/topics/resources/runtime-changes.html">執行階段設定變更</a> (例如使用者變更螢幕方向時) 使您的 Activity 意外重新啟動,而可能會終止您的工作者站執行緒。 + +請參閱 <a href="http://code.google.com/p/shelves/">Shelves</a> 範例應用程式的原始程式碼,以瞭解如何在遇到其中一種重新啟動情況時保留您的工作,以及如何在 Activity 遭到終止時適當取消該工作。 + +</p> + + +<h3 id="ThreadSafe">安全執行緒方法</h3> + +<p> 在某些情況下,您實作的方法可能是從多個執行緒呼叫,因此務必要撰寫成安全執行緒。 + </p> + +<p>這種情況主要發生在可從遠端呼叫的方法,例如<a href="{@docRoot}guide/components/bound-services.html">已繫結服務</a>中的方法。當在源自執行 {@link android.os.IBinder IBinder} 的相同處理程序中實作 {@link android.os.IBinder} 方法上的呼叫時,該方法是以呼叫端的執行緒執行。 + + + +不過,當呼叫源自另一個處理程序時,會從系統在相同處理程序中當成 {@link android.os.IBinder +IBinder} (未以處理程序的 UI 執行緒執行) 維護的執行緒集區,以選擇的執行緒來執行該方法。例如,雖然服務的 +{@link android.app.Service#onBind onBind()} 方法可從服務處理程序的 UI 執行緒呼叫,但以 {@link android.app.Service#onBind +onBind()} 傳回的物件實作的方法會從集區中的執行緒呼叫。 + +由於服務能有多個用戶端,同時也能有多個集區執行緒採用相同的 +{@link android.os.IBinder IBinder} 方法。因此 {@link android.os.IBinder +IBinder} 方法必須實作為安全執行緒。</p> + +<p> 同樣地,內容供應程式能接收源自其他處理程序的資料要求。 +雖然 {@link android.content.ContentResolver} 與 {@link android.content.ContentProvider} +類別會隱藏如何管理處理程序間通訊的詳細資料,但回應這些要求的 {@link +android.content.ContentProvider} 方法 — {@link +android.content.ContentProvider#query query()}、{@link android.content.ContentProvider#insert +insert()}、{@link android.content.ContentProvider#delete delete()}、{@link +android.content.ContentProvider#update update()} 和 {@link android.content.ContentProvider#getType +getType()} — 是在內容供應程式的處理程序中從執行緒集區呼叫,而不是該處理程序的 UI 執行緒。 +由於可能會同時有任意數目的執行緒呼叫這些方法,因此它們也要實作為安全執行緒。 + </p> + + +<h2 id="IPC">處理程序間通訊</h2> + +<p>Android 提供一項使用遠端程序呼叫 (RPC) 進行處理程序間通訊 (IPC) 的機制,RPC 是指 (以另一個處理程序) 從遠端執行由 Activity 或其他應用程式元件呼叫的方法,再加上要傳回給呼叫端的任何結果。 + + +這需要將方法呼叫與其資料分解成作業系統能夠瞭解的程度,將它從本機處理程序與位址空間傳輸到遠端處理程序與位址空間,然後再重新組合和重新實作呼叫。 + +接著,再以相反的方向傳輸傳回值。 +Android 提供進行這些 IPC 交易的所有程式碼,讓您可以專心定義及實作 RPC 程式設計介面。 + </p> + +<p>如要執行 IPC,您的應用程式必須使用 {@link +android.content.Context#bindService bindService()} 來繫結至服務。如需詳細資訊,請參閱<a href="{@docRoot}guide/components/services.html">服務</a>開發人員指南。</p> + + +<!-- +<h2>Beginner's Path</h2> + +<p>For information about how to perform work in the background for an indefinite period of time +(without a user interface), continue with the <b><a +href="{@docRoot}guide/components/services.html">Services</a></b> document.</p> +--> diff --git a/docs/html-intl/intl/zh-tw/guide/components/recents.jd b/docs/html-intl/intl/zh-tw/guide/components/recents.jd new file mode 100644 index 000000000000..d56c12c0e87b --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/components/recents.jd @@ -0,0 +1,256 @@ +page.title=總覽畫面 +page.tags="recents","overview" + +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> + + <h2>本文件內容</h2> + <ol> + <li><a href="#adding">新增工作到總覽畫面</a> + <ol> + <li><a href="#flag-new-doc">使用意圖旗標來新增工作</a></li> + <li><a href="#attr-doclaunch">使用 Activity 屬性來新增工作</a></li> + </ol> + </li> + <li><a href="#removing">移除工作</a> + <ol> + <li><a href="#apptask-remove">使用 AppTask 類別來移除工作</a></li> + <li><a href="#retain-finished">保留結束的工作</a></li> + </ol> + </li> + </ol> + + <h2>重要類別</h2> + <ol> + <li>{@link android.app.ActivityManager.AppTask}</li> + <li>{@link android.content.Intent}</li> + </ol> + + <h2>程式碼範例</h2> + <ol> + <li><a href="{@docRoot}samples/DocumentCentricApps/index.html">以文件為中心的應用程式</a></li> + </ol> + +</div> +</div> + +<p>總覽畫面 (也被稱為最近畫面、最近工作清單,或最近的應用程式) 是系統層級的 UI,可以列出最近存取的 <a href="{@docRoot}guide/components/activities.html">Activity</a> 與<a href="{@docRoot}guide/components/tasks-and-back-stack.html">工作</a>。 + +使用者可以透過清單導覽並選擇要繼續的工作,或是使用者可以滑動的方式從清單移除工作。 + +使用 Android 5.0 版本 (API 級別 21),相同 Activity (包含不同文件) 的多個執行個體可以在總覽畫面中顯示為工作。 +例如:對多份 Google 文件,Google 雲端硬碟能讓每份文件都對應一個工作。 +每份文件在總覽畫面中都會顯示為工作。 +</p> + +<img src="{@docRoot}images/components/recents.png" alt="" width="284" /> +<p class="img-caption"><strong>圖 1.</strong>總覽畫面會顯示三份 Google 雲端硬碟文件,每份都分別顯示為一項工作。 +</p> + +<p>一般而言,您應該允許系統定義如何在總覽畫面中呈現工作與 Activity,而且不需要修改此行為。 +然而,您的應用程式可以決定要如何與在何時於總覽畫面中顯示 Activity。 + +{@link android.app.ActivityManager.AppTask} 類別讓您可以管理工作,而 {@link android.content.Intent} 類別的 Activity 旗標可以讓您指定何時從總覽畫面新增或移除 Activity。 + +此外,<code><a href="{@docRoot}guide/topics/manifest/activity-element.html"> +<activity></a></code> 屬性可以讓您設定宣示說明中的行為。</p> + +<h2 id="adding">新增工作到總覽畫面</h2> + +<p>使用 {@link android.content.Intent} 類別的旗標可以新增工作,該工作針對何時與如何在總覽畫面中開啟或重新開啟文件,可給予更多控制權。 +當您使用 +<code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code>屬性時,您可以選擇永遠在新工作開啟文件或對文件重複使用現有工作。 + +</p> + +<h3 id="flag-new-doc">使用意圖旗標來新增工作</h3> + +<p>當您建立 Activity 的新文件時,您可以呼叫 + {@link android.app.ActivityManager.AppTask} 類別的 {@link android.app.ActivityManager.AppTask#startActivity(android.content.Context, android.content.Intent, android.os.Bundle) startActivity()}方法,傳送啟動 Activity 的意圖至新文件。 + +如要插入邏輯中斷,讓系統可以將您的 Activity 當作總覽視窗中的新工作,傳送啟動 Activity 的 {@link android.content.Intent}其 {@link android.content.Intent#addFlags(int) addFlags()} 方法中的 {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} 旗標。 + + +</p> + +<p class="note"><strong>注意:</strong>{@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} 旗標會取代 {@link android.content.Intent#FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET} 旗標,後者已從 Android 5.0 (API 級別 21) 起失效。 + +</p> + +<p>如果您在建立新文件時設定 {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK} 旗標,則系統永遠會在建立新工作時也在根目錄建立目標 Activity。此設定允許可以在一個以上的工作中開啟相同的文件。 + +下列程式碼示範主要 Activity 如何處理: +</p> + +<p class="code-caption"><a href="{@docRoot}samples/DocumentCentricApps/index.html"> +DocumentCentricActivity.java</a></p> +<pre> +public void createNewDocument(View view) { + final Intent newDocumentIntent = newDocumentIntent(); + if (useMultipleTasks) { + newDocumentIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); + } + startActivity(newDocumentIntent); + } + + private Intent newDocumentIntent() { + boolean useMultipleTasks = mCheckbox.isChecked(); + final Intent newDocumentIntent = new Intent(this, NewDocumentActivity.class); + newDocumentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); + newDocumentIntent.putExtra(KEY_EXTRA_NEW_DOCUMENT_COUNTER, incrementAndGet()); + return newDocumentIntent; + } + + private static int incrementAndGet() { + Log.d(TAG, "incrementAndGet(): " + mDocumentCounter); + return mDocumentCounter++; + } +} +</pre> + +<p class="note"><strong>注意:</strong>與 {@code FLAG_ACTIVITY_NEW_DOCUMENT} 旗標一起啟動的 Activity 務必要在宣示說明中設定 {@code android:launchMode="standard"} 屬性值 (預設)。 + +</p> + +<p>當主要 Activity 啟動新的 Activity 時,系統會透過現有工作搜尋其意圖和 Activity 意圖元件名稱及意圖資料相同的 Activity。 +如果找不到工作,或意圖已包含 {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK} 旗標,則會建立新的工作並使用 Activity 做為其根目錄。 + +如果找到工作,則會將該工作帶到前面並傳送新的意圖到 {@link android.app.Activity#onNewIntent onNewIntent()}。 +新的 Activity 會取得意圖並在總覽視窗中建立新的文件,如下列範例所示: + +</p> + +<p class="code-caption"><a href="{@docRoot}samples/DocumentCentricApps/index.html">NewDocumentActivity.java +</a></p> +<pre> +@Override +protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_new_document); + mDocumentCount = getIntent() + .getIntExtra(DocumentCentricActivity.KEY_EXTRA_NEW_DOCUMENT_COUNTER, 0); + mDocumentCounterTextView = (TextView) findViewById( + R.id.hello_new_document_text_view); + setDocumentCounterText(R.string.hello_new_document_counter); +} + +@Override +protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + /* If FLAG_ACTIVITY_MULTIPLE_TASK has not been used, this activity + is reused to create a new document. + */ + setDocumentCounterText(R.string.reusing_document_counter); +} +</pre> + + +<h3 id="#attr-doclaunch">使用 Activity 屬性來新增工作</h3> + +<p>Activity 也可以在宣示說明中指定為永遠啟動新工作,這可透過使用<code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code> 屬性的 <a href="{@docRoot}guide/topics/manifest/activity-element.html#dlmode"> +{@code android:documentLaunchMode}</a> 達成。 + +此屬性有四個值,在使用者使用應用程式開啟文件時,會產生下列效果: +</p> + +<dl> + <dt>"{@code intoExisting}"</dt> + <dd>Activity 會對文件重複使用現有的工作。設定 +{@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} 旗標,但「不」<em></em>設定 +{@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK} 旗標,會與上述<a href="#flag-new-doc">使用意圖旗標來新增工作</a>達到相同效果。 +</dd> + + <dt>"{@code always}"</dt> + <dd>Activity 會為文件建立新的工作,就算文件已開始也會建立新的工作。使用此值與設定 {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} 與 {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK} 旗標會達到相同效果。 + +</dd> + + <dt>"{@code none”}"</dt> + <dd>Activity 不會為文件建立新的工作。總覽視窗會將 Activity 當作預設:會顯示應用程式的單一工作,該工作會從使用者最後呼叫的 Activity 繼續。 + +</dd> + + <dt>"{@code never}"</dt> + <dd>Activity 不會為文件建立新的工作。設定此值會覆寫 {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} 與 {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK} 旗標的行為,如果任一個已於意圖中設定,總覽視窗會顯示應用程式的單一工作,該工作會從使用者最後呼叫的 Activity 繼續。 + + + +</dd> +</dl> + +<p class="note"><strong>注意:</strong>如果值不是 {@code none} 與 {@code never},則 Activity 必須使用 {@code launchMode="standard"} 定義。 +如果沒有指定此屬性,則會使用 +{@code documentLaunchMode="none"}。</p> + +<h2 id="removing">移除工作</h2> + +<p>依照預設,當 Activity 結束時,會自動將文件工作從總覽畫面移除。 +您可以使用 {@link android.app.ActivityManager.AppTask} 類別,搭配 {@link android.content.Intent} 旗標或 <code><a href="{@docRoot}guide/topics/manifest/activity-element.html"> +<activity></a></code> 屬性,來覆寫此行為。 +</p> + +<p>您可以完全從總覽畫面排除工作,設定方式為將 +<code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code> 屬性的<a href="{@docRoot}guide/topics/manifest/activity-element.html#exclude"> +{@code android:excludeFromRecents}</a> 設為 {@code true}。 +</p> + +<p>您可以設定應用程式可以納入總覽視窗的最大工作數目,方法是對 <code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code> 屬性 <a href="{@docRoot}guide/topics/manifest/activity-element.html#maxrecents">{@code android:maxRecents}</a> 設定整數值。 + + +預設值為 16。當達到工作的最大值時,會從總覽視窗移除最近最少使用的工作。 +{@code android:maxRecents} 的最大值是 50 (在低記憶體裝置上是 25);數值不可以小於 1。 +</p> + +<h3 id="#apptask-remove">使用 AppTask 類別來移除工作</h3> + +<p>在總覽畫面建立新工作的 Activity 中,您可以指定何時移除工作與結束所有相關 Activity,方法為呼叫{@link android.app.ActivityManager.AppTask#finishAndRemoveTask() finishAndRemoveTask()} 方法。 + +</p> + +<p class="code-caption"><a href="{@docRoot}samples/DocumentCentricApps/index.html">NewDocumentActivity.java +</a></p> +<pre> +public void onRemoveFromRecents(View view) { + // The document is no longer needed; remove its task. + finishAndRemoveTask(); +} +</pre> + +<p class="note"><strong>注意:</strong>使用 +{@link android.app.ActivityManager.AppTask#finishAndRemoveTask() finishAndRemoveTask()} 方法覆寫 {@link android.content.Intent#FLAG_ACTIVITY_RETAIN_IN_RECENTS} 標籤的使用,會在下面討論。 + +</p> + +<h3 id="#retain-finished">保留結束的工作</h3> + +<p>如果您要保留總覽畫面中的工作 (就算其 Activity 已結束), 方法為傳送啟動 Activity 的意圖其 {@link android.content.Intent#addFlags(int) addFlags()} 方法中的 +{@link android.content.Intent#FLAG_ACTIVITY_RETAIN_IN_RECENTS} 旗標。 +</p> + +<p class="code-caption"><a href="{@docRoot}samples/DocumentCentricApps/index.html">DocumentCentricActivity.java +</a></p> +<pre> +private Intent newDocumentIntent() { + final Intent newDocumentIntent = new Intent(this, NewDocumentActivity.class); + newDocumentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT | + android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS); + newDocumentIntent.putExtra(KEY_EXTRA_NEW_DOCUMENT_COUNTER, incrementAndGet()); + return newDocumentIntent; +} +</pre> + +<p>如要達到相同效果,可以設定 +<code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code> 屬性的 <a href="{@docRoot}guide/topics/manifest/activity-element.html#autoremrecents"> +{@code android:autoRemoveFromRecents}</a> 為 {@code false}。 +對文件 Activity 的預設值為 {@code true},對定期 Activity 的預設值則為 {@code false}。 +如同之前的討論,使用此屬性可以覆寫 {@link android.content.Intent#FLAG_ACTIVITY_RETAIN_IN_RECENTS} 旗標。 +</p> + + + + + + + diff --git a/docs/html-intl/intl/zh-tw/guide/components/services.jd b/docs/html-intl/intl/zh-tw/guide/components/services.jd new file mode 100644 index 000000000000..d6a440d88dd6 --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/components/services.jd @@ -0,0 +1,813 @@ +page.title=服務 +@jd:body + +<div id="qv-wrapper"> +<ol id="qv"> +<h2>本文件內容</h2> +<ol> +<li><a href="#Basics">基本概念</a></li> +<ol> + <li><a href="#Declaring">在宣示說明中宣告服務</a></li> +</ol> +<li><a href="#CreatingAService">建立已啟動的服務</a> + <ol> + <li><a href="#ExtendingIntentService">延伸 IntentService 類別</a></li> + <li><a href="#ExtendingService">延伸服務類別</a></li> + <li><a href="#StartingAService">啟動服務</a></li> + <li><a href="#Stopping">停止服務</a></li> + </ol> +</li> +<li><a href="#CreatingBoundService">建立已繫結的服務</a></li> +<li><a href="#Notifications">傳送通知給使用者</a></li> +<li><a href="#Foreground">在前景中執行服務</a></li> +<li><a href="#Lifecycle">管理服務的生命週期</a> +<ol> + <li><a href="#LifecycleCallbacks">實作生命週期回呼</a></li> +</ol> +</li> +</ol> + +<h2>重要類別</h2> +<ol> + <li>{@link android.app.Service}</li> + <li>{@link android.app.IntentService}</li> +</ol> + +<h2>範例</h2> +<ol> + <li><a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/ServiceStartArguments.html">{@code + ServiceStartArguments}</a></li> + <li><a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/LocalService.html">{@code + LocalService}</a></li> +</ol> + +<h2>另請參閱</h2> +<ol> +<li><a href="{@docRoot}guide/components/bound-services.html">已繫結的服務</a></li> +</ol> + +</div> + + +<p>{@link android.app.Service} 是可以在背景中長時間執行操作的應用程式元件,且不提供使用者介面。 +也是另一個可以啟動服務的應用程式元件,就算使用者切換至其他應用程式,也會繼續在背景中執行。 + +此外,元件也可以繫結至服務,以便與其互動,甚至執行處理程序間通訊 (IPC)。 +例如,服務可能處理網路交易、播放音樂、執行檔案輸入/輸出或與內容供應程式互動,這些都可以從背景執行。 + +</p> + +<p>服務基本上可以採取兩種形式:</p> + +<dl> + <dt>啟動</dt> + <dd>服務「啟動」表示應用程式元件 (例如 Activity) 透過呼叫 +{@link android.content.Context#startService startService()} 來啟動服務。一旦啟動,服務可以無限次數地在背景中執行,就算啟動服務的元件已終結也不會影響。 +通常,已啟動的服務會執行單一操作且不會將結果傳回呼叫端。例如,服務可能會透過網路下載或上傳檔案。 + +當操作完成時,服務應該會自行終結。 +</dd> + <dt>繫結</dt> + <dd>服務「繫結」表示應用程式元件透過呼叫 {@link +android.content.Context#bindService bindService()} 來繫結至服務。已繫結的服務提供主從式介面,讓元件可以與服務互動、傳送要求、取得結果,甚至可以使用處理程序間通訊 (IPC) 來跨程序達到目的。 + +已繫結的服務伴隨另一個繫結至服務的應用程式元件執行。 +多重元件可以一次繫結至服務,但是當所有元件都取消繫結時,服務就會被終結。 +</dd> +</dl> + +<p>雖然此文件通常分別討論服務的這兩種類別,但您的服務兩種方式都可以執行 — 可以啟動服務 (無限次數地執行) 也允許繫結。 +這僅與您是否實作兩種回呼方法而定:{@link +android.app.Service#onStartCommand onStartCommand()} 允許元件啟動服務,而 {@link +android.app.Service#onBind onBind()} 則允許繫結。 +</p> + +<p>不論您的應用程式是否啟動、繫結或兩者都有,任何應用程式元件可以使用服務 (就算來自不同的應用程式),與任何元件可以使用 Activity 的方式相同 — 使用 {@link android.content.Intent} 啟動服務。 + +然而,您可以宣告服務為私用、位於宣示說明檔案之中,與封鎖從其他應用程式存取。 +如需更多討論資訊,請詳見<a href="#Declaring">在宣示說明中宣告服務</a>。 + +</p> + +<p class="caution"><strong>注意:</strong>服務會在其託管程序的主執行緒中執行 — 服務<strong>不會</strong>建立自己的執行緒且<strong>不會</strong>在另外的程序中執行 (除非您另行指定)。 + +這代表如果您的服務即將從事任何 CPU 密集的作業或封鎖操作 (如播放 MP3 或連線網路),您應該在服務中建立新的執行緒來執行這些工作。 + +透過使用另外的執行緒,您會降低應用程式不回應 (ANR) 錯誤的風險,且應用程式的主執行緒仍可以專務於使用者與您的 Activity 互動。 + +</p> + + +<h2 id="Basics">基本概念</h2> + +<div class="sidebox-wrapper"> +<div class="sidebox"> + <h3>應該使用服務或執行緒?</h3> + <p>服務簡單而言就是可以在背景中執行的元件,就算使用者不與您的應用程式互動也不會影響。 +所以,您應該只在需要時才建立服務。 +</p> + <p>如果您必須在主執行緒外執行作業,但只有在使用者與您應用程式互動時才需要,則您可能應該建立新的執行緒而非服務。 +例如,如果您想要播放一些音樂,但只在執行您的 Activity 時播放,您可能在 +{@link android.app.Activity#onCreate onCreate()} 中建立執行緒,在 {@link +android.app.Activity#onStart onStart()} 中開始執行該執行緒,然後在 {@link android.app.Activity#onStop +onStop()} 中停止。 +也可以考慮使用 {@link android.os.AsyncTask} 或 {@link android.os.HandlerThread},來代替傳統的 {@link java.lang.Thread} 類別。 +如需更多有關執行緒的資訊,請參閱<a href="{@docRoot}guide/components/processes-and-threads.html#Threads">程序與執行緒</a>文件。 +</p> + <p>請記得如果您確實使用服務,依照預設會在您應用程式的主執行緒中執行該服務,所以,如果服務執行密集的操作或封鎖操作,則您仍應在服務中建立新的執行緒。 + +</p> +</div> +</div> + +<p>如要建立服務,您必須建立 {@link android.app.Service} 的子類別 (或是其現有的子類別之一)。 +在您的實作中,如果可以,您必須覆寫某些回呼方法,這些方法負責處理服務生命週期的關鍵層面,並提供元件繫結至服務的機制。 + +您應該覆寫的最重要回呼方法為:</p> + +<dl> + <dt>{@link android.app.Service#onStartCommand onStartCommand()}</dt> + <dd>當另一個元件 (如 Activity) 透過呼叫 {@link android.content.Context#startService +startService()} 來要求啟動服務時,系統會呼叫此方法。 +一旦執行此方法,服務會被啟動且可以無限次數地在背景中執行。 +如果您實作此方法,當作業完成時,您必須負責停止服務,方式為呼叫 {@link android.app.Service#stopSelf stopSelf()} 或 {@link +android.content.Context#stopService stopService()}。 +(如果您只想要提供繫結功能,您不需要實作此方法)。 +</dd> + <dt>{@link android.app.Service#onBind onBind()}</dt> + <dd>當其他元件想要與服務 (如執行 RPC) 繫結時,系統會透過呼叫 {@link android.content.Context#bindService +bindService()} 來呼叫此方法。 +在您實作此方法時,透過傳回 {@link android.os.IBinder},您必須提供用戶端可用來與服務通訊的介面。 +您必須永遠實作此方法,但如果您不想允許繫結,則您應該傳回 null。 +</dd> + <dt>{@link android.app.Service#onCreate()}</dt> + <dd>當第一次建立服務時,系統會呼叫此方法來執行一次性的設定程序 (在其呼叫 {@link android.app.Service#onStartCommand onStartCommand()} 或 +{@link android.app.Service#onBind onBind()} 之前)。 +如果已經執行服務,則不會呼叫此方法。 +</dd> + <dt>{@link android.app.Service#onDestroy()}</dt> + <dd>當不再使用服務且正在終結服務時,系統會呼叫此方法。您的服務應該實作此方法來清除任何如執行緒、註冊的接聽器,接收器等資源。 + +這會是服務接收到的最後呼叫。</dd> +</dl> + +<p>如果透過呼叫 {@link +android.content.Context#startService startService()} (這是呼叫至 {@link +android.app.Service#onStartCommand onStartCommand()} 的結果) 讓元件啟動服務,則服務仍會保持執行狀態,直到使用 {@link android.app.Service#stopSelf()} 讓服務自行停止或透過呼叫 +{@link android.content.Context#stopService stopService()} 由其他元件停止服務。 +</p> + +<p>如果元件呼叫 +{@link android.content.Context#bindService bindService()} 來建立服務 (且「沒有」呼叫 {@link +android.app.Service#onStartCommand onStartCommand()}<em></em>),則服務會伴隨繫結至服務的元件執行。 +一旦服務與所有用戶端解除繫結時,系統會終結服務。 +</p> + +<p>只有在記憶體不足且必須復原擁有使用者焦點的 Activity 其系統資源時,Android 系統才會強制停止服務。 +如果服務已繫結至擁有使用者焦點的 Activity,則該服務不太容易被終止,且如果服務被宣告為<a href="#Foreground">在前景中執行</a> (稍後會討論),則該服務幾乎不會被終止。 +否則,如果長時間執行於前景中啟動的服務,則系統會隨時間降低該服務在背景工作清單中的位置,且該服務會很容易被終止 — 如果已啟動您的服務,則您必須設計讓系統可以完美地處理重新啟動。 + + + +如果系統終止服務,則系統會在可以再度取得資源時立即重新啟動服務 (雖然這也會依據您從 {@link +android.app.Service#onStartCommand onStartCommand()} 傳回的值而有所不同,稍後會討論)。 +如需更多有關系統何時可能終結服務的資訊,請參閱<a href="{@docRoot}guide/components/processes-and-threads.html">程序與執行緒</a>文件。 + +</p> + +<p>在下列小節,您將看到您如何可以建立每種類型的服務與如何從不同應用程式元件使用。 +</p> + + + +<h3 id="Declaring">在宣示說明中宣告服務</h3> + +<p>就如同 Activity (與其他元件),您必須在您應用程式的宣示說明檔案中宣告所有服務。 +</p> + +<p>如要宣告您的服務,可以新增 <a href="{@docRoot}guide/topics/manifest/service-element.html">{@code <service>}</a> 元素為 <a href="{@docRoot}guide/topics/manifest/application-element.html">{@code <application>}</a>元素的子項。 + +例如:</p> + +<pre> +<manifest ... > + ... + <application ... > + <service android:name=".ExampleService" /> + ... + </application> +</manifest> +</pre> + +<p>如需更多有關在宣示說明中宣告您服務的行系資訊,請參閱 <a href="{@docRoot}guide/topics/manifest/service-element.html">{@code <service>}</a> 元素。 +</p> + +<p>您還可以在 <a href="{@docRoot}guide/topics/manifest/service-element.html">{@code <service>}</a> 元素中包含其他屬性,用來定義如啟動服務所需權限的屬性,與服務應該執行的程序。 + +<a href="{@docRoot}guide/topics/manifest/service-element.html#nm">{@code android:name}</a> 屬性是唯一必備的屬性 — 用來指定服務的類別名稱。 +一旦您發佈應用程式,您就不應該變更此名稱,因為如果這樣做,會由於依賴明確的意圖來啟動或繫結服務 (閱讀部落格貼文、<a href="http://android-developers.blogspot.com/2011/06/things-that-cannot-change.html">不能變更的事項</a>),造成程式碼中斷的風險。 + + + + +<p>如要確認您的應用程式是安全的,<strong>當啟動或繫結您的 {@link android.app.Service} 時,永遠使用明確意圖</strong>,且不要宣告服務的意圖篩選器。 +如果允許一定程度的模糊來決定啟動的服務是重要的,您可以提供您的服務意圖篩選器,並從 {@link +android.content.Intent} 排除元件名稱,但您仍需使用 {@link +android.content.Intent#setPackage setPackage()} 設定意圖的封裝,這會提供目標服務足夠明確、避免含糊的指示。 + + +</p> + +<p>此外,您可以確保只有您的應用程式可以使用您的服務,方法為包含 <a href="{@docRoot}guide/topics/manifest/service-element.html#exported">{@code android:exported}</a> 屬性並將其設定為 {@code "false"}。 + +這可以有效地避免其他應用程式啟動您的服務,就算使用明確意圖時也是如此。 +</p> + + + + +<h2 id="CreatingStartedService">建立已啟動的服務</h2> + +<p>已啟動的服務是由其他應用程式呼叫 {@link +android.content.Context#startService startService()} 所啟動的,由呼叫至服務的 +{@link android.app.Service#onStartCommand onStartCommand()} 方法所導致。</p> + +<p>當服務啟動,其生命週期與啟動服務的元件無關,且服務可以無限次數地在背景中執行,就算啟動服務的元件已終結也不會影響。 + +因此,當服務的工作藉由呼叫 {@link android.app.Service#stopSelf stopSelf()} 而完成時,服務應該會自行停止,或藉由呼叫 {@link android.content.Context#stopService stopService()},其他元件也可以停止服務。 + +</p> + +<p>如 Activity 等應用程式元件可以透過呼叫 {@link +android.content.Context#startService startService()} 與傳送用來指定服務及包含服務所要使用任何資料的 +{@link android.content.Intent},來啟動服務。服務會接收 {@link android.app.Service#onStartCommand +onStartCommand()} 方法中的此 {@link android.content.Intent}。 +</p> + +<p>例如,假設 Activity 需要一些資料到線上資料庫。藉由傳送意圖至 {@link +android.content.Context#startService startService()},Activity 可以啟動伴隨服務並傳送要儲存的資料。 +服務會接收 {@link +android.app.Service#onStartCommand onStartCommand()} 中的意圖,連線到網際網路,並執行資料庫交易。 +當操作完成時,服務應該會自行終結。 +</p> + +<p class="caution"><strong>注意:</strong>服務會在與應用程式相同的程序中執行,服務會在該程序中被宣告,且依照預設,會位於該應用程式的主執行緒之中。 +所以,在使用者與來自相同應用程式的 Activity 互動時,如果您的服務執行密集的操作或封鎖操作,則該服務將會降低 Activity 的效能。 + +要避免影響應用程式的效能,您應該在服務之中啟動新的執行緒。 +</p> + +<p>傳統上,有兩種類別可以延伸為建立已啟動的服務:</p> +<dl> + <dt>{@link android.app.Service}</dt> + <dd>這是所有服務的基本類別。當您延伸此類別時,建立用來執行所有服務工作的新執行緒是非常重要的,因為依照預設,服務會使用您應用程式的主執行緒,這會降低任何您應用程式所執行 Activity 的效能。 + + +</dd> + <dt>{@link android.app.IntentService}</dt> + <dd>這是 {@link android.app.Service} 的子類別,會使用 worker 執行緒來處理所有的啟動要求,一次一個。 +如果您不需要您的服務同時處理多個要求,則這是最佳的選項。 +您唯一要做的就是實作 {@link +android.app.IntentService#onHandleIntent onHandleIntent()},這會接收每個起始要求的意圖 ,所以您可以在背景執行。 +</dd> +</dl> + +<p>下列小節說明如何可以使用這些類別之一來實作您的服務。 +</p> + + +<h3 id="ExtendingIntentService">延伸 IntentService 類別</h3> + +<p>因為大多數的已啟動服務不需要同時處理多個要求(多重執行緒策略事實上是危險的),如果您使用 {@link android.app.IntentService} 類別來實作您的服務可能是最佳的方法。 + +</p> + +<p>{@link android.app.IntentService} 會達到下列目的:</p> + +<ul> + <li>建立預設的 worker 執行緒,該執行緒會執行所有傳送至 {@link +android.app.Service#onStartCommand onStartCommand()} 的意圖,並與您應用程式的主執行緒有所分別。 +</li> + <li>建立工作佇列,該佇列會一次傳送一個意圖到您的 {@link +android.app.IntentService#onHandleIntent onHandleIntent()} 實作,所以您絕對不需要擔心多重執行緒的問題。 +</li> + <li>在所有啟動要求都已處理後,停止服務,這樣您永遠不需要呼叫 +{@link android.app.Service#stopSelf}。</li> + <li>提供傳回 null 的 {@link android.app.IntentService#onBind onBind()} 其預設實作。 +</li> + <li>提供傳送意圖至工作佇列的 {@link android.app.IntentService#onStartCommand +onStartCommand()} 其預設實作,然後傳送至您的 {@link +android.app.IntentService#onHandleIntent onHandleIntent()} 實作。</li> +</ul> + +<p>所有這一切都說明了您要做的就是實作 {@link +android.app.IntentService#onHandleIntent onHandleIntent()} 來完成用戶端提供的工作。 +(雖然,您也需要提供小型建構函式給服務)。</p> + +<p>下列是 {@link android.app.IntentService} 實作的範例:</p> + +<pre> +public class HelloIntentService extends IntentService { + + /** + * A constructor is required, and must call the super {@link android.app.IntentService#IntentService} + * constructor with a name for the worker thread. + */ + public HelloIntentService() { + super("HelloIntentService"); + } + + /** + * The IntentService calls this method from the default worker thread with + * the intent that started the service. When this method returns, IntentService + * stops the service, as appropriate. + */ + @Override + protected void onHandleIntent(Intent intent) { + // Normally we would do some work here, like download a file. + // For our sample, we just sleep for 5 seconds. + long endTime = System.currentTimeMillis() + 5*1000; + while (System.currentTimeMillis() < endTime) { + synchronized (this) { + try { + wait(endTime - System.currentTimeMillis()); + } catch (Exception e) { + } + } + } + } +} +</pre> + +<p>您需要的是:{@link +android.app.IntentService#onHandleIntent onHandleIntent()} 的建構函式與實作。</p> + +<p>如果您決定也要覆寫其他回呼方法,如 {@link +android.app.IntentService#onCreate onCreate()}、{@link +android.app.IntentService#onStartCommand onStartCommand()} 或 {@link +android.app.IntentService#onDestroy onDestroy()},請確認呼叫超級實作,這樣 +{@link android.app.IntentService} 才可以適當處理 worker 執行緒的生命。</p> + +<p>例如,{@link android.app.IntentService#onStartCommand onStartCommand()} 必須傳回預設的實作 (這就是意圖如何被傳送至 {@link +android.app.IntentService#onHandleIntent onHandleIntent()}): +</p> + +<pre> +@Override +public int onStartCommand(Intent intent, int flags, int startId) { + Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show(); + return super.onStartCommand(intent,flags,startId); +} +</pre> + +<p>除了 {@link android.app.IntentService#onHandleIntent onHandleIntent()},您不需要呼叫超級類別的唯一方法就是 {@link android.app.IntentService#onBind +onBind()} (但只有在您的服務允許繫結時才需要實作)。 +</p> + +<p>在下一節,您會看到在延伸基本 {@link android.app.Service} 類別時,如何實作同類的服務,這需要寫更多程式碼,但是如果您需要同時處理多個啟動要求時,這可能是比較合適的處理方式。 + +</p> + + +<h3 id="ExtendingService">延伸服務類別</h3> + +<p>如同您在前一節看到的,使用 {@link android.app.IntentService} 可讓您已啟動服務的實作變得非常容易。 +然而,如果您要求您的服務執行多重執行緒 (取代透過工作佇列處理啟動要求),則您可以延伸 {@link android.app.Service} 類別來處理每個意圖。 + +</p> + +<p>為了對比之用,下列程式碼範例是 {@link +android.app.Service} 類別的實作,該類別執行與上述使用 {@link +android.app.IntentService} 範例相同的工作。也就是,對每個啟動要求,會使用 worker 執行緒來執行工作,且一次只處理一個要求。 +</p> + +<pre> +public class HelloService extends Service { + private Looper mServiceLooper; + private ServiceHandler mServiceHandler; + + // Handler that receives messages from the thread + private final class ServiceHandler extends Handler { + public ServiceHandler(Looper looper) { + super(looper); + } + @Override + public void handleMessage(Message msg) { + // Normally we would do some work here, like download a file. + // For our sample, we just sleep for 5 seconds. + long endTime = System.currentTimeMillis() + 5*1000; + while (System.currentTimeMillis() < endTime) { + synchronized (this) { + try { + wait(endTime - System.currentTimeMillis()); + } catch (Exception e) { + } + } + } + // Stop the service using the startId, so that we don't stop + // the service in the middle of handling another job + stopSelf(msg.arg1); + } + } + + @Override + public void onCreate() { + // Start up the thread running the service. Note that we create a + // separate thread because the service normally runs in the process's + // main thread, which we don't want to block. We also make it + // background priority so CPU-intensive work will not disrupt our UI. + HandlerThread thread = new HandlerThread("ServiceStartArguments", + Process.THREAD_PRIORITY_BACKGROUND); + thread.start(); + + // Get the HandlerThread's Looper and use it for our Handler + mServiceLooper = thread.getLooper(); + mServiceHandler = new ServiceHandler(mServiceLooper); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show(); + + // For each start request, send a message to start a job and deliver the + // start ID so we know which request we're stopping when we finish the job + Message msg = mServiceHandler.obtainMessage(); + msg.arg1 = startId; + mServiceHandler.sendMessage(msg); + + // If we get killed, after returning from here, restart + return START_STICKY; + } + + @Override + public IBinder onBind(Intent intent) { + // We don't provide binding, so return null + return null; + } + + @Override + public void onDestroy() { + Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show(); + } +} +</pre> + +<p>如同您看到的,比起使用 {@link android.app.IntentService},這需要花更多功夫。</p> + +<p>然而,因為您自行處理每個對 {@link android.app.Service#onStartCommand +onStartCommand()} 的呼叫,您可以同時執行多個要求。那不是這個範例要做的,但如果那是您想要的,那麼您可以針對每個要求建立新的執行緒,並立即執行 (代替等待之前的要求結束)。 + +</p> + +<p>請注意:{@link android.app.Service#onStartCommand onStartCommand()} 方法必須傳回整數。 +整數是一個數值,說明在系統終止服務的事件中,系統該如何繼續服務 (如上述的討論,雖然您可以修改,但 {@link +android.app.IntentService} 的預設實作會替您處理)。 +從 +{@link android.app.Service#onStartCommand onStartCommand()} 傳回的值必須是下列常數之一: +</p> + +<dl> + <dt>{@link android.app.Service#START_NOT_STICKY}</dt> + <dd>如果系統在 {@link android.app.Service#onStartCommand +onStartCommand()} 回傳後終止服務,除非有待決的意圖要傳送,否則請「不要」<em></em>建立服務。 +在非必要時與應用程式可以簡單地重新啟動任何未完成的工作時,這是避免執行服務的最安全方式。 +</dd> + <dt>{@link android.app.Service#START_STICKY}</dt> + <dd>如果系統在 {@link android.app.Service#onStartCommand +onStartCommand()} 回傳後終止服務,請重新建立服務並呼叫 {@link +android.app.Service#onStartCommand onStartCommand()},但「不要」<em></em>重新傳送最後的意圖。 +相反地,除非有待決的意圖要啟動服務傳送,否則系統會使用 null 意圖呼叫 {@link android.app.Service#onStartCommand onStartCommand()},如果有待決的意圖要啟動服務,則會傳送那些意圖。 + +這適用於媒體播放程式 (或類似服務) 這類不執行命令,但可以無次數限制執行與等待工作的服務。 +</dd> + <dt>{@link android.app.Service#START_REDELIVER_INTENT}</dt> + <dd>如果系統在 {@link android.app.Service#onStartCommand +onStartCommand()} 回傳後終止服務,請重新建立服務並使用傳送至服務的最後意圖呼叫 {@link +android.app.Service#onStartCommand onStartCommand()}。 +任何待決的意圖會反過來由後往前傳送。這適用的服務為主動執行如下載檔案等應該立即繼續的工作。 +</dd> +</dl> +<p>如需有關這些傳回值的詳細資訊,請參閱每個常數的連結參考文件。 +</p> + + + +<h3 id="StartingAService">啟動服務</h3> + +<p>您可以透過傳送 +{@link android.content.Intent} (指定要啟動的服務) 到 {@link +android.content.Context#startService startService()},從 Activity 或其他應用程式元件來啟動服務。Android 系統會呼叫服務的 {@link +android.app.Service#onStartCommand onStartCommand()} 方法並傳送 {@link +android.content.Intent} 給該方法。(絕對不要直接呼叫 {@link android.app.Service#onStartCommand +onStartCommand()})。</p> + +<p>例如,使用明確意圖並搭配 {@link android.content.Context#startService +startService()},Activity 可以啟動前小節範例中的服務 ({@code +HelloSevice}):</p> + +<pre> +Intent intent = new Intent(this, HelloService.class); +startService(intent); +</pre> + +<p>{@link android.content.Context#startService startService()} 方法會立即回傳且Android 系統會呼叫服務的 {@link android.app.Service#onStartCommand +onStartCommand()} 方法。 +如果尚未開始執行服務,系統會先呼叫 {@link +android.app.Service#onCreate onCreate()},然後呼叫 {@link android.app.Service#onStartCommand +onStartCommand()}。</p> + +<p>如果服務也不提供繫結,則與意圖一同傳送的 {@link +android.content.Context#startService startService()} 是在應用程式元件與服務間通訊的唯一模式。 +然而,如果您想要服務將結果送回,則啟動服務的用戶端會針對廣播建立 {@link android.app.PendingIntent}(搭配 {@link android.app.PendingIntent#getBroadcast getBroadcast()}) 並將其傳送至服務,該服務在啟動服務的 {@link android.content.Intent} 之中。 + + +然後服務就可使用廣播來傳送結果。 +</p> + +<p>多個啟動服務的要求會導致多個相關呼叫至服務的 +{@link android.app.Service#onStartCommand onStartCommand()}。然而,只允許一個要求可以停止服務 (使用 {@link android.app.Service#stopSelf stopSelf()} 或 {@link +android.content.Context#stopService stopService()})。 +</p> + + +<h3 id="Stopping">停止服務</h3> + +<p>已啟動的服務必須管理本身的生命週期。也就是,系統不會停止或終結服務,除非系統必須復原系統記憶體,而服務會在 {@link android.app.Service#onStartCommand onStartCommand()} 回傳後繼續執行。 + +所以,服務務必自我停止,可透過呼叫 {@link android.app.Service#stopSelf stopSelf()} 達成,或透過呼叫 {@link android.content.Context#stopService stopService()} 讓其他元件可以停止服務。 + +</p> + +<p>一旦要求使用 {@link android.app.Service#stopSelf stopSelf()} 或 {@link +android.content.Context#stopService stopService()} 停止,系統會盡快終結服務。 +</p> + +<p>然而,如果您的服務同時處理多個對 {@link +android.app.Service#onStartCommand onStartCommand()} 的要求,則在您開始處理啟動要求時,您不應該停止服務,因為您仍可能會收到新的啟動要求 (在第一個要求結束時停止可能會終止第二個要求)。 + +如要避免發生此問題,您可以使用 {@link android.app.Service#stopSelf(int)} 來確保您停止服務的要求會總是基於最新的啟動要求。 + +也就是,當您呼叫 {@link +android.app.Service#stopSelf(int)} 時,會傳送啟動要求的 ID (<code>startId</code> 已傳送至 {@link android.app.Service#onStartCommand onStartCommand()}) 至您相關的停止要求。 + +然而,如果服務在您可以呼叫 {@link +android.app.Service#stopSelf(int)} 之前,就收到新的啟動要求,則 ID 將不會相符,服務也不會停止。</p> + +<p class="caution"><strong>注意:</strong>在您的應用程式完成工作時,應用程式停止其服務是非常重要的,這可以避免浪費系統資源與消耗電池電力。 +如果必要,透過呼叫 {@link +android.content.Context#stopService stopService()},其他元件可以停止服務。 +就算您啟動服務的繫結,如果曾接收對 {@link +android.app.Service#onStartCommand onStartCommand()} 的呼叫,您永遠仍必須自行停止服務。 +</p> + +<p>如需有關服務生命週期的詳細資訊,請參閱下節<a href="#Lifecycle">管理服務的生命週期</a>。</p> + + + +<h2 id="CreatingBoundService">建立已繫結的服務</h2> + +<p>已繫結的服務是允許應用程式元件透過呼叫 {@link +android.content.Context#bindService bindService()} 來建立繫結的服務,這是為了建立長期的連線 (一般而言,並不允許元件透過呼叫 {@link +android.content.Context#startService startService()} 來「啟動」<em></em> )。 +</p> + +<p>當您想要透過處理程序間通訊 (IPC),與來自 Activity 的服務及您應用程式中的其他元件互動時,或是想要揭露您應用程式的特定功能給其他應用程式時,您應該建立已繫結的服務。 + +</p> + +<p>如要建立已繫結的服務,您必須實作 {@link +android.app.Service#onBind onBind()} 回呼方法並傳回 +{@link android.os.IBinder} (這用來定義與服務溝通的介面)。接著其他應用程式元件可以呼叫 +{@link android.content.Context#bindService bindService()} 來擷取介面並開始服務上的呼叫方法。 +服務只會為了服務所繫結應用程式元件才存在, +所以當沒有繫結服務的元件時,系統會終結服務 (當服務透過 +{@link android.app.Service#onStartCommand onStartCommand()} 啟動時,您「不」<em></em>需要停止已繫結的服務)。 +</p> + +<p>如要建立已繫結的服務,您必須做的第一件事就是定義用來指定用戶端如何與服務通訊的介面。 +介於服務與用戶端間的介面必須是 {@link android.os.IBinder} 的實作,也是您服務必須從 {@link android.app.Service#onBind +onBind()} 回呼方法傳回的。 + +一旦用戶端收到 {@link android.os.IBinder},可以透過介面開始與服務互動。 +</p> + +<p>服務一次可以繫結多個用戶端。當用戶端完成與服務互動時,會呼叫 {@link android.content.Context#unbindService unbindService()} 來取消繫結。 +一旦沒有用戶端與服務繫結,系統就會終結服務。 +</p> + +<p>有多個方法可以實作已繫結的服務,且實作比已啟動服務更加複雜,所以會在另一份有關<a href="{@docRoot}guide/components/bound-services.html">已繫結的服務</a>文件中討論已繫結的服務。 + +</p> + + + +<h2 id="Notifications">傳送通知給使用者</h2> + +<p>一旦執行,服務可以使用<a href="{@docRoot}guide/topics/ui/notifiers/toasts.html">快顯通知</a>或<a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">狀態列通知</a>來通知事件的使用者。</p> + +<p>快顯通知是一個會出現在目前視窗表面上短暫時間然後消失的訊息,此時狀態列通知會在狀態列提供一個圖示與訊息,使用者可以選擇然後採取行動 (例如啟動 Activity)。 + +</p> + +<p>通常,當已完成某些背景作業(如完成檔案下載) 且使用者現在可以採取行動時,狀態列通知是最佳的方法。 + +當使用者從擴展的檢視選取通知時,通知可以啟動 Activity (如檢視已下載檔案)。 +</p> + +<p>如需更多資訊,請參閱<a href="{@docRoot}guide/topics/ui/notifiers/toasts.html">快顯通知</a>或<a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">狀態列通知</a>開發人員指南。 +</p> + + + +<h2 id="Foreground">在前景中執行服務</h2> + +<p>前景服務是被認為使用者已主動意識到、就算在記憶體不足時系統也不會終結的服務。 +前景服務必須提供通知給狀態列,狀態列會放置在「進行中」標題之下,這表示除非服務已被停止或從前景移除,否則無法解除通知。 + + +</p> + +<p>例如,播放來自某服務音樂的音樂播放器應該被設為在前景中執行,因為使用者明確意識到這項操作。 + +狀態列中的通知可能會指出目前播放的歌曲,並允許使用者啟動 Activity 來與音樂播放器互動。 +</p> + +<p>如要要求您的服務在前景中執行,可以呼叫 {@link +android.app.Service#startForeground startForeground()}。此方法有兩個參數:一個整數用來唯一識別通知,以及 {@link +android.app.Notification} 供狀態列使用。 +例如:</p> + +<pre> +Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text), + System.currentTimeMillis()); +Intent notificationIntent = new Intent(this, ExampleActivity.class); +PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0); +notification.setLatestEventInfo(this, getText(R.string.notification_title), + getText(R.string.notification_message), pendingIntent); +startForeground(ONGOING_NOTIFICATION_ID, notification); +</pre> + +<p class="caution"><strong>注意:</strong>您給 {@link +android.app.Service#startForeground startForeground()} 的整數 ID 不能為 0。</p> + + +<p>如要從前景移除服務,可以呼叫 {@link +android.app.Service#stopForeground stopForeground()}。此方法有一個布林數,表示是否同時移除狀態列通知。 +此方法「不會」<em></em>停止服務。 +然而,如果您在服務仍於前景執行時停止服務,則也會移除通知。 +</p> + +<p>如需更多有關通知的資訊,請參閱<a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">建立狀態列通知</a>。 +</p> + + + +<h2 id="Lifecycle">管理服務的生命週期</h2> + +<p>服務的生命週期比 Activity 的生命週期要簡單多了。然而,密切關注如何建立與終結服務就更重要了,因為在使用者沒有意識到的狀況下可在背景中執行服務。 + +</p> + +<p>服務生命週期 — 從何時建立到何時終結 — 可以遵循兩種不同的路徑: +</p> + +<ul> +<li>已啟動的服務 + <p>當其他元件呼叫 {@link +android.content.Context#startService startService()} 時,會建立服務。然後服務可無限次數執行,且必須自行停止,方法是呼叫 {@link +android.app.Service#stopSelf() stopSelf()}。 +透過呼叫 {@link android.content.Context#stopService +stopService()},其他元件可以停止服務。 +當服務停止時,系統會終結服務。</p></li> + +<li>已繫結的服務 + <p>當其他元件 (一個用戶端) 呼叫 {@link +android.content.Context#bindService bindService()} 時,會建立服務。然後用戶端會透過 +{@link android.os.IBinder} 介面與服務通訊。用戶端也可以呼叫 +{@link android.content.Context#unbindService unbindService()} 來切斷連線。多重用戶端可以繫結至相同的服務,但是當所有用戶端都取消繫結時,系統就會終結服務。 +(服務「不」<em></em>需要自行停止)。 +</p></li> +</ul> + +<p>這兩種路徑不是完全獨立的。也就是,您可以繫結至已經使用 +{@link android.content.Context#startService startService()} 啟動的服務。例如,可以啟動背景音樂服務,方法是呼叫 {@link android.content.Context#startService +startService()} 並搭配可識別要播放音樂的 {@link android.content.Intent}。 +之後,使用者可能會想要透過播放器試試某些控制或取得關於目前歌曲的資訊,可以將 Activity 繫結至服務,方法為呼叫 {@link +android.content.Context#bindService bindService()}。 + +就這個的案子而言,除非所有的用戶端都取消繫結,否則 {@link +android.content.Context#stopService stopService()} 或 {@link android.app.Service#stopSelf +stopSelf()} 不會實際上停止服務。 </p> + + +<h3 id="LifecycleCallbacks">實作生命週期回呼</h3> + +<p>就如同 Activity,服務有您可以實作來監控服務狀態變更與在適合時段執行作業的生命週期回呼方法。 +下列服務會示範每個生命週期方法: +</p> + +<pre> +public class ExampleService extends Service { + int mStartMode; // indicates how to behave if the service is killed + IBinder mBinder; // interface for clients that bind + boolean mAllowRebind; // indicates whether onRebind should be used + + @Override + public void {@link android.app.Service#onCreate onCreate}() { + // The service is being created + } + @Override + public int {@link android.app.Service#onStartCommand onStartCommand}(Intent intent, int flags, int startId) { + // The service is starting, due to a call to {@link android.content.Context#startService startService()} + return <em>mStartMode</em>; + } + @Override + public IBinder {@link android.app.Service#onBind onBind}(Intent intent) { + // A client is binding to the service with {@link android.content.Context#bindService bindService()} + return <em>mBinder</em>; + } + @Override + public boolean {@link android.app.Service#onUnbind onUnbind}(Intent intent) { + // All clients have unbound with {@link android.content.Context#unbindService unbindService()} + return <em>mAllowRebind</em>; + } + @Override + public void {@link android.app.Service#onRebind onRebind}(Intent intent) { + // A client is binding to the service with {@link android.content.Context#bindService bindService()}, + // after onUnbind() has already been called + } + @Override + public void {@link android.app.Service#onDestroy onDestroy}() { + // The service is no longer used and is being destroyed + } +} +</pre> + +<p class="note"><strong>注意:</strong>不像 Activity 生命週期回呼方法,您「不」<em></em>需要呼叫這些回呼方法的超級類別實作。 +</p> + +<img src="{@docRoot}images/service_lifecycle.png" alt="" /> +<p class="img-caption"><strong>圖 2.</strong>服務生命週期。左圖顯示使用 {@link android.content.Context#startService +startService()} 建立服務的生命週期,右圖顯示使用 +{@link android.content.Context#bindService bindService()} 建立服務的生命週期。 +</p> + +<p>透過實作這些方法,您可以監控服務生命週期中的兩個巢狀迴圈: </p> + +<ul> +<li>服務的<strong>整個生命</strong>發生在 {@link +android.app.Service#onCreate onCreate()} 被呼叫的時間與 {@link +android.app.Service#onDestroy} 回傳的時間之間。就像 Activity,服務於 +{@link android.app.Service#onCreate onCreate()} 中開始其初始設定,並於 {@link +android.app.Service#onDestroy onDestroy()} 中釋出所有剩餘的資訊。例如,音樂播放服務可以建立執行緒,且音樂會於 {@link +android.app.Service#onCreate onCreate()} 中播放,然後在 {@link +android.app.Service#onDestroy onDestroy()} 中停止執行緒。 + + +<p>{@link android.app.Service#onCreate onCreate()} 與 {@link android.app.Service#onDestroy +onDestroy()} 方法可供所有服務呼叫,不論服務是否由 {@link android.content.Context#startService startService()} 或 {@link +android.content.Context#bindService bindService()} 所建立。 +</p></li> + +<li>服務的<strong>使用生命</strong>從對 {@link +android.app.Service#onStartCommand onStartCommand()} 或 {@link android.app.Service#onBind onBind()} 的呼叫開始。 +每個方法都被遞交 {@link +android.content.Intent},也會被分別傳送至 {@link android.content.Context#startService +startService()} 或 {@link android.content.Context#bindService bindService()}。 +<p>如果服務已啟動,使用生命的結束時間與整個生命結束的時間相同 (就算在 {@link android.app.Service#onStartCommand +onStartCommand()} 回傳後,服務仍在作用中)。 +如果服務已繫結,使用生命的會於 {@link +android.app.Service#onUnbind onUnbind()} 回傳時結束。</p> +</li> +</ul> + +<p class="note"><strong>注意:</strong>雖然已啟動的服務可藉由對 + {@link android.app.Service#stopSelf stopSelf()} 或 {@link +android.content.Context#stopService stopService()} 的呼叫來停止,但沒有服務的相對應回呼 (沒有 {@code onStop()} 回呼)。 +所有,除非服務已繫結至用戶端,系統會在服務停止時終結服務 — {@link +android.app.Service#onDestroy onDestroy()} 是唯一收到的回呼。 +</p> + +<p>圖 2 說明服務的回呼方法。雖然圖示區隔由 +{@link android.content.Context#startService startService()} 所建立的服務與由 +{@link android.content.Context#bindService bindService()} 所建立的服務,但請記住,任何服務不論是如何建立的,都可能可以允許使用者繫結。 + +所以,一開始使用 {@link android.app.Service#onStartCommand +onStartCommand()} 啟動的服務 (由用戶端呼叫 {@link android.content.Context#startService startService()})仍可以接收對 {@link android.app.Service#onBind onBind()} 的呼叫 (當用戶端呼叫 +{@link android.content.Context#bindService bindService()} 時)。 +</p> + +<p>針對建立提供已繫結的服務,如需更多詳細資訊,請參閱<a href="{@docRoot}guide/components/bound-services.html">已繫結的服務</a>文件,其中在<a href="{@docRoot}guide/components/bound-services.html#Lifecycle">管理已繫結服務的生命週期</a>包含更多有關 {@link android.app.Service#onRebind onRebind()}回呼方法的詳細資訊。 + + +</p> + + +<!-- +<h2>Beginner's Path</h2> + +<p>To learn how to query data from the system or other applications (such as contacts or media +stored on the device), continue with the <b><a +href="{@docRoot}guide/topics/providers/content-providers.html">Content Providers</a></b> +document.</p> +--> diff --git a/docs/html-intl/intl/zh-tw/guide/components/tasks-and-back-stack.jd b/docs/html-intl/intl/zh-tw/guide/components/tasks-and-back-stack.jd new file mode 100644 index 000000000000..e23301d641bc --- /dev/null +++ b/docs/html-intl/intl/zh-tw/guide/components/tasks-and-back-stack.jd @@ -0,0 +1,578 @@ +page.title=工作和返回堆疊 +parent.title=Activity +parent.link=activities.html +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> + +<h2>本文件內容</h2> +<ol> +<li><a href="#ActivityState">儲存 Activity 狀態</a></li></li> +<li><a href="#ManagingTasks">管理工作</a> + <ol> + <li><a href="#TaskLaunchModes">定義啟動模式</a></li> + <li><a href="#Affinities">處理親和性</a></li> + <li><a href="#Clearing">清除返回堆疊</a></li> + <li><a href="#Starting">開始工作</a></li> + </ol> +</li> +</ol> + +<h2>文章</h2> +<ol> + <li><a href="http://android-developers.blogspot.com/2010/04/multitasking-android-way.html"> Android 的多工作業方式 +</a></li> +</ol> + +<h2>另請參閱</h2> +<ol> + <li><a href="{@docRoot}design/patterns/navigation.html">Android 設計:導覽 +</a></li> + <li><a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>} 宣示說明元素 +</a></li> + <li><a href="{@docRoot}guide/components/recents.html">總覽畫面</a></li> +</ol> +</div> +</div> + + +<p>一個應用程式通常包含多個 <a href="{@docRoot}guide/components/activities.html">Activity</a>。每個 Activity 應根據使用者可執行的特定動作類型加以設計,且要能啟動其他 Activity。 + +例如,電子郵件應用程式可能會有一個可顯示新訊息清單的 Activity。 +當使用者選擇一則訊息,會開啟新的 Activity 以檢視該訊息。</p> + +<p>Activity 甚至可啟動裝置上其他應用程式的 Activity。例如,如果您的應用程式想要傳送一封電子郵件訊息,您可以定義一個意圖以執行「傳送」動作並包含一些資料,像是電子郵件地址和訊息。 + +其他應用程式中宣告處理此意圖類型的 Activity 就會開啟。 +在這種情況下,意圖就是要傳送電子郵件,因此電子郵件應用程式會啟動「撰寫」Activity (如果有多個 Activity 支援相同的意圖,則系統會讓使用者選擇要使用的 Activity)。 + +電子郵件傳送後,您的 Activity 就會繼續,並將電子郵件 Activity 視為您應用程式的一部分。 +雖然 Activity 可能來自不同的應用程式,但 Android 會將兩個 Activity 放在相同的工作中,以維護使用者體驗的流暢性。 + +<em></em></p> + +<p>工作是執行特定工作時,與使用者互動的 Activity 集合。 +Activity 會在堆疊 (返回堆疊<em></em>) 中依照每個 Activity 開啟的順序加以排列。 +</p> + +<!-- SAVE FOR WHEN THE FRAGMENT DOC IS ADDED +<div class="sidebox-wrapper"> +<div class="sidebox"> +<h3>Adding fragments to a task's back stack</h3> + +<p>Your activity can also include {@link android.app.Fragment}s to the back stack. For example, +suppose you have a two-pane layout using fragments, one of which is a list view (fragment A) and the +other being a layout to display an item from the list (fragment B). When the user selects an item +from the list, fragment B is replaced by a new fragment (fragment C). In this case, it might be +desireable for the user to navigate back to reveal fragment B, using the <em>Back</em> button.</p> +<p>In order to add fragment B to the back stack so that this is possible, you must call {@link +android.app.FragmentTransaction#addToBackStack addToBackStack()} before you {@link +android.app.FragmentTransaction#commit()} the transaction that replaces fragment B with fragment +C.</p> +<p>For more information about using fragments and adding them to the back stack, see the {@link +android.app.Fragment} class documentation.</p> + +</div> +</div> +--> + +<p>裝置主螢幕是大多數工作開始的地方。當使用者輕觸應用程式啟動組件上的某個圖示 (或主螢幕上的捷徑 ) 時,應用程式工作會移到前景。 + +如果應用程式沒有工作 (最近未使用應用程式),則會建立新的工作,且該應用程式的「主要」Activity 會以堆疊中的根 Activity 形式開啟。 + +</p> + +<p>當目前的 Activity 啟動另一個 Activity 時,會將新的 Activity 推到堆疊的頂端並取得焦點。 +之前的 Activity 會留在堆疊中,但已停止。Activity 停止後,系統會保留其使用者介面的目前狀態。 +當使用者按下 [返回] 按鈕<em></em>,會將目前的 Activity 從堆疊頂端推出 (Activity 已終結),並繼續進行之前的 Activity (還原其 UI 之前的狀態)。 + + +堆疊中的 Activity 不會重新整理,只會從堆疊推入和推出 — 由目前 Activity 啟動時推入堆疊,而當使用者使用 [返回] 按鈕離開時推出堆疊。<em></em> + +因此,返回堆疊會以「後進先出」的物件結構進行運作。 + +圖 1 透過時間軸將此行為視覺化,以時間軸顯示 Activity 間的進度以及每個時間點的目前返回堆疊。 + +</p> + +<img src="{@docRoot}images/fundamentals/diagram_backstack.png" alt="" /> +<p class="img-caption"><strong>圖 1.</strong>顯示工作中每個新 Activity 如何將項目新增到返回堆疊。 +當使用者按下 [返回] 按鈕,目前的 Activity 將會終結,而之前的 Activity 則會繼續進行。<em></em> + +</p> + + +<p>如果使用者持續按下 [返回]<em></em>,則會持續推出堆疊中的每個 Activity 以顯示之前的 Activity,直到使用者返回主螢幕 (或到工作開始時執行的 Activity)。 + + +當堆疊中的 Activity 全部移除後,工作將不再存在。</p> + +<div class="figure" style="width:287px"> +<img src="{@docRoot}images/fundamentals/diagram_multitasking.png" alt="" /> <p +class="img-caption"><strong>圖 2.</strong>兩個工作:工作 B 在前景收到使用者互動,而工作 A 在背景等待繼續。 +</p> +</div> +<div class="figure" style="width:215px"> + <img src="{@docRoot}images/fundamentals/diagram_multiple_instances.png" alt="" /> <p +class="img-caption"><strong>圖 3.</strong>單一 Activity 會具現化很多次。</p> +</div> + +<p>工作是一個緊密結合的單位,當使用者開始新的工作時可以移到「背景」,或透過 [首頁] 按鈕前往主螢幕。<em></em> +在背景時,工作中的所有 Activity 都會停止,但該工作的返回堆疊會保留下來 — 該工作純粹失去焦點,由另一個工作取而代之,如圖 2 所示。 + + +之後,工作可以返回「前景」,讓使用者繼續未完成的工作。 +例如,假設目前的工作 (工作 A) 的堆疊中有三個 Activity — 兩個位於目前的 Activity 下。 +使用者按下 [首頁] 按鈕,<em></em>然後從應用程式啟動新的應用程式。 + +當主螢幕出現時,工作 A 會移到背景。 +新的應用程式啟動時,系統會啟動該應用程式的工作 (工作 B),該應用程式會有自己的 Activity 堆疊。 +與該應用程式互動之後,使用者會再次回到首頁,並選取原來啟動工作 A 的應用程式。現在,工作 A 移到了前景 — 堆疊中的三個 Activity 全部保持不變,而堆疊中最頂端的 Activity 則會繼續進行。 + + + +此時,使用者也能切換回工作 B,前往首頁並選取啟動該工作的應用程式圖示 (或從<a href="{@docRoot}guide/components/recents.html">總覽畫面</a>選取應用程式工作)。這是在 Android 執行多工作業的範例。 + + + +</p> + +<p class="note"><strong>注意:</strong>背景可以一次執行多個工作。 +不過,如果使用者同時執行多個背景工作,系統可能會開始終結背景 Activity 以復原記憶體,導致 Activity 狀態遺失。 +請參閱下列有關 <a href="#ActivityState">Activity 狀態</a>的章節。 +</p> + +<p>由於返回堆疊中的 Activity 不會重新整理,如果您的應用程式允許使用者從一個以上的 Activity 中啟動特定 Activity,則會建立該 Activity 的新執行個體並推入堆疊 (而不會將 Activity 任何之前的執行個體移到最頂端)。 + + +因此,您應用程式中的一個 Activity 可能會具現化很多次 (甚至來自不同的工作),如圖 3 所示。 +也因為這樣,如果使用者使用 [返回] 按鈕瀏覽之前的資訊,Activity 的每個執行個體會依開啟的順序顯示<em></em> (每個會有自己的 UI 狀態)。 + + +不過,如果您不希望 Activity 具現化一次以上,則可以修改這個行為。 +如需詳細步驟,請參閱下文的<a href="#ManagingTasks">管理工作</a>。</p> + + +<p>摘要說明 Activity 和工作的預設行為:</p> + +<ul> + <li>當 Activity A 啟動 Activity B,Activity A 會停止,但系統會保留其狀態(例如捲軸位置和輸入表單的文字)。 + +如果使用者在 Activity B 按下 [返回] 按鈕,<em></em>Activity A 的狀態會復原並繼續執行。 +</li> + <li>當使用者按下 [首頁] 按鈕離開工作,<em></em>目前的 Activity 會停止且其工作會移到背景。 + +系統會保留工作中所有 Activity 的狀態。如果使用者稍後選取啟動工作的啟動組件圖示繼續執行工作,工作會移到前景並在堆疊頂端繼續執行 Activity。 + +</li> + <li>如果使用者按下 [返回] 按鈕<em></em>,會將目前的 Activity 從堆疊推出並終結。 + +堆疊中之前的 Activity 會繼續進行。Activity 終結後,系統將不會保留 Activity 的狀態。 +<em></em></li> + <li>Activity 可以具現化很多次,即使來自其他工作也一樣。</li> +</ul> + + +<div class="note design"> +<p><strong>導覽設計</strong></p> + <p>如需應用程式導覽如何在 Android 運作的詳細資訊,請參閱 Android 設計的<a href="{@docRoot}design/patterns/navigation.html">導覽</a>指南。</p> +</div> + + +<h2 id="ActivityState">儲存 Activity 狀態</h2> + +<p>如上所述,系統的預設行為會在 Activity 停止時保留 Activity 的狀態。 +如此一來,當使用者瀏覽之前的 Activity 時,其使用者介面的顯示方式將與離開時一樣。 +不過,您可以 — 也<strong>應該</strong> — 使用回呼方法主動保留 Activity 的狀態,以防止 Activity 遭到終結且必須重新建立的情況。 + +</p> + +<p>如果系統停止您其中一個 Activity (例如,當新的 Activity 開始或工作移到背景),當系統需要復原系統記憶體時,可能會完全終結該 Activity。 + +發生這種情況時,與 Activity 狀態相關的資訊都將遺失。如果發生這種情況,系統仍然知道該 Activity 位於返回堆疊中,但是當 Activity 移到堆疊頂端時,系統必須重新建立該 Activity (而不是繼續執行)。 + + +如果不想讓使用者的工作遺失,您應該在 Activity 中實作 {@link android.app.Activity#onSaveInstanceState onSaveInstanceState()} 回呼方法,主動保留該工作。 + + +</p> + +<p>如需儲存 Activity 狀態的詳細資訊,請參閱 <a href="{@docRoot}guide/components/activities.html#SavingActivityState">Activity</a> 文件。 +</p> + + + +<h2 id="ManagingTasks">管理工作</h2> + +<p>如上所述,Android 管理工作和返回堆疊的方式 — 將連續啟動的所有 Activity 放在相同的工作及「後進先出」堆疊中 — 對大多數應用程式而言非常好用,而且您不需擔心 Activity 與工作關聯的方式或它們如何存在於返回堆疊中。 + + +不過,您也許會想中斷一般的行為。 +您或許會希望應用程式的 Activity 可以在啟動時開始一個新的工作 (而不是放入目前的工作中);或者當您啟動一個 Activity 時,您可能會想使用其現有的執行個體 (而不是在返回堆疊頂端建立新的執行個體);又或者當使用者離開工作時,您想要清除返回堆疊中的所有 Activity,只留下根 Activity。 + + + +</p> + +<p>如要執行這些工作及更多工作,您可以使用 <a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a> 宣示說明元素中的屬性,以及您傳送到 {@link android.app.Activity#startActivity startActivity()} 之意圖中的旗標。 + + +</p> + +<p>就這個情況而言,您可以使用的主要 <a href="{@docRoot}guide/topics/manifest/activity-element.html"> +{@code <activity>}</a> 屬性包括:</p> + +<ul class="nolist"> + <li><a href="{@docRoot}guide/topics/manifest/activity-element.html#aff"> + {@code taskAffinity}</a></li> + <li><a href="{@docRoot}guide/topics/manifest/activity-element.html#lmode"> + {@code launchMode}</a></li> + <li><a href="{@docRoot}guide/topics/manifest/activity-element.html#reparent"> + {@code allowTaskReparenting}</a></li> + <li><a href="{@docRoot}guide/topics/manifest/activity-element.html#clear"> + {@code clearTaskOnLaunch}</a></li> + <li><a href="{@docRoot}guide/topics/manifest/activity-element.html#always"> + {@code alwaysRetainTaskState}</a></li> + <li><a href="{@docRoot}guide/topics/manifest/activity-element.html#finish"> + {@code finishOnTaskLaunch}</a></li> +</ul> + +<p>而您可以使用的主要意圖旗標包括:</p> + +<ul class="nolist"> + <li>{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK}</li> + <li>{@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP}</li> + <li>{@link android.content.Intent#FLAG_ACTIVITY_SINGLE_TOP}</li> +</ul> + +<p>在以下各節中,您將瞭解如何使用這些宣示說明屬性和意圖旗標,定義 Activity 與工作關聯的方式以及它們在返回堆疊中的行為。 +</p> + +<p>同時,還會分開討論工作與 Activity 如何在總覽畫面表示和進行管理。 +請參閱<a href="{@docRoot}guide/components/recents.html">總覽畫面</a>以取得詳細資訊。 +一般而言,您應該允許系統定義如何在總覽畫面中呈現工作與 Activity,而且不需要修改此行為。 +</p> + +<p class="caution"><strong>注意:</strong>大多數應用程式不應中斷 Activity 和工作的預設行為: +如果您判斷修改 Activity 的預設行為是必要的,請謹慎小心並記得測試啟動期間 Activity 的可用性,以及使用 [返回] 按鈕從其他 Activity 和工作瀏覽到此 Activity 的情況。請記得測試可能會與使用者預期的行為衝突的瀏覽行為。<em></em> + + +</p> + + +<h3 id="TaskLaunchModes">定義啟動模式</h3> + +<p>啟動模式可讓您定義 Activity 的新執行個體與目前工作關聯的方式。 +您可用兩種方法定義不同的啟動模式:</p> +<ul class="nolist"> + <li><a href="#ManifestForTasks">使用宣示說明檔案</a> + <p>當您在宣示說明檔案中宣告 Activity 時,您可以指定 Activity 啟動時該如何與工作關聯。 +</li> + <li><a href="#IntentFlagsForTasks">使用意圖旗標</a> + <p>當您呼叫 {@link android.app.Activity#startActivity startActivity()} 時,您可以在 {@link android.content.Intent} 包含一個旗標,宣告新 Activity 應如何 (或是否) 與目前的工作關聯。 + +</p></li> +</ul> + +<p>因此,如果 Activity A 啟動 Activity B,Activity B 可以在其宣示說明中定義它應如何與目前的工作 (如果有) 關聯,而且 Activity A 也能要求 Activity B 應如何與目前的工作關聯。 + +如果這兩個 Activity 皆定義 Activity B 應如何與工作關聯,相較於 Activity B 的要求 (如宣示說明中所定義),會優先採用 Activity A 的要求 (如意圖中所定義)。 + +</p> + +<p class="note"><strong>注意:</strong>某些宣示說明檔案中提供的啟動模式可能沒有對應的意圖旗標,同樣地,某些意圖旗標提供的啟動模式無法在宣示說明中定義。 + +</p> + + +<h4 id="ManifestForTasks">使用宣示說明檔案</h4> + +<p>當您在宣示說明檔案中宣告 Activity 時,您可以使用 <a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a> 元素的 <a href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code +launchMode}</a> 屬性,指定 Activity 應如何與工作關聯。 + +</p> + +<p><a href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code +launchMode}</a> 屬性可指定應如何將 Activity 啟動至工作內的指示。 +您可以為 +<code><a href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">launchMode</a></code> 屬性指定四種不同的啟動模式: +</p> + +<dl> +<dt>{@code "standard"} (預設模式)</dt> + <dd>預設。系統在啟動 Activity 的工作中建立新的執行個體,並將意圖路由至該處。 +Activity 可以具現化很多次,每個執行個體可屬於不同的工作,而且一個工作可以有多個執行個體。 +</dd> +<dt>{@code "singleTop"}</dt> + <dd>如果 Activity 的執行個體已經出現在目前工作的頂端,系統會透過呼叫其 {@link +android.app.Activity#onNewIntent onNewIntent()} 方法,將意圖路由至該執行個體,而不是建立新的 Activity 執行個體。 + +Activity 可以具現化很多次,每個執行個體可屬於不同的工作,而且一個工作可以有多個執行個體 (但僅限於返回堆疊頂端的 Activity 不是現有的 Activity 執行個體時<em></em>)。 + + + <p>例如,假設工作的返回堆疊包含根 Activity A 及 Activity B、C 及在最頂端的 D (堆疊為 A-B-C-D;D 在最頂端)。 +類型 D Activity 的意圖抵達。 +如果 D 有預設的 {@code "standard"} 啟動模式,則會啟動新的類別執行個體,且堆疊會變成 A-B-C-D-D。不過,如果 D 啟動模式為 {@code "singleTop"},D 的現有執行個體會透過 {@link +android.app.Activity#onNewIntent onNewIntent()} 接收意圖,這是因為它位於堆疊的最頂端 — 堆疊會維持 A-B-C-D。不過,如果類型 B Activity 的意圖抵達,則 B 的新執行個體會新增到堆疊中,即使其啟動模式為 {@code "singleTop"} 也是如此。 + + + +</p> + <p class="note"><strong>注意:</strong>建立新的 Activity 執行個體之後,使用者可按下 [返回]<em></em> 按鈕,返回之前的 Activity。 +但是,如果處理新意圖的是現有的 Activity 執行個體,則使用者無法按下 [返回]<em></em> 按鈕回到新意圖抵達 {@link android.app.Activity#onNewIntent +onNewIntent()} 之前的 Activity 狀態。 + + + +</p> +</dd> + +<dt>{@code "singleTask"}</dt> + <dd>系統會建立新的工作並在新工作的根目錄將 Activity 具現化。不過,如果 Activity 的執行個體已經出現在其他工作中,系統會透過呼叫其 {@link +android.app.Activity#onNewIntent onNewIntent()} 方法,將意圖路由至現有的執行個體,而不是建立新的執行個體。 + +一次只能有一個 Activity 執行個體。 + + <p class="note"><strong>注意:</strong>雖然 Activity 是在新工作中啟動,使用者仍可使用 [返回] 按鈕返回之前的 Activity。 +<em></em></p></dd> +<dt>{@code "singleInstance"}。</dt> + <dd>與 {@code "singleTask"} 一樣,差別在於系統不會將任何其他 Activity 啟動至保留執行個體的工作中。 +Activity 一律是其工作的唯一成員;使用此項目啟動的任何 Activity 會在個別的工作中開啟。 +</dd> +</dl> + + +<p>另外一個例子,Android 瀏覽器應用程式宣告網頁瀏覽器 Activity 應永遠在自己的工作中開啟 — 透過在 <a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a> 元素指定 {@code singleTask} 啟動模式。 +這表示如果您的應用程式發出開啟 Android 瀏覽器的意圖,其 Activity 不會與您的應用程式放在同一個工作中。<em></em> + + +而是會為瀏覽器啟動新的工作,或者如果瀏覽器已經有工作在背景執行,會將該工作帶出來處理新的意圖。 + +</p> + +<p>無論 Activity 在新工作啟動或與啟動該 Activity 之 Activity 的相同工作中啟動,使用者都能使用 [返回] 按鈕返回之前的 Activity。<em></em> +不過,如果您啟動指定 {@code singleTask} 啟動模式的 Activity,如果該 Activity 的執行個體存在於背景工作中,則該工作會整個移到前景。 + +此時,返回堆疊現在包含已帶出且位於堆疊頂端之工作的所有 Activity。 + +圖 4 說明這種類型的情況。</p> + +<img src="{@docRoot}images/fundamentals/diagram_backstack_singletask_multiactivity.png" alt="" /> +<p class="img-caption"><strong>圖 4.</strong>顯示含有啟動模式 "singleTask" 的 Activity 如何新增到返回堆疊。 +如果 Activity 已經是背景工作的一部份且有自己的返回堆疊,則整個返回堆疊都會帶出來,位於目前工作的最頂端。 + +</p> + +<p>如要進一步瞭解如何使用宣示說明檔案中的啟動模式,請參閱 <code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code> 元素,其中會有 {@code launchMode} 屬性和可接受值的詳細說明。 + + +</p> + +<p class="note"><strong>注意:</strong>您使用 <a href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code launchMode}</a> 屬性指定的 Activity 行為可被啟動 Activity 之意圖所含的旗標所覆寫,如下一節所述。 + +</p> + + + +<h4 id="#IntentFlagsForTasks">使用意圖旗標</h4> + +<p>啟動 Activity 時,您可以在傳送到 {@link +android.app.Activity#startActivity startActivity()} 的意圖中包含旗標,以修改 Activity 及其工作的預設關聯。 +您可以用來修改預設行為的旗標包括: +</p> + +<p> + <dt>{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK}</dt> + <dd>在新工作中啟動 Activity。如果工作已為您目前啟動的 Activity 執行,該工作會移到前景並復原至上個狀態,而且 Activity 會在 {@link android.app.Activity#onNewIntent onNewIntent()} 收到新的意圖。 + + + <p>這會產生與 {@code "singleTask"} <a href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code launchMode}</a> 值相同的行為,如上節所述。 +</p></dd> + <dt>{@link android.content.Intent#FLAG_ACTIVITY_SINGLE_TOP}</dt> + <dd>如果現在正在啟動的 Activity 是目前的 Activity (位於返回堆疊的頂端),則現有執行個體會收到 {@link android.app.Activity#onNewIntent onNewIntent()} 呼叫,而不會建立新的 Activity 執行個體。 + + + <p>這會產生與 {@code "singleTop"} <a href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code launchMode}</a> 值相同的行為,如上節所述。 +</p></dd> + <dt>{@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP}</dt> + <dd>如果正在啟動的 Activity 已在目前的工作中執行,則不會啟動新的 Activity 執行個體,而是會終結位於其上方的所有其他 Activity,且此意圖會透過 {@link android.app.Activity#onNewIntent onNewIntent()} 傳送到繼續執行的 Activity 執行個體 (現在位於頂端)。 + + + + <p>沒有任何 <a href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code launchMode}</a> 屬性值可以產生此行為。 +</p> + <p>{@code FLAG_ACTIVITY_CLEAR_TOP} 最常與 {@code FLAG_ACTIVITY_NEW_TASK} 搭配使用。 +一起使用時,這些旗標可以找出位於其他工作中的現有 Activity,然後將它放置於可以回應意圖的地方。 + + </p> + <p class="note"><strong>注意:</strong>如果指定 Activity 的啟動模式為 {@code "standard"},它也會從堆疊中移除,改為啟動新的執行個體處理傳入的意圖。 + + +這是因為當啟動模式為 {@code "standard"} 時,一律會為新的意圖建立新的執行個體。 + </p> +</dd> +</dl> + + + + + +<h3 id="Affinities">處理親和性</h3> + +<p>親和性<em></em>可指出 Activity 偏好屬於哪個工作。根據預設,相同應用程式的所有 Activity 間互相都有親和性。 +因此,根據預設,相同應用程式的所有 Activity 都偏好位於相同的工作。 +不過,您可以修改 Activity 的預設親和性。 +不同應用程式中定義的 Activity 可以共用親和性,或者相同應用程式中定義的 Activity 可以指派不同的工作親和性。 + +</p> + +<p>您可以使用 <a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a> 元素的 <a href="{@docRoot}guide/topics/manifest/activity-element.html#aff">{@code taskAffinity}</a> 屬性修改任何指定 Activity 的親和性。 + +</p> + +<p><a href="{@docRoot}guide/topics/manifest/activity-element.html#aff">{@code taskAffinity}</a> 屬性使用字串值,但必須與 <a href="{@docRoot}guide/topics/manifest/manifest-element.html"> +{@code <manifest>}</a> 元素中宣告的預設封裝名稱不同,因為系統使用該名稱來識別應用程式的預設工作親和性。 + + + +</p> + +<p>在兩種情況下會用到親和性:</p> +<ul> + <li>當啟動 Activity 的意圖包含 +{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} 旗標。 + + +<p>根據預設,新的 Activity 會啟動至 Activity (名為 {@link android.app.Activity#startActivity startActivity()}) 的工作中。 +系統會將它推入至與呼叫端相同的返回堆疊。 +不過,如果傳送至 +{@link android.app.Activity#startActivity startActivity()} 的意圖包含 {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} 旗標,則系統會找尋不同的工作來放置新的 Activity。 + +這通常是新工作。 +不過,它不一定要是新工作。如果現有工作中有與新 Activity 相同的親和性,Activity 會啟動至該工作中。 +如果沒有,會開始新的工作。</p> + +<p>如果此旗標導致 Activity 開始新的工作,而使用者按 [首頁] 按鈕離開它,必須要有方法可以讓使用者回來瀏覽這個工作。<em></em> + +有些實體 (例如,通知管理員) 總是從外部工作開始 Activity,從來不使用自己的工作,因此他們都會將 {@code FLAG_ACTIVITY_NEW_TASK} 放入傳送到 +{@link android.app.Activity#startActivity startActivity()} 的意圖。 + +如果您的 Activity 可以由外部實體呼叫且可能使用此旗標,記得要提供使用者獨立的方法回到啟動的工作,例如,透過啟動組件圖示 (工作的根 Activity 有一個 {@link android.content.Intent#CATEGORY_LAUNCHER} 意圖篩選器;請參閱下方的<a href="#Starting">開始工作</a>)。 + + + +</p> +</li> + + <li>當 Activity 的<a href="{@docRoot}guide/topics/manifest/activity-element.html#reparent"> +{@code allowTaskReparenting}</a> 屬性設為 {@code "true"}。 + <p>在這種情況下,當工作移到前景時,Activity 可以從其啟動的工作移到與其有親和性的工作。 +</p> + <p>例如,假設將報告所選城市天氣狀況的 Activity 定義為旅遊應用程式的一部份。 +它與相同應用程式中的其他 Activity 有相同的親和性 (預設的應用程式親和性),而且它允許與此屬性重設父代。 +當您的其中一個 Activity 開始氣象報告程式 Activity,它一開始屬於與您 Activity 相同的工作。 + +不過,當旅遊應用程式工作移到前景,氣象報告程式 Activity 就會重新指派給該工作,並在其中顯示。 +</p> +</li> +</ul> + +<p class="note"><strong>提示:</strong>如果從使用者的角度來看 {@code .apk} 檔案包含一個以上的「應用程式」,您可能會想要使用 <a href="{@docRoot}guide/topics/manifest/activity-element.html#aff">{@code taskAffinity}</a> 屬性對與每個「應用程式」關聯的 Activity 指派不同的親和性。 + +</p> + + + +<h3 id="Clearing">清除返回堆疊</h3> + +<p>如果使用者離開工作一段很長的時間,系統會清除根 Activity 以外所有 Activity 的工作 +。當使用者再次回到工作,只會復原根 Activity。 +系統會有這樣的行為是因為在一段很長的時間後,使用者很可能會放棄他們之前在做的工作,並回到工作開始其他新的工作。 + </p> + +<p>您可以使用下列一些 Activity 屬性來修改這個行為: </p> + +<dl> +<dt><code><a +href="{@docRoot}guide/topics/manifest/activity-element.html#always">alwaysRetainTaskState</a></code> +</dt> +<dd>如果這項屬性在工作的根 Activity 中設為 {@code "true"},則剛描述的預設行為不會發生。 +即使過了很長的一段時間,工作仍然會在堆疊保留所有的 Activity。 +</dd> + +<dt><code><a +href="{@docRoot}guide/topics/manifest/activity-element.html#clear">clearTaskOnLaunch</a></code></dt> +<dd>如果這項屬性在工作的根 Activity 中設為 {@code "true"},則剛描述的預設行為不會發生。 +即使過了很長的一段時間,工作仍然會在堆疊保留所有的 Activity。 +換句話說,它與 +<a href="{@docRoot}guide/topics/manifest/activity-element.html#always"> +{@code alwaysRetainTaskState}</a> 相反。即便使用者只離開工作一小段時間,使用者還是會回到工作的起始狀態。 +</dd> + +<dt><code><a +href="{@docRoot}guide/topics/manifest/activity-element.html#finish">finishOnTaskLaunch</a></code> +</dt> +<dd>這項屬性與 <a href="{@docRoot}guide/topics/manifest/activity-element.html#clear">{@code clearTaskOnLaunch}</a> 相似,但它在單一 Activity 上作業,而不是在整個工作。 + +它也會導致任何 Activity 離開,包含根 Activity。 +如果設成 {@code "true"},Activity 只會在目前的工作階段留在此工作中。 +如果使用者離開後再回到工作,該工作將不再存在。 +</dd> +</dl> + + + + +<h3 id="Starting">開始工作</h3> + +<p>您可以給予 Activity 一個意圖篩選器,將 +{@code "android.intent.action.MAIN"} 設定為指定的動作, +{@code "android.intent.category.LAUNCHER"} 設定為指定的類別,以便將該 Activity 設定為工作的進入點。 +例如:</p> + +<pre> +<activity ... > + <intent-filter ... > + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + ... +</activity> +</pre> + +<p>這類意圖篩選器可在應用程式啟動組件顯示 Activity 的圖示和標籤,讓使用者啟動 Activity 並回到 Activity 啟動後任何時間建立的工作。 + + +</p> + +<p>第二項功能很重要:使用者必須能夠在離開工作後,使用此 Activity 啟動組件回到此工作。 +由於這個原因,兩個將 Activity 標示為一律啟動工作的<a href="#LaunchModes">啟動模式</a> {@code "singleTask"} 和 +{@code "singleInstance"},應只能在 Activity 有 {@link android.content.Intent#ACTION_MAIN} 和 {@link android.content.Intent#CATEGORY_LAUNCHER} 篩選器時才能使用。 + + +例如,試想如果缺少篩選器會發生什麼情況: +意圖會啟動 {@code "singleTask"} Activity、起始新工作,然後使用者會花一些時間在該工作進行作業。 +之後,使用者按下 [首頁]<em></em> 按鈕。 +此工作現在會傳送到背景而且不會顯示。現在,使用者沒有辦法回到工作,這是因為應用程式啟動組件沒有代表此工作的項目。 +</p> + +<p>在您不希望使用者返回 Activity 的情況下,將 +<code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code> 元素的 +<a href="{@docRoot}guide/topics/manifest/activity-element.html#finish">{@code finishOnTaskLaunch}</a> 設定為 {@code "true"} (請參閱<a href="#Clearing">清除堆疊</a>)。 + +</p> + +<p>如要進一步瞭解工作和 Activity 在總覽畫面中的顯示及管理方式,請參閱<a href="{@docRoot}guide/components/recents.html">總覽畫面</a>。 + +</p> + +<!-- +<h2>Beginner's Path</h2> + +<p>For more information about how to use intents to +activate other application components and publish the intents to which your components +respond, continue with the <b><a +href="{@docRoot}guide/components/intents-filters.html">Intents and Intent +Filters</a></b> document.</p> +--> |