跨进程共享资源

本文深入探讨Android中通过设置sharedUserId实现不同APK间资源共享的方法,包括访问缓存目录下的文件、SP文件、资源文件及跨APK代码调用。详细介绍了如何利用sharedUserId在不同进程中共享data/data目录下的资源,并提供了具体示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

sharedUserId

Android中给每一个apk的进程分配了一个单独的userid,不同的userid拥有不同的进程,不同的userid也使得两个进行的资源进行了隔离,当需要共享两个apk的data/data/目录下或/data/user_de/0/包名/下资源时,将两个apk的userid需要设置为相同,即可达到共享资源的目的,或者将相同userid的两个apk运行在同一个进程时,也可以共享资源,下面主要针对相同uid配置位不同apk时的情况。

使用方式

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.shared_user_a"
    android:sharedUserId="com.example.shareduser">
</manifest>

通过设置两个apk为相同的sharedUserId,即使两个应用运行在不同的进程,也可以相互访问文件,图片等静态资源,当设置两个apk为相同的sharedUserId时,两个apk的签名需要相同。

访问(/data/data/app包名)缓存目录下的文件

android中,每个apk会有自己的缓存目录,或者是data/data/包名/或者data/user_de/0/包名/(该目录可以在加锁的时候访问),不同的apk是没有办法相互访问数据的,但是通过设置相同的userId之后(相同uid的apk需要签名相同),两个apk是可以相互访问缓存数据的。
App A会在onCreate的时候在自己的缓存目录下创建文件,同时我们给textview设置了监听,点击可以获取文本中的内容。

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    private TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        WriteSettings("app a write it");
        mTextView = findViewById(R.id.sharedText);
        mTextView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mTextView.setText(ReadSettings(MainActivity.this));
            }
        });
    }

    public void WriteSettings(String data) {
        FileOutputStream fOut = null;
        OutputStreamWriter osw = null;
        try {
            fOut = openFileOutput("settings.txt", MODE_PRIVATE);
            osw = new OutputStreamWriter(fOut);
            osw.write(data);
            osw.flush();
            Log.i(TAG, "WriteSettings: success");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (osw != null) {
                    osw.close();
                }
                if (fOut != null) {
                    fOut.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public String ReadSettings(Context context) {
        FileInputStream fIn = null;
        InputStreamReader isr = null;
        char[] inputBuffer = new char[255];
        String data = null;
        try {
            fIn = context.openFileInput("settings.txt");
            isr = new InputStreamReader(fIn);
            isr.read(inputBuffer);
            data = new String(inputBuffer);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (isr != null) {
                    isr.close();
                }
                if (fIn != null) {
                    fIn.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return data;
    }
}

APP B会在onCreate的时候首先读取App A缓存目录下的该文件,然后给该目录下的文件中写入内容,

public class MainActivity extends AppCompatActivity {
    private TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = findViewById(R.id.sharedText);

        try {
            //获取程序A的context,通过这个context我们可以访问到A程序的缓存目录下的文件,以及获取程序A的资源
            Context context = this.createPackageContext(
                    "com.example.shared_user_a", Context.CONTEXT_IGNORE_SECURITY);
            String msg = ReadSettings(context);
            mTextView.setText(msg);
            WriteSettings(context, "app b modified");
        } catch (PackageManager.NameNotFoundException e) {
            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.txt");
            isr = new InputStreamReader(fIn);
            isr.read(inputBuffer);
            data = new String(inputBuffer);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (isr != null) {
                    isr.close();
                }
                if (fIn != null) {
                    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.txt", MODE_PRIVATE);
            //此处调用并没有区别,但context此时是从程序A里面获取的
            osw = new OutputStreamWriter(fOut);
            osw.write(data);
            osw.flush();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (osw != null) {
                    osw.close();
                }
                if (fOut != null) {
                    fOut.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

运行App A后,运行App b,可以看到App b读取到了App a缓存目录下的文件内容,然后点击App a界面上的黑色文本,可以看到获取到了App b写入的内容。
在这里插入图片描述
如果没有为两个程序添加相同的shareduserid,App b是没有办法获取到另一个apk缓存目录下面的文件的,会报权限错误。

获取另一个进程的sp文件

通过设置相同的sharedUserId我们可以通过一个apk去访问另一个apk的sp文件下面的资源:
AppA,每次点击往sp中写入新的值:

        findViewById(R.id.changeTextInSp).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //程序A中
                SharedPreferences sp = MainActivity.this.getSharedPreferences("sp", MODE_PRIVATE);
                SharedPreferences.Editor editor = sp.edit();
                editor.putString("Rkey", "App a put " + mCount++ + " to sp");
                editor.commit();
            }
        });

AppB每次读取sp中的值:

// MODE_MULTI_PROCESS会重新加载文件
        textView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.i(TAG, "onCreate: start read sp");

                // MODE_MULTI_PROCESS会重新加载文件
                SharedPreferences sp = finalPackageContextA.getSharedPreferences("sp", Context.MODE_MULTI_PROCESS);
                String value = sp.getString("Rkey", "");
                Log.i(TAG, "onCreate:Rkey value is" + value);
                textView.setText(value);
            }
        });

效果,每次AppA改变sp中的值,AppB中可以重新读取到该文件中的值:在这里插入图片描述
在读取sp文件的时候,需要将mode设置为Context.MODE_MULTI_PROCESS,这样才会重新读取,否则会获取全局cache,查看代码:

    @Override
    public SharedPreferences getSharedPreferences(File file, int mode) {
        SharedPreferencesImpl sp;
        synchronized (ContextImpl.class) {
            final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
            sp = cache.get(file);
            if (sp == null) {
                checkMode(mode);
                if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) {
                    if (isCredentialProtectedStorage()
                            && !getSystemService(UserManager.class)
                                    .isUserUnlockingOrUnlocked(UserHandle.myUserId())) {
                        throw new IllegalStateException("SharedPreferences in credential encrypted "
                                + "storage are not available until after user is unlocked");
                    }
                }
                sp = new SharedPreferencesImpl(file, mode);
                cache.put(file, sp);
                return sp;
            }
        }
        if ((mode & Context.MODE_MULTI_PROCESS) != 0 ||
            getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) {
            // If somebody else (some other process) changed the prefs
            // file behind our back, we reload it.  This has been the
            // historical (if undocumented) behavior.
            sp.startReloadIfChangedUnexpectedly();
        }
        return sp;
    }

每次在getSharedPreferences()的时候,首先会去取全局的cache,没有cache则直接读取后返回,有cache读取cache,且如果mode为MODE_MULTI_PROCESS会去重新加载文件内容,这最然保证了读取内容的实时性,但是这也意味着重新加载文件带来的额外的消耗,同时Google不建议使用MODE_MULTI_PROCESS,推荐使用ContentProvider,因此这种读取方式也不建议使用,只是知道就好。

获取另一个apk的res目录下的资源

我们可以通过使用createPackageContext()接口创建另一个应用的context,通过这个context就可以 访问另一个程序res下面的资源,自己测试这里并不需要两个apk拥有相同的sharedUserId。

于是自己有个疑问就是难道一个程序知道另一个程序的包名就可以获取另一个程序的资源吗?这里难道没有什么安全校验吗?希望广大博友知道该问题的话可以给予解答

在App A的drawable目录下放置一个kite.png的图片,同时在strings中定义一个字符串,app_A_define_str,在测试的时候我们去掉App b的sharedUserId,部分代码如下:

...
   Resources resources = finalPackageContextA.getResources();
   int drawableRes = resources.getIdentifier("kite", "drawable", "com.example.shared_user_a");
   int str = resources.getIdentifier("app_A_define_str", "string", "com.example.shared_user_a");
   mTextView.setText(resources.getString(str));
   mTextView.setBackground(resources.getDrawable(drawableRes));
...

App b成功获取到App a中定义的资源和字符串

一个apk中调用另一个apk中的代码

AppA中我们定义一个类:

public class ResUtil {
    private static final String TAG = "ResUtil";

    public ResUtil() {
    }

    public void invokeAppACode() {
        Log.i(TAG, "invokeAppACode: I am app a");
    }
}

AppB中我们通过反射访问该类中的方法:

    private void invokeAppA() {
        Context AooAContext = null;
        try {
            AooAContext = this.createPackageContext("com.example.shared_user_a"
                    , Context.CONTEXT_INCLUDE_CODE
                            | Context.CONTEXT_IGNORE_SECURITY);
        } catch (PackageManager.NameNotFoundException e) {
            Log.i(TAG, "invokeAppA: NameNotFoundException");
        }

        ClassLoader loader = AooAContext.getClassLoader();
        Class<?> clazz = null;
        try {
            clazz = loader.loadClass("com.example.shared_user_a.ResUtil");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        try {
            Object a = clazz.getConstructor().newInstance();
            Method appACode = clazz.getMethod("invokeAppACode");
            appACode.invoke(a);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

    }

查看AppB进程中的日志,

com.example.shared_user_b I/ResUtil: invokeAppACode: I am app a

AppB进程成功调用了AppA中的代码,同样这里自己有个疑问就是一个apk调用另一个apk的代码不需要权限什么的吗,这里没有使用相同的sharedUid也是可以访问到的,如果有哪位博友知道的话希望能不解答下。
参考:
https://www.jianshu.com/p/19f021d2b2a8
https://blog.csdn.net/zy_jibai/article/details/84930231

### 易语言中实现跨进程共享内存的方式 在易语言中,可以通过调用 Windows API 来实现跨进程共享内存功能。具体来说,可以利用 `CreateFileMapping` 和 `MapViewOfFile` 函数来创建和映射文件映射对象[^1]。 #### 创建共享内存 以下是通过易语言实现跨进程共享内存的一个基本流程: 1. **定义必要的常量和函数声明** 需要先引入并声明所需的 Windows API 函数以及相关的常量。 ```易语言 .版本 2 .支持库 eAPI .局部变量 CreateFileMappingA, 整数型 .局部变量 MapViewOfFile, 整数型 .局部变量 UnmapViewOfFile, 整数型 .局部变量 CloseHandle, 整数型 ' 声明所需 API 函数 外部程序 CreateFileMappingA (整数 hFile, 整数 lpAttributes, 整数 flProtect, 整数 dwMaximumSizeHigh, 整数 dwMaximumSizeLow, 字节集 lpName) 外部程序 MapViewOfFile (整数 hFileMappingObject, 整数 dwDesiredAccess, 整数 dwFileOffsetHigh, 整数 dwFileOffsetLow, 整数 dwNumberOfBytesToMap) 外部程序 UnmapViewOfFile (地址 lpBaseAddress) 外部程序 CloseHandle (整数 hObject) ``` 2. **创建文件映射对象** 使用 `CreateFileMappingA` 函数创建一个命名的文件映射对象。此对象可以在多个进程中访问。 ```易语言 .版本 2 .子程序 创建共享内存, 整数型 .局部变量 文件句柄, 整数型 .局部变量 映射句柄, 整数型 ' 打开无效句柄表示不关联物理文件 文件句柄 = -1 ' PAGE_READWRITE 表示可读写权限 映射句柄 = CreateFileMappingA(文件句柄, 0, 4, 0, 1024, “Global\MySharedMemory”) ' 定义共享内存名称 返回 (映射句柄) 结束子程序 ``` 3. **映射视图到当前进程** 使用 `MapViewOfFile` 将文件映射对象映射到当前进程的地址空间。 ```易语言 .版本 2 .子程序 映射视图, 地址 .局部变量 映射句柄, 整数型 .局部变量 视图地址, 地址 映射句柄 = 创建共享内存() 如果 (映射句柄 ≠ 0) ' FILE_MAP_ALL_ACCESS 表示完全访问权限 视图地址 = MapViewOfFile(映射句柄, &H0F001F, 0, 0, 0) 结束如果 返回 (视图地址) 结束子程序 ``` 4. **释放资源** 当不再需要共享内存时,应解除映射并关闭句柄以释放资源。 ```易语言 .版本 2 .子程序 清理资源, , 私有 .局部变量 视图地址, 地址 .局部变量 映射句柄, 整数型 视图地址 = 获取视图地址() ' 自定义获取视图地址方法 如果 (视图地址 ≠ 0) UnmapViewOfFile(视图地址) 结束如果 映射句柄 = 获取映射句柄() ' 自定义获取映射句柄方法 如果 (映射句柄 ≠ 0) CloseHandle(映射句柄) 结束如果 结束子程序 ``` 以上代码展示了如何在易语言中使用 Windows API 实现跨进程共享内存的功能。需要注意的是,在实际应用中还需要处理错误情况以及同步机制,防止多线程或多进程间的竞争条件[^2]。 ### 注意事项 - 共享内存的名字必须唯一,并且在同一台计算机上的所有进程中可见。通常会加上全局前缀 `"Global\"` 或者会话特定的前缀 `"Local\"`。 - 不同进程间的数据一致性问题可能需要用到事件、互斥体等同步工具解决。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值