You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android多用户场景下User2读取User1中Admin App数据的问题

问题:Android多用户模式下跨用户读取ContentProvider数据失败

场景:

  • User1配置文件:安装Admin App,支持输入用户名和年龄,通过自定义ContentProvider存储数据
  • User2配置文件:安装Guest App,尝试读取User1中Admin App存储的数据,但读取失败;同一用户下两个App可正常读取

现有代码片段

Admin App 核心代码

public class MainActivity extends AppCompatActivity {
    private LinearLayout guestLayout;
    private EditText adminUsernameEditText, adminAgeEditText;
    private TextView displayTextView;
    private int guestCount = 0;
    private static final Uri SHARED_DATA_URI = Uri.parse("content://com.admin.guest.profile.provider/shared_data");

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        guestLayout = findViewById(R.id.guestLayout);
        adminUsernameEditText = findViewById(R.id.adminUsernameEditText);
        adminAgeEditText = findViewById(R.id.adminAgeEditText);
        displayTextView = findViewById(R.id.displayTextView);

        Button adminButton = findViewById(R.id.adminButton);
        Button addGuestButton = findViewById(R.id.addGuestButton);

        adminButton.setOnClickListener(v -> saveAdminData());
        addGuestButton.setOnClickListener(v -> addGuestButton());
    }

    @SuppressLint("SetTextI18n")
    private void saveAdminData() {
        String username = adminUsernameEditText.getText().toString().trim();
        String ageText = adminAgeEditText.getText().toString().trim();

        if (username.isEmpty()) {
            adminUsernameEditText.setError("Username cannot be empty");
            return;
        }
        if (ageText.isEmpty()) {
            adminAgeEditText.setError("Age cannot be empty");
            return;
        }

        int age;
        try {
            age = Integer.parseInt(ageText);
        } catch (NumberFormatException e) {
            adminAgeEditText.setError("Age must be a number");
            return;
        }

        ContentValues values = new ContentValues();
        values.put("username", username);
        values.put("age", age);

        getContentResolver().insert(SHARED_DATA_URI, values);
        displayTextView.setText("Admin data saved: Username - " + username + ", Age - " + age);
    }

    private void addGuestButton() {
        guestCount++;
        Button guestButton = new Button(this);
        guestButton.setText("Guest " + guestCount);
        guestButton.setOnClickListener(v -> displayAdminData());
        guestLayout.addView(guestButton);
    }

    private void displayAdminData() {
        ContentResolver contentResolver = getContentResolver();
        StringBuilder displayText = new StringBuilder("Admin Data:\n");

        try (Cursor cursor = contentResolver.query(SHARED_DATA_URI, null, null, null, null)) {
            if (cursor != null && cursor.moveToFirst()) {
                do {
                    @SuppressLint("Range") String username = cursor.getString(cursor.getColumnIndex("username"));
                    @SuppressLint("Range") int age = cursor.getInt(cursor.getColumnIndex("age"));
                    displayText.append("Username: ").append(username)
                            .append(", Age: ").append(age).append("\n");
                } while (cursor.moveToNext());
            } else {
                displayText.append("No data found.");
            }
        }
        displayTextView.setText(displayText.toString());
    }
}

Guest App 核心代码

public class MainActivity extends AppCompatActivity {
    private TextView displayTextView;
    private static final Uri SHARED_DATA_URI = Uri.parse("content://com.admin.guest.profile.provider/shared_data");
    private static final String TAG = "GuestActivity";

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

