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版本时的升级,也可能存在必定的安全权限问题。

相关文章
相关标签/搜索