Android 四大组件——ContentProvider(内容提供者)

目录

一、ContentProvider概述

[1.1 什么是ContentProvider?](#1.1 什么是ContentProvider?)

[1.2 ContentProvider架构与工作原理](#1.2 ContentProvider架构与工作原理)

核心组件解析

[1.3 Uri 详解](#1.3 Uri 详解)

Uri组成部分解析

协议(Scheme):

授权者(Authority):

路径(Path):

ID:

[1.4 ContentProvider的创建与实现](#1.4 ContentProvider的创建与实现)

创建ContentProvider的步骤

步骤1:定义ContentProvider子类

步骤2:实现数据库帮助类

步骤3:实现ContentProvider核心方法

步骤4:在AndroidManifest.xml中注册

[1.5 ContentResolver](#1.5 ContentResolver)

例子:简单的读取收件箱信息(查询数据)

例子:添加一个新的联系人(插入数据)

[1.6 通过ContentObserver监听ContentProvider的数据变化](#1.6 通过ContentObserver监听ContentProvider的数据变化)


一、ContentProvider概述

1.1 什么是ContentProvider?

ContentProvider是Android四大组件之一,它为应用程序间的数据共享提供了标准化接口。通过ContentProvider,应用可以将自己的数据安全地暴露给其他应用,同时也可以通过统一的接口访问其他应用的数据

1.2 ContentProvider架构与工作原理

核心组件解析

ContentResolver :客户端用于访问ContentProvider的接口类
ContentProvider数据提供者的实现类
ContentValues :键值对集合,用于存储数据
Cursor :查询结果集,类似于数据库的结果集
Uri统一资源标识符,标识ContentProvider中的数据

1.3 Uri 详解

Uri(Uniform Resource Identifier,统一资源标识符)是Android中用于标识资源的字符串 。在ContentProvider体系中,Uri是访问数据的唯一标识,类似于Web中的URL。

XML 复制代码
content://com.example.app.provider/table1/123
└──┬──┘ └────────────┬───────────┘└──┬──┘└─┬─┘
 协议      授权者(AUTHORITY)        路径   ID

Uri组成部分解析

协议(Scheme)
  • content:// - 访问ContentProvider

  • file:// - 访问文件系统

  • http:// - 网络资源

  • tel:// - 拨号

授权者(Authority)
  • ContentProvider的唯一标识

  • 格式:包名.provider

  • 示例:com.example.app.provider

路径(Path)
  • 标识具体的数据表或资源

  • 可包含多级路径

  • 示例:/users/profile/image

ID
  • 标识特定记录

  • 可选部分

  • 示例:/users/123

1.4 ContentProvider的创建与实现

我们很少会自己来定义ContentProvider,因为我们很多时候都不希望自己应用的数据暴露给 其他应用,虽然这样,学习如何ContentProvider还是有必要的,多一种数据传输的方式,是吧~

这是之前画的一个流程图:

创建ContentProvider的步骤

步骤1:定义ContentProvider子类
java 复制代码
public class MyContentProvider extends ContentProvider {
    private static final String TAG = "MyContentProvider";
    
    // 数据库相关
    private SQLiteDatabase mDatabase;
    private static final String DATABASE_NAME = "myapp.db";
    private static final int DATABASE_VERSION = 1;
    
    // 表名
    private static final String TABLE_USERS = "users";
    
    // 授权者标识
    public static final String AUTHORITY = "com.example.myapp.provider";
    
    // 定义Content URI
    public static final Uri CONTENT_URI = 
        Uri.parse("content://" + AUTHORITY + "/" + TABLE_USERS);
    
    // 定义MIME类型
    private static final String CONTENT_TYPE = 
        "vnd.android.cursor.dir/vnd.com.example.users";
    private static final String CONTENT_ITEM_TYPE = 
        "vnd.android.cursor.item/vnd.com.example.user";
    
    // URI匹配器
    private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    private static final int USERS = 1;
    private static final int USER_ID = 2;
    
    static {
        sUriMatcher.addURI(AUTHORITY, TABLE_USERS, USERS);
        sUriMatcher.addURI(AUTHORITY, TABLE_USERS + "/#", USER_ID);
    }
}
步骤2:实现数据库帮助类
java 复制代码
private static class DatabaseHelper extends SQLiteOpenHelper {
    private static final String CREATE_TABLE_USERS = 
        "CREATE TABLE " + TABLE_USERS + " (" +
        UsersColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
        UsersColumns.NAME + " TEXT NOT NULL, " +
        UsersColumns.EMAIL + " TEXT NOT NULL, " +
        UsersColumns.AGE + " INTEGER, " +
        UsersColumns.CREATED_AT + " DATETIME DEFAULT CURRENT_TIMESTAMP" +
        ")";
    
    public DatabaseHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }
    
    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_TABLE_USERS);
    }
    
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("DROP TABLE IF EXISTS " + TABLE_USERS);
        onCreate(db);
    }
}

// 定义列名接口
public interface UsersColumns extends BaseColumns {
    String NAME = "name";
    String EMAIL = "email";
    String AGE = "age";
    String CREATED_AT = "created_at";
}
步骤3:实现ContentProvider核心方法
java 复制代码
@Override
public boolean onCreate() {
    DatabaseHelper dbHelper = new DatabaseHelper(getContext());
    mDatabase = dbHelper.getWritableDatabase();
    return mDatabase != null;
}

@Override
public Cursor query(Uri uri, String[] projection, String selection,
                   String[] selectionArgs, String sortOrder) {
    
    SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
    queryBuilder.setTables(TABLE_USERS);
    
    int match = sUriMatcher.match(uri);
    switch (match) {
        case USERS:
            // 查询所有用户
            break;
        case USER_ID:
            // 根据ID查询单个用户
            queryBuilder.appendWhere(UsersColumns._ID + "=" + 
                                   uri.getLastPathSegment());
            break;
        default:
            throw new IllegalArgumentException("Unknown URI: " + uri);
    }
    
    Cursor cursor = queryBuilder.query(mDatabase, projection, selection,
                                      selectionArgs, null, null, sortOrder);
    
    // 设置通知URI,当数据变化时通知观察者
    cursor.setNotificationUri(getContext().getContentResolver(), uri);
    return cursor;
}

@Override
public Uri insert(Uri uri, ContentValues values) {
    long rowId;
    int match = sUriMatcher.match(uri);
    
    if (match != USERS) {
        throw new IllegalArgumentException("Unknown URI: " + uri);
    }
    
    // 添加创建时间
    if (!values.containsKey(UsersColumns.CREATED_AT)) {
        values.put(UsersColumns.CREATED_AT, System.currentTimeMillis());
    }
    
    rowId = mDatabase.insert(TABLE_USERS, null, values);
    
    if (rowId > 0) {
        Uri newUri = ContentUris.withAppendedId(CONTENT_URI, rowId);
        // 通知数据变化
        getContext().getContentResolver().notifyChange(newUri, null);
        return newUri;
    }
    
    throw new SQLException("Failed to insert row into " + uri);
}

@Override
public int update(Uri uri, ContentValues values, String selection,
                 String[] selectionArgs) {
    
    int count;
    int match = sUriMatcher.match(uri);
    
    switch (match) {
        case USERS:
            count = mDatabase.update(TABLE_USERS, values, selection, selectionArgs);
            break;
        case USER_ID:
            String id = uri.getLastPathSegment();
            String where = UsersColumns._ID + "=" + id;
            if (selection != null) {
                where += " AND (" + selection + ")";
            }
            count = mDatabase.update(TABLE_USERS, values, where, selectionArgs);
            break;
        default:
            throw new IllegalArgumentException("Unknown URI: " + uri);
    }
    
    if (count > 0) {
        getContext().getContentResolver().notifyChange(uri, null);
    }
    
    return count;
}

@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
    
    int count;
    int match = sUriMatcher.match(uri);
    
    switch (match) {
        case USERS:
            count = mDatabase.delete(TABLE_USERS, selection, selectionArgs);
            break;
        case USER_ID:
            String id = uri.getLastPathSegment();
            String where = UsersColumns._ID + "=" + id;
            if (selection != null) {
                where += " AND (" + selection + ")";
            }
            count = mDatabase.delete(TABLE_USERS, where, selectionArgs);
            break;
        default:
            throw new IllegalArgumentException("Unknown URI: " + uri);
    }
    
    if (count > 0) {
        getContext().getContentResolver().notifyChange(uri, null);
    }
    
    return count;
}

@Override
public String getType(Uri uri) {
    int match = sUriMatcher.match(uri);
    switch (match) {
        case USERS:
            return CONTENT_TYPE;
        case USER_ID:
            return CONTENT_ITEM_TYPE;
        default:
            throw new IllegalArgumentException("Unknown URI: " + uri);
    }
}
步骤4:在AndroidManifest.xml中注册
XML 复制代码
<application>
    <!-- 声明ContentProvider -->
    <provider
        android:name=".provider.MyContentProvider"
        android:authorities="com.example.myapp.provider"
        android:enabled="true"
        android:exported="true"
        android:readPermission="com.example.myapp.permission.READ_USERS"
        android:writePermission="com.example.myapp.permission.WRITE_USERS"
        android:grantUriPermissions="true">
        
        <!-- 定义路径权限 -->
        <path-permission
            android:pathPrefix="/users"
            android:permission="com.example.myapp.permission.READ_USERS"
            android:readPermission="true" />
            
        <!-- 定义URI权限 -->
        <grant-uri-permission android:path="/users/public/*" />
        
    </provider>
    
    <!-- 声明权限 -->
    <permission
        android:name="com.example.myapp.permission.READ_USERS"
        android:protectionLevel="normal" />
        
    <permission
        android:name="com.example.myapp.permission.WRITE_USERS"
        android:protectionLevel="dangerous" />
</application>

1.5 ContentResolver

其实很多时候我们用到ContentProvider并不是自己暴露自己的数据,更多的时候通过ContentResolver 来读取其他应用的信息,最常用的莫过于读取系统APP,信息,联系人, 多媒体信息等!如果你想来调用这些ContentProvider就需要自行查阅相关的API资料了!

承接上面的例子,可以执行基本的CRUD操作。

java 复制代码
public class ContentProviderClient {
    
    // ContentProvider的URI
    private static final Uri CONTENT_URI = 
        Uri.parse("content://com.example.myapp.provider/users");
    
    /**
     * 查询所有用户
     */
    public List<User> queryAllUsers() {
        List<User> users = new ArrayList<>();
        Cursor cursor = null;
        
        try {
            // 获取ContentResolver
            ContentResolver resolver = getContentResolver();
            
            // 执行查询
            cursor = resolver.query(
                CONTENT_URI,           // URI
                null,                  // 查询的列
                null,                  // 查询条件
                null,                  // 条件参数
                UsersColumns.CREATED_AT + " DESC"  // 排序
            );
            
            if (cursor != null && cursor.moveToFirst()) {
                do {
                    User user = new User();
                    user.setId(cursor.getLong(cursor.getColumnIndex(UsersColumns._ID)));
                    user.setName(cursor.getString(cursor.getColumnIndex(UsersColumns.NAME)));
                    user.setEmail(cursor.getString(cursor.getColumnIndex(UsersColumns.EMAIL)));
                    user.setAge(cursor.getInt(cursor.getColumnIndex(UsersColumns.AGE)));
                    users.add(user);
                } while (cursor.moveToNext());
            }
        } catch (Exception e) {
            Log.e("ContentProviderClient", "查询失败", e);
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
        
        return users;
    }
    
    /**
     * 插入用户
     */
    public Uri insertUser(User user) {
        ContentValues values = new ContentValues();
        values.put(UsersColumns.NAME, user.getName());
        values.put(UsersColumns.EMAIL, user.getEmail());
        values.put(UsersColumns.AGE, user.getAge());
        
        Uri newUri = getContentResolver().insert(CONTENT_URI, values);
        return newUri;
    }
    
    /**
     * 更新用户
     */
    public int updateUser(long userId, User user) {
        ContentValues values = new ContentValues();
        values.put(UsersColumns.NAME, user.getName());
        values.put(UsersColumns.EMAIL, user.getEmail());
        values.put(UsersColumns.AGE, user.getAge());
        
        Uri userUri = ContentUris.withAppendedId(CONTENT_URI, userId);
        
        return getContentResolver().update(
            userUri,
            values,
            null,
            null
        );
    }
    
    /**
     * 删除用户
     */
    public int deleteUser(long userId) {
        Uri userUri = ContentUris.withAppendedId(CONTENT_URI, userId);
        return getContentResolver().delete(userUri, null, null);
    }
    
}

例子:简单的读取收件箱信息(查询数据)

java 复制代码
private void getMsgs(){
    Uri uri = Uri.parse("content://sms/");
    ContentResolver resolver = getContentResolver();
    //获取的是哪些列的信息
    Cursor cursor = resolver.query(uri, new String[]{"address","date","type","body"}, null, null, null);
    while(cursor.moveToNext())
    {
        String address = cursor.getString(0);
        String date = cursor.getString(1);
        String type = cursor.getString(2);
        String body = cursor.getString(3);
        System.out.println("地址:" + address);
        System.out.println("时间:" + date);
        System.out.println("类型:" + type);
        System.out.println("内容:" + body);
        System.out.println("======================");
    }
    cursor.close();
}

例子:添加一个新的联系人(插入数据)

java 复制代码
private void AddContact() throws RemoteException, OperationApplicationException {
    //使用事务添加联系人
    Uri uri = Uri.parse("content://com.android.contacts/raw_contacts");
    Uri dataUri =  Uri.parse("content://com.android.contacts/data");

    ContentResolver resolver = getContentResolver();
    ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
    ContentProviderOperation op1 = ContentProviderOperation.newInsert(uri)
            .withValue("account_name", null)
            .build();
    operations.add(op1);

    //依次是姓名,号码,邮编
    ContentProviderOperation op2 = ContentProviderOperation.newInsert(dataUri)
            .withValueBackReference("raw_contact_id", 0)
            .withValue("mimetype", "vnd.android.cursor.item/name")
            .withValue("data2", "Coder-pig")
            .build();
    operations.add(op2);

    ContentProviderOperation op3 = ContentProviderOperation.newInsert(dataUri)
            .withValueBackReference("raw_contact_id", 0)
            .withValue("mimetype", "vnd.android.cursor.item/phone_v2")
            .withValue("data1", "13798988888")
            .withValue("data2", "2")
            .build();
    operations.add(op3);

    ContentProviderOperation op4 = ContentProviderOperation.newInsert(dataUri)
            .withValueBackReference("raw_contact_id", 0)
            .withValue("mimetype", "vnd.android.cursor.item/email_v2")
            .withValue("data1", "779878443@qq.com")
            .withValue("data2", "2")
            .build();
    operations.add(op4);
    //将上述内容添加到手机联系人中~
    resolver.applyBatch("com.android.contacts", operations);
    Toast.makeText(getApplicationContext(), "添加成功", Toast.LENGTH_SHORT).show();
}

1.6 通过ContentObserver监听ContentProvider的数据变化

相关推荐
2501_915921432 小时前
App Store 上架流程中常见的关键问题
android·ios·小程序·https·uni-app·iphone·webview
nono牛2 小时前
实战项目:设计一个智能温控服务
android·前端·网络·算法
00后程序员张12 小时前
python 抓包在实际项目中的合理位置,结合代理抓包、设备侧抓包与数据流分析
android·ios·小程序·https·uni-app·iphone·webview
灵感菇_13 小时前
Android Service全面解析
android·service·四大组件
alexhilton14 小时前
Jetpack ViewModel内幕:内部机制与跨平台设计
android·kotlin·android jetpack
_李小白16 小时前
【Android FrameWork】延伸阅读: Android应用安装过程
android
光头闪亮亮16 小时前
Android手持机扫码出入库的开发详解-6.APP下载更新
android
光头闪亮亮17 小时前
Android手持机扫码出入库的开发详解-7.SQLite CRUD操作
android
键来大师17 小时前
Android16 设置壁纸出现APK重启问题和悬浮控件等图标变成黑色图框
android·framework·rk3576