引言:本篇文章主要說(shuō)明四大組件與Application間調(diào)用的一些條件注意點(diǎn),包括Activity、Service等組件能夠被外界訪問(wèn)的條件等。關(guān)于四大組件特別是Service、ContentProvider和BroadcastReceiver需要注意的地方,可以查看本人另一篇文章:查漏補(bǔ)缺(三):重溫Service、BoardcastReceiver和ContentProvider。至于為什么沒(méi)有細(xì)講Activity,因?yàn)榭催^(guò)Activity生命周期以及其啟動(dòng)方式的讀者對(duì)于Activity已經(jīng)比較熟悉了,這里重點(diǎn)是講講一些細(xì)節(jié)上的東西。
一、問(wèn)題描述
- 在開(kāi)發(fā)一個(gè)App過(guò)程中,activityA通過(guò)Intent啟動(dòng)activityB,有顯式啟動(dòng)和隱式啟動(dòng)兩種方式,而官方的文檔中說(shuō)startActivity可能會(huì)報(bào)NotFoundException,表示被start的Activity不存在。因此,我們很容易忽略另一個(gè)可能的Exception,Permission Denial。
- 開(kāi)發(fā)多個(gè)App時(shí),想要讓AppB去調(diào)用AppA中的Activity(如:startActivity()方式),也許你什么Exception都不會(huì)得到,也可能會(huì)直接Force Close掉。
二、問(wèn)題原因
因?yàn)镾tart Activity時(shí),代碼是有去檢驗(yàn)permission的。
如下情況,可以成功startActivity而不會(huì)得到permission denial
- 同一個(gè)application下
- Uid相同
- permission匹配
- 目標(biāo)Activity的屬性Android:exported=”true”
- 目標(biāo)Activity具有相應(yīng)的IntentFilter,存在Action動(dòng)作或其他過(guò)濾器并且沒(méi)有設(shè)置exported=false
- 啟動(dòng)者的Pid是一個(gè)系統(tǒng)服務(wù)(System Server)的Pid【也就是系統(tǒng)服務(wù)前來(lái)調(diào)用普通App的Activity等】
- 啟動(dòng)者的Uid是一個(gè)System Uid(Android規(guī)定android.system.uid=1000,具有該Uid的application,我們稱之為獲得Root權(quán)限)
如果上述調(diào)節(jié),滿足一條,一般即可(與其他幾條不發(fā)生強(qiáng)制設(shè)置沖突),否則,將會(huì)得到Permission Denial的Exception而導(dǎo)致Force Close。
三、Uid機(jī)制
我們知道,Pid表示<u>進(jìn)程ID</u>,Uid表示<u>用戶ID</u>,只是Android和計(jì)算機(jī)不一樣,計(jì)算機(jī)每個(gè)用戶都具有一個(gè)Uid,哪個(gè)用戶start的程序,這個(gè)程序的Uid就是那個(gè)那個(gè)用戶,而Android中每個(gè)程序都有一個(gè)Uid,默認(rèn)情況下,Android會(huì)給每個(gè)程序分配一個(gè)<u>普通級(jí)別互不相同的Uid</u>,如果用互相調(diào)用,只能是Uid相同才行,這就使得共享數(shù)據(jù)具有了一定安全性,每個(gè)軟件之間是不能隨意獲得數(shù)據(jù)的。而同一個(gè)application只有一個(gè)Uid,所以application下的Activity之間不存在訪問(wèn)權(quán)限的問(wèn)題。
讓你的App將它里面含有的某些activity、service、provider等的數(shù)據(jù)進(jìn)行共享:
法一:完全暴露。這就是
android:exported=”true”的作用,而一旦設(shè)置了intentFilter之后,exported就默認(rèn)被設(shè)置為true了,除非再?gòu)?qiáng)制設(shè)為false。當(dāng)然,對(duì)那些沒(méi)有intentFilter的程序體,它的exported屬性默認(rèn)仍然是false,也就不能共享出去。-
法二:權(quán)限提示暴露。這就是為什么經(jīng)常要設(shè)置
<uses-permission name="xxxxx"/>的原因,如果人家設(shè)置了android:permission=”xxx.xxx.xx”那么,你就必須在你的App的AndroidManifast.xml中uses-permission xxx.xxx.xx才能訪問(wèn)人家的東西?!鞠旅嬉宰远x權(quán)限的用法來(lái)作為示例,而關(guān)于系統(tǒng)權(quán)限大全,可以查看本人的另一篇文章:Android字典(一) -- permission權(quán)限說(shuō)明?!?/p>-
<permission>格式
-
<!--自定義權(quán)限格式-->
<permission android:description="string resource" //權(quán)限描述(描述權(quán)限)
android:icon="drawable resource" //權(quán)限圖標(biāo)(描述權(quán)限)
android:label="string resource" ///權(quán)限標(biāo)簽(描述權(quán)限)
android:name="string" //權(quán)限名稱(描述權(quán)限)
android:permissionGroup="string"
//權(quán)限組。此屬性是可選的,被用于協(xié)助系統(tǒng)向用戶顯示權(quán)限,一般會(huì)像(`android:permissionGroup="android.permission-group.SYSTEM_TOOLS" `)這個(gè)一樣設(shè)置為標(biāo)準(zhǔn)系統(tǒng)組,很少自定義,最好使用已經(jīng)定義的,使用起來(lái)也方便。
android:protectionLevel=["normal" | "dangerous" | "signature" | "signatureOrSystem"]
//權(quán)限級(jí)別。必須聲明,用于告訴系統(tǒng)當(dāng)前應(yīng)用進(jìn)行訪問(wèn)控制,例如對(duì)于:如網(wǎng)絡(luò)訪問(wèn)(需付費(fèi))以及獲取聯(lián)系人(涉及隱私)等。
//normal:低風(fēng)險(xiǎn)權(quán)限,只要申請(qǐng)了就可以使用(在AndroidManifest.xml中添加<uses-permission>標(biāo)簽),安裝時(shí)不需要用戶確認(rèn);
//dangerous:高風(fēng)險(xiǎn)權(quán)限,安裝時(shí)需要用戶的確認(rèn)才可使用;
//signature:只有當(dāng)申請(qǐng)權(quán)限的應(yīng)用程序的數(shù)字簽名與聲明此權(quán)限的應(yīng)用程序的數(shù)字簽名相同時(shí)(如果是申請(qǐng)系統(tǒng)權(quán)限,則需要與系統(tǒng)簽名相同),才能將權(quán)限授給它;
//signatureOrSystem:簽名相同,或者申請(qǐng)權(quán)限的應(yīng)用為系統(tǒng)應(yīng)用(在system image中)。
/>
* 用法步驟:
首先在App中的androidManifast.xml文件中`<application>`標(biāo)簽內(nèi)部定義了自己的`<permission>`,然后在`<application>`標(biāo)簽之外使用`<uses-permission>`來(lái)聲明需要的權(quán)限,再讓自己的`<activity>`或者`<receiver>`等組件添加上`android:permission="你剛剛自定義的權(quán)限名稱"`來(lái)使你的組件擁有被調(diào)用時(shí)檢測(cè)對(duì)方App有沒(méi)有相應(yīng)權(quán)限的能力。
-
法三:私有暴露。使用
sharedUserId。在Android里面每個(gè)app都有一個(gè)唯一的linux user ID,則這樣權(quán)限就被設(shè)置成該應(yīng)用程序的文件只對(duì)該用戶可見(jiàn),只對(duì)該應(yīng)用程序自身可見(jiàn),而我們可以使他們對(duì)其他的應(yīng)用程序可見(jiàn),這會(huì)使我們用到SharedUserId,也就是讓兩個(gè)apk使用相同的userID,這樣它們就可以看到對(duì)方的文件。為了節(jié)省資源,具有相同ID的apk也可以在相同的linux進(jìn)程中進(jìn)行(注意,并不是一定要在一個(gè)進(jìn)程里面運(yùn)行),共享一個(gè)虛擬機(jī)。
??假如說(shuō)一個(gè)公司做了兩個(gè)產(chǎn)品,只想這兩個(gè)產(chǎn)品之間可互相調(diào)用,那么這個(gè)時(shí)候就必須使用shareUserID將兩個(gè)軟件的Uid強(qiáng)制設(shè)置為一樣的。這種情況下必須使用具有該公司簽名的簽名文檔才能,如果使用一個(gè)系統(tǒng)自帶軟件的ShareUID,例如Contact,那么無(wú)須第三方簽名。- 一般用法步驟:
- 第一個(gè)應(yīng)用程序?yàn)榈膍enifest文件代碼如下:
- 一般用法步驟:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.abc.serviceID"
android:versionCode="1"
android:versionName="1.0"
android:sharedUserId="com.abc.share">
//.......
2. 第二個(gè)應(yīng)用程序的menifest文件代碼如下:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.abc.clientID"
android:versionCode="1"
android:versionName="1.0"
android:sharedUserId="com.abc.share">
3. 假設(shè)我們從package=“com.abc.clientID”的程序獲取package="com.abc.serviceID"的程序的context:
```
Context context=this.createPackageContext("com.abc.serviceID",Context.CONTEXT_IGNORE_SECURITY);
```
#### Uid機(jī)制的意義在于:應(yīng)用程序獲得系統(tǒng)權(quán)限
?如果一個(gè)activity是由system process跑出來(lái)的,那么它就可以橫行霸道,任意權(quán)限,只是你無(wú)法開(kāi)發(fā)一個(gè)第三方application具有系統(tǒng)的Pid(系統(tǒng)Pid不固定),但是你<u>完全可以開(kāi)發(fā)一個(gè)具有系統(tǒng)Uid的程序,對(duì)系統(tǒng)中的所有程序任意訪問(wèn),只需在AndroidManifest.xml中聲明shareUserId為`android.uid.system`即可,生成的文件也必須經(jīng)過(guò)高權(quán)限簽名才行,一般不具備這種審核條件的application,google不會(huì)提供給你這樣的簽名文件。當(dāng)然你是在編譯自己的系統(tǒng)的話,想把它作成系統(tǒng)軟件程序,只需在Android.mk中聲明Certificate:platform則可以了,既采用系統(tǒng)簽名。</u>這個(gè)系統(tǒng)Uid的獲得過(guò)程,我們把它叫做獲得Root權(quán)限的過(guò)程。所以很多第三方系統(tǒng)管理軟件就是有Root權(quán)限的軟件,因?yàn)樗枰獙?duì)系統(tǒng)有任意訪問(wèn)的權(quán)限。那么它的Root簽名則需要和編譯的系統(tǒng)一致,例如官方的系統(tǒng)得用官方的簽名文件,CM的系統(tǒng)就得用CM的簽名文件。至于Android.mk文件以及相關(guān)配置等,可以參考下http://dengzhangtao.iteye.com/blog/1750782 ,http://blog.sina.com.cn/s/blog_628cc2b70101dcai.html
#### 拓展:Android整個(gè)permission機(jī)制
?AndroidManifest.xml里面的sharedUserID能夠讓不同的apk運(yùn)行在同一個(gè)進(jìn)程里,分享里面的數(shù)據(jù),比如Contacts等,當(dāng)然這個(gè)sharedUserID可以設(shè)置成“android.uid.system”就可以運(yùn)行在系統(tǒng)進(jìn)程中,有權(quán)修改系統(tǒng)數(shù)據(jù)。
?但僅僅有著一個(gè)sharedUserID并不能夠保證你的apk一定能運(yùn)行成功,怎么辦?簽名啊。如果你有Android的源碼就比較方便了,直接把Android.mk里面的LOCAL_CERTIFICATE 賦值為platform就行了。然后mm編輯,就能安裝了。因?yàn)樵诎惭b的時(shí)候,PackageManager會(huì)檢查,如果sharedUserId是system的,它會(huì)看這個(gè)apk的簽名是不是system.crt,如果不是,會(huì)報(bào)出permission deny的error。而把LOCAL_CERTIFICATE改成platform就等于給APK簽名。
?進(jìn)而可以通過(guò)這個(gè)問(wèn)題研究一下整個(gè)Android permission的機(jī)制。系統(tǒng)的安全機(jī)制通過(guò)給每個(gè)用戶分配單獨(dú)的uid和gid來(lái)實(shí)現(xiàn),Android系統(tǒng)中pid代表進(jìn)程ID,這個(gè)是有系統(tǒng)在程序運(yùn)行時(shí)分配的,這一點(diǎn)可以防止地址空間的數(shù)據(jù)共享,增強(qiáng)內(nèi)存空間的安全性。對(duì)于外部則用到了uid進(jìn)行封鎖。
?系統(tǒng)會(huì)給于用戶進(jìn)程單獨(dú)的uid,當(dāng)然系統(tǒng)也是要運(yùn)行進(jìn)程的,比如System,Radio,藍(lán)牙,IO設(shè)備。系統(tǒng)中的init.rc文件會(huì)詳細(xì)定義這些文件的權(quán)限。Android中對(duì)uid的定義是Root最高,其次是system,最低的是app。這是基于Linux系統(tǒng)的結(jié)果。
?那么在APP里,要對(duì)一些進(jìn)程進(jìn)行訪問(wèn),或者接受Broadcast,或者啟動(dòng)Activity、Service都是需要權(quán)限的,不能說(shuō)你的app什么都能做,這也是需要在manifest file中設(shè)置的。
?比如在startActivity時(shí),如果你start自己apk里的activity,它們會(huì)在同一個(gè)application下,那么自然也就使用一個(gè)uid,start過(guò)程自然沒(méi)有什么問(wèn)題。如果你需要start別人寫的Activity或者service,都需要用到同一個(gè)shareUserId才行,因?yàn)樵贏ctivityManagerService要啟動(dòng)activity的之前,會(huì)首先檢查uid,用checkPermission方法,透過(guò)binder獲得pid和uid,檢查你activity的binder的權(quán)限,如果你有權(quán)限則已,沒(méi)權(quán)限的話就會(huì)拋出security exception。至于broadcast,檢查則更為嚴(yán)格,會(huì)雙向的檢查發(fā)出者和接受者的權(quán)限。