Android ContentProvider 知识点总结

概述

ContentProvider:内容提供者,是Android四大组件之一,为其他app提供数据。其他app可以通过ContentResolver:内容解析器来增,删,查,改相关的数据。ContentProvider主要用于跨进程共享数据,它是一套标准的接口访问,其内部实现可以是SQLiteDatabase,文件,图片索引,媒体索引等。如果你不需要在多个应用间共享数据,可以使用SQLiteDatabase来实现。

ContentProvider简单使用

ContentProvider主要实现以下五个方法
  • onCreate:初始化provider时调用
  • insert:插入数据时调用
  • delete:删除数据时调用
  • query:用于查询数据
  • update:用于修改数据
ContentProvider内容提供者 简单示例实现如下:

/**
 * Created by xujinping on 2017/11/3.
 * ContentProvider 简单使用示例
 */

public class MyContentProvider extends ContentProvider {
    private static final String TAG = "MyContentProvider";
    private final static String DB_NAME = "my_demo.db";
    private final String TABLE_NAME = "Demo";
    private final int DB_VERSION = 2;
    private SQLiteDatabase mDataBase;
    private Context mContext;

    private static UriMatcher matcher = new UriMatcher(NO_MATCH);
    private final static String TABS[] = {
            Contract.DEMO_PATH
    };

    static {
        matcher.addURI(Contract.AUTHORITY, Contract.DEMO_PATH, 0);
    }

    @Override
    public boolean onCreate() {
        Log.i(TAG, "onCreate: ContentProvider");
        mContext = getContext();
        SQLDBHelper sqldbHelper = new SQLDBHelper(mContext);
        mDataBase = sqldbHelper.getWritableDatabase();
        return false;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        String tabName = getTabName(uri);
        if (TextUtils.isEmpty(tabName)) {
            return null;
        }
        Cursor cursor = mDataBase.query(tabName, projection, selection, selectionArgs, null, null, sortOrder);
        return cursor;
    }

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

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, ContentValues values) {
        String tabName = getTabName(uri);
        if (TextUtils.isEmpty(tabName)) {
            return null;
        }
        long id = mDataBase.insert(tabName, null, values);
        Uri resultUri = null;
        if (id != -1) {
            resultUri = ContentUris.withAppendedId(uri, id);
            ContentResolver resolver = mContext.getContentResolver();
            resolver.notifyChange(resultUri, null);
        }
        return resultUri;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        String tabName = getTabName(uri);
        if (TextUtils.isEmpty(tabName)) {
            return -1;
        }
        int count = mDataBase.delete(tabName, selection, selectionArgs);
        return count;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        String tabName = getTabName(uri);
        if (TextUtils.isEmpty(tabName)) {
            return -1;
        }
        int count = mDataBase.update(tabName, values, selection, selectionArgs);
        return count;
    }

    private String getTabName(Uri uri) {
        int code = matcher.match(uri);
        if (code == NO_MATCH) {
            return null;
        }
        return TABS[code];
    }

    private class SQLDBHelper extends SQLiteOpenHelper {

        public SQLDBHelper(Context context) {
            super(context, DB_NAME, null, DB_VERSION);
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
            //第一次安装使用该应用时调用
            createDemoTable1(db);

            final int firstVersion = 1;//该值是第一个版本的版本号,一直不变即可
            onUpgrade(db, firstVersion, DB_VERSION);
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            //只有数据库版本大于之前数据库版本,才会执行该方法,否者 都执行onCreate方法
            for (int i = oldVersion; i < newVersion; i++) {
                switch (i) {
                    case 1://升级为版本2
                        alertDemoTable1(db);
                        break;
                    case 2://升级为版本3
                        break;
                }
            }
        }
    }

    private void createDemoTable1(SQLiteDatabase db) {
        try {
            String sql = "create table " + TABLE_NAME + " ("
                    + "id integer primary key autoincrement, "
                    + "name text, "
                    + "age integer)";
            db.execSQL(sql);
            Log.i(TAG, "createDemoTable1: 创建demo表成功");
        } catch (Exception e) {
            Log.i(TAG, "createDemoTable1: 创建demo表失败");
        }
    }

    /**
     * 升级数据库
     * @param db
     */
    private void alertDemoTable1(SQLiteDatabase db) {
        try {
            db.execSQL("alter table " + TABLE_NAME + " add " + "address" + " text ");
            Log.i(TAG, "alertDemoTable1: 升级数据库成功");
        } catch (Exception e) {
            Log.i(TAG, "alertDemoTable1: 升级数据库失败");
        }
    }
}
ContentProvider 客户端实现如下
public class ContentProviderDemo extends Activity {
    private static final String TAG = "ContentProviderDemo";
    private ContentResolver provider;
    private TextView mContent;
    private int mIndex;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_content_provider);

        provider = getContentResolver();
        mContent = (TextView) findViewById(R.id.content);
    }

    public void insert(View view) {
        mIndex++;
        ContentValues values = new ContentValues();
        values.put("name", "王二" + mIndex);
        values.put("age", 21 + mIndex);
        values.put("address", "珠海" + mIndex);
        Uri uri = provider.insert(Contract.DEMO_URI, values);
        Log.i(TAG, "insert: uri===" + uri.getPath());
    }

    public void query(View view) {
        StringBuilder sb = new StringBuilder();
        Cursor cursor = provider.query(Contract.DEMO_URI, null, null, null, null);
        if (cursor != null && cursor.getCount() > 0) {
            while (cursor.moveToNext()) {
                int nameIndex = cursor.getColumnIndex("name");
                int ageIndex = cursor.getColumnIndex("age");
                int addressIndex = cursor.getColumnIndex("address");
                String name = cursor.getString(nameIndex);
                sb.append("name:").append(name);
                sb.append("  ");
                int age = cursor.getInt(ageIndex);
                sb.append("age:").append(age);
                sb.append("  ");
                String address = cursor.getString(addressIndex);
                sb.append("address:").append(address);
                sb.append("\n");
            }
            cursor.close();
            mContent.setText(sb.toString());
        }
    }
}

