Android——ContentProvider 内容提供者

描述

为了在应用程序之间交换数据,Android提供了ContentProvider,它是不同应用程序之间进行数据交换的标准API,当一个应用程序需要把自己的数据暴露给其他程序使用时,该应用程序就可通过提供ContentProvider来实现;其他应用程序就可通过ContentResolver来操作ContentResolver暴露的数据。

ContentProvider以指定Uri的形式对外提供数据,允许其他应用访问或修改数据;其他应用程序使用ContentResolver根据Uri去访问操作指定数据。

一旦某个应用程序通过 ContentProvider 暴露了自己的数据操作接口,那么不管该应用程序是否启动,其他应用程序都可通过该接口来操作该应用程序的内部数据,包括增加数据、删除数据、修改数据、查询数据等。

从源码分析ContentProvider的初始化

App进程启动 -> ActivityThread#main() -> ActivityThread#attach() -> ActivityManagerNative#attachApplication() ->
ActivityManagerService#attachApplication() -> ActivityManagerService#generateApplicationProvidersLocked() 。
generateApplicationProvidersLocked()这个方法通过 PackageManager 去获取解析后的应用的清单文件中 provider 信息,为每个 provider 新建 ContentProviderRecord 作为 ActivityManagerService 端的 ContentProvider 表现。

即ContentProvider的 onCreate()方法在ActivityThread#main()运行时间接调用,即ContentProvider是在APP启动的时候就初始化了。运行在主线程,不能做耗时的操作。

ContentProvider应用内数据共享

1、创建数据库类(使用数据库做数据共享)

public class DbHelper extends SQLiteOpenHelper {

    // 数据库名
    private static final String DATABASE_NAME = "pkqup.db";
    // 数据库版本号
    private static final int DATABASE_VERSION = 1;

    // 表名
    public static final String USER_TABLE_NAME = "user";
    public static final String ADDRESS_TABLE_NAME = "address";
    public static final String SPECIAL_CHARACTER = "/#";

