Android ContentProvider以及权限设置 sdk初始化方式

首先了解一下创建一个ContentProvider类需要哪些必要的参数(要继承ContentProvider父类):

1.URI(重写的每个函数中都会用得到的参数)

 2.MIME类型

因为重写的方法中要重写一个方法叫做getType()来返回MIME型

其中会用到一个类:UriMatcher

第一步:初始化UriMathcer对象:

UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH); 

第二步:在ContentProvider中注册Uri:

第三步:在getType中拿到对应的表名或者MIME参数;

 

 3.重写四个增删改查函数;

为什么说ContentProvider增删改查操作是线程安全的呢?

因为通过SQLiteOpenHelper.getWritableDatabase()/getReadableDatabase()   创建数据库类的对象时,

这两个方法都调用了getDatabaseLocked(),该方法已经实现了线程安全,优先采用getReadableDatabase()方法

getWritableDatabase() :方法以读写方式打开数据库,一旦数据库的磁盘空间满了,数据库就只能读而不能写,倘若使用的是getWritableDatabase() 方法就会报错

getReadableDatabase():方法则是先以读写方式打开数据库如果数据库的磁盘空间满了,就会打开失败,当打开失败后会继续尝试以只读方式打开数据库。如果该问题成功解决,则只读数据库对象就会关闭,然后返回一个可读写的数据库对象。 

ContentProvider使用分为了进程内访问和进程间通信访问:

同进程内通信访问:

1.创建数据库类

基本的建表sql语句:

String SQL="create table 表名("  

+"id integer primary key autoincrement,"

+"name varchar(20),"           //***************************varchar(20)或者char(20),price real(实数),page integer(int 类)等等

+"author char(8))";

再执行语句db.execSQL(SQL);

2:自定义 ContentProvider 类

在上面将ContentProvider的时候基本都有讲述;

public class MyProvider extends ContentProvider {

    private Context mContext;
    DBHelper mDbHelper = null;
    SQLiteDatabase db = null;

    public static final String AUTOHORITY = "cn.scu.myprovider";
    // 设置ContentProvider的唯一标识

    public static final int User_Code = 1;
    public static final int Job_Code = 2;

    // UriMatcher类使用:在ContentProvider 中注册URI
    private static final UriMatcher mMatcher;
    static{
        mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        // 初始化
        mMatcher.addURI(AUTOHORITY,"user", User_Code);
        mMatcher.addURI(AUTOHORITY, "job", Job_Code);
        // 若URI资源路径 = content://cn.scu.myprovider/user ,则返回注册码User_Code
        // 若URI资源路径 = content://cn.scu.myprovider/job ,则返回注册码Job_Code
    }

    // 以下是ContentProvider的6个方法

    /**
     * 初始化ContentProvider
     */
    @Override
    public boolean onCreate() {

        mContext = getContext();
        // 在ContentProvider创建时对数据库进行初始化
        // 运行在主线程,故不能做耗时操作,此处仅作展示
        mDbHelper = new DBHelper(getContext());
        db = mDbHelper.getWritableDatabase();

        // 初始化两个表的数据(先清空两个表,再各加入一个记录)
        db.execSQL("delete from user");
        db.execSQL("insert into user values(1,'Carson');");
        db.execSQL("insert into user values(2,'Kobe');");

        db.execSQL("delete from job");
        db.execSQL("insert into job values(1,'Android');");
        db.execSQL("insert into job values(2,'iOS');");

        return true;
    }

    /**
     * 添加数据
     */

    @Override
    public Uri insert(Uri uri, ContentValues values) {

        // 根据URI匹配 URI_CODE,从而匹配ContentProvider中相应的表名
        // 该方法在最下面
        String table = getTableName(uri);

        // 向该表添加数据
        db.insert(table, null, values);

        // 当该URI的ContentProvider数据发生变化时,通知外界(即访问该ContentProvider数据的访问者)
        mContext.getContentResolver().notifyChange(uri, null);

//        // 通过ContentUris类从URL中获取ID
//        long personid = ContentUris.parseId(uri);
//        System.out.println(personid);

        return uri;
        }

