为什么微信、QQ、淘宝等App都能拜访联系人(通讯录)呢?是因为Android存在一种利用之间的数据共享机制,即ContentProvider,ContentProvider作为Android四大组件之一,为存储和获取数据提供对立的接口,能够在不同的应用程序之间共享数据。对于ContentProvier而言,无论数据的起源是什么,它都认为是种表(同时也反对文件数据,只是表格模式用得比拟多),而后把数据组织成表格返回给使用者。
<!-- more -->
自定义ContentProvider
step1、自定义类继承于ContentProvider,实现要求的办法
step2、在配置文件中通过provider标签配置,通过android:name属性指定待配置的类,通过android:authorities属性受权,指定以后内容提供者的uri标识,必须惟一。
上面来展现一个B利用来操作A利用中的数据的例子:
首先在ContentProviderDemo这个工程里写一个名为MyContentProvider的ContentProvider:
public class MyContentProvider extends ContentProvider { private static final String TAG = "MyContentProvider"; private SQLiteDatabase sqLiteDatabase; public MyContentProvider() { } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { Log.i(TAG, "delete: "); return sqLiteDatabase.delete("stu_info", selection, selectionArgs); } @Override public String getType(Uri uri) { // TODO: Implement this to handle requests for the MIME type of the data // at the given URI. throw new UnsupportedOperationException("Not yet implemented"); } @Override public Uri insert(Uri uri, ContentValues values) { Log.i(TAG, "insert: "); // 参数解释:操作表的名称、能够为空的列、参数 sqLiteDatabase.insert("stu_info", null, values); return uri; } // 在ContentProvider创立时调用 @Override public boolean onCreate() { SQLiteOpenHelper helper = new SQLiteOpenHelper(getContext(), "stu.db", null, 1) { @Override public void onCreate(SQLiteDatabase db) { db.execSQL("create table stu_info (id integer primary key autoincrement," + " name varchar(30) not null, age integer," + " gender varchar(2) not null)"); Log.i(TAG, "onCreate: 数据库创立胜利"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } }; sqLiteDatabase = helper.getWritableDatabase(); return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { Log.i(TAG, "query: "); return sqLiteDatabase.query("stu_info", projection, selection, selectionArgs, sortOrder, null, null); } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { Log.i(TAG, "update: "); return sqLiteDatabase.update("stu_info", values, selection, selectionArgs); }}
对于四大组件之一的ContentProvider同样须要在AndroidManifest.xml中申明:
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" package="cn.tim.contentproviderdemo"> <application android:allowBackup="true" .... android:theme="@style/AppTheme"> <provider android:name=".MyContentProvider" android:authorities="cn.tim.myprovider" android:enabled="true" android:exported="true" /> ... </application></manifest>
必须通过android:name属性指定待配置的类,通过android:authorities属性受权,指定以后内容提供者的uri标识,必须惟一,因为对于应用ContentProvier的App来说,这是惟一能够找到该ContentProvier的信息,就像坐标一样,是惟一能够确定你的地位的信息。
能够看到在这个类外面次要蕴含了CRUD等办法,还有getType()办法和onCreate()办法。所以为什么说对于ContentProvier而言,无论数据的起源是什么,它都认为是种表,而后把数据组织成表格。因为这恰好对应了表中数据的CRUD。至于getType()办法是做什么当初能够不论,整个MyContentProvider不过是在初始化的时候创立了数据库,拿到了SQLiteDatabase对象,而后MyContentProvider其中的CRUD办法实现成了数据库的操作方法而已。如果对数据库不太熟悉,能够参考之前的文章《SQLite原理与使用》,外面有具体介绍应用办法。
值得注意的是,尽管咱们在MyContentProvider的CRUD中应用了SQLite数据库,然而其实这和ContentProvider自身并没有关系,数据的增删改查咱们齐全也能够用HashMap这种数据结构存在内存中,或者存成文件的模式,一行文本就代表一个数据对象,这里为了不便演示所以间接采纳了SQLite。
ContentProviderDemo这个工程就完结了,因为作为内容提供者,它无需提供操作界面。上面看看使用者,也就是图中的OtherApplication。当然在这个示例中,这个工程名称为ContentAcquireDemo,界面和《SQLite原理与使用》中的界面截然不同,只不过是在CRUD的时候不再是操作本地SQLite,而是操作ContentProviderDemo中的MyContentProvider:
public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; ContentResolver contentResolver; private EditText etId; private EditText etName; private EditText etAge; private String sex = "男"; private ListView lvData; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); etId = findViewById(R.id.et_id); etName = findViewById(R.id.et_name); etAge = findViewById(R.id.et_age); // 单选框组件 RadioGroup rgSex = findViewById(R.id.rg_sex); lvData = findViewById(R.id.lv_data); // 获取ContentResolver对象 contentResolver = getContentResolver(); // 设置单选框的监听 rgSex.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { @Override public void onCheckedChanged(RadioGroup group, int checkedId) { switch (checkedId){ case R.id.rb_female: sex = "女"; break; case R.id.rb_male: sex = "男"; break; } } }); flushStuData(); } private void flushStuData() { Uri uri = Uri.parse("content://cn.tim.myprovider"); List<StudentInfo> stuList = new ArrayList<>(); // 参数解释:表名、要查问的字段、列条件、列条件参数、GroupBy、having、orderBy Cursor cursor = contentResolver.query(uri, null, null, null, null); if(cursor !=null && cursor.moveToFirst()){ do{ int id = cursor.getInt(0); String name = cursor.getString(1); int age = cursor.getInt(2); String sex = cursor.getString(3); stuList.add(new StudentInfo(id, name, age, sex)); } while (cursor.moveToNext()); cursor.close(); } lvData.setAdapter(new StuInfoAdapter(this, stuList)); } public void operatorData(View view) { Uri uri = Uri.parse("content://cn.tim.myprovider"); int viewId = view.getId(); switch (viewId) { case R.id.btn_add: ContentValues values = new ContentValues(); values.put("name", etName.getText().toString()); values.put("age", Integer.parseInt(etAge.getText().toString())); values.put("gender", sex); contentResolver.insert(uri, values); // 刷新数据展现 flushStuData(); Toast.makeText(MainActivity.this, "增加胜利", Toast.LENGTH_SHORT).show(); break; case R.id.btn_update: String idStr = etId.getText().toString(); ContentValues updateValues = new ContentValues(); // Key - value updateValues.put("name", etName.getText().toString()); updateValues.put("age", Integer.parseInt(etAge.getText().toString())); updateValues.put("gender", sex); contentResolver.update(uri, updateValues, "id=?", new String[]{idStr}); Toast.makeText(MainActivity.this, "更新胜利", Toast.LENGTH_SHORT).show(); flushStuData(); break; case R.id.btn_delete: String deleteIdStr = etId.getText().toString(); contentResolver.delete(uri, "id=?", new String[]{deleteIdStr}); // 刷新数据展现 flushStuData(); Toast.makeText(MainActivity.this, "删除胜利", Toast.LENGTH_SHORT).show(); break; } }}
能够看到,其实应用content://cn.tim.myprovider这个ContentProvider同样达到了CRUD的成果,须要留神的就是别把URI写错了就行,所以上面来看看URI的解析:
Uri匹配之UriMatcher
UriMatcher:在ContentProvider创立时,制订好匹配规定,当调用了ContentProvider中的操作方法时,利用匹配类去匹配传的uri,依据不同的uri给出不同的解决。
当初在MyContentProvider的onCrate()办法中定义一个UriMatcher匹配器,并且给出匹配规定如下:
public class MyContentProvider extends ContentProvider { ... private UriMatcher matcher; @Override public int delete(Uri uri, String selection, String[] selectionArgs) { Log.i(TAG, "delete: "); int matchCode = matcher.match(uri); switch (matchCode){ case 1001: Log.i(TAG, "delete: 匹配到门路是/hello"); break; default: Log.i(TAG, "delete: 执行删除数据库内容操作"); return sqLiteDatabase.delete("stu_info", selection, selectionArgs); } return 0; } ... // 在ContentProvider创立时调用 @Override public boolean onCreate() { ... sqLiteDatabase = helper.getWritableDatabase(); // 参数代表无奈匹配 // content://cn.tim.myprovider/hello matcher = new UriMatcher(UriMatcher.NO_MATCH); // Authority 、门路、匹配码 matcher.addURI("cn.tim.myprovider", "hello", 1001); return true; } ...}
这样在另一个App中应用MyContentProvider的delete()办法的时候就会进行URI匹配判断:
contentResolver.delete(Uri.parse("content://cn.tim.myprovider/hello"), null, null);
大家在今后的开发中可能会用到更多的匹配模式,接下来咱们学习UriMatcher更多匹配:
UriMatcher还能够应用匹配通配符来匹配任意不确定的值:
matcher = new UriMatcher(UriMatcher.NO_MATCH);// Authority 、门路、匹配码matcher.addURI("cn.tim.myprovider", "hello", 1001);// 匹配 cn.tim.myprovider/hello/任意数字matcher.addURI("cn.tim.myprovider", "hello/#", 1002);// 匹配 cn.tim.myprovider/world/任意字符串matcher.addURI("cn.tim.myprovider", "world/*", 1003);return true;
Uri与Uri自带的解析办法
应用Uri自带的解析办法
当初ContentAcquireDemo假如增加办法是这样调用的,即把参数写在Uri外面,这样在ContentProviderDemo工程的MyContentProvider中又是如何解析的呢?
Uri insertUri = Uri.parse("content://cn.tim.myprovider/whatever?name=Tim&age=22&gender=男");Uri uri = contentResolver.insert(insertUri, new ContentValues());long newId = ContentUris.parseId(uri);Toast.makeText(this, "增加胜利: Id" + newId, Toast.LENGTH_SHORT).show();
MyContentProvider.java的要害代码:
@Overridepublic Uri insert(Uri uri, ContentValues values) { long id = 0; // 为了放弃原来的形式不做变更,所以这里须要判断一下 if(values.size() > 0){ id = sqLiteDatabase.insert("stu_info", null, values); }else { String authority = uri.getAuthority(); String path = uri.getPath(); String query = uri.getQuery(); String name = uri.getQueryParameter("name"); String age = uri.getQueryParameter("age"); String gender = uri.getQueryParameter("gender"); Log.i(TAG, "insert:->主机名:" + authority + ",门路:" + path + ",查问数据:" + query + ",姓名:" + name + ",age:" + age + ",gender:" + gender); values.put("name", name); values.put("age", age); values.put("gender", gender); id = sqLiteDatabase.insert("stu_info", null, values); } return ContentUris.withAppendedId(uri, id); }
果然通过这样的Uri自带的解析形式来传递参数也是OK的。
对于Uri必须晓得的
这样的解析形式波及到Uri的组成和构造问题,首先来说一说URI和Uri是什么关系吧,Uri是Android的API,扩大了JavaSE中URI的一些性能来特定的实用于Android开发,所以大家在开发时,只应用Android 提供的Uri即可。
Uri对立资源标识符(Uniform Resource Identifier),有时咱们又看到URL这样的货色,他们之间的又是什么关系呢?对立资源标志符URI就是在某一规定下能把一个资源举世无双地标识进去,比方想要标识一个我国公民,只有用身份证号就能够作为惟一标识,然而应用其余形式也能够用来标识惟一集体,比方:集体定位协定://中华人名共和国/陕西省/西安市/临潼区/斜口街道/西安工程大学/8#宿舍/A120/邹长林, 这个字符串同样标识出了惟一的一个人,起到了URI的作用,所以URL是URI的子集。URL是以形容人的地位来惟一确定一个人的。
所以对立资源标志符URI就是在某一规定下能把一个资源举世无双地标识进去,URL就是某主机上的某门路上的文件来惟一确定一个资源,也就是定位的形式来实现的URI,即URL是URI的一种实现。
对于更多Uri构造和代码提取的材料能够参考《Uri详解之——Uri构造与代码提取》。
应用零碎的ContentProvider
上面是通过读写零碎通讯录和读取短信的几个小例子,作为ContentProvider应用练习:
读取通讯录
public void visitAddressBook(View view) { ContentResolver resolver = getContentResolver(); //联系人姓名 + Id Uri uri = ContactsContract.Contacts.CONTENT_URI; //联系人电话 Uri uriPhone = ContactsContract.CommonDataKinds.Phone.CONTENT_URI; Cursor cursor = resolver.query(uri, null, null, null, null); while(cursor!= null && cursor.moveToNext()){ String name = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME)); String contactId = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID)); Log.i(TAG, "visitAddressBook: name = " + name + ", id = " + contactId); String selection = ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=" +contactId; Cursor phoneCursor = resolver.query(uriPhone,null, selection, null, null); while (phoneCursor != null && phoneCursor.moveToNext()){ String phone = phoneCursor.getString(phoneCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)); Log.i(TAG, "visitAddressBook: name = " + name + ", phone = " + phone); } if(phoneCursor != null) phoneCursor.close(); } if(cursor != null) cursor.close();}
增加通讯录
public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; // 申请权限申请码 private static final int REQUEST_READ_SMS = 1001; // 查看权限,这种写法次要是针对比拟新的Android6.0及当前的版本 public static void verifyStoragePermissions(Activity activity) { int smsPermission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.READ_SMS); int contactsPermission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.READ_CONTACTS); int writeContactsPermission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_CONTACTS); if (smsPermission != PackageManager.PERMISSION_GRANTED || contactsPermission != PackageManager.PERMISSION_GRANTED || writeContactsPermission != PackageManager.PERMISSION_GRANTED) { // 如果没有权限须要动静地去申请权限 ActivityCompat.requestPermissions( activity, // 权限数组 new String[]{Manifest.permission.READ_SMS, Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS}, // 权限申请码 REQUEST_READ_SMS ); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); verifyStoragePermissions(this); } public void addAddressBook(View view) { //1、往一个ContentProvider中插入一条空数据,获取新生成的Id //2、利用刚刚生成的Id别离组合姓名和电话号码往另一个ContentProvider中插入数据 ContentValues values = new ContentValues(); ContentResolver resolver = getContentResolver(); Uri uri = resolver.insert(ContactsContract.RawContacts.CONTENT_URI, values); if(uri == null) throw new RuntimeException("插入新联系人失败"); values.clear(); long id = ContentUris.parseId(uri); // 插入姓名 values.put(ContactsContract.CommonDataKinds.StructuredName.RAW_CONTACT_ID, id); values.put(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, "Mike"); values.put(ContactsContract.CommonDataKinds.StructuredName.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE); uri = resolver.insert(ContactsContract.Data.CONTENT_URI, values); if(uri != null) Log.i(TAG, "addAddressBook: 插入姓名,id = " + ContentUris.parseId(uri)); //插入电话信息 values.clear(); values.put(ContactsContract.CommonDataKinds.Phone.RAW_CONTACT_ID, id); values.put(ContactsContract.CommonDataKinds.Phone.NUMBER, "15720918678"); //增加号码 values.put(ContactsContract.CommonDataKinds.Phone.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE); values.put(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE); //增加号码类型 uri = resolver.insert(ContactsContract.Data.CONTENT_URI, values); if(uri != null) Log.i(TAG, "addAddressBook: 插入电话号码,id = " + ContentUris.parseId(uri)); }}
读取短信
短信类型 | Uri |
---|---|
短信箱 | content://sms |
收件箱 | content://sms/inbox |
发件箱 | content://sms/sent |
草稿箱 | content://sms/draft |
public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; // 申请权限申请码 private static final int REQUEST_READ_SMS = 1001; // 查看权限,这种写法次要是针对比拟新的Android6.0及当前的版本 public static void verifyStoragePermissions(Activity activity) { int smsPermission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.READ_SMS); int contactsPermission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.READ_CONTACTS); if (smsPermission != PackageManager.PERMISSION_GRANTED || contactsPermission != PackageManager.PERMISSION_GRANTED) { // 如果没有权限须要动静地去申请权限 ActivityCompat.requestPermissions( activity, // 权限数组 new String[]{Manifest.permission.READ_SMS, Manifest.permission.READ_CONTACTS}, // 权限申请码 REQUEST_READ_SMS ); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); verifyStoragePermissions(this); } public void visitMessage(View view) { ContentResolver resolver = getContentResolver(); Uri uri = Uri.parse("content://sms"); Cursor cursor = resolver.query(uri, null, null, null, null); while(cursor != null && cursor.moveToNext()){ int addressIndex = cursor.getColumnIndex("address"); int bodyIndex = cursor.getColumnIndex("body"); String address = cursor.getString(addressIndex); String body = cursor.getString(bodyIndex); Log.i(TAG, "visitMessage:" + address + ":" + body); } if(cursor != null) cursor.close(); }}
在AndroidManifest.xml配置一下权限:
<uses-permission android:name="android.permission.READ_SMS"/>
ContentProvider的长处
ContentProvider的底层实现是Binder,更多对于Binder的内容能够参考官网文档《Binder》。ContentProvider为利用间的数据交互提供了一个平安的环境:容许把本人的利用数据依据需要凋谢给其余利用进行CRUD,而不必放心因为间接凋谢数据库权限而带来的平安问题。而其余对外共享数据的形式,数据拜访形式会因数据存储的形式而不同而发生变化,底层存储形式变更会影响下层,使拜访数据变得更加简单。而采纳ContentProvider形式,其解耦了底层数据的存储形式,使得无论底层数据存储采纳何种形式,外界对数据的拜访形式都是对立的,这使得拜访简略且高效。
原文地址:《探索ContentProvider》