`
emowuyi
  • 浏览: 1479033 次
文章分类
社区版块
存档分类
最新评论

Android sharedUserId研究记录

 
阅读更多

签名简介:

在Android系统中,所有安装到系统的应用程序都必有一个数字证书,此数字证书用于标识应用程序的作者和在应用程序之间建立信任关系,。这个数字证书并不需要权威的数字证书签名机构认证,它只是用来让应用程序包自我认证的。

调试时,ADT会自动的使用debug密钥为应用程序签名。debug密钥是一个名为debug.keystore的文件,它的位置:系统盘符:/Documents and Settings/XXX/.android/debug.keystore “XXX”对应于windows操作系统用户名。

主要涉及工具有三个,keytool、jarsigner和zipalign

1)keytool:生成数字证书,即密钥,也就是上面说到的扩展名为.keystore的那类文件;
2)jarsigner:使用数字证书给apk文件签名;
3)zipalign:对签名后的apk进行优化,提高与Android系统交互的效率(Android SDK1.6版本开始包含此工具)

通常,可直接通过Ecplise的adt插件提供的功能来签名。(详细可见网络其他资源,本文主要讨论shareUserId)

shareUserId介绍:

Android给每个APK进程分配一个单独的空间,manifest中的userid就是对应一个分配的Linux用户ID,并且为它创建一个沙箱,以防止影响其他应用程序(或者其他应用程序影响它)。用户ID 在应用程序安装到设备中时被分配,并且在这个设备中保持它的永久性。

通常,不同的APK会具有不同的userId,因此运行时属于不同的进程中,而不同进程中的资源是不共享的,在保障了程序运行的稳定。然后在有些时候,我们自己开发了多个APK并且需要他们之间互相共享资源,那么就需要通过设置shareUserId来实现这一目的。

通过Shared User id,拥有同一个User id的多个APK可以配置成运行在同一个进程中.所以默认就是可以互相访问任意数据. 也可以配置成运行成不同的进程, 同时可以访问其他APK的数据目录下的数据库和文件.就像访问本程序的数据一样。

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">

\data\data\自定义的package\ 路径下的互相访问

每个安装的程序都会根据自己的包名在手机文件系统的data\data\your package\建立一个文件夹(需要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 {
            //此处调用并没有区别,但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);
            //此处调用并没有区别,但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。

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的含义。

设置相同的资源名

再看另一种方式,在A和B中都设置相同的资源名。

假如A和B都定义一个“share_test”的变量(即都有R.String.share_test),A的内容是“hello A”,B的内容是”hello B”。有个show的函数。

private String show(Context context){
        return context.getResources().getString(R.string.share_test);
}

看上去似乎很不错,我们只要传递不同的context进去,就能利用R.string.share_test这个共同ID来动态显示不同的内容,而且不需要调整id的代码。

但是,我们再仔细看下关于getString(int id)的参数,它传递的是一个int变量值,这个值又从哪里来呢?在项目的gen文件夹下面能够找到”R.java”这个文件,该文件中对系统的各种变量自动生成了一个唯一标示。

//A的R.java
    public static final class string {
        public static final int action_settings=0x7f050001;
        public static final int app_name=0x7f050000;
        public static final int hello_world=0x7f050002;
        public static final int share_test =0x7f050003;
    }
//B的R.java
public static final class string {
        public static final int action_settings=0x7f050001;
        public static final int app_name=0x7f050000;
        public static final int hello_world=0x7f050002;
        public static final int share_test =0x7f050004;
        public static final int share_test_2=0x7f050003;
    }

仔细看可以发现,R的自动生成规则不是根据你取的名字来固定的。也就是说A和B都定义一个“share_test”,但他们的int值可能不一样! (事实上,只有极少数情况下才能做到两个R文件完全相同。)

因此,通过设置相同的资源名这种方式是不安全的,除非你能确保两个APP中的R的变量int都相同,否则极容易造成很难发现的隐性bug(逻辑含义错误、超界等)。

PS:验证该问题的时候,我曾异想天开的想到有无可能编译器或下层代码智能到对R的解析在运行时完成。比如编译时按照B的Rid通过编译,但是取值操作是在运行时动态解析完成的,这时候R对应的是A的R,如果有那么智能,我们就可以忽略int的不一致了。简单的测试后证明,这只是我无聊的想法,其实看见形参是int型就基本确认不会存在这种现象了,而且由于context.getResources().getString()是一个通用的函数,如果真这么做了只会造成数不清的bug。

访问安全性

上文中通过测试,验证了同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版本时的升级,也可能存在一定的安全权限问题。

分享到:
评论

相关推荐

    Android-sharedUserId数据共享

    Android-sharedUserId数据共享

    Android SharedUserID Demo

    Android SharedUserID Demo 包含: 1.同一Apk中的同一包中的Activity调用时进程状况验证 2.同一Apk中的不同包的Activity调用时进程状况验证 3.同一Apk中Activity process属性修改后进程状况验证 4.不同Apk中...

    安卓按键模拟点击相关-Android手机屏幕助手用来替换NavigationBar虚拟导航按键。由于涉及到系统按键消息。所以需要在源码环境下编译需要系统签名需要声明androidsharedUserId=android.uid.system.zip

    Android手机屏幕助手,用来... 所以需要在源码环境下编译,需要系统签名,需要声明android_sharedUserId=_android.uid.system_ .zip,太多无法一一验证是否可用,程序如果跑不起来需要自调,部分代码功能进行参考学习。

    使用Android Studio实现为系统级的app签名

    我们在做系统级的app开发时,往往会在AndroidManifest.xml文件中添加:android:sharedUserId=”android.uid.system”以获取系统级的权限,如果你正在使用Android Studio进行开发,编译生成的apk会因为签名问题无法...

    sharedUserId实现不同APK数据共享

    sharedUserId实现不同APK数据共享

    android系统签名工具

    通过在每个App中使用sharedUserId设置即可共享系统账户权限,比如android:sharedUserId="android.uid.system" 这样就是用了system这个uid了。给apk增加系统签名,否则一些系统apk将无法安装,使用命令: java -jar ...

    如何安装系统认证签名过的APK

    系统中所有使用 android.uid.system 作为共享 UID 的 APK ,都会首先在 manifest 节点中增加android:sharedUserId=“android.uid.system”,然后在 Android.mk 中增加 LOCAL_CERTIFICATE := platform。可以参见 S

    Android的权限机制之—— “沙箱”机制sharedUserId和签名

    NULL 博文链接:https://dengzhangtao.iteye.com/blog/1989065

    Android 插件Demo

    Android 插件Demo 事例 android:sharedUserId="org.igeek.android.pluginframework.demo

    Android系统签名文件生成工具

    app获取系统级权限有两种方法,一个是root,另一个是在AndroidManifest中添加android:sharedUserId="android.uid.system",但是Manifest文件中加入sharedUserId后,我们会发现一个问题,就是INSTALL_FAILED_SHARED_...

    8.1 android 串口编程

    android:sharedUserId=“android.uid.system” 不过这样的话,app就要放到源码里编译了。 2、 /dev/ttyS4要有读写权限 (你的串口不一定是这个ttyS4) ls -l /dev/ttyS4 如果没有读写权限,记得在init.rc里面添加 ...

    android 添加系统权限 INSTALL_FAILED_SHARED_USER_INCOMPATIBLE

    原因:apk的AndroidManifest.xml中声明了android:sharedUserId="android.uid.system",但没有相应的签名 解决方案: 1. 找到编译目标系统时的签名证书platform.pk8和platform.x509.pem,在android源码目录build\...

    Android皮肤架包

    Android皮肤架包,此架包很小,但是包含了更换皮肤的功能,全局可用,详情请认真读此说明:使用架包方法(我们在创建Activity的时候会继承Activity,然而使用此架包我们就继承BaseActivity这个类和Activity没有任何...

    android功能模块 插件化demo

    避免升级时频繁的打包,下载和安装apk,把新增的功能用插件化的形式,安装插件即可升级apk

    Android实现系统重新启动的功能

    首先定义布局文件: 代码如下:&lt;?xml version=”1.0″ encoding=”utf-8″?&gt;&lt;manifest xmlns:android=”http://schemas.android.com/apk/res/android” package u201dcom.hzhi....android:sharedUserId&gt; 

    Android实现关机重启的方法分享

    实现系统重启的APK需要system的权限,在AndroidManifest.xml中增加android:sharedUserId=”android.uid.system”,再修改签名即可; 具体方法参考: 点击打开链接 1、使用PowerManager来实现:代码: 代码如下:...

    AndroidManifest.xml文件详解.ppt

    android:sharedUserId 指定在多个包之间共享的用户ID android:versionCode 内部版本号 android:versionName 版本名称 描述:定义一个用来控制其他包对本包内的组件访问的全乡对象。这将产生一个新的结构体,应用组件...

    Android5.1系统通过包名给应用开放系统权限的方法

    常用的给应用开放系统权限的方法是直接将该应用做成系统应用(即在AndroidManifest.xml文件里加上:android:sharedUserId=”android.uid.system”),但是这种做法限制了应用本身的自由,也就是说经过系统签名后的...

    Android 应用更换皮肤实现方法

    AndroidManifest.xml中配置android:sharedUserId有相同的属性值  满足以上条件,两个apk就能互相访问数据了。  Android应用更换皮肤功能的实现步骤  1、应用程序和皮肤程序的AndroidManifest.xml中配置 XML/HTML...

Global site tag (gtag.js) - Google Analytics