天天看點

android:sharedUserIdandroid:sharedUserId

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,關於模式後面會詳細介紹。

android:sharedUserIdandroid:sharedUserId

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即可。

android:sharedUserIdandroid:sharedUserId

這時候,我們發現把代碼中的模式改成private的,A應用任然可以通路資料了,其實也好了解,他們兩個的uid都相同了,A的檔案就是B的,B的就是A的了,他們兩個沒有沙盒的概念了,資料也是透明的了。

是以這裡我們就看到了,使用shareUserId可以達到多個應用之間的資料透明性互相通路。

注意:這裡有一個誤點,就是這裡所有的修改的前提是這個應用的AndroidManifest.xml本身就定義了這個shareUserId,然後我們可以反編譯看到這個值,把我們自己的shareUserId改成他的就可以了,但是如果這個應用本身沒有這個屬性,那麼這裡就沒有辦法的,為什麼呢,如果要添加,那就是另外一條路了,就是逆向,修改AndroidManifest.xml之後,還需要從新打包在驗證,但是這時候沒必要了,我們也知道有時候回編譯還是很艱難的,如果都能回編譯了,那都不需要這些工作了,是以這裡需要注意的一個前提。

那麼修改之後是不是真的可以呢?

答案是肯定不可以的,如果可以的話,那google也太傻比了,其實Android系統中有一個限制,就是說如果多個應用的uid相同的話,那麼他們的apk簽名必須一緻,不然是安裝失敗的,如下錯誤:

android:sharedUserIdandroid:sharedUserId

通過上面的分析,我們就知道了,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

android:sharedUserIdandroid:sharedUserId

這個工程很簡單,我們編譯安裝運作即可。

在弄一個宿主工程:ShareUserIdHost

android:sharedUserIdandroid:sharedUserId

這裡有一個核心方法,我們首先通過插件工程的包名:cn.wjdiankong.shareuseridplugin;建立出一個Context對象。

這裡看到第二參數有兩個模式:

Context.CONTEXT_INCLUDE_CODE:這個标志是在我們需要執行插件中的某段代碼需要加上的值。(下面代碼缺失會發現報錯了,找不到指定的類。是以如果想運作代碼的話,這個值一定要加上。)

CONTEXT_IGNORE_SECURITY:這個标志是必須的,是忽視安全性,如果沒有這個值的話,那麼我們通路什麼都是失敗的。(下面代碼缺失報安全錯誤)

得到了Context變量之後,我們下面就可以通過反射來執行代碼和擷取資源了,這裡需要注意的是,一定要先拿到Context對應的ClassLoader,然後才能加載對應的類,ClassLoader一定是Context的,是插件工程中的類加載器。