    /**
     * 查询数据
     */
    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        // 根据URI匹配 URI_CODE,从而匹配ContentProvider中相应的表名
        // 该方法在最下面
        String table = getTableName(uri);

//        // 通过ContentUris类从URL中获取ID
//        long personid = ContentUris.parseId(uri);
//        System.out.println(personid);

        // 查询数据
        return db.query(table,projection,selection,selectionArgs,null,null,sortOrder,null);
    }

    /**
     * 更新数据
     */
    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        // 由于不展示,此处不作展开
        return 0;
    }

    /**
     * 删除数据
     */
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        // 由于不展示,此处不作展开
        return 0;
    }

    @Override
    public String getType(Uri uri) {

        // 由于不展示,此处不作展开
        return null;
    }

    /**
     * 根据URI匹配 URI_CODE,从而匹配ContentProvider中相应的表名
     */
    private String getTableName(Uri uri){
        String tableName = null;
        switch (mMatcher.match(uri)) {
            case User_Code:
                tableName = DBHelper.USER_TABLE_NAME;
                break;
            case Job_Code:
                tableName = DBHelper.JOB_TABLE_NAME;
                break;
        }
        return tableName;
        }
    }

3.Manifest清单注册类

<provider android:name="MyProvider"
                android:authorities="cn.scu.myprovider"
                    />

4.进程内访问 ContentProvider中的数据

public class MainActivity extends AppCompatActivity {

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

        /**
         * 对user表进行操作
         */

        // 设置URI
        Uri uri_user = Uri.parse("content://cn.scu.myprovider/user");

        // 插入表中数据
        ContentValues values = new ContentValues();
        values.put("_id", 3);
        values.put("name", "Iverson");


        // 获取ContentResolver
        ContentResolver resolver =  getContentResolver();
        // 通过ContentResolver 根据URI 向ContentProvider中插入数据
        resolver.insert(uri_user,values);

        // 通过ContentResolver 向ContentProvider中查询数据
        Cursor cursor = resolver.query(uri_user, new String[]{"_id","name"}, null, null, null);
        while (cursor.moveToNext()){
            System.out.println("query book:" + cursor.getInt(0) +" "+ cursor.getString(1));
            // 将表中数据全部输出
        }
        cursor.close();
        // 关闭游标

        /**
         * 对job表进行操作
         */
        // 和上述类似,只是URI需要更改,从而匹配不同的URI CODE,从而找到不同的数据资源
        Uri uri_job = Uri.parse("content://cn.scu.myprovider/job");
        
        // 插入表中数据
        ContentValues values2 = new ContentValues();
        values2.put("_id", 3);
        values2.put("job", "NBA Player");

        // 获取ContentResolver
        ContentResolver resolver2 =  getContentResolver();
        // 通过ContentResolver 根据URI 向ContentProvider中插入数据
        resolver2.insert(uri_job,values2);

        // 通过ContentResolver 向ContentProvider中查询数据
        Cursor cursor2 = resolver2.query(uri_job, new String[]{"_id","job"}, null, null, null);
        while (cursor2.moveToNext()){
            System.out.println("query job:" + cursor2.getInt(0) +" "+ cursor2.getString(1));
            // 将表中数据全部输出
        }
        cursor2.close();
        // 关闭游标
}
}

跨进程使用ContentProvider:

进程1

1.创建数据库类;

2.自定义ContentProvider类;

3.注册

前两步和同进程内相同;

注册需要加上exported属性,来表示可以被其他进程使用;

 android:exported="true"

进程2

1.调用ContentResolver进行访问;

这一步和进程内访问相同;

contentprovider的权限设置:

①.无限制:

如果一个provider 用了exported=true,那么任意三方调用方都能对其进行调用;

②.只对满足特定条件三方才进行暴露:

provider方

<provider
    android:name=".MyContentProvider"
    android:authorities="com.anddle.mycontentprovider"
    android:enabled="true"
    android:exported="true"
    android:permission="com.anddle.provideraccess" />

   <permission
        android:name="com.anddle.provideraccess"
        android:label="provider pomission"
        android:protectionLevel="normal" />

1.配置清单中加android:permission="com.anddle.provideraccess" 

2.再定义permission,其对应的name与声明的权限对应

调用方:

清单文件中声明: <uses-permission android:name="com.anddle.provideraccess" /> 即可调用provider

③.在②只对满足特定条件基础上并区分读写

