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访问该ContentProviderandroid: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
相关产品推荐
相关产品推荐