    //在构造方法中指定数据库的 数据库名 和 数据库版本号
    public DbHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        // 创建两个表格:用户表 和地址表
        try {
            db.execSQL("CREATE TABLE IF NOT EXISTS " + USER_TABLE_NAME + " (" + User.INDEX   + " INTEGER PRIMARY KEY AUTOINCREMENT," + User.NAME + " TEXT, " + User.USER_ID + " TEXT)");
            db.execSQL("CREATE TABLE IF NOT EXISTS " + ADDRESS_TABLE_NAME + " (" + Address.INDEX   + " INTEGER PRIMARY KEY AUTOINCREMENT,"+ Address.NAME + " TEXT, " + Address.PHONE + " TEXT)");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        try {
            //数据库版本发生变化,删除旧表
            db.execSQL("DROP TABLE IF EXISTS " + USER_TABLE_NAME);
            db.execSQL("DROP TABLE IF EXISTS " + ADDRESS_TABLE_NAME);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2、自定义 ContentProvider 类

public class MyContentProvider extends ContentProvider {

    private DbHelper dbHelper;
    private SQLiteDatabase db;
    private ContentResolver resolver;

    // UriMatcher类使用:在ContentProvider 中注册URI
    private static final UriMatcher uriMatcher;

    // 用户定义列名->数据库列名的映射
    private static final HashMap userHashMap;
    private static final HashMap addressHashMap;


    // 定义ContentProvider的授权信息,即唯一标识
    public static final String AUTHORITY = "com.pkqup.android.note";

    // 定义Uri匹配返回码
    public static final int USER_CODE = 1;
    public static final int USER_CODE_SINGLE = 2;
    public static final int ADDRESS_CODE = 3;
    public static final int ADDRESS_CODE_SINGLE = 4;

    // 设置URI
    // Uri uri = Uri.parse("content://com.carson.provider/User/1")
    // 上述URI指向的资源是:名为 com.carson.provider 的 ContentProvider  中表名 为`User` 中的 `id`为1的数据

    // 特别注意:URI模式存在匹配通配符* 和 #

    // *:匹配任意长度的任何有效字符的字符串
    // 以下的URI 表示 匹配provider的任何内容
    // content://com.example.app.provider/*

    // #:匹配任意长度的数字字符的字符串
    // 以下的URI 表示 匹配provider中的table表的所有行
    // content://com.example.app.provider/table/#



    // 在静态代码块中初始化 UriMatcher
    static {
        // 初始化 UriMatcher
        // 常量UriMatcher.NO_MATCH ,不匹配任何路径的返回码 即初始化时不匹配任何东西
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

        // 在ContentProvider 中注册URI(即addURI())
        uriMatcher.addURI(AUTHORITY, DbHelper.USER_TABLE_NAME, USER_CODE);
        uriMatcher.addURI(AUTHORITY, DbHelper.USER_TABLE_NAME + DbHelper.SPECIAL_CHARACTER,
                USER_CODE_SINGLE);

        uriMatcher.addURI(AUTHORITY, DbHelper.ADDRESS_TABLE_NAME, ADDRESS_CODE);
        uriMatcher.addURI(AUTHORITY, DbHelper.ADDRESS_TABLE_NAME + DbHelper.SPECIAL_CHARACTER,
                ADDRESS_CODE_SINGLE);
        // 若URI资源路径 = content://com.pkqup.android.note/user ,则返回注册码USER_CODE,
        // 即 mMatcher.match(User.USER_CONTENT_URI)的返回值
        // 若URI资源路径 = content://com.pkqup.android.note/address ,则返回注册码ADDRESS_CODE,
        // 即 mMatcher.match(Address.ADDRESS_CONTENT_URI)的返回值

        userHashMap = new HashMap<>();
        userHashMap.put(User.INDEX, User.INDEX);
        userHashMap.put(User.NAME, User.NAME);
        userHashMap.put(User.USER_ID, User.USER_ID);

        addressHashMap = new HashMap<>();
        addressHashMap.put(Address.INDEX, Address.INDEX);
        addressHashMap.put(Address.NAME, Address.NAME);
        addressHashMap.put(Address.PHONE, Address.PHONE);
    }


    // onCreate()方法在ActivityThread#main()运行时间接调用,即ContentProvider是在APP启动的时候就初始化了。运行在主线程,不能做耗时的操作。
    @Override
    public boolean onCreate() {
        resolver = getContext().getContentResolver();
        dbHelper = new DbHelper(getContext());
        db = dbHelper.getWritableDatabase();
        return true;
    }

    @Override
    public String getType(Uri uri) {
        return null;
    }


    // 注:以下增删改成四个方法的说明:
    // 1、下面4个方法由外部进程回调,并运行在ContentProvider进程的Binder线程池中(不是主线程)
    // 2、存在多线程并发访问,需要实现线程同步
    // 3、若ContentProvider的数据存储方式是使用SQLite &
    // 一个,则不需要,因为SQLite内部实现好了线程同步,若是多个SQLite则需要,因为SQL对象之间无法进行线程同步
    // 4、若ContentProvider的数据存储方式是内存,则需要自己实现线程同步


    @Override
    public Uri insert(Uri uri, ContentValues values) {
        Uri newUri;
        switch (uriMatcher.match(uri)) {
            case USER_CODE:
                long user_id = db.insert(DbHelper.USER_TABLE_NAME, "", values);
                if (user_id < 0) {
                    throw new SQLiteException("Unable to insert " + values + " for " + uri);
                }
                newUri = ContentUris.withAppendedId(uri, user_id);
                resolver.notifyChange(newUri, null);
                break;
            case ADDRESS_CODE:
                long property_id = db.insert(DbHelper.ADDRESS_TABLE_NAME, "", values);
                if (property_id < 0) {
                    throw new SQLiteException("Unable to insert " + values + " for " + uri);
                }
                newUri = ContentUris.withAppendedId(uri, property_id);
                resolver.notifyChange(newUri, null);
                break;
            default:
                throw new IllegalArgumentException("Error Uri: " + uri);
        }
        return newUri;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        int count;
        switch (uriMatcher.match(uri)) {
            case USER_CODE:
                count = db.delete(DbHelper.USER_TABLE_NAME, selection, selectionArgs);
                break;
            case USER_CODE_SINGLE:
                String booking_id = uri.getPathSegments().get(1);
                count = db.delete(DbHelper.USER_TABLE_NAME,
                        User.INDEX + "=" + booking_id
                                + (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""),
                        selectionArgs);
                break;

            case ADDRESS_CODE:
                count = db.delete(DbHelper.ADDRESS_TABLE_NAME, selection, selectionArgs);
                break;
            case ADDRESS_CODE_SINGLE:
                String property_id = uri.getPathSegments().get(1);
                count = db.delete(DbHelper.ADDRESS_TABLE_NAME,
                        User.INDEX + "=" + property_id
                                + (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""),
                        selectionArgs);
                break;
            default:
                throw new IllegalArgumentException("Unnown URI" + uri);
        }
        getContext().getContentResolver().notifyChange(uri, null);
        return count;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        int count;
        switch (uriMatcher.match(uri)) {
            case USER_CODE:
                count = db.update(DbHelper.USER_TABLE_NAME, values, selection, selectionArgs);
                break;
            case USER_CODE_SINGLE:
                String booking_id = uri.getPathSegments().get(1);
                count = db.update(DbHelper.USER_TABLE_NAME, values,
                        User.INDEX + "=" + booking_id
                                + (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""),
                        selectionArgs);
                break;

            case ADDRESS_CODE:
                count = db.update(DbHelper.ADDRESS_TABLE_NAME, values, selection, selectionArgs);
                break;
            case ADDRESS_CODE_SINGLE:
                String property_id = uri.getPathSegments().get(1);
                count = db.update(DbHelper.ADDRESS_TABLE_NAME, values,
                        Address.INDEX + "=" + property_id
                                + (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""),
                        selectionArgs);
                break;
            default:
                throw new IllegalArgumentException("Unnown URI" + uri);
        }
        getContext().getContentResolver().notifyChange(uri, null);
        return count;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
            String sortOrder) {
        Cursor cursor = null;
        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
        String orderBy;
        switch (uriMatcher.match(uri)) {
            case USER_CODE:
                qb.setTables(DbHelper.USER_TABLE_NAME);
                // 用户定义列名->数据库列名的映射
                qb.setProjectionMap(userHashMap);
                if (TextUtils.isEmpty(sortOrder)) {
                    orderBy = User.DEFAULT_SORT_ORDER;
                } else {
                    orderBy = sortOrder;
                }
                cursor = qb.query(db, projection, selection, selectionArgs, null, null, orderBy);
                // 用来为Cursor对象注册一个观察数据变化的URI
                cursor.setNotificationUri(getContext().getContentResolver(), uri);
                break;
            case ADDRESS_CODE:
                qb.setTables(DbHelper.ADDRESS_TABLE_NAME);
                // 用户定义列名->数据库列名的映射
                qb.setProjectionMap(addressHashMap);
                if (TextUtils.isEmpty(sortOrder)) {
                    orderBy = Address.DEFAULT_SORT_ORDER;
                } else {
                    orderBy = sortOrder;
                }
                cursor = qb.query(db, projection, selection, selectionArgs, null, null, orderBy);
                // 用来为Cursor对象注册一个观察数据变化的URI
                cursor.setNotificationUri(getContext().getContentResolver(), uri);
                break;
        }
        return cursor;
    }
}

3、在AndroidManifest文件中注册创建的 ContentProvider类


   
   


   

4、进程内访问 ContentProvider的数据
4.1定义实体类

public class User {

    public static final String INDEX = "_index";// 主键
    public static final String NAME = "name";
    public static final String USER_ID = "userId";


    // 定义 user 表的 uri
    public static final Uri USER_CONTENT_URI =
            Uri.parse("content://" + MyContentProvider.AUTHORITY + "/" + DbHelper.USER_TABLE_NAME);

    // 定义 user 表的 uri
    public static final Uri USER_CONTENT_URI_SINGLE =
            Uri.parse("content://" + MyContentProvider.AUTHORITY + "/" + DbHelper.USER_TABLE_NAME
                    + DbHelper.SPECIAL_CHARACTER);

    // 排序方式,定义为主键排序
    public static final String DEFAULT_SORT_ORDER = INDEX;

    private String name;
    private String userId;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }
}

4.2、定义ContentProvider数据库操作类

public class UserUtils {

    private Context context;

    public UserUtils(Context context) {
        this.context = context;
    }

    public void insertUser(String name, String age) {
        ContentValues values = new ContentValues();
        values.put(User.NAME, name);
        values.put(User.USER_ID, age);
        context.getContentResolver().insert(User.USER_CONTENT_URI, values);
    }

    public void deleteUser() {
        context.getContentResolver().delete(User.USER_CONTENT_URI, null, null);
    }

    public void updateUser(String name, String age) {
        ContentValues values = new ContentValues();
        values.put(User.NAME, name);
        values.put(User.USER_ID, age);
        context.getContentResolver().update(User.USER_CONTENT_URI, values, null, null);
    }

    public User queryUser() {
        User user = new User();
        Cursor mCursor = context.getContentResolver().query(User.USER_CONTENT_URI, null, null, null,
                User.DEFAULT_SORT_ORDER);
        if (null != mCursor) {
            while (mCursor.moveToNext()) {
                String name = mCursor.getString(mCursor.getColumnIndexOrThrow(User.NAME));
                String age = mCursor.getString(mCursor.getColumnIndexOrThrow(User.USER_ID));
                user.setName(name);
                user.setUserId(age);
            }
            mCursor.close();
        }
        return user;
    }


    // 数据库查询方法的参数说明
    public void query(Uri uri, String[] projection, String selection, String[] selectionArgs,
            String sortOrder) {
        // projection表示要查询哪些列,比如查询媒体库,可能要关注音乐的艺术家,长度,文件位置等。
        // selection表示查询条件,就是查询里面的where语句,
        // selectionArgs是查询条件的值。
        // sortOrder是排序方式。
    }

    // 根据id查询名称
    public String getUserNameFrom(String userId) {
        String userName = "";
        Cursor mCursor = context.getContentResolver().query(User.USER_CONTENT_URI,
                new String[] {User.NAME, User.USER_ID}, "userId=?", new String[] {userId},
                User.DEFAULT_SORT_ORDER);
        if (null != mCursor) {
            while (mCursor.moveToNext()) {
                userName = mCursor.getString(mCursor.getColumnIndexOrThrow(User.NAME));
            }
            mCursor.close();
        }
        return userName;
    }
}

进程间数据共享

1、创建数据库类
见 DbHelper 类。同上。

2、自定义 ContentProvider 类
见 MyContentProvider 类。同上。

3、在AndroidManifest文件中注册创建的 ContentProvider类,注意配置权限和外部可使用属性。
同上。

4、在外部应用中也需要配置相同的权限







5、外部应用通过提供的Uri 对ContentProvider进行增删改查。
见 UserUtils 类。同上。

你可能感兴趣的:(Android——ContentProvider 内容提供者)