在 Mac 上使用 Android USB 網路共用
心血來潮跑去用 Boot camp 安裝了 Windows 10 Insider Preview (載點)
有幾個不錯的新功能,有像 Mac 或 Ubuntu 那樣的虛擬桌面了,可以讓不同桌面放不同的工作很方便。
然後視窗貼邊/分割的功能變的更好用,拉視窗去撞桌面左右兩邊,要往左或往右將畫面切一半的時候,會自動跳出其他視窗讓你選另一半要放什麼。
Win 8 的 Metro APP(現在好像叫 Modern APP)變成可以不用全螢幕了,像是小算盤這種東西可以小小的一個視窗用就好,何必把整個螢幕佔滿呢?
我是裝中文版的 Cortana 還不能用,而且畢竟是預覽版,有些地方有BUG,還有中文化部分也有些地方沒翻譯到或顯示亂碼,可以期待一下正式上市,我有買 Win 8 可以免費直升:D
這篇主要是要筆記一下新增的快捷鍵:
Win + ↑/↓/←/→ 控制視窗放大縮小左右貼邊
Win + Tab 類似 Mac 的 Mission Control 列出所有視窗和桌面,讓你選視窗或是切換虛擬桌面
Win + Ctrl + ←/→ 切換虛擬桌面
Win + Ctrl + D 建立虛擬桌面
Win + Ctrl + F4 關閉目前虛擬桌面
另外,MBPR 的鍵盤比標準的鍵盤少了一些按鍵,其中我滿常用的 Print Screen 要用組合鍵來按
Print Screen = fn + Shift + F11
Scroll Lock = fn + Shift + F12
詳細:https://support.apple.com/zh-tw/HT5636
原文:http://developer.android.com/guide/components/intents-filters.html
Intent 是傳遞訊息的物件,可以用來跟其他 APP Component 要求一個動作,Intents 讓幾個 Components 之間交流更加方便,以下有三個使用情況:
Activity
:startActivity()
來啟動一個新的 Activity 實例。Intent 描述要啟動哪種 Activity 還有攜帶必須的資料。Service
:startService()
來啟動 Service 去處理一次性操作(像是下載一個檔案)。Intent 描述要啟動的 Service 來啟動並攜帶必要的資料給他。bindService()
綁定服務到其他 Component。更多資訊詳見 Services 手冊。Broadcast
:sendBroadcast()
、sendOrderedBroadcast()
或 sendStickyBroadcast()
傳送廣播到其他 APP。Intent 有兩種類型:
Explicit intents(明確意圖)有具體說明要啟動的 Component 名稱(完整的 Class name),你會在你自己的 APP 使用 Explicit intent 來啟動 Component 因為你知道 Activity 或 Service 的 Class name。例如啟動一個新的 Activity 來回映使用者的動作或是啟動一個 Service 在背景下載檔案。
Implicit intents(含蓄意圖)沒有指定的 Component 名稱,Intent 宣告要做的動作,並允許其他 APP 的 Component 去應對他。舉例來說,如果你要顯示出使用者在地圖中的位置,你可以使用 Implicit intent 來要求其他有這方面能力的 APP 來顯示出地圖中特定的位置。
當你建立一個 Explicit intent 來啟動 Activity 或 Service,系統會立刻啟動 Intent 物件指定的 APP Component。
當你建立 Implicit intent,Android 系統會透過比較 Intent 與 Intent filters 在 Manifest 長案中宣告的內容來尋找並啟動適合的 Component。如果 Intent 符合 Intent filter,系統會啟動 Component 並傳遞 Intent 物件,如果有多個 Intent filter 相符,系統會顯示一個對話,讓使用者選擇要用那個 APP。
Intent filter 在 APP 的 Manifest 中宣告,用來說明 Intent 想接收 Component 的類型。例如,為一個 Activity 宣告 Intent filter,讓其他 APP 可以為了特定類型的 Intent 直接啟動你的 Activity。同樣的,你可以不幫你的 Activity 宣告任何 Intent filter,他就只能被 Explicit Intent 啟動。
小心:為了保證你的 APP 是安全的,總是使用 Explicit intent 來啟動 Service 並且不要為你的 Services 宣告 intent filter,使用 Implicit internt 啟動 Service 危害安全的,因為你不能確定那個服務會回應這個 Intent,使用者看不到那個 Service 被啟動了,從 Android 5.0(API level 21)開始,系統會在你使用 Implicit intent 呼叫
bindService()
時丟出例外。
上圖為 Implicit intent 如何重送給系統來啟動其他 Activity:[1] 一個 Activity
(Activity A) 用 Action 的內容建立一個 Intent
然後傳入 startActivity()
方法。 [2] Android 系統搜尋所有 APP,尋找一個符合 Intent
的 Intent filter
。當找到時 [3] 系統會啟動符合的 Activity
(Activity A)呼叫他的 onCreate()
方法並將 Intent
傳給他。
Intent 物件攜帶資訊讓 Android 系統確定那個 Component 要被啟動(像是確切的 Component 名稱或是 Component 種類),為了正確的執行這個動作也會有額外的資訊讓接收的 Component 使用(像是要採取的行動,和這個動作需要的資料)。
Intent 主要的資訊內容如下:
要啟動的 Component 名稱。
這是可選項目,但這是要讓 Intent explicit 的話就是很重要的資訊,代表 Intent 應該只能被傳遞給被定義名稱的 APP Component,如果沒有 Component name,Intent 就會是 Implicit 的,系統藉由其他的 Intent 資訊(像是下面會介紹的 Action、Data 和 Category)來決定應該要將 Intent 傳遞給那個 Component。如果你需要啟動你 APP 中特定的 Component,你應該具體說明 Component name。
Note:當啟動 Service 時,永遠都要明確指定 Component name,否則你無法確定那個 Service 會回應你的 Intent,使用者也看不到那個 Service 啟動了。
這裡是要傳入 ComponentName
物件,也就是目標 Component 的完整 Class name,包含 APP 的 Package name。例如,com.example.ExampleActivity
。你可以呼叫 setComponent()
、setClass()
、setClassName
或是用 Intent 的建構式來設定 Component name。
是一個字串,用來表示要做的動作(像是 view 或 pick)。
在 Broadcast intent 的例子中,發生了正在報告的 Action,Action 主要決定了 Intent 其他部分的結構,特別是他包含了哪些 Data 或 Extras。
你可以在你的 APP(或是給其他 APP 用來請求你 APP 中的 Components) 指定自己的 Actions 來給 Intent 使用,但是你通常應該使用 Intent 類別或其他 framework 的類別中 constants defined 的 Action。這裡有一些用來啟動 Activity 的常見 Actions:
startActivity()
。更多一般定義的 Action 內容,參考 Intent 類別的文件,其他 Actions 被定義在 Android framework 中,像是 Settings 的 Action 用來在系統設定 APP 中打開特定的畫面。
你可以使用 setAction()
或是 Intent 的建構式來為 Intent 設定具體的 Action。
如果你要定義自己的 Action,請使用你 APP 的 Package name 作為前綴,像是:static final String ACTION_TIMMETRAVEL = "com.example.action.TIMETRAVEL";
Data 是參考到要用到的資料的 URI(URI 物件)和/或 MIME 類型的資料。資料的類型是由 Intent 的 Action 決定。例如,如果 Action 是 ACTION_EDIT
,資料應該要包含被編輯的文件的URI。
當 Intent 建立時,通常需要具體說明資料的類型(MIME Type)還有資料的 URI。例如,儘管 URI 的格式是相似的,Activity 允許顯示影像但可能不允許播放影片檔。所以具體說明資料的 MIME type 可以幫助 Android 系統尋找最佳的 Component 來回應你的 Intent。然而 MIME type 有時可以透過 URI 推理出來,特別是當資料是 content
時(URI 指示資料在裝置上的位置並由 ContentProvider
控制,這樣就能讓資料的 MIME type 變成系統可以看見的)。
要只設定資料 URI 可以呼叫 setData()
,只設定 MIME type 可以呼叫 setType()
,必要時可以用 setDataAndType
一次把 URI 和 MIME type。
Category 是包含有關要處理這個 Intent 的 Component 類型的額外資訊字串。Intent 中可以放入任意數量的 Category 描述,但大多數的 Intent 不需要 Category,這裡有一些常見的 Categories:CATEGORY_BROWSABLE
目標 Activity 允許自己啟動網頁瀏覽器來顯示透過連結參考到的資料,像是影像或 e-mail 訊息。CATEGORY_LAUNCHER
這種 Activity 是任務中初始的 Activity,並且列於系統的應用程式啟動程序。
可以在 Intent 的文件來察看完整的 Category 清單。
可以用 addCategory
來加入 Category。
以上列出的屬性(Component name、Action、Data、Category)用來為 Intent 定義出他的特性。Android 系統透過讀取這些屬性,可以決定哪個 APP Component 應該被啟動。
然而 Intent 可以攜帶一些額外的資料,而不影響系統決定使用哪個 Component,Intent 也支援下面這些資訊:
Extra 是一組 Key-value pair,用來攜帶完成請求所需要的額外的資訊,就像一些 Action 使用特定種類的資料 URI,有些 Action 也需要使用特定的 Extra。
可以使用各種 putExtra()
方法來加入 Extra 資料,每個方法都有兩個參數:Key 名稱和 Value。你也可以建立一個 Bundle
物件將所有 Extra 用 putExtra()
插入 Intent 的 Bundle 中。
例如當你用 ACTION_SEND
來建立一個 Intent 用來傳送 email,你可以用 EXTRA_EMAIL
Key 來宣告 to
收信人,並且用 EXTRA_SUBJECT
來宣告 subject
主旨。
Intent 類別宣告很多 EXTRA_*
常數來標準化資料型別,如果你需要宣告自己的 Extra keys(為了讓你的的 APP 收到 Intent),請確認他包含你的 APP 的 package name 像下面這樣:static final String EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS";
Flags 定義在 Intent 類別中,作為 Intent 的 Metadata 的功能。Flags 可以告訴 Android 系統如何啟動一個 Activity(例如,Activity 應該是屬於哪一個 task
),還有在啟動後如何對應他(例如,他是否屬於最近活動的 Activities 列表)。
更多資訊詳見 setFlags() 方法
Explicit intent 用來啟動特定的 APP Component,像是你的 APP 中特定的 Activity 或是 Service,要建立一個 Explicit intent 要為 Intent 物件定義 Component 名稱,其他 Intent 屬性則是可選的。
例如,要為你的 APP 建立一個 Service,用來從網路上下載檔案,叫做 DownloadService
,你可以像這樣來寫程式碼:// Executed in an Activity, so 'this' is the Context
// The fileUrl is a string URL, such as "http://www.example.com/image.png"
Intent downloadIntent = new Intent(this, DownloadService.class);
downloadIntent.setData(Uri.parse(fileUrl));
startService(downloadIntent);
Intent(Context, Class)
建構式傳入 APP Context 還有 Component 的 Class 物件。因此在這個 APP 中這個 Intent 明確的啟動 DownloadService
類別。
更多關於建立與啟動 Service 的資訊,詳見 Service 手冊。
原文:http://developer.android.com/guide/topics/security/permissions.html
這篇翻的有點爛…
Android 是一個 privilege-separated(權限分離)的作業系統,其中每個應用程式使用不同的系統 ID 來執行(Linux user ID 和 group ID),其中一部份系統也有獨立的 ID。Linux 依此將每個應用程式從系統中彼此隔離。
其他劃分更細的安全功能由 Permission
的機制提供,他會強行限制 Process 可以執行的特定操作。每個 URI Permissions 允許特定的訪問去存取明確的資料區塊。
這篇文章會描述開發者如何讓應用程式使用 Android 提供的安全功能,Android Open Source Project 有提供 Android Security Overview 可以看看。
Android 安全架構的設計重心在於-預設所有應用程式沒有 Permission 去做任何影響其他應用程式、系統或是使用者的操作,包含讀取或寫入使用者的個人資料(包含通訊錄和電子郵件)、讀取或寫入其他應用程式的檔案、執行網路訪問或是保持設備清醒(不進入休眠)等等。
因為每個 Android 應用程式是在沙盒程序中執行,應用程式必須明確的分享資料和資源,他們必須宣告他們需要的 Permissions 以要求基本沙盒沒有提供的額外功能。
應用程式靜態的宣告他們需要的 Permission,Android 系統會在安裝應用程式時即時要求使用者同意讓 APP 使用這些 Permission,Android 沒有動態的(在執行期中)產生 permissions 的機制,因為這樣會讓使用者經驗變的更複雜而影響安全性。
應用程式沙盒不依賴於編譯應用程式的技術。特別是 Dalvik VM 不是安全的邊界,任何 APP 可以執行 Native code(詳見 Android SDK)。任何類型的應用程式-JAVA、Native 或是混和的都是在沙盒中使用同樣的方式,而且彼此之間有相同程度的安全性。
(這句在工三小XD)
所有的 APK(.apk 檔案)必須簽署一個憑證,憑證的 Private key 在應用程式的開發者手上。這個憑證用來識別應用程式的作者,不需要由 Certificate Authority(CA,應該是指有公信力的第三方憑證機構)簽署,他允許 Android 應用程式使用自行簽核的憑證,用來讓系統能允許或拒絕應用程式訪問 Signature-level permissions(要有相同憑證的才能使用的 Permissions)、允許或拒絕應用程式請求給予和另一個應用程式相同的 Linux ID(要有相同的憑證才能使用同一個 Linux ID)。
Android 在安裝的時候會給予每個 package 一個明確的 Linux User ID,當 package 在裝置上時這個 ID 都會保持不變,在不同的裝置上,相同的 package 可能會有不同的 UID,重要的是每個 package 在特定裝置上會有不同的 UID。
因為 Process 等級的安全措施,任兩個 Package 的程式通常不能在同一個 Process 中執行,因為他們要用不同的 Linux 使用者來執行。你可以在 AndroidManifest.xml
的 manifest
標籤中為每個 Package 使用 sharedUserId
屬性,讓他們分配到相同的 User ID。為了兩個 Packages 的安全性,他們被視為相同的應用程式,使用相同的 User ID 和檔案 Permission。請注意,為了保持安全,只有相同簽署的兩個應用程式可以使用相同的 User ID。
應用程式儲存的任何資料會被分配到該應用程式的 User ID 而不是一般的存取其他 Packages。當使用 getSharedPreferences(String, int)
、openFileOutput(String, int)
或是 openOrCreateDatabase(String, int, SQLiteDatabase.CursorFactory)
建立新檔案時,你可以使用 MODE_WORLD_READABLE
或/和 MODE_WORLD_WRITEABLE
標記來允許任何其他的 Package 去讀寫檔案。當這些標記被設定時,檔案的擁有者還是你的應用程式,但會被設定為全域讀寫的 Permissions ,讓其他應用程式可以看到他。
基本的 Android 應用程式預設是沒有相關的 Permission,意思是他不能做任何事情,這樣會對使用者經驗或是裝置上的資料有不好的影響,要使用裝置上被保護的功能,你必須在 AndroidManifest.xml
加入 <uses-permission>
標籤來宣告應用程式需要的 Permissions。
例如,這個 APP 需要顯示剛收到的 SMS 訊息:<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.app.myapp" >
<uses-permission android:name="android.permission.RECEIVE_SMS" />
...
</manifest>
在 APP 安裝時,Package Installer 授予 APP 請求的 Permission,基於檢查 APP 宣告的這些 Permissions 和與使用者互動的簽名。使用者確認不是在應用程式執行時完成,APP 會在安裝時准許特定的 Permission,就可以使用他需要的功能,或是 Permission 沒有被允許,任何嘗試使用這些功能就會失敗,不會提示使用者。
通常 Permission failure 會有 SecurityException
的結果回傳給應用程式。但是不能保證每個地方都會發生。舉例來說 sendBroadcast(Intent)
方法會檢查資料傳送到每個 Receiver 的 Permissions,當這個方法結束時,如果 Permission failure 時,你將不會接到這個例外。然而在幾乎所有狀況下,Permission failure 將會記錄在 system log 中。
然而在一般的使用情況下(像是當 APP 從 Google Play Store 安裝),如果使用者沒有允許 APP 要求的 Permissions,APP 將會無法安裝。所以一般來說你不必擔心執行中失去 Permissions,因為 APP 已經安裝好了救代表所有你要求的 Permissions 已經得到了允許。
Permissions 由 Android 系統提供,可以在 Manifest.permission
找到。任何應用程式也可能強制定義他自己的 Permission,所以這邊沒辦法列出所有可用的 Permissions。
在你的程式操作過程中,特定的 Permission 可能會在很多地方被強制執行:
警告:隨著平台版本更新,可能會出現新的 Permission 限制,為了使用特定的 API,你的 APP 可能需要去要求之前版本不需要的 Permission。因為現存的 APP 可以自由的訪問那些 API,但 Android 可能會在 APP 的 Manifest 中要求新的 Permission 來避免 APP 在新的平台版本中壞掉。Android 基於
targetSdkVersion
屬性的值來決定 APP 是否需要 Permission,如果這個值低於 Permission 新增的版本,Android 會加入這個 Permission。舉例來說
WRITE_EXTERNAL_STORAGE
Permission 在 API Level 4 被加入,用來限制存取共享的儲存空間。如果你的targetSdkVersion
在 3 以下,這個 Permission 會在新的 Android 版本中被加入你的 APP。要小心這件事發生在你的 APP,你的 APP 會在 Google Play 的清單中列出這些 Permission 要求,就算你沒有明確的要求他們。
要避免這個狀況,並移除你沒有用到的預設 Permission,要盡可能的將
targetSdkVersion
設的更高,你可以在 Build.VERSION_CODES 文件中看到哪些版本新增了哪些 Permission。(白話一下:Android 版本更新以後,可能會對一些以前不需要要求 Permission 的功能加上新的 Permission 來做限制,在你的 APP 沒有更新的情況下,在新版本的 Android 中,不管你的 APP 有沒有去用這些 Permission 他都會自動要求這些新增的 Permission,以避免你的 APP 有用到這些功能卻沒有要求這些 Permission 而當掉,為了讓新版 Android 使用者安裝你的 APP 時不要被一堆存取權嚇到,要盡量的把
targetSdkVersion
設高一點來避免被自動加上這些新的 Permission。)
要實施你自己的 Permission,你必須在 AndroidManifest.xml
中使用一個以上的 <permission>
標籤宣告他們。
舉例來說,可以用宣告 Permission 來讓 APP 控制誰可以啟動他的其中一個 Activity:<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.me.app.myapp" >
<permission android:name="com.me.app.myapp.permission.DEADLY_ACTIVITY"
android:label="@string/permlab_deadlyActivity"
android:description="@string/permdesc_deadlyActivity"
android:permissionGroup="android.permission-group.COST_MONEY"
android:protectionLevel="dangerous" />
...
</manifest>
<protectionLevel>
屬性是必須的,描述 Permission 潛在的風險並告訴系統 APP 要求這個 Permission 時該如何決定是否要允許,詳見文件。
<permissionGroup>
屬性是可選的,只是用來幫助系統對使用者顯示 Permissions。你通常會設定為一般系統群組(android.Manifest.permission_group
)或在一些罕見的狀況中用自己定義的群組,應該要優先使用現有的群組,因為這樣可以簡化讓使用者看到的 Permission UI。
注意 label
與 description
應該要用來描述 Permission。這些 string resources 可以在瀏覽 Permission 清單(android:label)時顯示給使用者看,或是顯示單一 Permission 的細節(android:description)。label
應該要寫的比較簡短,用幾個字描述這個 Permission 保護的關鍵部分。description
應該要用幾句話來描述這個 Permission 被允許去做什麼事。我們習慣用兩個句子來寫 description
,第一句描述 Permission,第二句警告使用者,如果允許這個 Permission 會發生什麼不好的事情。
這是為 CALL_PHONE Permission 撰寫 label
和 descript
的範例:<string name="permlab_callPhone">直接撥打電話</string>
<string name="permdesc_callPhone">允許應用程式在你沒有干涉的狀況下撥打電話。惡意的應用程式可能會讓你產生預料之外的帳單。注意這不能允許應用程式撥打緊急電話。</string>
你可以用應用程式設定或是 shell command adb shell pm list permissions
來看到目前系統中定義的 Permission。要用應用程式設定可以從 設定 > 應用程式 選擇一個 APP 並捲動到下面就能看到這個 APP 使用的 Permission。如果你是開發者,你可以用 adb 的 -s
選項來顯示 Permissions 看起來向下面這樣:$ adb shell pm list permissions -s
All Permissions:
Network communication: view Wi-Fi state, create Bluetooth connections, full
Internet access, view network state
Your location: access extra location provider commands, fine (GPS) location,
mock location sources for testing, coarse (network-based) location
Services that cost you money: send SMS messages, directly call phone numbers
...
限制訪問系統或應用程式中全部 Components 的高級 Permissions 可以透過 AndroidManifest.xml
來應用,在你想套用這個設定的 Component 的 android:permission
屬性中為 Permission 命名即可限制存取這個 Component。
Activity
Permissions(用於 <activity>
標籤)限制誰可以啟動相關的 Activity
。Permission 會在 Context.startActivity()
和 Activity.startActivityForResult()
時檢查,如果呼叫者沒有要求這些權限,SecurityException
的例外會被丟出來。
Service
(用於 <service>
標籤)限制誰可以啟動與綁定相關的 Service
。Permission 會在 Context.startService()
和 Context.stopService()
和 Context.bindService()
時檢查,如果呼叫者沒有要求這些權限,SecurityException
的例外會被丟出來。
BroadcastReceiver
(用於 <receiver>
標籤)限制誰可以送出廣播到特定的 Receiver
。Permission 會在 Context.sendBroadcast()
回傳,當系統常識傳送提交的廣播給特定 Recevier
時檢查,如果結果是 Permission Failure 不會給呼叫者丟出例外,就只會不傳送 Intent
而已。相同的,Permission 可以讓 Context.registerReceiver()
控制誰可以傳送廣播給程式中註冊的 Receiver。另一方面 Permission 也可以在呼叫 Context.sendBroadcast()
時限制哪個 BroadcastReceiver
物件可以允許接受廣播(詳見下面)。
ContentProvider
(用於 <provider>
標籤)限制誰可以在 ContentProvider
訪問資料。(Content Providers 有重要的額外安全措施叫做 URI Permisions
會在後面介紹)不像其他 Components,他有兩種獨立的 Permission 屬性,你可以設定 android:readPermission
來限制誰可以從 Provider 中讀取資料,也可以設定 android:writePermission
來限制誰可以寫入他。注意!如果 provider 同時被讀取和寫入 Permission 保護,只持有寫入 Permission 不代表你可以讀取 Provider。Permission 會在你第一次取回 Provider(如果你沒有讀取或寫入其中一種 Permission,會丟出一個 SecurityException 的例外)並且對他做操作時被檢查。使用 ContentResolver.insert()
、ContentResolver.update()
和 ContentResolver.delete()
需要寫入 Permission,在這些情況下沒去要求 Permission 就呼叫他們會出現丟出 SecurityException 的結果。
此外自訂的 Permission 可以傳送 Intents
去註冊 BroadcastReceiver
(如上面描述的),你也可以在傳送廣播時要求你訂立的 Permission。藉由傳入 Permission 的字串來呼叫 Context.sendBroadcast()
,你可以要求 Receiver
的 APP 必須要持有這個 Permission 才能接收你的廣播。
注意,Receiver
和 Broadcaster
可以要求 Permission。當他們要求 Permission 時 Permission 的檢查需要傳給 Intent
來傳遞給相關的目標。
呼叫 Service 時可以用 Context.checkCallingPermission()
方法來自訂微調 Permission,傳入你希望得到的 Permission 字串,他會回傳一個整數來表示 Permission 在呼叫這個方法的 Process 中有沒有被允許(回傳 PERMISSION_GRANTED 表示有允許或 PERMISSION_DENIED 沒允許)。注意,這個方法只有正在執行從別的 Process 呼叫的情況下能使用,通常是透過一個從 Service 發佈的 IDL 介面或是其他方式提供給其他 Process。
這裡有一些實用的方法來檢查 Permission,如果你有其他 Process 的 Pid,你可以使用 Context 的方法 Context.checkPermission(String, int, int)
來檢查這個 Pid 的 Permission。如果你有其他 APP 的 Package Name,你可以直接使用 PackageManager 的方法 PackageManager.checkPermission(String, String)
來查出該 Package 是否有被允許特定的 Permission。
到目前為止我們介紹的標準的 Permission 系統對於 Content Providers 是不足的,Content Provider 可能會想要保護自己的讀取和寫入 Permission,同時直接使用他的用戶端也需要特定的 URI 傳給其他應用程式讓他們操作這些資料。郵件應用程式的附件就是一個典型的例子,訪問郵件被 Permissions 保護,因為這是私密的用戶資料,然而如果有個連結到影像附件的 URI 要傳給 Image Viewer,Image Viewer 將會沒有 Permission 而無法開啟附件,因為他沒有理由持有訪問所有 e-mail 的 Permission。
解決這個問題的方案是 per-URI permissions:當啟動 Activity 或將結果回傳給 Activity,可以設定 Intent.FLAG_GRANT_READ_URI_PERMISSION
和/或 Intent.FLAG_GRANT_WRITE_URI_PERMISSION
。這樣不管他是否有任何 Permission 去存取 Intent 對應的 Content Provider 中的資料,都能在 Intent 中讓收到的 Activity 允許 Permission 訪問特定資料的 URI。
這種機制允許一個常見的 Capability-style 模型-當使用者互動(打開附件、選擇一個聯絡人等)操作被臨時的 Fine-grained Permission 允許。這樣可以減少應用程式必須的 Permission 只留下與應用程式行為有關的行為。
微調 URI Permission 需要和 Content Provider 合作持有這些 URI。強烈建議 Content Providers 實做這個功能,宣告 android:grantUriPermissions
屬性或 <grant-uri-permissions> 標籤
。
更多資訊可以在這些方法的介紹中找到:
Context.grantUriPermission()
Context.revokeUriPermission()
Context.checkUriPermission()
原文:http://developer.android.com/guide/practices/compatibility.html
Android 設計為可以在非常多不同種類的裝置上執行,像是手機、平版或是電視。作為開發人員,你的 APP 有很大範圍、不同裝置的潛在用戶。為了讓你的 APP 可以成功的在各種裝置上執行,他應該要能相容一些功能差異,並提供能適應不同螢幕配置的靈活 UI。
為了方便你朝著這個目標努力,Android 提供一個動態 APP framework 讓你可以提供特定配置的靜態 APP Resources(像是為不同螢幕尺寸撰寫不同的 XML layouts),然後 Android 會基於目前裝置的配置來讀取適合的 Resources。因此設計你的 APP 時配合一些預先規劃與額外的 APP Resources 你可以發佈能針對多樣化裝置優化使用者經驗的單一 APK。
如果有必要的話你可以具體說明 APP 的功能需求來控制哪種裝置可以在 Google Play 商店中安裝你的 APP。這裡會解釋如何讓你控制哪種裝置可以存取你的 APP、如何讓你的 APP 讓正確範圍的使用者看到。更多關於如何讓 APP 調整成適合不同的裝置請閱讀 Supporting Different Devices。
當你閱讀更多關於 Android 開發的東西,你可能會在各種情況中遇到相容性
一詞,有兩種相容性:裝置相容性
和 APP 相容性
。
因為 Android 是一個 Open source 專案,任何生產者都可以製造執行 Android OS 的裝置,只有在他可以執行為 Android 執行環境撰寫的 APP 時這個裝置才符合 Android 相容性。關於確切的詳情,Android 執行環境是由 Android Compatibility Program 定義,每個裝置必須通過 Compatibility Test Suite(CTS)的測試才能視為有相容性。
至於 APP 開發者,你不需要擔心裝置是否符合相容性,因為只有和 Android 相容的裝置會有 Google Play Store。所以你可以放心,從 Google Play 商店中安裝你的 APP 的就是使用與 Android 相容的裝置。
然而,你不需要考慮你的 APP 是否和各種配置的潛在的裝置是有相容性的,因為 Android 執行在各式各樣的裝置配置上,有些功能不會每個裝置都有,像是一些裝置可能沒有電子羅盤。如果你的 APP 主要功能需要使用電子羅盤,你的 APP 只對擁有電子羅盤的裝置有相容性。
APP 可以透過 Playform APIs 來利用 Android 支援的各種功能,有些功能是基於硬體的(像是電子羅盤),有些是基於軟體的(像是 APP Widgets),還有些有需求平台版本。並非所有裝置都支援所有功能,所以你可能需要基於 APP 需求的功能去控制你的 APP 可用性。
為了幫你的 APP 取得更多的潛在用戶,你需要盡可能的讓你的單一 APK 檔可以支援更多的裝置配置,在大多情況下,你可以在執行期讓一些可選功能失效,並且提供具有不同配置的 APP Resources(像是為不同螢幕尺寸準備不同的 layouts)。如果有必要你可以依據下面這些裝置特性,透過 Google Play 商店限制 APP 的可用性:
為了讓你管理基於裝置功能的 APP 可用性,Android 幫所有硬體與軟體功能定義了可能不適用於所有裝置的 feature ID,例如電子羅盤的 feature ID 為 FEATURE_SENSOR_COMPASS
,APP Widgets 的是 FEATURE_APP_WIDGETS
。
如果必要的話,當使用者的裝置沒有提供你 APP 的 manifest 檔案中 <uses-feature>
裡宣告的功能,你可以不讓使用者安裝。
例如你的 APP 最於沒有電子羅盤的裝置是無意義的,你可以在 manifest 中宣告電子羅盤為 required
像這樣:<manifest ... >
<uses-feature android:name="android.hardware.sensor.compass"
android:required="true" />
...
</manifest>
Google Play Store 會比較使用者裝置有沒有提供 APP 需求的功能來確認你的 APP 是否相容於每個裝置。如果裝置沒有提供所有 APP 要求的功能,使用者就不能安裝這個 APP。
如果你的 APP 的主要功能不需要要求裝置功能,你應該把 required
屬性社為 false
並且在執行期檢查裝置提供的功能,如果 APP 的某些功能在裝置上是不可用的,可以適當的將 APP 的功能減少。例如你可以呼叫 hasSystemFeature()
來查詢功能是否可用:PackageManager pm = getPackageManager();
if (!pm.hasSystemFeature(PackageManager.FEATURE_SENSOR_COMPASS)) {
// This device does not have a compass, turn off the compass feature
disableCompassFeature();
}
關於可用於控制使用者能不能透過 Google Play Store 安裝你的 APP 的篩選器的所有資訊,請看 Filters on Google Play 文件。
Note:有些 system permissions 需要裝置功能可用才能用,像是如果 APP 要求使用 BLUETOOTH 的 permission,也一定會需求
FEATURE_BLUETOOTH
裝置功能。
你可以來關閉篩選器讓 APP 能在沒有藍芽的裝置使用,在<uses-feature>
標籤中 把required
的設定改為false
即可。更多關於裝置功能需求的資訊請閱讀 Permissions that Imply Feature Requirements。
不同的裝置可能會執行不同版本的 Android Platform,像是 Android 4.0 或 Android 4.4。每個後續的平台版本通常會新增一些先前版本不能用的 APIs。為了要指示哪些 APIs 是可用的,每個平台版本會定義一個 API level。例如 Android 1.0 是 API level 1,Android 4.4 是 API level 19。
API level 允許你宣告 APP 相容的最低版本,在<uses-sdk>
manifest 標籤中加入 minSdkVersion
屬性。
例如,Calendar Provider APIs 是在 Android 4.0(API level 14)新增的,如果你的 APP 需要使用這個 APIs 來提供功能,你必須將 API level 14 宣告為 APP 的最低支援版本,像這樣:<manifest ... >
<uses-sdk android:minSdkVersion="14" android:targetSdkVersion="19" />
...
</manifest>
minSdkVersion
屬性宣告 APP 相容的最低版本,targetSdkVersion
屬性宣告 APP 經過你優化的最高版本。當你使用 Android APIs 的文件,Android 為使用以前平台版本 APIs 的 APP 提供了後續版本的相容性,所以你的 APP 總是能相容未來版本的 Android。
Note:
targetSdkVersion
屬性不會阻止你的 APP 在更高平台版本的裝置上安裝,但還是很重要,因為他指示系統你的 APP 是否應該從更新的版本繼承改變過的行為,如果你沒有將targetSdkVersion
更新到最新的版本,系統會假設你的 APP 在最新版本執行時需要向下相容一些行為。例如 Android 4.4 改變的行為中,Alarms 由AlarmManager
APIs 建立的,目前 Alarms 是不精確的,因為系統會批次的做警告來節省電力,但如果你的 APP target API level 小於 19,系統還是會保留先前 API 的行為。
然而,如果你的 APP 使用較新平台版本新增的 APIs ,但不是用在核心功能上,你可以在執行期檢查 API level,當 API level 太低時來適當的減少這個功能。在這個情況下為你的核心功能盡可能的設定 minSdkVersion
為最低值,然後比較目前系統的版本—Build.VERSION.SDK_INT
與 Build.VERSION_CODE.版本代號
是不是符合你想檢查的 API level,例如:if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
// Running on something older than API level 11, so disable
// the drag/drop features that use ClipboardManager APIs
disableDragAndDrop();
}
Android 執行在很多種不同尺寸的裝置,從手機、平板到電視。為了用螢幕尺寸去分類裝置,Android 為每個裝置定義了兩個特性:screen size(螢幕的物理尺寸)和 screen density(螢幕上像素的物理密度,叫做 DPI, Dot per inch)。為了更簡單的設定不同的配置,Android 概括的把他們分類,讓他們更容易被標記:
你的 APP 預設會相容所有螢幕尺寸和密度,因為系統會為你的 UI layout 對每種螢幕進行適當的調整,並為每種螢幕選擇使用適當的 Image resources。然而,你必須為不同的螢幕尺寸加入專門的 layouts 還有為不同的螢幕密度提供優化的點陣圖來為每種螢幕配置優化使用者經驗。
更多關於如何為不同的螢幕建立替代 Resources 還有如何在特定的螢幕尺寸限制你的 APP,請閱讀 Supporting Different Screens。
你可能會需要為了商業或法律的因素去限制 APP 的可用性,例如一個顯示倫敦火車時刻表的 APP 對英國以外的用戶不太有用,在這種情況下,Google Play 商店在開發者的控制台中提供過濾選項,允許你非技術性的去控制 APP 可用性,像是使用者的地區或電信商。
技術相容性的篩選(像是需求某個硬體元件)的資訊總是包含在你的 APK 檔案中,但是非技術相容性(像是地理位置)總是由 Google Play 開發者控制台來管理。
原文:http://developer.android.com/guide/components/fundamentals.html
Android apps 用 JAVA 這個程式語言撰寫,與其他資料或 Resource 檔案一起由 Android SDK tools 編譯成一個 APK(Android Package 副檔名為 .apk
),APK 檔案包含了 Android APP 的所有內容,可以用來讓 Android 裝置安裝 APP。
當 APP 安裝在裝置上時,每個 Android APP 活在他自己的安全沙盒中:
如此 Android 系統實踐了最小權限原則(principle of least privilege),也就是說每個 APP 預設只能存許他需要的 Components 來做他的工作。這樣建立了一個非常安全的環境讓 APP 不能存取其他系統沒給他權限的部分。
然而還是有些方法讓 APP 和其他 APP 分享資料,還有讓 APP 存許系統服務:
考量這些東西改變了 Android APP 存在系統中的基礎,這份文件將介紹:
Android 系統設計的獨特之處是任何 APP 可以啟動另一個 APP 的 Component。
例如:如果你希望使用者從攝影機拍攝一張影像,可能是從別的 APP 來拍攝,然後你的 APP 可以使用影像,而不是自己開發一個擷取影像的 Activity。
你不需要自己加入或是連結相機 APP 的程式碼。你可以簡單的啟動攝影 APP 來擷取影像,完成後照片可以回傳給你的 APP,讓你使用它。在使用者看來就好像相機 APP 是你的 APP 的一部份。
當系統啟動一個 Component,為 APP 啟動一個 Process(如果它沒有在執行的話)並實現 Component 所需的類別。
例如:如果你的 APP 開啟一個攝影機 APP 的 Activity 來拍攝照片,這個 Activity 的 Process 是在攝影機 APP 而不是你的 APP 中。
Android 不像其他大多數的系統,Android 的 APP 沒有單一的入口點(例:沒有main() 函式)。APP 運行在系統中的一個單獨的 Process 中,每個 APP 有他的檔案權限,限制其他的 APP 存取,因此你的 APP 不能直接啟動其他 APP 的元件。
而 Android 系統是可以的,在其他 APP 啟動一個 Component,你必須提供一個訊息給系統,指定你的意圖(Intent)來開啟 Component,然後系統啟動 Component 為你服務。
App components 是 Android app 的重要基石,有四種不同類型的 Components 每種類型有不同的目的、並有明顯的生命週期來定義 component 何時被創建和銷毀。
Activity 代表使用者介面中的一個畫面,例如電子郵件 APP,他有一個 Activity 用來顯示新郵件、另一個 Activity 撰寫電子郵件、另一個 Activity 用來讀取郵件。
雖然這個電子郵件 APP 的 UX 是由這些 Activities 一起工作來建立的,但每個 Activity 是相互獨立的。
因此其他 APP 可以啟動這些 Activities 中的任何一個(如果這個電子郵件 APP 允許的話)。
例如:相機 APP 可以開啟電子郵件 APP 中撰寫郵件的 Activity 以便使用者分享他們的照片。
Activity 被作為 Activity 的子類別實現,詳閱 Activity 的 developer guide。
Service 是在後台執行,負責執行長時間運行的操作或是遠端處理。
Service 不提供使用者介面。
例如:一個 Service 可以在背景撥放音樂,而使用者可以在其他 APP 或上網,不會被撥放音樂的 Activity 阻擋到介面。另一個 component 像是一個 Activity 可以啟動該服務,讓它執行並與它結合,以便和它互動。
Service 被作為 Service 的子類別實現,詳閱 Service 的 developer guide。
Content provider 管理 APP 的共享資料,你可以儲存資料在檔案系統、SQLite 資料庫、網路上或是任何你的 APP 可以訪問的儲存位置。
通過 Content provider,其他 APP 可以查詢甚至修改資料(如果使用者允許的話)。
例如:APP 系統提供一個 Content provider 來管理聯絡人訊息,因此任何 APP 拿到權限時都可以查詢聯絡人訊息的 Content provider(ContactsContract.Data) 來讀寫個人訊息。
Content provider 也可以專用在你的 APP 讀寫資料而不共享給其他 APP。
例如:筆記本 APP 使用一個 Content provider 來儲存筆記。
Content provider 被實現為 ContentProvider 的子類別,必須實現一組標準 API 來讓其他 APP 存取,詳閱 ContentProvider 的 developer guide。
用來回應全系統的廣播通知。
有許多廣播從系統發出,例如:廣播會宣布螢幕被關閉、電池低電量或是螢幕截了一張圖。
APP 也可以發起廣播,例如:為了讓其他 APP 知道資料已經被下載到設備上供他們使用。
雖然 Broadcast receiver 沒有顯示出使用者介面,他們可以在收到廣播時建立一個狀態欄通知來提醒使用者。
通常廣播接收器只是一個關聯其他 components 的門戶,工作量很小。
例如:廣播接收器可能會根據一個事件來啟動一個服務。
Broadcast receiver 被實現為 BroadcastReceiver 的子類別,每個廣播由一個 Intent 物件來傳遞,詳閱 BroadcastReceiver 的 developer guide。
其中三種 Compnents-Avtivity, Service, Broadcast Receiver 通過一個非同步消息來啟動,稱為 Intent(意圖)。
執行中,Intent 讓 Component 互相結合(可以把它看成是要求其他 Component 啟動的使者)。
Intent 由 Intent 物件建立,他定義一個訊息來啟動其他特定的 Component 或是其他特定 Component 的”型別”,一個 Intent 可以是明示的或暗示的。
(這邊應該是說 Intent 可以啟動某一個特定的組件,或是只指定啟動某一類型的組件,像是要求要啟動某個相機 APP,但是讓使用者自己選要用哪個相機 APP 來拍照之類的)
對於 Activities 和 Services,一個 Intent 定義一個動作來執行(例如:來看或是發送東西),而且可能作用於特定 URI 中的資料(該 Component 被啟動可能需要知道的其他事情)。
例如:一個 Intent 可能傳達一個請求給一個 Activity 來顯示影像或打開網頁。
某些情況下,你可以啟動 Activity 來獲得結果,在這種情況下,這個 Activity 也可以回傳一個 Intent(例如你可以發出一個 Intent 讓使用者選擇一個聯絡人資料,並將他回傳給你,回傳的 Intent 包含一個URI指向被選擇的聯絡人)。
在 Broadcast Receiver中,Intent 只是簡單地把要公布的東西廣播出來(例如一個廣播指示設備電池電量低時,只包含一個已知的字串 “battery is low”)。
Components 中只有 Content Provider 不是由 Intent 來啟動。
相反的,他是由目標透過 ContentResolver 來請求。
Content Resolver(內容解析器)處理所有與內容提供者之間的所有直接交易(存取?),使該 Component 不需要與提供者執行交易,而是呼叫 ContentResolver 物件中的方法。
這樣在內容提供者和 Component 請求訊息之間留下一層抽象(這是為了安全性)。
有以下不同的方法來啟動每種Component:
更多Intent的資訊,詳閱 Intents and Intent Filters 文件。
在 Android 系統啟動 App Component 之前,系統必須透過讀取 App 的 AndroidManifest.xml 檔案來知道有那些 Component 存在。你的 App 必須聲明所有 Component 在這個檔案中,而這個檔案必須在 App 專案的跟目錄下。
Manifest 除了聲明 App 的 Component 以外還做了很多事情,像是:
Manifest 的主要任務是告訴系統這個 App 有哪些元件和元件的相關資訊。
例如:一個 manifest 檔案可以像以下這樣聲明 Activity:<?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>
<application>
中的 android:icon
屬性填入 Resource 作為 APP 的識別 Icon
<activity>
的 android:name
屬性說明完整的 Activity 子類別的物件名稱,android:label
屬性用 String 說明 Activity 的 User-visible label
你可以在這裡用這些標籤元素宣告所有的元件:
<activity>
<service>
<receiver>
<provider>
更多關於 Manifest 檔案的結構,可以閱讀 App Manifest 文件。
如同上面討論的,如要啟動 Components 你可以用 Intent 來啟動 Activities, Services 和 Broadcast Receivers,你可以在 Intent 中設定一個明確的 Component 作為目標(用物件名稱)。然而 Intents 真正厲害在於 Implicit Intents 的概念,Implicit Intents 可以簡單的描述了要操作的動作類型(另外也可以加上你希望操作這些動作的資料),並允許系統在裝置上找到一個 Component,讓系統去啟動與操作他。如果有不只一個 Components 可以操作這個 Inetnet 描述的動作,使用者就可以自己選出一個 Component 來用。
系統從裝置上的其他 APP 的 Manifest 檔案提供中的 Intent filters
識別出可以回應這個 Intent 的 Components。
當你在 Manifest 宣告一個 Activity,你可以選擇在把 Intent filter 加進去,這樣他就能回應給其他 APP 的 Intents。你可以加入 <intent-filter>
作為 Component 的子元素來宣告 Intent filter。
例如你要建立一個 email APP,有一個撰寫新郵件的 Activity,你可以宣告 Intent filter 來回應 Send
Intents,像這樣:<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>
當其他 APP 用 ACTION_SEND 建立一個 Intent,然後呼叫 startActivity(),系統可能就會啟動你的 Activity 來讓使用者撰寫並寄出 email。
更多關於建立 Intent filters 的資訊可以看 Intents and Intent Filters 文件。
Android 支援提供不同的功能與效能的多種裝置,為了防止 APP 安裝在某個缺乏你需要的功能的裝置上,清楚的定義你的 APP 支援哪種類行的裝置是很重要的,可以在 Manifest 中宣告裝置和軟體需求。這些宣告大多只是提供一些訊息,系統不會讀取他們,但是外在的服務,像是 Google Play 會讀取他們,提供使用者搜尋時做為篩選出適合他們裝置的 APP 用。
例如,如果你的 APP 需要 攝影機還有使用 Android 2.1(API Level 7)的APIs,你需要在你的 Manifest 檔案宣告這些需求:<manifest ... >
<uses-feature android:name="android.hardware.camera.any"
android:required="true" />
<uses-sdk android:minSdkVersion="7" android:targetSdkVersion="19" />
...
</manifest>
現在,沒有攝影機還有 Android 版本低於 2.1 的裝置就不能在 Google Play 安裝你的 APP。
然而你也可以宣告 APP 使用攝影機,但不是必須的。在這個案例中,你的 APP 會把 required
屬性設為 false
,然後在執行期中檢查裝置有沒有攝影機來決定停用攝影機相關的功能。
更多關於管理 APP 和不同裝置的相容性的資訊在 Device Compatibility 文件中。
Android APP 不只是由程式碼組成的,他需要獨立於原始碼的 Resources,像是影像、影片檔和任何為 APP 做出視覺表現有關的東西。例如你會需要定義一些動畫、選單、樣式、顏色還有定義出 Activity UI 的 Layout XML 檔。使用 APP Resources 可以更簡單的更新 APP 的許多東西而不用更動程式碼。藉由提供一組 Alternative Resources ,可以讓你針對多樣化的裝置去優化配置(像是不同的語言或螢幕尺寸)。
SDK Build tools 為你的 Android Project 中的每個 Resource 定義一個唯一的整數 ID,讓你可以使用在 APP 程式碼中參考到其他 XML 中定義的 Resource。例如你的 APP 中有個影像檔叫做 logo.png
(存在 res/drawable/
目錄中),SDK tools 會產生一個 Resource ID 叫做 R.drawable.logo
,你可以用這個 ID 參考到這張影像來把他加入到 UI 中。
其中最重要的是,為你的程式碼分別提供 Resources,讓 Alternative Resources 可以有為不同裝置配置的能力。例如在 XML 中定義 UI 的字串,你可以把字串翻譯成其他語言,並把這些字串獨立在另一個檔案,然後放在加上 language Qualifier
的檔案目錄中(像是 res/values-fr/
用來存法語),根據使用者的語言設定,Android 系統會幫 UI 套用適合的字串。
Android 為你的 Alternative Resources 支援非常多不同的 Qualifier
,Qualifier
是在檔案資料夾名稱中的短字串,根據裝置設定來定義哪種 Resources 該被使用。舉例來說你應該常常會幫 Activity 建立不同的 layout 來對應不同的螢幕方向和尺寸,例如當裝置在 Portrait Orientation(高)的狀態下,你可能會希望 Layout 中的按鈕是垂直排列的,但是當螢幕是在 Landscape Orientation(寬)的狀態下,你會希望按鈕是水平對齊的。你可以定義兩個不同的 Layouts 並分別幫他們的檔案目錄名稱套用適合的 Qualifier
,然後系統會根據現在的裝置方向,自動的套用適合的 Layout。
Bound Services 是一個 Client-Server 界面的伺服器。允許元件(向是 Activity)去綁定服務、傳送請求、收到答覆還有主持 Interprocess communication (IPC)。
原文:http://developer.android.com/guide/components/processes-and-threads.html
當一個 APP 元件啟動,而且沒有其他 APP 元件在執行,Android 系統會開啟一個新的 Single thread Linux process 給這個 APP 執行。
基本上,所有同一個 APP 的元件是在同一個 Thread 和 Process(叫做 “main” thread)上執行。如果一個 APP 元件啟動,而這個 APP 已經有 Thread 了(其他元件還在這個APP上執行),這時元件會啟動在同一個 Thread 上執行。
你也可以安排你 APP 上的不同的元件在獨立的 Process 上執行,你可以為任何 Process 建立額外的 Thread。
這個文件將討論 Process 和 Thread 在 Android 應用程式中如何運作。
如果你需要自己控制 Process,可以從 manifest 檔案來控制。
manifest 裡面有各種 元件的標籤 <activity>
, <service>
, <receiver>
, <provider>
,用 android:process
屬性可以更動元件執行的 Process。
你可以幫每個元件設定這個屬性讓他們執行在自己的 Process 中,你也可以設定 android:process
讓不同 APP 的元件執行在同一個 Process,讓他們共享同一個 Linux user ID 並且用同一個憑證註冊。
<application>
也支援 android:process
屬性,用來設定所有元件的預設值。
Android 決定在某些時候關閉 Process,當記憶體不足以供應其他使用者正在使用的 Process 需求時,APP 元件執行的 Process 會被殺掉。當重新執行這些元件時,會有新的 Process 給他們執行。
要決定殺掉某個 Process,Android 系統會衡量他們對於使用者的重要性,例如:會先殺掉用來服務某個不再顯示畫面的 Activity 會比看的到畫面的 Activity 容易被殺掉。
決定是否終止 Process 是由裡面執行的元件來決定,決定刪除哪個 Process 的規則會在下面討論。
Android 系統會試著讓 APP Process 盡可能的存活更久,但是最後還是會需要刪除舊的 Process 來回收記憶體給新的、更重要的 Process 使用。要決定哪個 Process 要留著,哪個 Process 要殺掉,系統基於元件的狀態給每個元件執行的 Process 一個 importance hierarchy
(重要性等級),低重要性的 Process 會先被刪掉,再來是第二低重要性的…等,來回收系統資源。
重要性有五個等級,以下介紹不同種類的 Process,依照重要性排列(第一個是最重要的,會最後殺掉):
Foreground process
(前景)Visible process
(可見)Service process
(服務)Background process
(背景)Empty process
(空的)Android 會盡可能的將 Process 的重要性等級評的較高,如果一個 Process 提供一個服務而且是可見的,他的 Process 評分會是可見 Process 而不是服務 Process。
當應用程式啟動,系統會建立一個 Thread 來給應用程式執行,稱為 “main”。這個 Thread 非常重要因為他負責指派事件給合適的 UI widgets 包含 drawing 事件。他也是應用程式從 Android UI toolkit(android.widget 和 android.view packages 的元件)和元件交流的 Thread。因此 main thread 有時候會呼叫 UI thread。
系統不會為每個元件的實例建立獨立的 Thread,所有的元件在同一個 Process 中執行,實做在 UI thread 中,這個 Thread 指派系統呼叫每個元件。因此回應 system callbacks 的方法總是在 UI thread 中執行(像是 onKeyDown() 這種回應使用者的動作或是生命週期 callback 方法)。
舉例來說,當一個使用者觸碰畫面上的按鈕,APP 的 UI thread 指派 touch event 給 widget,讓他將自己設定為按下的狀態並告訴 event queue 將請求作廢,UI thread 將請求移除以後再通知 widget 讓他重新繪製。
當你的 APP 密集的進行影響 UI 的工作,這個 Single thread 的模型將會變的效能很差,除非你正確的實現你的應用程式。
具體來說,如果每一件事都發生在 UI thread,像是從網路訪問請求資料庫這種長時間的操作,將會讓你目前的 UI 卡住。當這個 Thread 被卡住,就沒辦法指派事件,包含 drawing event,在使用者的觀點,應用程式看起來就像當掉了。更糟的是如果 UI thread 被卡住超過幾秒鐘(目前大約是5秒)使用者會看到這個應用程式沒有回應(ANR)的視窗問使用者要不要關掉 APP,使用者通常會決定關掉你的 APP 並且很生氣的把他移除。
另外 Android UI toolkit 並不是 Thread-safe。所以你不能在 Worker thread 操作 UI ,你必須在 UI thread 進行所有對 UI 的操作,因此對 Android 的 Single thread 模型有兩個簡單的規則:
因為 Single thread 模型像上面說的那樣,不讓 UI 卡住的去回應應用程式 UI 非常重要,如果你要進行一些不會即時反應的操作,你應該要確保讓這些事在別的 Thread 中執行(background
或 worker
thread)。
例如,下面有一些 click listener 的程式碼,要在一個獨立的 Thread 中下載一個影像並讓他顯示在 ImageView:public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
Bitmap b = loadImageFromNetwork("http://example.com/image.png");
mImageView.setImageBitmap(b);
}
}).start();
}
乍看之下看起來可以順利執行,因為他建立一個新的 Thread 來負責網路操作,然而這違反了 Single thread 模型第二條規則:不要從 UI thread 以外的地方存取 Android UI toolkit。
這裡簡單的從 Worker thread 取代 UI thread 去修改 ImageView,這會造成未定義或是意外的行為的結果,如果出問題DEBUG的時候可能會難以追蹤又耗時。
要修正這個問題,Android 提出幾個方法來從其他 Thread 存取 UI thread:
例如你可以用 View.post(Runnable)
來修正上面的程式碼:public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png");
mImageView.post(new Runnable() {
public void run() {
mImageView.setImageBitmap(bitmap);
}
});
}
}).start();
}
這樣就變成 Thread-safe 了:網路操作在其他 Thread 結束,同時 ImageView 由 UI thread 操作。
然而如果有一組複雜的操作,這類的程式碼會很複雜而且難以維持,要處理更多與 Worker thread 複雜的互動,你可以考慮在你的 Worker thread 使用 Handler 來處理和 UI thread 之間的訊息傳遞。
或許有更好的解決方法,就是繼承 AsyncTask 這個類別,他可以簡單的處理 需要和 UI 互動的 Worker thread 任務。
AsyncTask 允許你在 UI 做非同步的工作,他把會造成 Blocking 的操作放在 Worker thread,之後在發佈結果到 UI thread,這樣就不需要自己去掌握 thread 和 Handle。
要使用這個,你必須繼承 AsyncTask 並在子類別實現 doInBackground()
Callback 方法,這會在 Background thread 執行。要更新你的 UI,你需要實現 onPostExecute()
,這將會傳遞 doInBackground()
的結果並在 UI thread 執行,所以你可以安全的更新你的 UI,然後你就可以呼叫 execute()
在 UI thread 執行任務。
舉例來說,如果你用 AsyncTask 實現上面的範例:public void onClick(View v) {
new DownloadImageTask().execute("http://example.com/image.png");
}
private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
/** The system calls this to perform work in a Worker thread and
* delivers it the parameters given to AsyncTask.execute() */
protected Bitmap doInBackground(String... urls) {
return loadImageFromNetwork(urls[0]);
}
/** The system calls this to perform work in the UI thread and delivers
* the result from doInBackground() */
protected void onPostExecute(Bitmap result) {
mImageView.setImageBitmap(result);
}
}
這樣你的程式碼就變的安全又簡單了,因為會把工作分別在 Worker thread 和 UI thread 上完成。
如果要瞭解如何使用這個類別,你可以閱讀 AsyncTask 的參考資料,這裡只會對他如何運作做一個簡單的概述:
注意:另一個可能會遇到的問題是由於執行期的配置改變(像是使用者改變了畫面的方向),你使用的 Worker thread 在 Activity 可能會意外的重新啟動,可能會破壞你的 Worker thread。
要知道如何在重新啟動時保持留的任務,還有當 Activity 被消除時如何正確的取消工作,請看 Shelves 範例程式的原始碼。
在某些情況下,你實現的方法可能會在不同的 Thread 中被呼叫,所以必須要寫成 Thread-safe 才行。
主要是讓方法可以被遠端呼叫,像是 Bound Services 中的方法。
TODO
原文:http://developer.android.com/guide/topics/graphics/drawable-animation.html
可以用 xml 像下面這樣來定義動畫,檔案放在 res/drawable
目錄下,這個範例中檔名叫做 rocket_thrust.xml
。<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="true">
<item android:drawable="@drawable/rocket_thrust1" android:duration="200" />
<item android:drawable="@drawable/rocket_thrust2" android:duration="200" />
<item android:drawable="@drawable/rocket_thrust3" android:duration="200" />
</animation-list>
android:oneshot
為 true
時,動畫會從第一張影像開始播放一次,停在最後一張,若為 false
動畫會一直重複播放。
需要使用 ImageView
來顯示 Drawable 動畫、Drawable 動畫的類別叫做 AnimationDrawable
,要播放動畫要呼叫 AnimationDrawable.start()。
有兩種方法可以設定動畫:
在 ImageView 直接把動畫資源設定好,AnimationDrawable,再用 getBackground() 拿出來。AnimationDrawable rocketAnimation;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ImageView rocketImage = (ImageView) findViewById(R.id.rocket_image);
rocketImage.setBackgroundResource(R.drawable.rocket_thrust);
rocketAnimation = (AnimationDrawable) rocketImage.getBackground();
}
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
rocketAnimation.start();
return true;
}
return super.onTouchEvent(event);
}
這個方法需要多用一個 Resources
來取得 Drawable Resource 給 AnimationDrawable,再用 setImageDrawable() 設定給 ImageView。AnimationDrawable rocketAnimation;
Resources res;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
res = getResources();
ImageView rocketImage = (ImageView) findViewById(R.id.rocket_image);
rocketAnimation = (AnimationDrawable) res.getDrawable(R.drawable.rocket_thrust);
rocketImage.setImageDrawable(rocketAnimation);
}
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
rocketAnimation.start();
return true;
}
return super.onTouchEvent(event);
}
上面兩種方法的最下面有個 onTouchEvent() 要點一下畫面才會呼叫 start() 開始播放動畫,如果要讓動畫在 Activity 一載入就開始播放,要用 onWindowFocusChanged()
當 Window 被 Focus 時就會動了(一般會想說在 OnCreate() 裡面呼叫 start(),但是這時候 AnimationDrawable 其實還沒載入到 Window,所以不能在這裡呼叫 start() )。@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
rocketAnimation.start();
}
書上有提到可以用程式碼建立 Drawable Animation,把上面的範例直接改會變成下面這樣。AnimationDrawable rocketAnimation;
Resources res;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
res = getResources();
ImageView rocketImage = (ImageView) findViewById(R.id.rocket_image);
//--------------
rocketAnimation = new AnimationDrawable;
rocketAnimation.setOneShot(true);
rocketAnimation.addFrame(res.getDrawable(R.drawable.rocket_thrust1), 200);
rocketAnimation.addFrame(res.getDrawable(R.drawable.rocket_thrust2), 200);
rocketAnimation.addFrame(res.getDrawable(R.drawable.rocket_thrust3), 200);
//--------------
rocketImage.setImageDrawable(rocketAnimation);
}
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
rocketAnimation.start();
return true;
}
return super.onTouchEvent(event);
}
變成直接把 AnimationDrawable new 出來,再把原本寫在 xml 裡面的東西設定給他。