ContentProvider 共享数据

AndroidManifest.xml配置文件

    <--声明其他进程访问该provider所需要的权限!-->
    <permission
        android:name="com.example.xjp.demo.contentprovider.READ_PROVIDER"
        android:protectionLevel="normal" />
    <permission
        android:name="com.example.xjp.demo.contentprovider.WRITE_PROVIDER"
        android:protectionLevel="normal" />

  <provider
        android:name=".contentprovider.MyContentProvider"
        android:authorities="com.example.xjp.demo.contentprovider"
        android:exported="true"
        android:multiprocess="true"//是否允许多个进程多个实例?默认为false,即只有一个ContentObserver实例
        android:readPermission="com.example.xjp.demo.contentprovider.READ_PROVIDER"
        android:writePermission="com.example.xjp.demo.contentprovider.WRITE_PROVIDER"/>

如果你想要让其他app能访问你的ContentProvider数据,必须做如下事情:

  1. 在配置文件中设置ContentProvider 的authorities属性
  2. 在配置文件中设置ContentProvider 的exported属性为true
  3. 在配置文件中设置ContentProvider 的读写权限属性
  4. 在配置文件中注册ContentProvider 的读写权限

为了让第三方应用使用你的ContentProvider,第三方应用应该在AndroidManifest.xml中声明ContentProvider读写权限:

<uses-permission android:name="com.example.xjp.demo.contentprovider.READ_PROVIDER"/>
<uses-permission android:name="com.example.xjp.demo.contentprovider.WRITE_PROVIDER"/>

ContentProvider多线程问题

ContentProvider为多个应用提供数据,那么ContentProvider肯定有多线程访问的问题,那么ContentProvider是不是线程安全的呢?答案是:ContentProvider是线程不安全的。所以如果你的应用中涉及到ContentProvider多线程问题,ContentProvider中insert,delete,update方法必须同步,否则将会出现数据错乱问题。解决方案如下:给这三个方法都加上类锁即可。

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, ContentValues values) {
        //加类锁
        synchronized (MyContentProvider.class) {
            String tabName = getTabName(uri);
            if (TextUtils.isEmpty(tabName)) {
                return null;
            }
            long id = mDataBase.insert(tabName, null, values);
            Uri resultUri = null;
            if (id != -1) {
                resultUri = ContentUris.withAppendedId(uri, id);
                ContentResolver resolver = mContext.getContentResolver();
                resolver.notifyChange(resultUri, null);
            }
            return resultUri;
        }
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        //加类锁
        synchronized (MyContentProvider.class) {
            String tabName = getTabName(uri);
            if (TextUtils.isEmpty(tabName)) {
                return -1;
            }
            int count = mDataBase.delete(tabName, selection, selectionArgs);
            return count;
        }
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        //加类锁
        synchronized (MyContentProvider.class) {
            String tabName = getTabName(uri);
            if (TextUtils.isEmpty(tabName)) {
                return -1;
            }
            int count = mDataBase.update(tabName, values, selection, selectionArgs);
            return count;
        }
    }

ContentObserver

ContentObserver用于监听ContentProvider数据变化,我们上面的例子在插入数据时,会通过ContentResolver#notifyChange()方法来发送一个数据更新通知,让外部监听器知道该数据发生变化了。ContentObserver使用示例如下:

 provider = getContentResolver();
 provider.registerContentObserver(Contract.DEMO_URI, true, new MyContentObserver(new Handler()));

private class MyContentObserver extends ContentObserver {
        public MyContentObserver(Handler handler) {
            super(handler);
        }

        @Override
        public void onChange(boolean selfChange, Uri uri) {
            Log.i(TAG, "onChange: uri====" + uri.toString());
        }
    }

ContentProvider 初始化时机

ContentProvider初始化时机有点神秘,在app起来了就已经初始化了,但是也不知道具体什么时机?有谁来初始化它?我们可以根据堆栈来看看

这里写图片描述

右上图可以看出,ContentProvider 是在创建应用的Application时由ActivityThread创建的。所以app一起动时,该app的所有ContentProvider都初始化了。

注意:在ContentProvider 的onCreate初始化方法中不要做耗时操作,否则会影响整个app的启动速度

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值