一,写在前面 

       我们知道Android有四大组件,ContentProvider是其中之一,顾名思义:内容提供者。什么是内容提供者呢?一个抽象类,可以暴露应用的数据给其他应用。应用里的数据通常说的是数据库,事实上普通的文件,甚至是内存中的对象,也可以作为内容提供者暴露的数据形式。为什么要使用内容提供者呢?从上面定义就知道,内容提供者可以实现应用间的数据访问,一般是暴露表格形式的数据库中的数据。内容提供者的实现机制是什么呢?由于是实现应用间的数据通信,自然也是两个进程间的通信,其内部实现机制是Binder机制。那么,内容提供者也是实现进程间通信的一种方式。

         

        事实上在开发中,很少需要自己写一个ContentProvider,一般都是去访问其他应用的ContentProvider。本篇文章之所以去研究如何自己写一个ContentProvider,也是为了更好的在开发中理解:如何访问其他应用的内容提供者。

二,实现一个ContentProvider

接下来介绍如何自己去实现一个内容提供者,大致分三步进行:

        1,继承抽象类ContentProvider,重写onCreate,CUDR,getType六个方法;

        2,注册可以访问内容提供者的uri

        3,清单文件中配置provider

        第一步,onCreate()方法中,获取SQLiteDatabase对象;CUDR方法通过对uri进行判断,做相应的增删改查数据的操作;getType方法是返回uri对应的MIME类型。

        第二步,创建静态代码块,static{...code},在类加载的时候注册可以访问内容提供者的uri,使用类UriMatcher的addURI(...)完成。

        第三步,注册内容提供者,加入authorities属性,对外暴露该应用的内容提供者。


直接上代码,应用B的MyContentProvider,如下:

public class MyContentProvider extends ContentProvider {private DbOpenHelper helper;private SQLiteDatabase db;private static UriMatcher uriMatcher;public static final String AUTHORITY = "com.example.mycontentprovider.wang";public static final int CODE_PERSON = 0;static {uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);uriMatcher.addURI(AUTHORITY, "person", CODE_PERSON);}@Overridepublic boolean onCreate() {helper = DbOpenHelper.getInstance(getContext());db = helper.getWritableDatabase();//在数据库里添加一些数据initData();return true;}public void initData() {for (int i = 0; i < 5; i++) {ContentValues values = new ContentValues();values.put("name", "kobe" + (i + 1));values.put("age", 21 + i);db.insert("person", null, values);}}@Overridepublic String getType(Uri uri) {return null;}public String getTableName(Uri uri) {if (uriMatcher.match(uri) == CODE_PERSON) {return "person";} else {//...}return null;}@Overridepublic Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) {String tableName = getTableName(uri);if (tableName == null) {throw new IllegalArgumentException("uri has not been added by urimatcher");}Cursor cursor = db.query(tableName, projection, selection, selectionArgs, null, null, null);return cursor;}@Overridepublic Uri insert(Uri uri, ContentValues values) {String tableName = getTableName(uri);if (tableName == null) {throw new IllegalArgumentException("uri has not been added by urimatcher");}db.insert(tableName, null, values);//数据库中数据发生改变时,调用getContext().getContentResolver().notifyChange(uri, null);return uri;}@Overridepublic int delete(Uri uri, String selection, String[] selectionArgs) {String tableName = getTableName(uri);if (tableName == null) {throw new IllegalArgumentException("uri has not been added by urimatcher");}int row = db.delete(tableName, selection, selectionArgs);if (row > 0) {getContext().getContentResolver().notifyChange(uri, null);}return row;}@Overridepublic int update(Uri uri, ContentValues values, String selection,String[] selectionArgs) {String tableName = getTableName(uri);if (tableName == null) {throw new IllegalArgumentException("uri has not been added by urimatcher");}int row = db.update(tableName, values, selection, selectionArgs);if (row > 0) {getContext().getContentResolver().notifyChange(uri, null);}return row;}}
DbOpenHelper代码如下:

public class DbOpenHelper extends SQLiteOpenHelper {public DbOpenHelper(Context context, String name, CursorFactory factory,int version) {super(context, name, factory, version);}private static DbOpenHelper helper;public static synchronized DbOpenHelper getInstance(Context context) {if (helper == null) {//创建数据库helper = new DbOpenHelper(context, "my_provider.db", null, 1);}return helper;}//创建表@Overridepublic void onCreate(SQLiteDatabase db) {String sql = "create table person (_id integer primary key autoincrement, name Text, age integer)";db.execSQL(sql);}//数据库升级时,回调该方法@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}}
          在MyContentProvider$onCreate方法中,通过一个抽象帮助类SQLiteOpenHelper的子类实例,调用getWritableDatabase()获取SQLiteDatabase实例。先简单介绍下SQLiteOpenHelper,DbOpenHelper中我们提供一个getInstance的方法,用于获得SQLiteOpenHelper的一个子类实例,并采用单例设计模式;onCreate方法:创建数据库的表,且可以创建多个表;onUpgrade方法:在数据库版本发生改变时,该方法被回调,可以加入修改表的操作的代码。在MyContentProvider$onCreate方法中获取了SQLiteDatabase实例就可以操作数据库,下面分析第二步的注册uri。

    

