Android資訊清單檔詳解(二),android資訊清單檔詳解
我們知道,Android系統的各個模組提供了非常強大的功能(比如電話,電源和設定等),通過使用這些功能,應用程式可以表現的更強大,更靈活。不過,使用這些功能並不是無條件的,而是需要擁有一些許可權。接下來,我們就開始講解另一個非常重要的知識點——應用程式許可權聲明,其中主要包括應用程式的許可權聲明,自訂應用程式的存取權限和SDK版本限定。
1.<uses-permission>——應用程式的許可權申請
| 許可權 |
描述 |
| android.permission.ACCESS_NETWORK_STATE |
允許應用程式訪問網路狀態 |
| android.permission.ACCESS_WIFI_STATE |
允許應用程式訪問WI-FI狀態資訊 |
| com.android.voicemail.permission.ADD_VOICEMALL |
允許應用程式往系統中添加一封語音信箱 |
| android.permission.BATTERY_STATS |
允許應用程式更新手機電池統計資訊 |
| android.permission.BIND_APPWIDGET |
允許代理程式更新AppWidget服務哪個應用程式可以訪問AppWidget的資料 Launcher是使用此許可權的一個執行個體 |
| android.permission.BLUETOOTH |
允許應用程式串連一個已經配對的藍牙裝置 |
| android.permission.BLUETOOTH_ADMIN |
允許應用程式主動發現和配對藍牙裝置 |
| android.permission.BROADCAST_PACKAGE_REMOVED |
允許醫用程式發送應用程式套件組合已經卸載的通知 |
| android.permission.BROADCAST_SMS |
允許應用程式廣播簡訊回執通知 |
| android.permission.BROADCAST_STICKY |
允許應用程式廣播Sticky Intent。 有些廣播的資料在其廣播完成後被放在系統中,這樣應用程式可以快速存取它們的資料,而無需等到下一個廣播到來 |
| android.permission.CALL_PHONE |
允許應用程式初始化一次撥打電話 |
| android.permission.CAMERA |
請求訪問攝像裝置 |
| android.permission.CHANGE_CONFIGURATION |
允許應用程式修改當前的配置,比如語言種類,螢幕方向等。比如,我們的設定模組就使用了這個許可權 |
| android.permission.CHANGE_NEWWORK_STATE |
允許應用程式改變串連狀態 |
| android.permission.CHANGE_WIFI_STATE |
允許應用程式改變WI-FI串連狀態 |
| android.permission.DEVICE_POWER |
允許應用程式訪問底層裝置電源管理 |
| android.permission.EXPAND_STATUS_BAR |
允許應用程式展開或者收合狀態列 |
| android.permission.INSTALL_LOCATION_PROVIDER |
允許應用程式安裝一個資料提供者到本地管理器中 |
| android.permission.INSTALL_PACKAGES |
允許應用程式安裝另一個應用程式 |
| android.permission.INTERNET |
允許應用程式開啟網路。 如果希望開發一個和網路相關的應用程式,那麼首先應該考慮是否需要這個許可權 |
| android.permission.KILL_BACKGROUND_PROCESSES |
允許應用程式調用killBackgroundProcesses()方法 |
| android.permission.MODIFY_PHONE_STATE |
允許修改電話狀態,但不包括撥打到電話 |
| android.permission.MOUNT_FORMAT_FILESYSTEMS |
允許應用程式格式化可移除的外部存放裝置 |
| android.permission.MOUNT_UNMOUNT_FILESYSTEMS |
允許應用程式掛載或者卸載外部存放裝置 |
| android.permission.NFC |
允許應用程式執行NFC的輸入輸出操作 |
| android.permission.READ_CALENDAR |
允許應用程式讀取日曆的資料 |
| android.permission.READ_CONTACTS |
允許應用城西讀取連絡人的資料 |
| android.permission.READ_PHONE_STATE |
允許應用程式訪問電話狀態 |
| android.permission.READ_SMS |
允許應用程式訪問簡訊資訊 |
| android.permission.RECEIVE_BOOT_COMPLETED |
允許應用程式在系統完成以後接受到android.intent.action.BOOT_COMPLETED廣播 |
| android.permission.RECEIVE_MMS |
允許應用程式監控MMS |
| android.permission.RECEIVE_SMS |
允許應用程式監控SMS |
| android.permission.RECEIVE_WAP_PUSH |
允許應用程式監控WAP的推送資訊 |
| android.permission.SEND_SMS |
允許應用程式主動發送短息 |
| android.permission.SET_TIME |
允許應用程式設定系統時間 |
| android.permission.SET_TIME_ZONE |
允許應用程式設定系統時區 |
| android.permission.SET_WALLPAPER |
允許應用程式設定案頭壁紙 |
| android.permission.STATUS_BAR |
允許應用程式操作(開啟,關閉,禁用)狀態列和它的表徵圖 |
| android.permission.VIBRATE |
允許應用程式允許應用程式訪問震動裝置 |
| android.permission.WAKE_LOCK |
允許應用程式使用電源管理器的螢幕鎖功能 |
| android.permission.WRITE_CALENDAR |
允許使用者寫入行事曆資料。如果我們只申請了這個許可權,那麼我們對日曆的資料只有寫入權限沒有讀許可權 |
| android.permission.WRITE_CONTACTS |
允許使用者寫入連絡人資料,如果我們只申請了這個許可權,那麼我們對連絡人的資料只有寫入許可權,沒有讀許可權 |
| android.permission.WRITE_EXTERNAL_STORAGE |
允許應用程式把資料寫入外部存放裝置 |
| android.permission.WRITE_SETTINGS |
允許應用程式讀寫系統設定 |
| android.permission.WRITE_SMS |
允許應用程式寫簡訊 |
應用程式在不同的情境下可能需要上表所示的某些許可權,比如當我們需要使用SD卡時,則需要申請SD卡相關許可權。下面我們舉例來解釋這個問題。
在這個執行個體中,我們將改造HelloWorld應用程式,並在sdcard的根目錄下添加一個名為“abc.txt”的文字檔。由於需要訪問外部儲存設器,因此需要申請android.permission.WRITE_EXTERNAL_STORAGE許可權,否則代碼將會失敗。具體步驟如下所示。
①需要在HelloWorld應用程式的AndroidManifest.xml檔案中添加相應的許可權,如下列代碼所示:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
②要在原來的代碼中添加一些建立檔案的代碼,如下所示:
public class MainActivity extends FragmentActivity { private static final String SDCARD= Environment.getExternalStorageDirectory()+File.separator; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if(savedInstanceState==null){ getSupportFragmentManager().beginTransaction().add(android.R.id.content,new FileFragment()).commit(); } } public static class FileFragment extends Fragment { public FileFragment(){} @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view=inflater.inflate(R.layout.file_fragment,container,false); Button mybut= (Button) view.findViewById(R.id.mybut); mybut.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { File sdcardFile=new File(SDCARD+"abc.txt"); try { sdcardFile.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } }); return view; } }}
③啟動程式,這時在sdcard所連結的目錄下,我們會發現已經建立了abc.txt檔案,如所示。
最後我們來做一個實驗,將AndroidManifest.xml檔案中的<uses-permission>節點刪除,在相同的目錄下並沒有發現所建立的檔案,這樣操作之後,我們就能在日誌裡面發現一些異常資訊,如所示。
從日誌中我們可以發現程式拋出了java.io.IOException異常,並且提示“Permission denied”,而它發生的地方,正好是建立檔案的地方。因此,我們可以得出一個結論,在試圖讀寫外部存放裝置的時候,必須先要申請android.permission.WRITE_EXTERNAL_STORAGE這個許可權,否則程式拋出警告性異常,相關操作也就無法進行。
Android提供了豐富的軟硬體功能模組,它能讓應用程式變得強大,開發過程也更便捷,但在使用前,必須要為應用程式申請必要的許可權。這點非常重要,否則應用程式就會出現莫名其妙的錯誤。
正如前面的執行個體中看到的結果,如果沒有相應的許可權,就無法建立檔案,而程式並沒有顯示一個異常的提示,這時我們就可能要花費大量的時間去找問題的根源。
因此,本人建議大家開發之前仔細分析需求,分析應用為什麼功能,而這些動能是否需要許可權才可以訪問。
2.<permission>節點——自訂應用程式的存取權限
前面我們學習了如何使用許可權。其實,應用程式除了可以使用許可權之外,還可以定義自己的許可權,用來限制對本應用程式或其他應用程式的特殊組件或功能訪問。在這裡,我們來學習<permission>節點的作用——如何聲明自己的許可權。
手動向AndroidManifest.xml檔案中添加一個<permission>節點,它只能包含在<manifest>節點下,其文法如下所示:
<permission android:description="string resource"
android:icon="drawable resource"
android:logo="drawable resource"
android:label="string resource"
android:name="string"
android:permissionGroup="string"
android:protectionLevel=["normal"|"dangerous"|"signature"|"signatureOrSystem"]/>
以上代碼中,需要說明的有以下3個屬性。
①android:name:聲明許可權的名稱。這個名稱必須是唯一的,因此,應該使用Java風格的命名,比如com.test.permission.TEST。
②android:permissionGroup:聲明許可權從屬於哪一個許可權組,這個許可權組可以是Android先行編譯的,也可以是自訂的。下表列除了Android系統先行編譯的系統許可權組。
| 許可權組名稱 |
描述 |
| android.permission-group.ACCOUNTS |
用於直接存取由帳號管理器管理的帳號 |
| android.permission-group.COST_MONEY |
用於使使用者不需要直接參与就可花錢的許可權 |
| android.permission-group.DEVELOPMENT_TOOLS |
與開發特徵相關的許可權群 |
| android.permission-group.HARDWARE_CONTROLS |
用於提供直接存取裝置硬體的許可權 |
| android.permission-group.LOCATION |
用於允許使用者訪問使用者當前位置的許可權 |
| android.permission-group.MESSAGES |
用於允許應用程式以使用者的名義發送資訊或者攔截使用者收到的資訊的許可權 |
| android.permission-group.NETWORK |
適用於提供網路服務的存取權限 |
| android.permission-group.PERSONAL_INFO |
適用於提供訪問到使用者私人資料的許可權,如連絡人,日曆事件和電子郵件資訊 |
| android.permission-group.PHONE_CALLS |
適用於關聯訪問和修改電話狀態的許可權,比如攔截去電,讀取和修改電話的狀態 |
| android.permission-group.STORAGE |
與SD卡訪問有關的許可權組 |
| android.permission-group.SYSTEM_TOOLS |
與系統API有關的許可權組 |
③android:protectionLevel:描述了隱含在許可權中的潛在風險,該屬性的值可以是下表中的一個字串。
| 值 |
意義 |
| normal |
預設值。低風險的許可權,它可以使請求的應用程式訪問孤立的應用程式級的功能,給其他應用程式,系統或者使用者帶來最小的風險。系統在安裝時,會自動授予這種類型的許可權給請求的應用程式,無須使用者明確聲明。 |
| dangerous |
高風險許可權,它將事請求的應用程式能訪問使用者的私人資料或者控制那些會對使用者產生負面影響的裝置。由於這種許可權存在潛在的風險,系統可能不會自動被賦予請求的應用程式。例如,任何一個由應用程式請求的危險許可權可能會顯示給使用者,並且在處理之前被要求確認。例如,以下許可權就屬於此類許可權。 <permission android:name="android.permission.RECEIVE_SMS" android:permissionGroup="android.permission-group.MESSAGES" android:protectionLevel="dangerous"/> 如果應用程式使用了這個許可權,就有可能導致其他應用程式無法收到簡訊通知,此時我們認為使用這種許可權是危險的。 |
| signature |
簽名層級。系統只在請求的應用程式用同樣的簽名作為聲明許可權時才授予該許可權。如果認證匹配,則系統不通知使用者或者無須使用者明確批准就可以自動授權。 |
| signatureOrSystem |
簽名或者系統層級。系統僅僅將它授給Android系統鏡像檔案(*.img檔案)中的應用程式,或者是和系統鏡像中的那些用同樣認證簽名的應用程式。一般情況下,盡量避免使用該選項,這是由於signature保護層級應滿足大多數需求和工程,而不管應用程式被確切地安裝在何處。signatureOrSystem許可權適用於特定的特殊環境,在這樣的環境裡,多個廠商已經將應用程式構建到系統鏡像中,並且需要明確共用特定特徵。 |
在<permission>節點中,除了上面介紹的3個屬性外,還有其他一些屬性只是為了便於閱讀而存在,這裡我們不在詳細介紹。
在Android系統提供的應用程式中,有一些定義了自己的許可權,比如Launcher。下面的程式碼片段用Launcher 的AndroidManifest.xml片段來說明如何手動聲明自己的許可權:
<permission
android:name="com.android.launcher.permission.INSTALL_SHORT-CUT"
android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
android:protectionLevel="normal"
android:label="@string/permlab_install_shortcut"
android:description="@string/perdesc_install_shortcut"/>
因為聲明許可權這一功能使用頻率比較低,因此讀者在開發應用程式的時候需要思考是否有必要聲明自己的許可權。
3.<uses-sdk>節點——SDK版本限定
大家都知道,軟體對於平台版本是的一定要求的。如果平台版本能夠達到軟體啟動並執行要求,那就能保證軟體的穩定性。比如,大家都知道NFC功能是不能在Android 1.5中啟動並執行,如果正在開發帶類似功能的應用程式,那就必須對所在平台有所要求,而<uses-sdk>節點正是用來滿足這種需求的。
<uses-sdk>節點使用一個整型值來表達應用程式與一個或多個Android平台版本的相容性。值得注意的是,這些整型值代表的是API層級。應用程式給定的API層級將和一個給定Android系統的API層級做比較。當然,對於不同的Android裝置,這可能會有所不同。需要說明的是,這個節點用於指定API層級,而不是用於指定SDK的版本號碼或Android平台的。
使用此節點的代碼如下所示:
<uses-sdk android:minSdkVersion="integer"
android:targetSdkVersion="integer"
android:maxSdkVersion="integer"/>
①android:minSdkVersion:用於指定要運行應用程式所需的最小API層級。如果系統的API層級比該屬性指定的值要小,則Android系統會阻止使用者安裝此應用。在大多數情況下,應指定這個屬性。如果沒有指定該屬性,那麼系統會認為此屬性值為“1”。
②android:targetSdkVersion:用於指定應用程式的目標API層級。
③android:maxSdkVersion:指定最大的API層級。
4.<instrumentation>節點——應用的監控器
<instrumentation>節點用於監控應用程式與系統互動,它會在應用程式組件執行個體化之前被執行個體化。這個節點在多數情況下用於單元測試。其文法結構如下:
<instrumentation android:functionalTest=["true"|"false"]
android:handleProfiling=["true"|"false"]
android:icon="drawable resource"
android:label="string resource"
android:name="string"
android:targetPackage="string">
下面詳細說明這些屬性:
①android:functionalTest:標識這個Instrumentation類是否作為一個功能測試運行,它的預設值是false。
②android:handleProfiling:標識這個Instrumentation對象是否開啟和關閉效能分析,它的預設值是false。
③android:icon:這個屬性代表這個Instrumentation類的表徵圖
④android:label:該屬性是Instrumentation類的標題。
⑤android:name:該屬性是Instrumentation子類的名稱,是一個類的Java風格的名稱,例如com.example.liyuanjinglyj.myInstrumentation。
⑥android:targetPackage:該屬性是需要監控的目標應用程式名稱,這個名稱來自與目標應用程式AndroidManifest.xml中<manifest>節點的package屬性值。
當然在eclipse需要專門建立一個測試專案,而且還要配置這個節點,但是在android studio並不需要這麼做,考慮到2015年底Google不再支援eclipse,所以只講解android studio單元測試,而講解這個節點是因為到現在eclipse依然有大量的開發人員使用,所以,講解了節點的詳細說明。
下面舉例說明如何進行單元測試:
①建立項目,依然拿我們一直使用的HelloWorld做實驗,你會發現項目目錄裡面有這樣一個測試包:
②右鍵點擊這個包,選擇NEW-CLASS功能表項目,建立MyFirstTest繼承自ActivityInstrumentationTestCase2<T>,如所示:
package com.example.liyuanjing.helloworld;import android.test.ActivityInstrumentationTestCase2;/** * Created by liyuanjing on 2015/7/14. */public class MyFirstTest extends ActivityInstrumentationTestCase2<MainActivity>{ public MyFirstTest(){ super(MainActivity.class); }}
在這段代碼中,ActivityInstrumentationTestCase2<T>中的<T>要換成我們測試的類,因為我們要測試MainActivity所以換成這個。
③我們測試內容為,在編輯框中輸入一些字元,當點擊按鈕後,文字框等於編輯框輸入的字串,MainActivity代碼如下:
public class MainActivity extends FragmentActivity { private static final String SDCARD= Environment.getExternalStorageDirectory()+File.separator; private FileFragment fileFragment=new FileFragment(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if(savedInstanceState==null){ getSupportFragmentManager().beginTransaction().add(android.R.id.content,fileFragment).commit(); } } public static class FileFragment extends Fragment { private View mRootView; public FileFragment(){} @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view=inflater.inflate(R.layout.file_fragment,container,false); final EditText myEdit=(EditText)view.findViewById(R.id.myEdit); final TextView content=(TextView)view.findViewById(R.id.content); Button myBut=(Button)view.findViewById(R.id.myBut); myBut.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { content.setText(myEdit.getText().toString()); } }); this.mRootView=view; return view; } public View getRootView(){ return this.mRootView; } } public FileFragment getFragment(){ return fileFragment; }}
測試類別的代碼如下:
public class MyFirstTest extends ActivityInstrumentationTestCase2<MainActivity>{ private TextView content; private EditText myEdit; private Button myBut; private MainActivity mainActivity; public MyFirstTest(){ super(MainActivity.class); } @Override protected void setUp() throws Exception { super.setUp(); this.mainActivity=(MainActivity)getActivity(); this.content=(TextView)this.mainActivity.getFragment().getRootView().findViewById(R.id.content); this.myEdit=(EditText)this.mainActivity.getFragment().getRootView().findViewById(R.id.myEdit); this.myBut=(Button)this.mainActivity.getFragment().getRootView().findViewById(R.id.mybut); } public void testButtonClick(){ getInstrumentation().sendCharacterSync(KeyEvent.KEYCODE_H); getInstrumentation().sendCharacterSync(KeyEvent.KEYCODE_E); getInstrumentation().sendCharacterSync(KeyEvent.KEYCODE_L); getInstrumentation().sendCharacterSync(KeyEvent.KEYCODE_L); getInstrumentation().sendCharacterSync(KeyEvent.KEYCODE_O); getInstrumentation().waitForIdleSync(); getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { myBut.performClick(); } }); getInstrumentation().waitForIdleSync(); SystemClock.sleep(1000); assertEquals(this.content.getText().toString(),this.myEdit.getText().toString()); }}
注意:
Ⅰ在MyFirstTest類的構造放中,由於指定了被測試專案的Activity的詳細資料,就使得MyFirstTest與Activity關聯上。
ⅡsetUp()方法是一個重寫的方法,它的作用是在測試前做必要的設定,比如執行個體化一些對象,開啟網路等,它與tearDown()方法成對出現。這裡我們沒有實現tearDown()方法,當測試完成以後,架構會自動回調基類,也就是tearDown()方法。
最後,運行測試類別,如所示:
當系統完成測試後,就可以在Android studio整合式開發環境中得到所示的結果:
在圖中可以看到,編寫的Test測試類別傳回值為OK,這就證明測試達到了預期的目的。