成都哪里做网站备案,郑州专业做网站公,wordpress 新手指南,表单大师 做网站你手机里的通讯录#xff0c;存储了所有联系人的信息。如果你想把这些联系人信息分享给其他App#xff0c;就可以通过ContentProvider来实现。。
一、什么是 ContentProvider
ContentProvider 是 Android 四大组件之一#xff0c;负责实现跨应用程序的数据共享与访问…
你手机里的通讯录存储了所有联系人的信息。如果你想把这些联系人信息分享给其他App就可以通过ContentProvider来实现。。
一、什么是 ContentProvider
ContentProvider 是 Android 四大组件之一负责实现跨应用程序的数据共享与访问通过统一接口封装数据存储细节提供标准化操作方式。其中主要功能包括 数据抽象层将应用内部的数据如 SQLite 数据库、文件等封装成统一的接口对外提供。跨应用数据共享允许其他应用安全地访问和操作本应用的数据。数据权限控制通过 URI 和权限机制精确控制数据的访问范围。统一数据访问提供类似数据库的 CRUD 操作接口简化数据使用。 二、ContentProvider 的核心概念 URI统一资源标识符 格式content://authority/path/id示例content://com.example.provider/users/1authority标识 ContentProvider通常为应用包名 provider 名path标识要访问的数据集合id可选标识具体记录 ContentResolver 应用通过 ContentResolver 与 ContentProvider 通信提供 query ()、insert ()、update ()、delete () 等方法 Cursor 查询结果的返回类型类似数据库查询结果集通过 Cursor 获取和遍历数据
三、ContentProvider 的实现步骤
以下是实现一个简单 ContentProvider 的完整步骤
1.创建数据模型
// User.java
public class User {private int id;private String name;private int age;// getters and setters
}
2.创建 SQLiteOpenHelper 管理数据库
// DatabaseHelper.java
public class DatabaseHelper extends SQLiteOpenHelper {private static final String DB_NAME user.db;private static final int DB_VERSION 1;public static final String TABLE_NAME users;public DatabaseHelper(Context context) {super(context, DB_NAME, null, DB_VERSION);}Overridepublic void onCreate(SQLiteDatabase db) {db.execSQL(CREATE TABLE TABLE_NAME ( _id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, age INTEGER););}Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {db.execSQL(DROP TABLE IF EXISTS TABLE_NAME);onCreate(db);}
}
3.实现 ContentProvider
// UserProvider.java
public class UserProvider extends ContentProvider {private DatabaseHelper dbHelper;public static final String AUTHORITY com.example.provider;public static final Uri CONTENT_URI Uri.parse(content:// AUTHORITY /users);Overridepublic boolean onCreate() {dbHelper new DatabaseHelper(getContext());return true;}Overridepublic Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {SQLiteDatabase db dbHelper.getReadableDatabase();return db.query(DatabaseHelper.TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);}Overridepublic Uri insert(Uri uri, ContentValues values) {SQLiteDatabase db dbHelper.getWritableDatabase();long id db.insert(DatabaseHelper.TABLE_NAME, null, values);return ContentUris.withAppendedId(CONTENT_URI, id);}Overridepublic int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {SQLiteDatabase db dbHelper.getWritableDatabase();return db.update(DatabaseHelper.TABLE_NAME, values, selection, selectionArgs);}Overridepublic int delete(Uri uri, String selection, String[] selectionArgs) {SQLiteDatabase db dbHelper.getWritableDatabase();return db.delete(DatabaseHelper.TABLE_NAME, selection, selectionArgs);}Overridepublic String getType(Uri uri) {return vnd.android.cursor.dir/vnd.com.example.provider.users;}
}
4.在 AndroidManifest.xml 中注册 Provider
providerandroid:name.UserProviderandroid:authoritiescom.example.providerandroid:exportedtrueandroid:grantUriPermissionstrue
/provider
四、ContentProvider 的使用示例
其他应用通过 ContentResolver 访问该 Provider
// 查询所有用户
Cursor cursor getContentResolver().query(UserProvider.CONTENT_URI, null, null, null, null
);// 插入新用户
ContentValues values new ContentValues();
values.put(name, John);
values.put(age, 30);
Uri newUri getContentResolver().insert(UserProvider.CONTENT_URI, values);// 更新用户
ContentValues updateValues new ContentValues();
updateValues.put(age, 31);
int count getContentResolver().update(UserProvider.CONTENT_URI, updateValues, name?, new String[]{John}
);// 删除用户
int deleted getContentResolver().delete(UserProvider.CONTENT_URI, age ?, new String[]{40}
);
五、跨应用权限控制
配置目标实现方式跨应用调用权限 调用方声明uses-permission,Porvider方配置android:exportedtrue。 动态权限申请针对dangerous级别权限调用方需在运行时请求用户授权路径级访问控制Provider方通过path-permission细化权限调用方需匹配声明
1. 声明 Provider 权限
!-- 定义自定义权限 --
permission android:namecom.example.READ_USERS android:protectionLeveldangerous /
permission android:namecom.example.WRITE_USERS android:protectionLeveldangerous / !-- 应用权限到 Provider --
provider android:name.UserProvider !-- Provider 实现类的全路径 -- android:authoritiescom.example.provider !-- 唯一标识符与Contract类一致 -- android:exportedtrue !-- 是否允许其他应用访问默认 false -- android:readPermissioncom.example.READ_USERS android:writePermissioncom.example.WRITE_USERS / protectionLevel 设为 dangerous 表示需用户手动授权。readPermission/writePermission自定义权限控制。 2. 路径级权限细化可选
若 Provider 方通过 path-permission 限制特定路径调用方需确保拥有对应权限
!-- Provider 方配置 --
provider ... path-permission android:pathPrefix/admin android:permissioncom.example.ADMIN_PERMISSION /
/provider
3. 调用方配置
manifest ... !-- 声明权限 -- uses-permission android:namecom.example.READ_USERS / uses-permission android:namecom.example.WRITE_USERS / !-- 如果存在路径细化调用方需声明额外权限 -- uses-permission android:namecom.example.ADMIN_PERMISSION / application ... !-- 无 Provider 声明直接通过 ContentResolver 调用 -- /application
/manifest
4. 动态权限申请
在调用方的 Activity/Fragment 中实现动态权限申请流程
public class MainActivity extends AppCompatActivity { private static final int REQUEST_READ_PERMISSION 100; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 检查权限 if (ContextCompat.checkSelfPermission(this, com.example.READ_USERS) ! PackageManager.PERMISSION_GRANTED) { // 权限未授予显示申请弹窗 ActivityCompat.requestPermissions(this, new String[]{com.example.READ_USERS}, REQUEST_READ_PERMISSION); } else { // 已授权执行数据访问 queryData(); } } // 处理权限申请结果 Override public void onRequestPermissionsResult(int requestCode, NonNull String[] permissions, NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode REQUEST_READ_PERMISSION) { if (grantResults.length 0 grantResults[0] PackageManager.PERMISSION_GRANTED) { queryData(); } else { // 权限被拒绝提示用户 Toast.makeText(this, 权限被拒绝无法读取数据,Toast.LENGTH_SHORT).show(); } } } private void queryData() { // 通过 ContentResolver 访问 Provider 数据 Cursor cursor getContentResolver().query( UserContract.CONTENT_URI, null, null, null, null ); // 处理查询结果... }
} 同一权限组内的权限只需申请一次如 READ_CONTACTS 和 WRITE_CONTACTS 属于同一组 4. 用户拒绝后引导设置
若用户勾选“不再询问”需引导用户前往系统设置手动开启权限可通过 shouldShowRequestPermissionRationale 判断。
if (ActivityCompat.shouldShowRequestPermissionRationale(this,com.example.READ_USERS)) { // 用户之前可能拒绝过权限但未勾选“不再询问”// 展示解释性弹窗后再次申请
} else { // 用户勾选“不再询问”或系统禁止权限如厂商定制 ROM 限制// 跳转系统设置界面手动开启权限 Intent intent new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.setData(Uri.parse(package: getPackageName())); startActivity(intent);
} 若用户从未请求过该权限shouldShowRequestPermissionRationale() 也会返回 false。但此时代码通常不会进入此分支因首次请求时直接调用 requestPermissions()。 部分设备可能不支持 Settings.ACTION_APPLICATION_DETAILS_SETTINGS需增加异常捕获并提示用户手动查找权限设置 。 六、数据变更通知
角色职责客户端注册ContentObserver并实现onChange回调逻辑如刷新UIContentProvider数据变更时调用notifyChange触发通知系统服务通过ContentService统一管理观察者完成消息分发
1. 客户端注册观察者
在使用数据的客户端如 Activity、Fragment中通过 ContentResolver 注册 ContentObserver并指定监听的目标 URI从而实时更新UI。
// 使用者Activity通过ContentResolver注册观察者
getContentResolver().registerContentObserver( UserContract.CONTENT_URI, true, // 是否监听子 URI new ContentObserver(new Handler()) { Override public void onChange(boolean selfChange) { // 数据变化时触发回调} }
); registerContentObserver 是客户端主动调用的方法用于绑定观察者与目标数据 URI。true 表示监听该 URI 及其所有子路径如 content://com.example.provider/users/的数据变更。 2. 提供者触发通知
在 ContentProvider 中当数据发生变更如 insert、update、delete时需调用 notifyChange 方法触发回调
// 在 Provider 的 insert/update/delete 方法中
getContext().getContentResolver().notifyChange(uri, null); notifyChange 会通知所有注册了该 URI 的观察者。可通过第二个参数 observer 指定跳过特定观察者通常设为 null。 3. 系统级支持
ContentService负责管理所有注册的观察者以树形结构维护 URI 监听关系实现高效的跨进程通知分发。Binder 机制底层通过 Binder 传递观察者对象封装为 Transport 代理确保跨进程通信的可行性。 客户端需主动注册观察者监听 URI而通知触发由 Provider 发起两者通过系统服务协同实现实时数据同步。 七、ContentProvider 的性能优化
1.使用 SQLite 事务
批量操作时使用事务提高性能
SQLiteDatabase db dbHelper.getWritableDatabase();
db.beginTransaction();
try {// 执行多个操作db.setTransactionSuccessful();
} finally {db.endTransaction();
}
索引优化
对经常查询的字段添加索引
db.execSQL(CREATE INDEX IF NOT EXISTS idx_name ON users(name););
避免在主线程进行耗时操作
使用 Loader 或异步任务执行查询
getSupportLoaderManager().initLoader(0, null, this);
八、ContentProvider 的安全注意事项 谨慎设置 android:exported 仅在需要对外共享数据时设置为 true默认值为 false可防止外部访问 输入验证 对传入的 selection 和 projection 参数进行验证
private void validateProjection(String[] projection) {if (projection ! null) {for (String col : projection) {if (!allowedColumns.contains(col)) {throw new IllegalArgumentException(Invalid column: col);}}}
}