       注册uri的目的就是确定哪些URI可以访问应用的数据,通常这些uri是由其他应用传递过来的,在后面访问uri的模块中会有所了解。UriMatcher可以用于注册uri,看起来就像一个容器,可以存储uri,还可以判断容器中是否有某一个uri。事实上,UriMatcher内部维护了一个ArrayList集合。查看UriMatcher的构造函数,代码如下:

public UriMatcher(int code)    {        mCode = code;        mWhich = -1;        mChildren = new ArrayList();        mText = null;    }

        由此可见UriMatcher并不是一个什么陌生的东西,就是学习Java时接触到的ArrayList集合,只是将添加uri,判断uri的操作做了相应的封装。addURI(String authority,String path, int code),authority,path后面会讲到;code:与uri一一对应的int值,后面在判断uri是否添加到UriMatcher时,是先将该uri转化为code,再进行判断。

       

         接下里分析CUDR操作,我们重写了这样四个方法:query,insert,delete,update,这个四个方法的参数都是想访问该应用的其他用户传递过来的,重点看uri。那么这个uri是如何构成的呢?uri = scheme + authorities + path。先看这样一个uri,

uri = "content://com.example.mycontentprovider.wang/a/b/c",

        scheme:"content://";

        authorities:com.example.mycontentprovider.wang;authorities就是在清单文件中配置的authorities属性的值,唯一标识该应用的内容提供者。

        path:/a/b/c;path里面常常放的是一些表名,字段信息,确定访问该数据库中哪个表的哪些数据,具体是访问哪些数据还要看CUDR对该uri做了怎样的操作。

          

        在getTableName方法中,我们调用uriMatcher.match(uri)获取uri对应的code,如果该code没有注册过,则抛出异常IllegalArgumentException。也就是说,在其他应用访问本应用的内容提供者时,如果uri“不合法”,那么会抛出IllegalArgumentException异常。

        然后调用SQLiteDatabase的query,insert,delete,update四个方法进行增删改查数据,值得一提的是,在增加,删除,修改数据后,需要调用内容解决者ContentResolver的notifyChange(uri,observer),通知数据发生改变。getType方法返回uri请求文件的MIME类型,这里返回null;


          清单文件中注册provider代码如下:

        
       authorities(也称,授权)属性必须指定相应的值,唯一标识该内容提供者,每个内容提供者的authorities的值都不同,它是访问的uri的一部分。

       exported属性:若没有intent-filter,则默认false,不可访问;若有intent-filter,则默认true,可以访问。亦可手动设置

       还可以添加权限属性,有兴趣的哥们可以自己去研究。以上就是自己写一个内容提供者的过程,分三步完成。下面展示另一个应用A,如何访问该应用的ContentProvider。

三,访问ContentProvider

应用A的代码,xml布局:

实体类Person代码如下:

package com.example.mcontentprovider.domain;public class Person {public int _id;public String name;public int age;public Person() {super();}public Person(int _id, String name, int age) {super();this._id = _id;this.name = name;this.age = age;}@Overridepublic String toString() {return "Person [name=" + name + ", age=" + age + "]";}}

MainActivity代码如下:

public class MainActivity extends Activity implements OnClickListener {private Button btn_add;private Button btn_deleteAll;private Button btn_query;private Button btn_update;private ContentResolver cr;private static final String AUTHORITIES = "com.example.mycontentprovider.wang";private MyContentObserver observer;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);cr = getContentResolver();observer = new MyContentObserver(new Handler());cr.registerContentObserver(Uri.parse(uri), false, observer);initView();}public void initView() {btn_add = (Button) findViewById(R.id.btn_add);btn_deleteAll = (Button) findViewById(R.id.btn_delete);btn_query = (Button) findViewById(R.id.btn_query);btn_update = (Button) findViewById(R.id.btn_update);btn_add.setOnClickListener(this);btn_deleteAll.setOnClickListener(this);btn_query.setOnClickListener(this);btn_update.setOnClickListener(this);}private String uri = "content://" + AUTHORITIES + "/person";@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.btn_add:new Thread(){public void run() {//休眠3秒,模拟异步任务SystemClock.sleep(3000);add();};}.start();break;case R.id.btn_delete:Log.e("MainActivity", "删除名字为Tom的数据");cr.delete(Uri.parse(uri), "name = ?", new String[]{"Tom"});break;case R.id.btn_query:Cursor cursor = cr.query(Uri.parse(uri), null, null, null, null);ArrayList persons = new ArrayList();while (cursor.moveToNext()) {int _id = cursor.getInt(0);String name = cursor.getString(1);int age = cursor.getInt(2);persons.add(new Person(_id, name, age));}Log.e("MainActivity", persons.toString());break;case R.id.btn_update:Log.e("MainActivity", "更改最后一条数据的name为paul");ContentValues values2 = new ContentValues();values2.put("name", "paul");//获取数据库的行数Cursor cursor2 = cr.query(Uri.parse(uri), null, null, null, null);int count = cursor2.getCount();cr.update(Uri.parse(uri), values2, "_id = ?", new String[]{count + ""});break;default:break;}}private void add() {Log.e("MainActivity", "添加一条name为Tom,age为21的数据");ContentValues values = new ContentValues();values.put("name", "Tom");values.put("age", 21);cr.insert(Uri.parse(uri), values);}private class MyContentObserver extends ContentObserver {public MyContentObserver(Handler handler) {super(handler);}@Overridepublic void onChange(boolean selfChange) {Toast.makeText(getApplicationContext(), "数据改变啦!!!", 0).show();super.onChange(selfChange);}}}

