Android学习笔记——内容提供器
来源:互联网 发布:kit scream软件 编辑:程序博客网 时间:2024/06/02 03:10
参考书籍:Android第一行代码(第二版).郭霖著
文件存储、SharedPreferences存储及数据库存储所保存的数据只能在当前应用程序中访问(虽然文件存储和SharedPreferences存储中提供了MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE操作模式供其它应用程序访问当前应用数据,但在Android4.2中已废弃)。
跨程序数据共享应使用更安全可靠的内容提供器(Android跨程序共享数据的标准方式)。内容提供器提供一套完整机制,允许一个程序访问另一个程序中数据(保证数据安全性),可选择对哪部分数据进行共享(保证隐私不会泄露)。
1、运行时权限
之前的Android权限机制在保护用户安全和隐私方面作用有限。在Android6.0中引入运行时权限功能,更好保护用户安全隐私。
(1)Android权限机制
项目中添加权限声明后,如果用户在低于6.0系统的设备上安装此程序,就会清楚知晓此程序申请了哪些权限从而决定是否安装,但容易造成”店大欺客”的情况。
在6.0系统中加入了运行时权限功能,用户不需在安装软件时一次性授予所有申请的权限,在软件使用过程中可再对某一权限进行授予。
Android将所有权限归成两类:普通权限(不会直接威胁用户安全隐私,自动授权)和危险权限(可能会触及隐私、影响设备安全性,必须用户手动点击授权)。
危险权限:
除以上9组24个权限外都是普通权限(在AndroidManifest.xml中添加权限声明即可)。注意:每个危险权限都属于一个权限组,在进行运行时权限处理时使用的是权限名(用户一旦同意授权,对应权限组中所有其他权限会同时被授权)。
(2)在程序运行时申请权限
新建一个RuntimePermissionTest项目。修改布局文件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:id="@+id/make_call" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Make Call" /></LinearLayout>
修改MainAcitivity:
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) { try {//直接拨打电话,必须声明权限,为防止程序崩溃,所有操作都放在异常捕获代码块中 Intent intent = new Intent(Intent.ACTION_CALL); intent.setData(Uri.parse("tel:10086")); startActivity(intent); }catch (SecurityException e){ e.printStackTrace(); } } }); }}
修改AndroidManifest.xml文件,添加:
<uses-permission android:name="android.permission.CALL_PHONE" />
这样在低于Android6.0系统手机上可以运行,但在6.0及更高版本系统运行没有任何效果,会提示权限被禁止的错误信息(必须进行运行时处理)。
修改MainActivity:
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){ //判断用户是否已给我们授权,第二个参数是具体权限名,使用checkSelfPermission方法的返回值与PackageManager.PERMISSION_GRANTED作比较,相等说明已授权 ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CALL_PHONE }, 1); //没授权则需调用ActivityCompat.requestPermissions方法向用户申请授权,第二个参数是String数组,把要申请的权限名放入即可, // 第三个参数是请求码,只要是唯一值即可 }else { //已授权则直接执行拨打电话逻辑操作 call(); } } }); } private void call(){ try {//直接拨打电话,必须声明权限,为防止程序崩溃,所有操作都放在异常捕获代码块中 Intent intent = new Intent(Intent.ACTION_CALL); intent.setData(Uri.parse("tel:10086")); startActivity(intent); }catch (SecurityException e){ e.printStackTrace(); } } //调用requestPermissions()方法后,系统会弹出一个权限申请对话框(用户选择同意或拒绝),不论哪种结果,最终都会回调到onRequestPermissionsResult()方法中 //授权结果会封装在grantResults参数中 @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull 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: } }}
重新运行程序,点击按钮。
点击DENY:
再次点击按钮,选择ALLOW:
如果再次点击Make Call按钮就不会再弹出权限申请对话框了。如想关闭授权,进入Settings->Apps->RuntimePermissionTest->Permissions,进行关闭即可。
2、访问其他程序中的数据
内容提供器有两种用法:使用现有的内容提供器读取和操作相应程序中数据,创建自己的内容提供器给程序数据提供外部访问接口。
如果应用程序通过内容提供器对其数据提供了外部访问接口,其他任何应用程序都可对这部分数据进行访问。(Android中自带的电话簿、短信、媒体库等都提供了类似的访问接口)
(1)ContentResolver的基本用法
如果要想访问内容提供器中共享的数据,就一定要借助ContentResolver类(通过Context中的getContentResolver方法获取实例)。此类提供了一系列CRUD方法。
不同于SQLiteDatabases,此类中的增删查改方法不接收表名,用Uri参数代替(内容URI)。内容URI由两部分构成:authority(区分不同应用程序,一般用程序包名)和path(区分不同表),如:
content://com.example.app.provider/table1
得到内容URI后,需解析成Uri对象(Uri.parse()方法)才可作为参数传入。可使用此Uri对象查询table1中的数据:
Cursor cursor = getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder);
仍然返回Cursor对象。
增删改方法类似。
(2)读取系统联系人
需先手动添加几个联系人以便稍后读取。新建一个ContactsTest项目,编写布局文件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <ListView android:id="@+id/contacts_View" android:layout_width="match_parent" android:layout_height="match_parent" /></LinearLayout>
为了将关注重点放在读取系统联系人上,没有使用RecyclerView(代码偏多)。修改MainActivity:
public class MainActivity extends AppCompatActivity { ArrayAdapter<String> adapter; List<String> 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<String>(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); //ContactsContract.CommonDataKinds.Phone.CONTENT_URI常量为Uri.parse()解析出来的结果 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); } //通知刷新ListView adapter.notifyDataSetChanged(); } }catch (Exception e){ e.printStackTrace(); }finally { if (cursor != null){ //关闭Cursor对象 cursor.close(); } } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { switch (requestCode){ case 1: if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){ readContacts(); }else { Toast.makeText(this, "You denied the permisson", Toast.LENGTH_SHORT).show(); } break; default: } }}
还需声明读取系统联系人权限:
运行程序测试。
3、创建自己的内容提供器
(1)创建步骤
新建类继承ContentProvider来创建内容提供器。ContentProvider类中有6个抽象方法,继承时需重写。新建MyProvider:
public class MyProvider extends ContentProvider { @Override //初始化内容提供器时调用,通常在此完成对数据库的创建和升级等操作,返回true表示初始化成功 //只有当存在ContentResolver尝试访问程序中数据时,内容提供器才会被初始化 public boolean onCreate() { return false; } @Nullable @Override //从内容提供器中查询数据,参数:第一哪张表,第二哪些列,第三第四约束查询哪些行,第五对结果进行排序,结果存在Cursor对象中 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { return null; } @Nullable @Override //根据传入的内容URI返回相应的MIME类型 public String getType(Uri uri) { return null; } @Nullable @Override //向内容提供器添加一条数据,uri参数确定目标表,新数据保存在values参数中。返回用于表示这条新记录的URI public Uri insert(Uri uri, ContentValues values) { return null; } @Override //删除内容提供器中数据。返回被删除的行数 public int delete(Uri uri, String selection, String[] selectionArgs) { return 0; } @Override //更新内容提供器中已有数据。返回受影响的行数 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { return 0; }}
几乎每个方法都有Uri参数(调用ContentResolver的增删查改方法时传递过来的)。
需要对传入的Uri进行解析,分析出调用方期望访问的表和数据。内容URI主要有两种格式,可通过通配符分别匹配:
匹配任意表(*表示匹配任意长度任意字符)
content://com.example.app.provider/*
匹配table1表中任意行数据(#表示匹配任意长度数字)
content://com.example.app.provider/table1/#
接着借助UriMatcher类实现匹配内容URI功能。此类提供了addURI()方法,接收三个参数:authority,path,自定义代码。当调用UriMatcher的match()方法时,将Uri对象闯入,返回值为能匹配这个Uri对象对应的自定义代码(可判断调用方期望访问哪张表中数据)。修改MyProvider:
public class MyProvider extends ContentProvider { public static final int TABLE1_DIR = 0; public static final int TABLE1_ITEM = 1; public static final int TABLE2_DIR = 2; public static final int TABLE2_ITEM = 3; private static UriMatcher uriMatcher; static { uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); //将期望匹配的内容URI格式传入 uriMatcher.addURI("com.example.app.provider", "table1", TABLE1_DIR); uriMatcher.addURI("com.example.app.provider", "table1/#", TABLE1_ITEM); uriMatcher.addURI("com.example.app.provider", "table2", TABLE2_DIR); uriMatcher.addURI("com.example.app.provider", "table2/#", TABLE2_ITEM); } ... @Nullable @Override //从内容提供器中查询数据,参数:第一哪张表,第二哪些列,第三第四约束查询哪些行,第五对结果进行排序,结果存在Cursor对象中 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { switch (uriMatcher.match(uri)){//匹配成功,返回相应自定义代码 case TABLE1_DIR: //查询table1表中所有数据 break; case TABLE1_ITEM: //查询table1表中的单条数据 break; case TABLE2_DIR: //查询table2表中所有数据 break; case TABLE2_ITEM: //查询table2表中的单条数据 break; default: break; } ... } ...
以查询方法为例,其他方法类似。getType()用于获取Uri对象对应的MIME类型,MIME字符串主要由三部分组成:
a.以vnd开头
b.如内容URI以路径结尾,后接android.cursor.dir/,如以id结尾,则接android.cursor.item/.
c.最后接上vnd..。
继续实现getType()中逻辑:
...public String getType(Uri uri) { switch (uriMatcher.match(uri)){ case TABLE1_DIR: return "vnd.android.cursor.dir/vnd.com.example.app.provider.table1"; case TABLE1_ITEM: return "vnd.android.cursor.item/vnd.com.example.app.provider.table1"; case TABLE2_DIR: return "vnd.android.cursor.dir/vnd.com.example.app.provider.table2"; case TABLE2_ITEM: return "vnd.android.cursor.item/vnd.com.example.app.provider.table2"; default: break; } return null; } ...
(2)跨程序数据共享
在DatabaseTest项目上开发,通过内容提供器加入外部访问接口。首先将MyDatabaseHelper中的Toast去掉(跨程序访问不能使用Toast),然后创建内容提供器,右击包名->New->Other->Content Provider,命名为DatabaseProvider, authority指定为包名.provider.Exported是否允许外部程序访问,Enable是否启用,都勾选。
修改DatabaseProvider:
public class DatabaseProvider extends ContentProvider { public static final int BOOK_DIR = 0; public static final int BOOK_ITEM = 1; public static final int CATEGORY_DIR = 2; public static final int CATEGORY_ITEM = 3; public static final String AUTHORITY = "com.example.jojo.databasetest.provider"; private static UriMatcher uriMatcher; private MyDatabaseHelper dbHelper; static { uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); uriMatcher.addURI(AUTHORITY, "book", BOOK_DIR); uriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM); uriMatcher.addURI(AUTHORITY, "category", CATEGORY_DIR); uriMatcher.addURI(AUTHORITY, "category/#", CATEGORY_ITEM); } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { // 删除数据 SQLiteDatabase db = dbHelper.getReadableDatabase(); int deletedRows = 0; switch (uriMatcher.match(uri)){ case BOOK_DIR: deletedRows = db.delete("Book", selection, selectionArgs); break; case BOOK_ITEM: String bookId = uri.getPathSegments().get(1); deletedRows = db.delete("Book", "id = ?", new String[]{ bookId }); break; case CATEGORY_DIR: deletedRows = db.delete("Category",selection, selectionArgs); break; case CATEGORY_ITEM: String categoryId = uri.getPathSegments().get(1); deletedRows = db.delete("Category", "id = ?", new String[]{ categoryId }); break; default: break; } return deletedRows; } @Override public String getType(Uri uri) { switch (uriMatcher.match(uri)){ case BOOK_DIR: return "vnd.android.cursor.dir/vnd.com.example.jojo.databasetest.provider.book"; case BOOK_ITEM: return "vnd.android.cursor.item/vnd.com.example.jojo.databasetest.provider.book"; case CATEGORY_DIR: return "vnd.android.cursor.dir/vnd.com.example.jojo.databasetest.provider.category"; case CATEGORY_ITEM: return "vnd.android.cursor.item/vnd.com.example.jojo.databasetest.provider.category"; default: break; } return null; } @Override public Uri insert(Uri uri, ContentValues values) { //添加数据 SQLiteDatabase db = dbHelper.getReadableDatabase(); Uri uriReturn = null; 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; case CATEGORY_DIR: case CATEGORY_ITEM: long newCategoryId = db.insert("Category", null, values); uriReturn = Uri.parse("content://" + AUTHORITY + "/Category/" + newCategoryId); break; default: break; } return uriReturn; } @Override public boolean onCreate() { //创建MyDatabaseHelper实例,返回true表示内容提供器创建成功,这时数据库已经完成创建升级 dbHelper = new MyDatabaseHelper(getContext(),"BookStore.db", null, 2); return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { //查询数据 SQLiteDatabase db = dbHelper.getReadableDatabase(); Cursor cursor = null; switch (uriMatcher.match(uri)){ case BOOK_DIR: cursor = db.query("Book", projection, selection, selectionArgs, null, null, sortOrder); break; case BOOK_ITEM: //getPathSegments将内容URI权限后的部分以“/”符号分割,把分割后的结果放入到一个字符串列表中, // 此列表的第0个位置存放的就是路径,第1个位置存放的是id String bookId = uri.getPathSegments().get(1); cursor = db.query("Book", projection, "id = ?", new String[]{bookId},null, null, sortOrder); break; case CATEGORY_DIR: cursor = db.query("Category", projection, selection, selectionArgs, null, null, sortOrder); break; case CATEGORY_ITEM: String categoryId = uri.getPathSegments().get(1); cursor = db.query("Category", projection, "id = ?", new String[]{categoryId},null, null, sortOrder); break; default: break; } return cursor; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { //更新数据 SQLiteDatabase db = dbHelper.getReadableDatabase(); int updateRows = 0; switch (uriMatcher.match(uri)){ case BOOK_DIR: updateRows = db.update("Book", values, selection, selectionArgs); break; case BOOK_ITEM: String bookId = uri.getPathSegments().get(1); updateRows = db.update("Book", values, "id = ?", new String[]{bookId}); break; case CATEGORY_DIR: updateRows = db.update("Category", values, selection, selectionArgs); break; case CATEGORY_ITEM: String categoryId = uri.getPathSegments().get(1); updateRows = db.update("Category", values, "id = ?", new String[]{categoryId}); break; default: break; } return updateRows; }}
内容提供器一定要注册才能使用(Android Studio快捷方式创建的已自动完成这一步)。
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.jojo.databasetest"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> ... <provider android:name=".DatabaseProvider" android:authorities="com.example.jojo.databasetest.provider" android:enabled="true" android:exported="true"></provider> </application></manifest>
现在先将模拟器中的DatabaseTest程序删掉(放置遗留数据造成干扰),然后重新运行安装。关闭掉此项目,新建一个ProviderTest项目(用于访问DatabaseTest中数据)。修改布局文件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:id="@+id/add_data" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Add To Book" /> <Button android:id="@+id/query_data" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Query From Book" /> <Button android:id="@+id/update_data" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Update Book" /> <Button android:id="@+id/delete_data" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Delete From Book" /></LinearLayout>
修改MainActivity:
public class MainActivity extends AppCompatActivity { private String newId; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button addData = (Button) findViewById(R.id.add_data); addData.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //添加数据 Uri uri = Uri.parse("content://com.example.jojo.databasetest.provider/book"); ContentValues values = new ContentValues(); values.put("name", "A Clash of Kings"); values.put("author", "George Martin"); values.put("pages",1040); values.put("price", 22.85); Uri newUri = getContentResolver().insert(uri, values); newId = newUri.getPathSegments().get(1); } }); Button queryData = (Button) findViewById(R.id.query_data); queryData.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Uri uri = Uri.parse("content://com.example.jojo.databasetest.provider/book"); Cursor cursor = getContentResolver().query(uri, null, null, null, null); if (cursor != null){ while (cursor.moveToNext()){ String name = cursor.getString(cursor.getColumnIndex("name") ); String author = cursor.getString(cursor.getColumnIndex("author")); int pages = cursor.getInt(cursor.getColumnIndex("pages")); double price = cursor.getDouble(cursor.getColumnIndex("price")); Log.d("MainActivity", "book name is " + name); Log.d("MainActivity", "book author is " + author); Log.d("MainActivity", "book pages is " + pages); Log.d("MainActivity", "book price is " + price); } cursor.close(); } } }); Button updateData = (Button) findViewById(R.id.update_data); updateData.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //更新刚添加的数据,表中其他行不受影响 Uri uri = Uri.parse("content://com.example.jojo.databasetest.provider/book/" + newId); ContentValues values = new ContentValues(); values.put("name", "A Storm of Swords"); values.put("pages",1216); values.put("price", 24.05); getContentResolver().update(uri, values, null, null); } }); Button deleteData = (Button) findViewById(R.id.delete_data); deleteData.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Uri uri = Uri.parse("content://com.example.jojo.databasetest.provider/book/" + newId); getContentResolver().delete(uri, null, null); } }); }}
运行程序,分别点击增删改按钮,点击查询按钮查看日志信息。
添加后查询
更新后查询
- android学习笔记——内容提供器
- Android学习笔记——内容提供器
- android 学习笔记 内容提供器ContentResolver
- android学习笔记——服务、内容提供器、广播接收器、应用程序上下文
- android 学习笔记 创建自己的内容提供器
- Android第一行代码学习笔记五----内容提供器
- Android开发——内容提供器
- Android内容提供器——ContentProvider
- Nginx学习笔记——提供静态内容
- 内容提供器--学习笔记(1)
- 学习笔记(九)内容提供器
- ContentProvider内容提供器学习笔记
- Android学习-内容提供器 数据共享
- Android学习随笔(13)------内容提供器
- android内容提供器
- Android内容提供器
- 【Android】安卓学习笔记之用内容提供器访问其他程序中的数据
- Android学习笔记(七)内容提供器(Content Provider)
- 正则表达式汇总
- windows下 定时删除tomcat日志和缓存。可以保留天数
- Qt 之 ActiveX控件跑官方例程记录
- python爬虫学习hellow world
- AGTC
- Android学习笔记——内容提供器
- (四)Angular4 英雄征途HeroConquest-主从结构
- hadoop学习笔记 hadoop的配置
- Linux Mint安装matplotlib basemap
- Win10 Hyper-V虚拟机联网、共享文件方法
- Android 国际化
- java 使用spring实现读写分离
- 不能为虚拟电脑**打开一个新任务 VT-x is disabled in the BIOS for all CPU modes
- 一个菜鸟的oracle导入dmp文件过程