    private void displayAdminData() {
        ContentResolver contentResolver = getContentResolver();
        StringBuilder displayText = new StringBuilder("Admin Data:\n");

        try (Cursor cursor = contentResolver.query(SHARED_DATA_URI, null, null, null, null)) {
            if (cursor != null && cursor.moveToFirst()) {
                do {
                    @SuppressLint("Range") String username = cursor.getString(cursor.getColumnIndex("username"));
                    @SuppressLint("Range") int age = cursor.getInt(cursor.getColumnIndex("age"));
                    displayText.append("Username: ").append(username)
                            .append(", Age: ").append(age).append("\n");
                    Log.d(TAG, "Fetched data - Username: " + username + ", Age: " + age);
                } while (cursor.moveToNext());
            } else {
                displayText.append("No data found.");
                Log.d(TAG, "No data found in ContentProvider.");
            }
        }
        displayTextView.setText(displayText.toString());
    }
}

已配置的Manifest查询规则(Guest App)

<queries>
    <!-- Query Admin App for accessing the content provider -->
    <package android:name="com.admin.guest.profile" />
</queries>

解决方案

Android多用户模式下,每个用户拥有独立的App沙箱,默认ContentProvider仅对当前用户可见,需通过以下步骤实现跨用户数据访问:

1. 修改Admin App的ContentProvider注册配置

在Admin App的AndroidManifest.xml中,给ContentProvider添加跨用户访问的属性:

<provider
    android:name=".YourContentProviderClass"
    android:authorities="com.admin.guest.profile.provider"
    android:exported="true"
    android:grantUriPermissions="true"
    android:singleUser="false" <!-- 允许跨用户访问(Android 10+) -->
    android:multiprocess="true">
</provider>
  • android:singleUser="false":声明ContentProvider不局限于单个用户,允许其他用户访问
  • android:exported="true":允许外部App访问该ContentProvider
  • android:grantUriPermissions="true":授予临时URI访问权限

2. 在Guest App中使用跨用户Context访问数据

Guest App需要获取User1的用户ID,创建对应用户的Context来访问其ContentProvider:

private void displayAdminData() {
    UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
    List<UserHandle> users = userManager.getUserProfiles();
    int targetUserId = -1;

    // 遍历找到非当前用户(User1)的ID
    for (UserHandle user : users) {
        int userId = UserHandle.getUserId(user.getIdentifier());
        if (userId != android.os.Process.myUserHandle().getIdentifier()) {
            targetUserId = userId;
            break;
        }
    }

    if (targetUserId == -1) {
        displayTextView.setText("找不到目标用户");
        return;
    }

    try {
        // 创建目标用户的Context
        Context targetContext = createContextAsUser(UserHandle.of(targetUserId), 0);
        ContentResolver contentResolver = targetContext.getContentResolver();
        StringBuilder displayText = new StringBuilder("Admin Data:\n");

        try (Cursor cursor = contentResolver.query(SHARED_DATA_URI, null, null, null, null)) {
            if (cursor != null && cursor.moveToFirst()) {
                do {
                    @SuppressLint("Range") String username = cursor.getString(cursor.getColumnIndex("username"));
                    @SuppressLint("Range") int age = cursor.getInt(cursor.getColumnIndex("age"));
                    displayText.append("Username: ").append(username)
                            .append(", Age: ").append(age).append("\n");
                    Log.d(TAG, "Fetched data - Username: " + username + ", Age: " + age);
                } while (cursor.moveToNext());
            } else {
                displayText.append("No data found.");
                Log.d(TAG, "No data found in ContentProvider.");
            }
        }
        displayTextView.setText(displayText.toString());
    } catch (SecurityException e) {
        Log.e(TAG, "无跨用户访问权限", e);
        displayTextView.setText("无跨用户访问权限");
    }
}

3. 添加跨用户访问权限

在两个App的AndroidManifest.xml中添加权限:

<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
  • 这些是系统级权限,普通App需通过adb授予:
adb shell pm grant com.your.guest.package android.permission.INTERACT_ACROSS_USERS_FULL

4. 验证数据存储的全局性

确保Admin App的ContentProvider使用的数据库不存储在用户隔离目录(如getFilesDir()),可选择全局存储路径或依赖ContentProvider的跨用户配置自动处理数据可见性。

内容的提问来源于stack exchange,提问作者Star

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 19:29:52