美文网首页
Android——ContentProvider 内容提供者

Android——ContentProvider 内容提供者

作者: pkqgo | 来源:发表于2017-11-16 00:22 被阅读0次

    描述

    为了在应用程序之间交换数据,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<String, String> userHashMap;
        private static final HashMap<String, String> 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类

    
       <!-- 声明访问ContentProvider的权限-->
       <permission
           android:name="com.android.pkqup.androidnote.PROVIDER"
           android:protectionLevel="normal" />
    
    
       <provider
               android:name=".content_provider_test.MyContentProvider"
               //定义授权信息
               android:authorities="com.pkqup.android.note"
               //设置外部应用可访问
               android:exported="true"
              //设置自定义权限
    android:permission="com.android.pkqup.androidnote.PROVIDER" />
    

    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、在外部应用中也需要配置相同的权限

    <!--自定义上个应用ContentProvider需要的权限-->
    <permission
         android:name="com.android.pkqup.androidnote.PROVIDER"
         android:protectionLevel="normal" />
    
    <!--使用该权限-->
    <uses-permission android:name="com.android.pkqup.androidnote.PROVIDER"/>
    
    

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

    相关文章

      网友评论

          本文标题:Android——ContentProvider 内容提供者

          本文链接:https://www.haomeiwen.com/subject/vftkvxtx.html