轉載: http://www.cnblogs.com/wotakuc/archive/2013/03/27/2984423.html
1.shareUserId介紹:
Android給每個APK進程分配一個單獨的空間,manifest中的userid就是對應一個分配的Linux用戶ID,
并且為它創(chuàng)建一個沙箱,以防止影響其他應用程序(或者其他應用程序影響它)。
用戶ID 在應用程序安裝到設備中時被分配,并且在這個設備中保持它的永久性。
通常,不同的APK會具有不同的userId,因此運行時屬于不同的進程中,而不同進程中的資源是不共享的,
在保障了程序運行的穩(wěn)定。然后在有些時候,我們自己開發(fā)了多個APK并且需要他們之間互相共享資源,
那么就需要通過設置shareUserId來實現這一目的。
通過Shared User id,擁有同一個User id的多個APK可以配置成運行在同一個進程中.所以默認就是可以互相訪問任意數據. 也可以配置成運行成不同的進程, 同時可以訪問其他APK的數據目錄下的數據庫和文件.就像訪問本程序的數據一樣。
2.shareUserId設置:
在需要共享資源的項目的每個AndroidMainfest.xml中添加shareuserId的標簽。
android:sharedUserId="com.example"
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">
3.不同APP的(/data/data/app包名/文件)的共享
每個安裝的程序都會根據自己的包名在手機文件系統(tǒng)的/data/data/app包名/建立一個文件夾(需要su權限才能看見),用于存儲程序相關的數據。
在代碼中,我們通過context操作一些IO資源時,相關文件都在此路徑的相應文件夾中。比如默認不設置外部路徑的文件、DB等等。
正常情況下,不同的apk無法互相訪問對應的app文件夾。但通過設置相同的shareUserId后,就可以互相訪問了。代碼如下。
//程序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();
}
}
}
//程序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 {
//此處調用并沒有區(qū)別,但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 {
fOut = context.openFileOutput("settings.dat", MODE_PRIVATE);
//此處調用并沒有區(qū)別,但context此時是從程序A里面獲取的
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。
4.Resources和SharedPreferences的共享
通過shareuserId共享,我們可獲取到程序A的context。因此,我們就可以通過context來獲取程序A對應的各種資源。
比較常用的就是Raw資源的獲取,如一些軟件的apk皮膚包就是采用了這種技術,將主程序和皮膚資源包分在兩個apk中。
獲取Resources很簡單,在程序A和B的mainfest中設置好相同的shareuserId后,通過createPackageContext獲取context即可。
之后就和原來的方式一樣,通過getResources函數獲取各種資源,只是此時的context環(huán)境是目標APP的context環(huán)境。
//在B中獲取A的各種資源
Context friendContext = this.createPackageContext( "com.example.shareusertesta", Context.CONTEXT_IGNORE_SECURITY);
Resources res = friendContext.getResources();
int xId = res.getIdentifier("xxx", "drawable", "com.example.shareusertesta"); //R.string.xxx
int yId = res.getIdentifier("yyy", "string", "com.example.shareusertesta"); //R.Drawable.yyy
res.getString(xId);
res.getDrawable(yId);
5.訪問安全性(簽名)
上文中通過測試,驗證了同key下設置相同shareuserid后可共享資源,否則失敗。
但還有兩種情況尚未討論。一是假設A和C用兩個不同的簽名,但設置相同的shareuserid,那么能否共享資源。
二是假設A用簽名后的apk安裝,C用usb直連調試(即debug key),兩者設置相同的shareuserid,那么能否共享資源。
經過測試,不論是USB調試還是新簽名APK都安裝不上。
再進一步測試后發(fā)現,能否安裝取決于之前手機中是否已經存在對應該shareduserId的應用。
如有,則需要判斷簽名key是否相同,如不同則無法安裝。也就是說,如果你刪除a和b的應用先裝c,此時c的安裝正常,
而原來a和b的安裝就失敗了(a、b同key,c不同key,三者userId相同)。
6.其他討論
1 android:sharedUserId="android.uid.system" 如果這么設置,可實現提權的功能,修改系統(tǒng)時間等需要core權限的操作就可完成了。
但看到有人說會造成sd卡讀取bug,網上有不少解決方案(未測試)。
2 修改shareuserId后,usb開發(fā)調試安裝沒有問題,但是利用Ecplise打包簽名APK后,
部分機型會造成無法安裝的問題。網上有提到需要源碼環(huán)境mm打包或其他,較麻煩暫未驗證。
目前測試了三臺機子:三星S3自帶系統(tǒng)失??;華為一機子成功;三星一刷官方anroid系統(tǒng)的機子成功。
初步估計部分廠商修改了一定的內核,造成安裝失敗,具體兼容性情況有待進一步測試
3 使用shareuserid后,對同系列的產品的簽名key必須統(tǒng)一,不要丟失。否則后面開發(fā)的系列app就無法獲取數據了。
此外,注意從沒有userId的版本到有userId版本時的升級,也可能存在一定的安全權限問題。