provider方:

<provider
    android:name=".MyContentProvider"
    ......
    android:readPermission
="com.anddle.provideraccess.read" />

<permission
        android:name="com.anddle.provideraccess.read"
        android:label="provider pomission"
        android:protectionLevel="normal" />

1.配置清单中加android:readpermission(writepermission)="com.anddle.provideraccess.read" 

2.再定义permission,其对应的name与声明的权限对应

调用方:

清单文件中声明: <uses-permission android:name="com.anddle.provideraccess.read" /> 即可调用provider

做Android SDK开发的时候,一般我们会将初始化的方法封装为,然后让调用SDK的开发者在Application的onCreate方法中进行初始化。但是目前一些主流的SDK框架,并没有提供相关的方法进行初始化,但是我们在使用的时候也能正常使用,通过挖掘其源码,可以看出来他们一般使用的ContentProvider来进行SDK的初始化的,目前使用ContentProvider的知名SDK有:ButterKnife、Leakcanary、BlockCanary...等等。

一、ContentProvider初始化SDK库的实现

要实现在ContentProvider初始化SDK库,首先要在库中创建一个 ContentProvider,然后在 ContentProvider 的 onCreate() 方法中借助 getContext() 返回的 Context 来完成你的库初始化,当然,这个 Context 的实际类型就是应用的 Application。

下面是通过ContentProvider实现SDK库初始化的示例代码:

class ToolContentProvider : ContentProvider() {
    override fun onCreate(): Boolean {
        Log.e(GlobalConfig.LOG_TAG, "ToolContentProvider onCreate")
        AppContextHelper.init(context!!.applicationContext)
        AppContextHelper.initRoomDB(context!!.applicationContext)
        return true
    }
    override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor? {
        return null
    }
    override fun getType(uri: Uri): String? {
        return null
    }
    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        return null
    }
    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
        return 0
    }
    override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?): Int {
        return 0
    }
}
<provider
      android:name=".ToolContentProvider"
      android:authorities="${applicationId}.library-tool"
      android:exported="false" />
class MaoApplication : Application() {
    private lateinit var currentActivityRef: WeakReference<Activity>;
    override fun attachBaseContext(base: Context?) {
        super.attachBaseContext(base)
        Log.e(GlobalConfig.LOG_TAG, "MaoApplication attachBaseContext")
    }
    override fun onCreate() {
        super.onCreate()
        Log.e(GlobalConfig.LOG_TAG, "MaoApplication onCreate")
        initMMKV()
        initCodeView()
	}
    /**
     * 初始化MMKV工具
     */
    private fun initMMKV() {
        Log.e(GlobalConfig.LOG_TAG, "init MMKV")
        MMKV.initialize(this);
    }
    private fun initCodeView() {
        CodeProcessor.init(this)
    }
}

通过ContentProvider实现SDK库初始化的功能实现了,那么 ContentProvider 的 onCreate() 方法是什么时候被调用的呢?

参考文章: Application 的 onCreate 和 attachBaseContext_emmmmsuperdan的博客-CSDN博客。可以看到,它是介于 Application 的 attachBaseContext(Context) 和 onCreate() 之间所调用的,Application 的 attachBaseContext(Context) 方法被调用这就意味着 Application 的 Context 被初始化了。这也再次说明我们确实可以通过ContentProvider来进行SDK库的初始化,并且执行时间在Application的onCreate之前。

二、ContentProvider初始化SDK库的优缺点

优点:

  1. 不需要使用SDK库的开发者调用初始化库的流程,降低了接入成本
  2. 代码侵入更低,使得SDK库的代码隔离性做的更好,而且方便升级和维护。

缺点:

  1. 不一定适用SDK库的使用场景,因为在 ContentProvider 的 onCreate() 执行在 Application 的 onCreate() 方法之前,倘若你的库需要有其它业务的依赖,那么就不适合这种方式了。
  2. 需要注意应用安全漏洞问题,避免组件暴露,需要在声明provider的时候,配置exported为false
  3. 必须注意Provider的authorities千万别写死,否则两个引入同样SDK的App就无法共存了

三、ContentProvider初始化SDK库实现的源码分析

参考application初始化文章: Application 初始化流程_emmmmsuperdan的博客-CSDN博客

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值