        在应用A中,我们设定uri = "content://" + AUTHORITIES + "/person",增删改查的操作对应都是该uri。事实上,只要内容提供者注册了的uri都可以访问,这里暂且让uri都相同。有兴趣的哥们可以尝试一下,若uri不合法,确实会抛出IllegalArgumentException异常。在实际开发中,最重要的是寻找到需要的uri,然后进行CUDR操作,如何进行CUDR操作不是本篇重点,不做讲解。

       注意到代码里添加数据时,这里创建了一个线程,使线程休眠了3s,用于模拟添加大量数据时的异步操作。同时注册了一个内容观察者用于监听数据变化,cr.registerContentObserver(Uri.parse(uri), false, observer)。第一个参数:监听的uri。第二个参数:若为true,表示以该uri字串为开头的uri都可以监听;若为false,表示只能监听该uri。第三个参数:ContentObserver子类实例,数据发生改变时回调onChange方法。

       执行点击操作,查看log。

       查询;

       添加->查询;(在点击添加按钮后,过了3秒左右,弹出toast,显示"数据改变啦!!!")

       删除->查询;

       更改->查询;

       log如下:


         这里解释下,在添加数据时,为何模拟异步操作。有这样一个场景:当数据添加进内容提供者的数据库中后,才可以执行某一个操作。那么onChange方法被回调时,就是一个很好的时机去执行某一个操作。

        可能有的哥们要问:在应用A中调用了ContentResolver的CUDR方法,那么怎么应用B中数据库的数据为何能变化呢?表面上可以这样理解:应用A在调用ContentResolver的CUDR方法时,会使应用B中对应的CUDR方法被调用,而uri则是应用A传递给应用B的。而为何“会使应用B中对应的CUDR方法被调用”,但是是Binder机制实现的。包括被回调的onChange方法也是Binder机制才能实现的,试想数据增删改查操作是在应用B完成的,为何在应用B中调用notifyChange方法通知数据改变后,应用A的onChange方法能被回调。

        侃了这么多,拿代码来点一下,查看ContentResolver$notifyChange源码如下:

public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork,            int userHandle) {        try {            getContentService().notifyChange(                    uri, observer == null ? null : observer.getContentObserver(),                    observer != null && observer.deliverSelfNotifications(), syncToNetwork,                    userHandle);        } catch (RemoteException e) {        }    }
继续查看ContentResolver$getContentService方法:

public static IContentService getContentService() {        if (sContentService != null) {            return sContentService;        }        IBinder b = ServiceManager.getService(CONTENT_SERVICE_NAME);        if (false) Log.v("ContentService", "default service binder = " + b);        sContentService = IContentService.Stub.asInterface(b);        if (false) Log.v("ContentService", "default service = " + sContentService);        return sContentService;    }
         sContentService不就是代理对象么,调用代理对象的notifyChange(...)方法:内部会调用transact方法向服务发起请求;然后onTransact(...)被调用,会调用IContentService接口的notifyChange方法完成通信。接口IContentService中方法的重写是在extends IContentService.Stub的类中,也就是ContentService。

四,另外

        好了,上面只是简单点了一下,说明ContentProvider暴露数据给其他应用访问,内部就是Binder机制原理实现的。常用进程间通信方式有:AIDL,ContentProvider,Messenger等。


       这篇文章就分享到这里啦,有疑问可以留言,亦可纠错,亦可补充,互相学习...^_^




          

   

         



 



       

更多相关文章

  1. “罗永浩抖音首秀”销售数据的可视化大屏是怎么做出来的呢?
  2. Nginx系列教程(三)| 一文带你读懂Nginx的负载均衡
  3. 不吹不黑!GitHub 上帮助人们学习编码的 12 个资源,错过血亏...
  4. android launcher之检测应用程序图标在桌面是否存在
  5. android中的设计模式--观察者模式
  6. Android的活动介绍
  7. android的CursorLoader用法小结
  8. Android(安卓)判断用户2G/3G/4G移动数据网络
  9. Android应用优化(7)数据库操作

随机推荐

  1. Mac下关于The "android" command is depr
  2. ArcGIS API For Android离线地图的实现
  3. Android中RecyclerView Adapter的骚操作
  4. 睡眠唤醒机制简介
  5. 《Android和PHP最佳实践》官方站
  6. Android: 打印Bundle内容
  7. Android崩溃后重启
  8. onAttachToWindow() 调用
  9. android实现电话状态监控
  10. Android(安卓)Bundle类