一、数据共享
1、内容提供者
- 内容提供者 ContentProvider 为 APP 存取内部数据提供统一的外部接口,让不同的应用之间得以共享数据

2、流程理解
- Client APP 将用户的输入内容通过 ContentProvider 跨进程通信传递给 Server APP

3、数据访问
- 利用 ContentProvider 只实现服务端 APP 的数据封装,如果客户端 APP 想访问对方的内部数据,就要通过内容解析器 ContentResolver 来访问

4、URI
- URI(统一资源标识符 Universal Resource ldentifer)代表数据操作的地址,每一个ContentProvider 都会有唯一的地址
content://【authority】/【data path】/【id】
字段 | 说明 |
---|---|
content:// | 通用前缀,表示该 URI 用于 ContentProvider 定位资源 |
authority | 用来确定具体提供资源的 ContentProvider 一般 authority 都由类的小写全称组成,以保证唯一性 |
data_path | 数据路径,用来确定请求的是哪个数据集 |
id | 数据编号,用来请求单条数据,如果是多条则忽略此字段 |
二、内容提供者实现
1、项目创建
- 创建两个 module,分别为 server 和 client
2、server
(1)数据库操作类
- UserDBHelper 类
package com.my.server.database;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class UserDBHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "user.db";
private static final int DB_VERSION = 1;
public static final String TABLE_NAME = "user_info";
private static UserDBHelper userDBHelper;
private UserDBHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
// 使用单例模式获取数据库帮助器的唯一实例
public static UserDBHelper getInstance(Context context) {
if (userDBHelper == null) {
userDBHelper = new UserDBHelper(context);
}
return userDBHelper;
}
@Override
public void onCreate(SQLiteDatabase db) {
// 创建数据库,执行建表语句
String sql = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" +
"user_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +
"user_name VARCHAR NOT NULL," +
"user_age INTEGER NOT NULL" +
");";
db.execSQL(sql);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
}
(2)资源类
- UserInfoContent 类
package com.my.server.content;
import android.net.Uri;
public class UserInfoContent {
public static final String AUTHORITIES = "com.my.server.provider.UserInfoProvider";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITIES + "/user");
public static final String USER_ID = "user_id";
public static final String USER_NAME = "user_name";
public static final String USER_AGE = "user_age";
}
(3)内容提供者
- 创建内容提供者,右击包名 -> 【Other】 -> 【Content Provider】 -> 填写相关信息 -> 【Finish】
- UserInfoProvider 类,默认
package com.my.server.provider;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
public class UserInfoProvider extends ContentProvider {
public UserInfoProvider() {
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// Implement this to handle requests to delete one or more rows.
throw new UnsupportedOperationException("Not yet implemented");
}
@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) {
// TODO: Implement this to handle requests to insert a new row.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public boolean onCreate() {
// TODO: Implement this to initialize your content provider on startup.
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// TODO: Implement this to handle query requests from clients.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// TODO: Implement this to handle requests to update one or more rows.
throw new UnsupportedOperationException("Not yet implemented");
}
}
- 在清单文件 AndroidManifest.xml 中配置 provider 组件
- AndroidManifest.xml
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AndroidDevelop">
<!-- 将 authorities 设置为内容提供者的全类名 -->
<provider
android:name=".provider.UserInfoProvider"
android:authorities="com.my.server.provider.UserInfoProvider"
android:enabled="true"
android:exported="true">
</provider>
...
</application>
- UserInfoProvider 类
package com.my.server.provider;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.util.Log;
import com.my.server.content.UserInfoContent;
import com.my.server.database.UserDBHelper;
public class UserInfoProvider extends ContentProvider {
private static final String TAG = UserInfoProvider.class.getSimpleName();
private UserDBHelper userDBHelper;
private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
private static final int USES = 1;
static {
URI_MATCHER.addURI(UserInfoContent.AUTHORITIES, "/user", USES);
}
@Override
public boolean onCreate() {
Log.i(TAG, "-------------------- onCreate");
userDBHelper = UserDBHelper.getInstance(getContext());
// 返回 true 代表 provider 实例化成功,返回 false 代表失败
return true;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
Log.i(TAG, "-------------------- insert");
if (URI_MATCHER.match(uri) == USES) {
SQLiteDatabase wd = userDBHelper.getWritableDatabase();
wd.insert(UserDBHelper.TABLE_NAME, null, values);
}
return uri;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
Log.i(TAG, "-------------------- query");
Cursor cursor = null;
if (URI_MATCHER.match(uri) == USES) {
SQLiteDatabase rd = userDBHelper.getReadableDatabase();
cursor = rd.query(userDBHelper.TABLE_NAME, null, selection, selectionArgs,
null, null, null);
}
return cursor;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int count = 0;
if (URI_MATCHER.match(uri) == USES) {
SQLiteDatabase wd = userDBHelper.getWritableDatabase();
count = wd.delete(userDBHelper.TABLE_NAME, selection, selectionArgs);
wd.close();
}
return count;
}
@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 int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// TODO: Implement this to handle requests to update one or more rows.
throw new UnsupportedOperationException("Not yet implemented");
}
}
2、client
(1)清单文件
<!-- Android 11 要求实现声明需要访问的软件包 -->
<queries>
<package android:name="com.my.server"></package>
<!-- 也可以声明需要访问的 provider -->
<!-- <provider android:authorities="com.my.server.provider.UserInfoProvider"></provider> -->
</queries>
(2)实体类
- User 类
package com.my.client.entity;
public class User {
public Integer id;
public String name;
public Integer age;
public User() {}
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
public User(Integer id, String name, Integer age) {
this.id = id;
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
(3)资源类
- UserInfoContent
package com.my.client.content;
import android.net.Uri;
public class UserInfoContent {
public static final String AUTHORITIES = "com.my.server.provider.UserInfoProvider";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITIES + "/user");
public static final String USER_ID = "user_id";
public static final String USER_NAME = "user_name";
public static final String USER_AGE = "user_age";
}
(4)XML 文件
- activity_client_input.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="5dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20dp"
android:text="姓名:" />
<EditText
android:id="@+id/et_name"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20dp"
android:text="年龄:" />
<EditText
android:id="@+id/et_age"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content" />
</LinearLayout>
<Button
android:id="@+id/btn_insert"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:text="插入" />
<Button
android:id="@+id/btn_query_by_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:text="根据姓名查询" />
<Button
android:id="@+id/btn_delete_by_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:text="根据姓名删除" />
</LinearLayout>
(5)Java 代码
- ClientInputActivity 类
package com.my.client;
import androidx.appcompat.app.AppCompatActivity;
import android.annotation.SuppressLint;
import android.content.ContentValues;
import android.database.Cursor;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import com.my.client.content.UserInfoContent;
import com.my.client.entity.User;
public class ClientInputActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = ClientInputActivity.class.getSimpleName();
private EditText et_name;
private EditText et_age;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_client_input);
et_name = findViewById(R.id.et_name);
et_age = findViewById(R.id.et_age);
Button btn_insert = findViewById(R.id.btn_insert);
Button btn_query_by_name = findViewById(R.id.btn_query_by_name);
Button btn_delete_by_name = findViewById(R.id.btn_delete_by_name);
btn_insert.setOnClickListener(this);
btn_query_by_name.setOnClickListener(this);
btn_delete_by_name.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_insert:
ContentValues values = new ContentValues();
values.put(UserInfoContent.USER_NAME, et_name.getText().toString());
values.put(UserInfoContent.USER_AGE, Integer.parseInt(et_age.getText().toString()));
getContentResolver().insert(UserInfoContent.CONTENT_URI, values);
Toast.makeText(this, "插入完成", Toast.LENGTH_SHORT).show();
break;
case R.id.btn_query_by_name:
Cursor cursor = getContentResolver().query(UserInfoContent.CONTENT_URI, null, "user_name=?",
new String[]{et_name.getText().toString()}, null);
if (cursor != null) {
while (cursor.moveToNext()) {
User user = new User();
@SuppressLint("Range") int id = cursor.getInt(cursor.getColumnIndex(UserInfoContent.USER_ID));
@SuppressLint("Range") String name = cursor.getString(cursor.getColumnIndex(UserInfoContent.USER_NAME));
@SuppressLint("Range") int age = cursor.getInt(cursor.getColumnIndex(UserInfoContent.USER_AGE));
user.id = id;
user.name = name;
user.age = age;
Log.i(TAG, "---------- " + user);
}
cursor.close();
}
break;
case R.id.btn_delete_by_name:
int count = getContentResolver().delete(UserInfoContent.CONTENT_URI,
"user_name=?", new String[]{et_name.getText().toString()});
if (count > 0) {
Toast.makeText(this, "删除完成", Toast.LENGTH_SHORT).show();
}
break;
}
}
}
三、动态权限申请
1、基本介绍
- Android 系统为了防止某些 APP 滥用权限,从 6.0 开始引入了运行时权限管理机制,允许 APP 在运行过程中动态检查是否拥有某项权限,一旦发现缺少某种必需的权限,则系统会自动弹出小窗提示用户去开启该权限
2、动态权限申请步骤
- 检查 APP 是否开启了指定权限
- 调用 ContextCompat 的 checkSelfPermission 方法
- 请求系统弹窗,以便用户选择是否开启权限
- 调用 ActivityCompat 的 requestPermissions 方法,即可命令系统自动弹出权限申请窗口
- 判断用户的权限选择结果
- 重写活动页面的权限请求回调方法 onRequestPermissionsResult,在该方法内部处理用户的权限选择结果
3、Lazy 模式
(1)清单文件
- AndroidManifest.xml
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.SEND_SMS" />
(2)工具类
- PermissionUtil 类
package com.my.client.util;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Build;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
public class PermissionUtil {
// 检查多个权限,返回 true 表示已完全启用权限,返回 false 表示未完全启用权限
public static boolean checkPermission(Activity act, String[] permissions, int requestCode) {
// Android 6.0 之后开始采用动态权限管理
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// 有权限,PackageManager.PERMISSION_GRANTED
// 无权限,PackageManager.PERMISSION_DENIED
// 保存一份权限查看结果的标识
int check = PackageManager.PERMISSION_GRANTED;
// 遍历权限列表,判断对应权限是否开启
for (String permission : permissions) {
check = ContextCompat.checkSelfPermission(act, permission);
// 只要有一个权限未开启,就退出遍历,进行权限申请
if (check != PackageManager.PERMISSION_GRANTED) {
break;
};
}
// 权限未开启,请求系统弹窗,让用户选择是否开启权限
if (check != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(act, permissions, requestCode);
return false;
}
}
return true;
}
// 检查权限结果数组,返回 true 表示权限都已开启,返回 false 表示至少有一个权限未开启
public static boolean checkGrant(int[] grantResults) {
if (grantResults != null) {
for (int grantResult : grantResults) {
if (grantResult != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
return false;
}
}
- ToastUtil 类
package com.my.client.util;
import android.app.Activity;
import android.widget.Toast;
public class ToastUtil {
public static void show(Activity act, String str) {
Toast.makeText(act, str, Toast.LENGTH_SHORT).show();
}
}
(3)XML 文件
- activity_permission_lazy.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="5dp">
<Button
android:id="@+id/btn_contact"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:text="读取通讯录"
android:textSize="15dp" />
<Button
android:id="@+id/btn_sms"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="收发短信"
android:textSize="15dp" />
</LinearLayout>
(4)Java 代码
- PermissionLazyActivity 类
package com.my.client;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.Manifest;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.view.View;
import android.widget.Button;
import com.my.client.util.PermissionUtil;
import com.my.client.util.ToastUtil;
public class PermissionLazyActivity extends AppCompatActivity implements View.OnClickListener {
private static final String[] PERMISSIONS_CONTACTS = new String[]{
Manifest.permission.READ_CONTACTS,
Manifest.permission.WRITE_CONTACTS
};
private static final String[] PERMISSIONS_SMS = new String[]{
Manifest.permission.SEND_SMS,
Manifest.permission.READ_SMS
};
private static final int REQUEST_CODE_CONTACTS = 1;
private static final int REQUEST_CODE_SMS = 2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_permission_lazy);
Button btn_contact = findViewById(R.id.btn_contact);
Button btn_sms = findViewById(R.id.btn_sms);
btn_contact.setOnClickListener(this);
btn_sms.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_contact:
PermissionUtil.checkPermission(this, PERMISSIONS_CONTACTS, REQUEST_CODE_CONTACTS);
break;
case R.id.btn_sms:
PermissionUtil.checkPermission(this, PERMISSIONS_SMS, REQUEST_CODE_SMS);
break;
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case REQUEST_CODE_CONTACTS:
if (PermissionUtil.checkGrant(grantResults)) {
ToastUtil.show(this, "通讯录权限获取成功");
} else {
ToastUtil.show(this, "通讯录权限获取失败");
jumpToSetting();
}
break;
case REQUEST_CODE_SMS:
if (PermissionUtil.checkGrant(grantResults)) {
ToastUtil.show(this, "收发短信权限获取成功");
} else {
ToastUtil.show(this, "收发短信权限获取失败");
jumpToSetting();
}
break;
}
}
// 跳转到应用设置界面
public void jumpToSetting() {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.fromParts("package", getPackageName(), null));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
}
4、Hungry 模式
(1)清单文件
- 同 【三 - 3 - (1)】
(2)工具类
- 同 【三 - 3 - (2)】
(3)XML 文件
- activity_permission_hungry.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="5dp">
<Button
android:id="@+id/btn_contact"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:text="读取通讯录"
android:textSize="15dp" />
<Button
android:id="@+id/btn_sms"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="收发短信"
android:textSize="15dp" />
</LinearLayout>
(4)Java 代码
- PermissionHungryActivity 类
package com.my.client;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.view.View;
import android.widget.Button;
import com.my.client.util.PermissionUtil;
import com.my.client.util.ToastUtil;
public class PermissionHungryActivity extends AppCompatActivity implements View.OnClickListener {
private static final String[] PERMISSIONS_ALL = new String[]{
Manifest.permission.READ_CONTACTS,
Manifest.permission.WRITE_CONTACTS,
Manifest.permission.SEND_SMS,
Manifest.permission.READ_SMS
};
private static final String[] PERMISSIONS_CONTACTS = new String[]{
Manifest.permission.READ_CONTACTS,
Manifest.permission.WRITE_CONTACTS
};
private static final String[] PERMISSIONS_SMS = new String[]{
Manifest.permission.SEND_SMS,
Manifest.permission.READ_SMS
};
private static final int REQUEST_CODE_CONTACTS = 1;
private static final int REQUEST_CODE_SMS = 2;
private static final int REQUEST_ALL = 3;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_permission_hungry);
Button btn_contact = findViewById(R.id.btn_contact);
Button btn_sms = findViewById(R.id.btn_sms);
btn_contact.setOnClickListener(this);
btn_sms.setOnClickListener(this);
PermissionUtil.checkPermission(this, PERMISSIONS_ALL, REQUEST_ALL);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_contact:
PermissionUtil.checkPermission(this, PERMISSIONS_CONTACTS, REQUEST_CODE_CONTACTS);
break;
case R.id.btn_sms:
PermissionUtil.checkPermission(this, PERMISSIONS_SMS, REQUEST_CODE_SMS);
break;
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case REQUEST_CODE_CONTACTS:
if (PermissionUtil.checkGrant(grantResults)) {
ToastUtil.show(this, "通讯录权限获取成功");
} else {
ToastUtil.show(this, "通讯录权限获取失败");
jumpToSetting();
}
break;
case REQUEST_CODE_SMS:
if (PermissionUtil.checkGrant(grantResults)) {
ToastUtil.show(this, "收发短信权限获取成功");
} else {
ToastUtil.show(this, "收发短信权限获取失败");
jumpToSetting();
}
break;
case REQUEST_ALL:
if (PermissionUtil.checkGrant(grantResults)) {
ToastUtil.show(this, "所有权限获取成功");
} else {
ok: for (int i = 0; i < grantResults.length; i++) {
if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
switch (permissions[i]) {
case Manifest.permission.READ_CONTACTS:
case Manifest.permission.WRITE_CONTACTS:
ToastUtil.show(this, "通讯录权限获取失败");
jumpToSetting();
break ok;
case Manifest.permission.SEND_SMS:
case Manifest.permission.READ_SMS:
ToastUtil.show(this, "收发短信权限获取失败");
jumpToSetting();
break ok;
}
}
}
}
break;
}
}
// 跳转到应用设置界面
public void jumpToSetting() {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.fromParts("package", getPackageName(), null));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
}