内容提供器(Content Provider)理论好无聊啊啊啊啊啊啊还是实践有意思啊啊啊不慌不慌整理完之后就去吃饭回来洗衣服啊啊啊
有9个危险权限。。而且是权限组
1.从奇怪的例子开始 (运行时权限)
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
构建了一个隐式Intent ,Intent的action指定为Intent.ACTION_CALL ,这是一个系统内置的打电话的动作。之前指定的action是Intent.ACTION_DIAL ,表示打开拨号界面,这个是不需要声明权限的,而Intent.ACTION_CALL 则可以直接拨打电话,因此必须声明权限。另外为了防止程序崩溃,我们将所有操作都放在了异常捕获代码块当中。
(低于6.0的版本。高于还是会报错,这就需要
动态申请
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button makeCall = (Button) findViewById(R.id.make_call); makeCall.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest. permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) { // 1 先检查有没有赋予好权限。如果没有的话,就去申请一下,有就直接打 // 这个函数接受了context,和权限名 ActivityCompat.requestPermissions(MainActivity.this, new String[]{ Manifest.permission.CALL_PHONE }, 1); // 2 request这个接受3个方法,实例,数组( 权限名字) 请求码 } else { call(); } } }); } // 4 call 这还是一样,只不过6.0版本后面都这么写~ private void call() { try { Intent intent = new Intent(Intent.ACTION_CALL); intent.setData(Uri.parse("tel:10086")); startActivity(intent); } catch (SecurityException e) { e.printStackTrace(); } } @Override // 3 弹出对话框后返回的 。得到的结果ok,就可以call了 public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { switch (requestCode) { case 1: if (grantResults.length > 0 && grantResults[0] == PackageManager. PERMISSION_GRANTED) { call(); } else { Toast.makeText(this, "You denied the permission", Toast.LENGTH_ SHORT).show(); } break; default: } } }
总结一下:
1 先判断是否有申请,没申请就动态申请一下 2 申请传入 实例、权限名、请求码~ 3 判断一下申请后的返回结果,如果ok就可以拨打 4 拨打使用intent,打开了对应程序。
2.访问其他程序中的数据
emmmm总体来说思只需要获取到该应用程序的内容URI,然后借助ContentResolver进行CRUD操作就可以了。但是我们还是看自己创建自己的内容提供器……
ContentResolver类,可以通过context的中的getContentResolver() 方法获取到该类的实例。ContentResolver中提供了一系列的方法用于对数据进行CRUD操作。但它接受的是uri,authority和path 前面是包名字 如com.example.app.provider 后面是区分表名 最前面加协议 最标准写法就是
content://com.example.app.provider/table2
解析的话,只要在前面加上Uri uri=Uri.parse("....上面的...") 变成uri对象!对象!对象!就可以进去了
增删改查,,和数据库都差不多。折叠代码看一下
这个代码。一个是,listview是要adapter装的~
ListView contactsView = (ListView) findViewById(R.id.contacts_view);
adapter = new ArrayAdapter(this, android.R.layout. simple_list_
item_1, contactsList);
contactsView.setAdapter(adapter);
后面也是这样新增add 最后的adapter要更新一下qwq
contactsList.add(displayName + "\n" + number);
adapter.notifyDataSetChanged();
其他的都是标准的申请权限的写法吧。。。查询是用的cursor、
public class MainActivity extends AppCompatActivity { ArrayAdapteradapter; List contactsList = new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ListView contactsView = (ListView) findViewById( R.id.contacts_view); adapter = new ArrayAdapter (this, android.R.layout. simple_list_ item_1, contactsList); contactsView.setAdapter(adapter); if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_ CONTACTS) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{ Manifest. permission.READ_CONTACTS }, 1); } else { readContacts(); } } private void readContacts() { Cursor cursor = null; try { // 查询联系人数据 cursor = getContentResolver().query(ContactsContract.CommonDataKinds. Phone.CONTENT_URI, null, null, null, null); if (cursor != null) { while (cursor.moveToNext()) { // 获取联系人姓名 String displayName = cursor.getString(cursor.getColumnIndex (ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)); // 获取联系人手机号 String number = cursor.getString(cursor.getColumnIndex (ContactsContract.CommonDataKinds.Phone.NUMBER)); contactsList.add(displayName + "\n" + number); } adapter.notifyDataSetChanged(); } } catch (Exception e) { e.printStackTrace(); } finally { if (cursor != null) { cursor.close(); } } } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { switch (requestCode) { case 1: if (grantResults.length > 0 && grantResults[0] == PackageManager. PERMISSION_GRANTED) { readContacts(); } else { Toast.makeText(this, "You denied the permission", Toast.LENGTH_ SHORT).show(); } break; default: } } } 下面重点看一下readContacts() 方法,可以看到,这里使用了ContentResolver的query() 方法来查询系统的联系人数据。不过传入的Uri 参数怎么有些奇怪啊?为什么没有调用Uri.parse() 方法去解析一个内容URI字符串呢?这是因为ContactsContract.CommonDataKinds.Phone 类已经帮我们做好了封装,提供了一个CONTENT_URI 常量,而这个常量就是使用Uri.parse() 方法解析出来的结果。接着我们对Cursor 对象进行遍历,将联系人姓名和手机号这些数据逐个取出,联系人姓名这一列对应的常量是ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME ,联系人手机号这一列对应的常量是ContactsContract.CommonDataKinds.Phone.NUMBER 。两个数据都取出之后,将它们进行拼接,并且在中间加上换行符,然后将拼接后的数据添加到ListView的数据源里,并通知刷新一下ListView。最后千万不要忘记将Cursor 对象关闭掉。 这样就结束了吗?还差一点点,读取系统联系人的权限千万不能忘记声明。修改AndroidManifest.xml中的代码
最后xml里一定要声明一下
啊~ 下午醒了继续 困困(?)qwq 现在使用资金创建的内容提供器
3. 使用自己的内容提供器
继承ContextProvider、用子类继承它的时候,需要将这6个方法全部重写。新建MyProvider 继承自ContentProvider
content://com.example.app.provider/table1/1
这就表示调用方期望访问的是com.example.app这个应用的table1表中id为1的数据。
内容URI的格式主要就只有以上两种,以路径结尾就表示期望访问该表中所有的数据,以id结尾就表示期望访问该表中拥有相应id的数据。我们可以使用通配符的方式来分别匹配这两种格式的内容URI,规则如下。
*:表示匹配任意长度的任意字符。
#:表示匹配任意长度的数字。
所以,一个能够匹配任意表的内容URI格式就可以写成:
content://com.example.app.provider/*
而一个能够匹配table1表中任意一行数据的内容URI格式就可以写成:
content://com.example.app.provider/table1/#
再借助UriMatcher类 实现匹配内容URI的功能。UriMatcher中addURI() 方法,3个参数,把authority 、path 和一个自定义代码传进去。返回值是某个能够匹配这个Uri 对象所对应的自定义代码,利用这个代码,我们就可以判断出调用方期望访问的是哪张表中的数据了。
这是啥意思呢?
有这样的代码:
public static final int TABLE2_ITEM = 3;//0 1 2 3
private static UriMatcher uriMatcher;
static {//
静态代码块
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI("com.example.app.provider", "table1", TABLE1_DIR);//same 0 1 2 3 --------------------------
public Cursor query(Uri uri, String[] projection, String selection, String[]
selectionArgs, String sortOrder) {
switch (uriMatcher.match(uri)) {
case TABLE1_DIR:
// 查询table1表中的所有数据
break;
//这样感觉好像是重写了query,每次再调用query的时候,会多match一下。匹配了,就会返回自定义代码,确定好是哪个表。????
还有getType方法,MIME先不看,先看实例
MIME(折叠)
一个内容URI所对应的MIME字符串主要由3部分组成,Android对这3个部分做了如下格式规定。
1 必须以vnd 开头。
2 如果内容URI以路径结尾,则后接android.cursor.dir/ ,如果内容URI以id结尾,则后接android.cursor.item/ 。
3 最后接上vnd.. 。
所以,对于content://com.example.app.provider/table1这个内容URI,它所对应的MIME类型就可以写成:
vnd.android.cursor.dir/vnd.com.example.app.provider.table1
这次来实现getType() 方法中的逻辑
@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)) {
case TABLE1_DIR:
return "vnd.android.cursor.dir/vnd.com.example.app.provider.table1";// 2 3 4
一个完整的内容提供器就创建完成了,现在任何一个应用程序都可以使用ContentResolver来访问我们程序中的数据。所有的CRUD操作都一定要匹配到相应的内容URI格式才能进行的,而我们当然不可能向UriMatcher中添加隐私数据的URI,所以这部分数据根本无法被外部程序访问到
【实战】:
创建时Exported 属性表示是否允许外部程序访问我们的内容提供器,Enabled 属性表示是否启用这个内容提供器。将两个属性都勾中
代码很长。
解析的时候 query()它 调用了Uri 对象的getPathSegments() 方法,它会将内容URI权限之后的部分以“/”符号进行分割,并把分割后的结果放入到一个字符串列表中,那这个列表的第0个位置存放的就是路径,第1个位置存放的就是id了。得到了id之后,再通过selection 和selectionArgs 参数进行约束,就实现了查询单条数据的功能。
=========【大段 代码】
这个逻辑有一些麻烦。但是,新建的是content provider
这样呢,会自动先把某些给你建立好~编辑即可
1 常量字符串,一个是访问所有,一个是按照id来
2 静态的添加到uriMatch里面去(
UriMatcher中addURI() 方法,3个参数,把authority 、path 和一个自定义代码传进去。返回值是某个能够匹配这个Uri 对象所对应的自定义代码)
3 query 查询哪个,uri怎么匹配是通过前面的设置好的静态常量来匹配的,这样就直接返回了一个封装好的查询语句。大概就只能这么查询吧,别的没有给封装鸭。嗯,有点懂了,因为都是封装好的uri,只能使用限定的插入删除功能,真安全啊~~~
uri.getPathSegments().get(1); 这个只是解析一下那长长的一串,,把里面,,,那个啥的分出来。提取出id
稍微总结一下:
uriMatcher实例接受addURI()参数,对于字符串常量巴拉巴拉~
在具体的query里面,要先匹配uriMatcher的match()方法,才能传送回去对应的数据参数回去。就像是管家一样,我想买日本的xx妆品,但是有违禁品啊不能买,那拜托代买好了,拜托单上只有安全商品。那就~~~
拜托 的过程是这样的,1建立 db getwriteable(这个一样)
2 创建返回值(插入是uri,增删是int,查询是cursor,, 不过前三个好像返回值都是int鸭~) Uri uriReturn = null; 反正后来还要用嘛;
3 switch来决定它在代买的那边是什么牌子,买好之后return个uri,就理解成订单号吧;看下面
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
case BOOK_ITEM:
long newBookId = db.insert("Book", null, values);
uriReturn = Uri.parse("content://" + AUTHORITY + "/book/" +
newBookId);
break;//..... ect
getType 是所有的内容提供器都必须提供的一个方法,用于获取Uri 对象所对应的MIME类型。
insert是写在一起的.,其他对于整表和按照id都不一样丫..
这里呢已经自动完成了因为新建的就是provider
现在justcallme这个项目就已经拥有了跨程序共享数据的功能了
继续看
来个添加数据 的范例:
public void onClick(View v) {
// 添加数据
Uri uri = Uri.parse("content://com.example.homework_justcallme. provider/contact");
ContentValues values = new ContentValues();
values.put("name", "A Clash of Kings");
values.put("number", "12334");
Uri newUri = getContentResolver().insert(uri, values);
newId = newUri.getPathSegments().get(1);
}
添加数据的话,1 先搞好了指定uri,照样封装了values,只不过装进去的时候是使用的封装好的Uri newUri = getContentResolver().insert(uri, values);(你依然写好了购物清单,只不过是网上下单,避免了自己亲自去)
这样其他的也都是,查询也是 先Uri.parse(
再使用这个uri ursor cursor = getContentResolver().query(uri, null, null, null,null);返回的数据呢都还是一样的,因为只是装起来了而已,用起来一样用~
==============待补充,自己写了实验理解更深刻了
==============待补充,自己写了实验理解更深刻了