sharedUserId

shareUserId介紹:

Android給每一個APK進程分配一個單獨的空間,manifest中的userid就是對應一個分配的Linux用戶ID,而且爲它建立一個沙箱,以防止影響其餘應用程序(或者其餘應用程序影響它)。用戶ID 在應用程序安裝到設備中時被分配,而且在這個設備中保持它的永久性。android

一般,不一樣的APK會具備不一樣的userId,所以運行時屬於不一樣的進程中,而不一樣進程中的資源是不共享的,在保障了程序運行的穩定。而後在有些時候,咱們本身開發了多個APK而且須要他們之間互相共享資源,那麼就須要經過設置shareUserId來實現這一目的。數據庫

經過Shared User id,擁有同一個User id的多個APK能夠配置成運行在同一個進程中.因此默認就是能夠互相訪問任意數據. 也能夠配置成運行成不一樣的進程, 同時能夠訪問其餘APK的數據目錄下的數據庫和文件.就像訪問本程序的數據同樣。安全

 

shareUserId設置:

在須要共享資源的項目的每一個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\自定義的package\ 路徑下的互相訪問

每一個安裝的程序都會根據本身的包名在手機文件系統的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。

 

Resources和SharedPreferences的共享

經過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

 

資源的R.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」的變量。兩種方案是否可行,咱們在下面討論。

 

SharedPreferences傳遞R.id

先來看下方案一,最簡單的能想到的方式就是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版本時的升級,也可能存在必定的安全權限問題。

相關文章
相關標籤/搜索