学习流程来自《第一行代码》(第二版)
Content Provider主要用于在不同应用之间实现跨程序数据共享。
Android运行时权限
Android在6.0系统中加入了运行时权限功能,将所有权限归为了两类 :
1. 普通权限 :系统自动帮我们授权
2. 危险权限 :需要用户手动授权 一共9组,24个权限。一旦用户同意授权,那么该权限组中所有的其他权限也会同时被授权。
在程序运行时申请权限
activity_main.xml
<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>
点击按钮触发打电话的逻辑。
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); // 6.0以下版本运行 权限 代码
makeCall.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
try {
Intent intent = new Intent(Intent.ACTION_CALL); // 系统内置的打电话的动作
intent.setData(Uri.parse("tel:10086"));
startActivity(intent); // 需要在AndroidManifest.xml中声明
} catch (SecurityException e) {
e.printStackTrace();
}
}
});
}
}
会要求我们申请权限
AndroidManifest.xml
<uses-permission android:name="android.permission.CALL_PHONE" />
运行,在6.0版本以上的手机是会报错的
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 view) {
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) { // 判断用户是不是已经授权checkSelfPermission(context, 具体权限) 相等就已经授权
// ContextCompat.checkSelfPermission,主要用于检测某个权限是否已经被授予,方法返回值为PackageManager.PERMISSION_DENIED或者PackageManager.PERMISSION_GRANTED。当返回DENIED就需要进行申请授权了。
ActivityCompat.requestPermissions(MainActivity.this, new String[]{ Manifest.permission.CALL_PHONE}, 1); // 申请权限 这是一个异步方法用于向用户申请授权
//Activity activity,String[] permissions,int requestCode
// Activity实例, 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();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { // 授权结果封装在grantResults参数中 处理申请回掉 int requestCode
switch (requestCode) { // 对于权限的申请结果,首先验证requestCode定位到你的申请,然后验证grantResults对应于申请的结果,这里的数组对应于申请时的第二个权限字符串数组。
// 如果你同时申请两个权限,那么grantResults的length就为2,分别记录你两个权限的申请结果
case 1: // ActivityCompat.requestPermissions()方法中的第三个参数
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // 申请结果是通过
call();
} else {
Toast.makeText(this, "You denied the permission", Toast.LENGTH_LONG).show();
}
break;
default:
break;
}
}
}
点击允许,再次点击Make Call
可以在设置中对授予的权限进行关闭。
访问其他程序的数据
内容提供器有两种使用方法 :
1. 使用现有的内容提供器来读取和操作相应程序中的数据
2. 创建自己的内容提供器给程序的数据提供外部访问的接口
利用ContentResolver类(getContentResolver()方法获得)
内容提供器的唯一标识符是内容URI参数(由两部分组成authority和path,Content://com.example.app.provider/table1)
读取系统联系人
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/contacts_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>
利用ListView 来显示从手机中读取的联系人信息
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_expandable_list_item_1, contactsList);
contactsView.setAdapter(adapter); // 配置ListView
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); // 查询联系人数据(联系人的URI,指定查询的列名,指定where的约束条件,为where中的占位符提供具体的值,指定查询结果的排序方式)
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_LONG).show();
}
break;
default:
break;
}
}
}
添加读取联系人的权限
<uses-permission android:name="android.permission.READ_CONTACTS" />
创建自己的内容提供器
通过新建类继承ContentProvider来创建一个内容提供器。
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.yezhou.example.com.databasetest.provider";
private static UriMatcher uriMatcher;
private MyDatabaseHelper dbHelper;
static { // 初始化操作,将期望匹配的几种uri格式添加
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTHORITY, "book", BOOK_DIR);
uriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM);
uriMatcher.addURI(AUTHORITY, "category", CATEGORY_DIR);
}
public DatabaseProvider() {
}
@Override
public boolean onCreate() {
dbHelper = new MyDatabaseHelper(getContext(), "BookStore.db", null, 2);
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) { // 查询数据
// TODO: Implement this to handle query requests from clients.
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:
String bookId = uri.getPathSegments().get(1); // 将内容URI权限之后的部分以"/"分割 0位置路径 1位置就是id
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 Uri insert(Uri uri, ContentValues values) { // 添加数据
SQLiteDatabase db = dbHelper.getWritableDatabase();
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 int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) { // 更新数据
SQLiteDatabase db = dbHelper.getWritableDatabase();
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;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) { // 删除数据
SQLiteDatabase db = dbHelper.getWritableDatabase();
int deleteRows = 0;
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
deleteRows = db.delete("Book", selection,selectionArgs);
break;
case BOOK_ITEM:
String bookId = uri.getPathSegments().get(1);
deleteRows = db.delete("Book", "id = ?", new String[] { bookId});
break;
case CATEGORY_DIR:
deleteRows = db.delete("Category", selection,selectionArgs);
break;
case CATEGORY_ITEM:
String categoryId = uri.getPathSegments().get(1);
deleteRows = db.delete("Category", "id = ?", new String[] {categoryId});
break;
default:
break;
}
return deleteRows;
}
@Override
public String getType(Uri uri) { // 用于获取Uri对象所对应的MIME类型
// 必须以vnd开头 如果内容URI以路径结尾,则后接android.cursor.dir/,如果内容URI以id结尾,则后接android.cursor.item/
// 最后接上vnd.authority.path
switch (uriMatcher.match(uri)) {
case BOOK_DIR:
return "vnd.android.cursor.dir/vnd.com.yezhou.example.com.databasetest.provider.book";
case BOOK_ITEM:
return "vnd.android.cursor.item/vnd.com.yezhou.example.com.databasetest.provider.book";
case CATEGORY_DIR:
return "vnd.android.cursor.dir/vnd.com.yezhou.example.com.databasetest.provider.category";
case CATEGORY_ITEM:
return "vnd.android.cursor.item/vnd.com.yezhou.example.com.databasetest.provider.category";
default:
break;
}
return null;
}
}
<provider
android:name=".DatabaseProvider"
android:authorities="com.yezhou.example.com.databasetest.provider"
android:enabled="true"
</provider>
<!--用于对DatabaseProvider进行注册,name 类名,-->
我们自己的内容提供器创建好了。
新建一个Android Project
<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>
public class MainActivity extends AppCompatActivity { // 用于读取DatabaseTest应用数据库中的数据
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 view) {
Uri uri = Uri.parse("content://com.yezhou.example.com.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); // 将id取出
}
});
Button queryData = (Button) findViewById(R.id.query_data);
queryData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) { // 查询数据
Uri uri = Uri.parse("content://com.yezhou.example.com.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("admin", "book name is " + name);
Log.d("admin", "book name is " + author);
Log.d("admin", "book name is " + pages);
Log.d("admin", "book name is " + price);
}
cursor.close();
}
}
});
Button updateData = (Button) findViewById(R.id.update_data);
updateData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Uri uri = Uri.parse("content://com.yezhou.example.com.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 view) {
Uri uri = Uri.parse("content://com.yezhou.example.com.databasetest.provider/book/" + newId);
getContentResolver().delete(uri, null, null); // newId必须有值
}
});
}
}
新项目就可以访问到之前项目中的数据了。
第三方ContentURI,匹配手机上所有app的Provider Authority,找到App,通过UriMatcher再次过滤,不匹配丢弃,匹配相应请求。
此博文为个人学习笔记,仅供个人学习使用,希望对大家有帮助。