Android給每一個APK進程分配一個單獨的空間,manifest中的userid就是對應一個分配的Linux用戶ID,而且爲它建立一個沙箱,以防止影響其餘應用程序(或者其餘應用程序影響它)。用戶ID 在應用程序安裝到設備中時被分配,而且在這個設備中保持它的永久性。android
一般,不一樣的APK會具備不一樣的userId,所以運行時屬於不一樣的進程中,而不一樣進程中的資源是不共享的,在保障了程序運行的穩定。而後在有些時候,咱們本身開發了多個APK而且須要他們之間互相共享資源,那麼就須要經過設置shareUserId來實現這一目的。數據庫
經過Shared User id,擁有同一個User id的多個APK能夠配置成運行在同一個進程中.因此默認就是能夠互相訪問任意數據. 也能夠配置成運行成不一樣的進程, 同時能夠訪問其餘APK的數據目錄下的數據庫和文件.就像訪問本程序的數據同樣。安全
在須要共享資源的項目的每一個AndroidMainfest.xml中添加shareuserId的標籤。app
android:sharedUserId="com.example"ide
id名自由設置,但必須保證每一個項目都使用了相同的sharedUserId。一個mainfest只能有一個Shareuserid標籤。函數
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.shareusertesta" android:versionCode="1" android:versionName="1.0" android:sharedUserId="com.example">
每一個安裝的程序都會根據本身的包名在手機文件系統的data\data\your package\創建一個文件夾(須要su權限才能看見),用於存儲程序相關的數據。工具
在代碼中,咱們經過context操做一些IO資源時,相關文件都在此路徑的相應文件夾中。好比默認不設置外部路徑的文件、DB等等。測試
正常狀況下,不一樣的apk沒法互相訪問對應的app文件夾。但經過設置相同的shareUserId後,就能夠互相訪問了。代碼以下。ui
apk A:this
//程序A: public class MainActivityA extends Activity { TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = (TextView)findViewById(R.id.textView1); WriteSettings(this, "123"); } public void WriteSettings(Context context, String data) { FileOutputStream fOut = null; OutputStreamWriter osw = null; try { //默認創建在data/data/xxx/file/ fOut = openFileOutput("settings.dat", MODE_PRIVATE); osw = new OutputStreamWriter(fOut); osw.write(data); osw.flush(); Toast.makeText(context, "Settings saved", Toast.LENGTH_SHORT) .show(); } catch (Exception e) { e.printStackTrace(); Toast.makeText(context, "Settings not saved", Toast.LENGTH_SHORT) .show(); } finally { try { osw.close(); fOut.close(); } catch (IOException e) { e.printStackTrace(); } } }
apk B:
/程序B: public class MainActivityB extends Activity { TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = (TextView) this.findViewById(R.id.textView1); try { //獲取程序A的context Context ctx = this.createPackageContext( "com.example.shareusertesta", Context.CONTEXT_IGNORE_SECURITY); String msg = ReadSettings(ctxDealFile); Toast.makeText(this, "DealFile2 Settings read" + msg, Toast.LENGTH_SHORT).show(); WriteSettings(ctx, "deal file2 write"); } catch (NameNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public String ReadSettings(Context context) { FileInputStream fIn = null; InputStreamReader isr = null; char[] inputBuffer = new char[255]; String data = null; try { //此處調用並無區別,但context此時是從程序A裏面獲取的 fIn = context.openFileInput("settings.dat"); isr = new InputStreamReader(fIn); isr.read(inputBuffer); data = new String(inputBuffer); textView.setText(data); Toast.makeText(context, "Settings read", Toast.LENGTH_SHORT).show(); } catch (Exception e) { e.printStackTrace(); Toast.makeText(context, "Settings not read", Toast.LENGTH_SHORT) .show(); } finally { try { isr.close(); fIn.close(); } catch (IOException e) { e.printStackTrace(); } } return data; } public void WriteSettings(Context context, String data) { FileOutputStream fOut = null; OutputStreamWriter osw = null; try { //此處調用並無區別,但context此時是從程序A裏面獲取的 fOut = context.openFileOutput("settings.dat", MODE_PRIVATE); osw = new OutputStreamWriter(fOut); osw.write(data); osw.flush(); Toast.makeText(context, "Settings saved", Toast.LENGTH_SHORT) .show(); } catch (Exception e) { e.printStackTrace(); Toast.makeText(context, "Settings not saved", Toast.LENGTH_SHORT) .show(); } finally { try { osw.close(); fOut.close(); } catch (IOException e) { e.printStackTrace(); } } } }
若是A和B的mainfest中設置了相同的shareuserId,那麼B的read函數就能正確讀取A寫入的內容。不然,B沒法獲取該文件IO。
經過這種方式,兩個程序之間不須要代碼層級的引用。之間的約束是,B須要知道A的file下面存在「settings.dat」這個文件以及B須要知道A的package的name。
經過shareuserId共享,咱們可獲取到程序A的context。所以,咱們就能夠經過context來獲取程序A對應的各類資源。比較經常使用的就是Raw資源的獲取,如一些軟件的apk皮膚包就是採用了這種技術,將主程序和皮膚資源包分在兩個apk中。
獲取Resources很簡單,在程序A和B的mainfest中設置好相同的shareuserId後,經過createPackageContext獲取context便可。以後就和原來的方式同樣,經過getResources函數獲取各類資源,只是此時的context環境是目標APP的context環境。
//B中調用 Context friendContext = this.createPackageContext( "com.example.shareusertesta",Context.CONTEXT_IGNORE_SECURITY); //在B中獲取A的各類資源 friendContext.getResources().getString(id); friendContext.getResources().getDrawable(id);
可看見,與通常獲取資源的方式並無區別,只是獲取context時有所不一樣。很簡單的就能想到咱們會在項目中對資源操做、IO操做等分裝一個工具類,經過傳遞context來區分目標,這樣能很好的簡化複雜性。
分析這段代碼,可看見程序A和B之間的聯繫有三個:
1 mainfest中聲明shareuserId時須要知道一個共同的userId
2 createpackageContext時須要知道目標APK的package的name
3 獲取資源時須要知道該資源的對應ID
在上面的三個聯繫中,1和2並不複雜,可是「3 獲取資源時須要知道該資源的對應ID」,這一點是一種比較麻煩的約束,會形成一些複雜的狀況。
好比,在程序A中咱們添加了一個String資源share_test_a ,如今須要在B中獲取該資源。因而咱們就經過context.getResources().getString(id)來獲取。
注意,share_test_a是在A中定義的,在A裏面咱們能夠簡單的經過「R.string.share_test_a」來標示id。可是在程序B中,咱們並未在strings.xml中定義過「share_test_a」這個string,所以不存在「R.string.share_test_a」這個標示ID,也就是說連編譯都不經過,。
那麼,咱們該怎麼來獲取ID呢?通常會想到兩種方法,一是利用外部存儲文件保存A中的這個id,而後在B中讀取id後再獲取資源;二是在B中一樣定義一個」share_test_a」的變量。兩種方案是否可行,咱們在下面討論。
先來看下方案一,最簡單的能想到的方式就是File、DB和SharedPreferences。三者原理相同擇一便可。以SharedPreferences舉例。
/程序A中SharedPreferences sp = this.getSharedPreferences("sp", MODE_PRIVATE); Editor editor = sp.edit(); editor.putInt("Rkey", R.string.share_test_a); editor.commit();
//程序B中Context friendContext = this.createPackageContext("com.example.shareusertesta",Context.CONTEXT_IGNORE_SECURITY); SharedPreferences sp = friendContext.getSharedPreferences("sp", MODE_PRIVATE);int Rkey = sp.getInt("Rkey", 0); String ts = friendContext.getResources().getString(Rkey);
從上面代碼看到,咱們經過SharedPreferences間接的中轉了R的id。二者的約束是對存儲的內容須要一個key來命名並在兩個app中統一,以及須要知道該key對應的類型(int、string等)。
這種方式能夠準確的獲取資源,不須要A和B之間有代碼級別的引用。但須要添加一層map來協調key的含義
不可行。
上文中經過測試,驗證了同key下設置相同shareuserid後可共享資源,不然失敗。
但還有兩種狀況還沒有討論。一是假設A和C用兩個不一樣的簽名,但設置相同的shareuserid,那麼可否共享資源。二是假設A用簽名後的apk安裝,C用usb直連調試(即debug key),二者設置相同的shareuserid,那麼可否共享資源。
通過測試,不管是USB調試仍是新簽名APK都安裝不上。
再進一步測試後發現,可否安裝取決於以前手機中是否已經存在對應該shareduserId的應用。若有,則須要判斷簽名key是否相同,如不一樣則沒法安裝。也就是說,若是你刪除a和b的應用先裝c,此時c的安裝正常,而原來a和b的安裝就失敗了(a、b同key,c不一樣key,三者userId相同)。
1 android:sharedUserId="android.uid.system" 若是這麼設置,可實現提權的功能,修改系統時間等須要core權限的操做就可完成了。但看到有人說會形成sd卡讀取bug,網上有很多解決方案(未測試)。
2 修改shareuserId後,usb開發調試安裝沒有問題,可是利用Ecplise打包簽名APK後,部分機型會形成沒法安裝的問題。網上有提到須要源碼環境mm打包或其餘,較麻煩暫未驗證。
目前測試了三臺機子:三星S3自帶系統失敗;華爲一機子成功;三星一刷官方anroid系統的機子成功。初步估計部分廠商修改了必定的內核,形成安裝失敗,具體兼容性狀況有待進一步測試
3 使用shareuserid後,對同系列的產品的簽名key必須統一,不要丟失。不然後面開發的系列app就沒法獲取數據了。此外,注意從沒有userId的版本到有userId版本時的升級,也可能存在必定的安全權限問題。