android:sharedUserId
1、前言
Android給每個APK程序分配一個單獨的空間,manifest中的userid就是對應一個分配的Linux使用者ID,並且為它建立一個沙箱,以防止影響其他應用程式(或者被其他應用程式影響)。
通常,不同的APK會具有不同的userId,因此執行時屬於不同的程序中,而不同程序中的資源是不共享的(比如只能訪問/data/data/自己包名下面的檔案),保障了程式執行的穩定。然後在有些時候,我們自己開發了多個APK並且需要他們之間互相共享資源,那麼就需要通過設定shareUserId來實現這一目的。
通過Shared User id,擁有同一個User id的多個APK可以配置成執行在同一個程序中,可以互相訪問任意資料。也可以配置成執行成不同的程序, 同時可以訪問其他APK的資料目錄下的資料庫和檔案,就像訪問本程式的資料一樣(使用IPC機制,不同程序之間,比如AIDL)。
2、shareUserId的屬性的最大作用是什麼呢?
前面說了,Android中每個app都對應一個uid,每個uid都有自己的一個沙箱,這是基於安全考慮的,那麼說到沙箱,我們會想到的是data/data/XXXX/目錄下面的所有資料,因為我們知道這個目錄下面的所有資料是一個應用私有的,一般情況下其他應用是沒有許可權訪問的,當然root之後是另外情況,這裡就不多說了。這裡只看沒有root的情況,下面我們在來看一個場景:
A應用和B應用都是一家公司的,現在想在A應用中能夠拿到B引用儲存的一些值,那麼這時候該怎麼辦呢?
這時候就需要用到了shareUserId屬性了,但是這裡我們在介紹shareUserId屬性前,我們先來看一個簡單的例子:
還是使用上面的兩個工程:
ShareUserIdPlugin中的MainActivity.java程式碼如下:
這裡很簡單,我們使用SharedPreferences來儲存一個密碼,注意模式是:Context.MODE_PRIVATE,關於模式後面會詳細介紹。
Context提供了幾種模式:
1、Context.MODE_PRIVATE:為預設操作模式,代表該檔案是私有資料,只能被應用本身訪問,在該模式下,寫入的內容會覆蓋原檔案的內容,如果想把新寫入的內容追加到原檔案中。可以使用Context.MODE_APPEND
2、Context.MODE_APPEND:模式會檢查檔案是否存在,存在就往檔案追加內容,否則就建立新檔案。
3、Context.MODE_WORLD_READABLE和Context.MODE_WORLD_WRITEABLE用來控制其他應用是否有許可權讀寫該檔案。
MODE_WORLD_READABLE:表示當前檔案可以被其他應用讀取;
MODE_WORLD_WRITEABLE:表示當前檔案可以被其他應用寫入
其他應用讀取該應用目錄下此配置檔案中passwd肯定是失敗的。
如果我們想讓A應用訪問到B應用的資料,我們可以這麼做:把B應用建立模式改成可讀模式的,那麼A應用就可以操作了,那麼這就有一個問題,A應用可以訪問了,其他應用也可以訪問了,這樣所有的應用都可以訪問B應用的沙盒資料了,太危險了。
所以要用另外的一種方式,那麼這時候就要用到shareUserId屬性了,我們只需要將B應用建立方式還是private的,然後A應用和B應用公用一個uid即可,我們下面就來修改一下程式碼,還是前面的那兩個工程,修改他們的AndroidManifest.xml,添shareUserId即可。
這時候,我們發現把程式碼中的模式改成private的,A應用任然可以訪問資料了,其實也好理解,他們兩個的uid都相同了,A的檔案就是B的,B的就是A的了,他們兩個沒有沙盒的概念了,資料也是透明的了。
所以這裡我們就看到了,使用shareUserId可以達到多個應用之間的資料透明性互相訪問。
注意:這裡有一個誤點,就是這裡所有的修改的前提是這個應用的AndroidManifest.xml本身就定義了這個shareUserId,然後我們可以反編譯看到這個值,把我們自己的shareUserId改成他的就可以了,但是如果這個應用本身沒有這個屬性,那麼這裡就沒有辦法的,為什麼呢,如果要新增,那就是另外一條路了,就是逆向,修改AndroidManifest.xml之後,還需要從新打包在驗證,但是這時候沒必要了,我們也知道有時候回編譯還是很艱難的,如果都能回編譯了,那都不需要這些工作了,所以這裡需要注意的一個前提。
那麼修改之後是不是真的可以呢?
答案是肯定不可以的,如果可以的話,那google也太傻比了,其實Android系統中有一個限制,就是說如果多個應用的uid相同的話,那麼他們的apk簽名必須一致,不然是安裝失敗的,如下錯誤:
通過上面的分析,我們就知道了,Android中是不允許相同的uid的不同簽名的應用。
3、通過shareduserid來獲取系統許可權
(1)在AndroidManifest.xml中新增android:sharedUserId="android.uid.system"
(2)在Android.mk檔案裡面新增LOCAL_CERTIFICATE := platform(使用系統簽名)
(3)在原始碼下面進行mm編譯
這樣生成的apk能夠獲取system許可權,可以在任意system許可權目錄下面進行目錄或者檔案的建立,以及訪問其他apk資源等(注意建立的檔案(夾)只有建立者(比如system,root除外)擁有可讀可寫許可權-rw-------)。
4、擴充套件
系統中所有使用android.uid.system作為共享UID的APK,都會首先在manifest節點中增加android:sharedUserId="android.uid.system",然後在Android.mk中增加LOCAL_CERTIFICATE := platform。可以參見Settings等
系統中所有使用android.uid.shared作為共享UID的APK,都會在manifest節點中增加android:sharedUserId="android.uid.shared",然後在Android.mk中增加LOCAL_CERTIFICATE := shared。可以參見Launcher等
系統中所有使用android.media作為共享UID的APK,都會在manifest節點中增加android:sharedUserId="android.media",然後在Android.mk中增加LOCAL_CERTIFICATE := media。可以參見Gallery等。
5、看看如何在一個app中去訪問另外一個app的程式碼和資源等資訊?
在說這個知識點之前,我們需要了解的一個知識點,就是我們可以通過一個包名來得到對應的Context的全域性變數,可以直接使用Context的一個靜態方法:createPackageContext
關於這個方法其實很簡單,他有兩個引數:
第一個引數:需要構造出來Context的包名字串
第二個引數:構造出來的Context的開啟模式
下面我們可以直接使用一個例子來看看效果:
首先我們弄一個外掛工程:ShareUserIdPlugin
這個工程很簡單,我們編譯安裝執行即可。
在弄一個宿主工程:ShareUserIdHost
這裡有一個核心方法,我們首先通過外掛工程的包名:cn.wjdiankong.shareuseridplugin;創建出一個Context物件。
這裡看到第二引數有兩個模式:
Context.CONTEXT_INCLUDE_CODE:這個標誌是在我們需要執行外掛中的某段程式碼需要加上的值。(下面程式碼缺失會發現報錯了,找不到指定的類。所以如果想執行程式碼的話,這個值一定要加上。)
CONTEXT_IGNORE_SECURITY:這個標誌是必須的,是忽視安全性,如果沒有這個值的話,那麼我們訪問什麼都是失敗的。(下面程式碼缺失報安全錯誤)
得到了Context變數之後,我們下面就可以通過反射來執行程式碼和獲取資源了,這裡需要注意的是,一定要先拿到Context對應的ClassLoader,然後才能載入對應的類,ClassLoader一定是Context的,是外掛工程中的類載入器。
下面我們執行結果看看:
執行成功了啦~~是不是很簡單呢。