1. ContentPorvider
ContentProvider为App存取内部数据提供统一的外部接口,让不同的应用之间得以分享数据。
Client App 将用户的输入内容,通过ContentProvider跨进程通信传递给Server App。
代码示例
Server端
database类:
public class UserDBHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "user.db";
public static final String TABLE_NAME = "user_info";
private static final int DB_VERSION = 1;
private static UserDBHelper mHelper = null;
private UserDBHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
// 利用单例模式获取数据库帮助器的唯一实例
public static UserDBHelper getInstance(Context context) {
if (mHelper == null) {
mHelper = new UserDBHelper(context);
}
return mHelper;
}
// 创建数据库,执行建表语句
@Override
public void onCreate(SQLiteDatabase db) {
String sql = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" +
"_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +
" name VARCHAR NOT NULL," +
" age INTEGER NOT NULL," +
" height LONG NOT NULL," +
" weight FLOAT NOT NULL," +
" married INTEGER NOT NULL);";
db.execSQL(sql);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
Provider类
public class UserInfoProvider extends ContentProvider {
private static final UriMatcher URI_MATCH=new UriMatcher(UriMatcher.NO_MATCH);
private static final int USERS=1;
private static final int USER=2;
static {
URI_MATCH.addURI(UserInfoContent.AUTHORITY,"/user",USERS);
URI_MATCH.addURI(UserInfoContent.AUTHORITY,"/user/#",USER);
}
private UserDBHelper dbHelper;
public UserInfoProvider() {
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int count = 0;
switch (URI_MATCH.match(uri)){
//删除单行
case USERS:
SQLiteDatabase db1 = dbHelper.getWritableDatabase();
count = db1.delete(UserDBHelper.TABLE_NAME,selection,selectionArgs);
db1.close();
break;
case USER:
String id = uri.getLastPathSegment();
SQLiteDatabase db2 =dbHelper.getWritableDatabase();
count = db2.delete(UserDBHelper.TABLE_NAME, "_id=?", new String[]{id});
db2.close();
break;
}
// Implement this to handle requests to delete one or more rows.
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 Uri insert(Uri uri, ContentValues values) {
// TODO: Implement this to handle requests to insert a new row.
if(URI_MATCH.match(uri)==USERS){
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.insert(UserDBHelper.TABLE_NAME,null,values);
}
return uri;
}
@Override
public boolean onCreate() {
// TODO: Implement this to initialize your content provider on startup.
dbHelper=UserDBHelper.getInstance(getContext());
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
if(URI_MATCH.match(uri)==USERS){
SQLiteDatabase db = dbHelper.getReadableDatabase();
return db.query(UserDBHelper.TABLE_NAME,projection,selection,selectionArgs,null,null,null);
}
return null;
}
@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
android:name=".provider.UserInfoProvider"
android:authorities="com.example.chapter07_server.provider.UserInfoProvider"
android:enabled="true"
android:exported="true"/>
Client端
public class ContentWriteActivity extends AppCompatActivity implements View.OnClickListener {
private EditText et_name;
private EditText et_age;
private EditText et_height;
private EditText et_weight;
private CheckBox ck_married;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_content_write);
et_name = findViewById(R.id.et_name);
et_age = findViewById(R.id.et_age);
et_height = findViewById(R.id.et_height);
et_weight = findViewById(R.id.et_weight);
ck_married = findViewById(R.id.ck_married);
findViewById(R.id.btn_save).setOnClickListener(this);
findViewById(R.id.btn_delete).setOnClickListener(this);
findViewById(R.id.btn_read).setOnClickListener(this);
}
@SuppressLint("Range")
@Override
public void onClick(View v) {
if(v.getId()==R.id.btn_save){
ContentValues values = new ContentValues();
values.put(UserInfoContent.USER_NAME, et_name.getText().toString());
values.put(UserInfoContent.USER_AGE, Integer.parseInt(et_age.getText().toString()));
values.put(UserInfoContent.USER_HEIGHT, Integer.parseInt(et_height.getText().toString()));
values.put(UserInfoContent.USER_WEIGHT, Float.parseFloat(et_weight.getText().toString()));
values.put(UserInfoContent.USER_MARRIED, ck_married.isChecked());
getContentResolver().insert(UserInfoContent.CONTENT_URI,values);
ToastUtil.show(this,"保存成功");
}else if(v.getId()==R.id.btn_read){
Cursor cursor = getContentResolver().query(UserInfoContent.CONTENT_URI, null, null, null,null,null);
if(cursor!=null){
while(cursor.moveToNext()){
User info=new User();
info.id = cursor.getInt(cursor.getColumnIndex(UserInfoContent._ID));
info.name = cursor.getString(cursor.getColumnIndex(UserInfoContent.USER_NAME));
info.age = cursor.getInt(cursor.getColumnIndex(UserInfoContent.USER_AGE));
info.height = cursor.getInt(cursor.getColumnIndex(UserInfoContent.USER_HEIGHT));
info.weight = cursor.getFloat(cursor.getColumnIndex(UserInfoContent.USER_WEIGHT));
info.married = cursor.getInt(cursor.getColumnIndex(UserInfoContent.USER_MARRIED)) == 1 ? true : false;
Log.d("lzk",info.toString());
}
cursor.close();
}
}else if(v.getId()==R.id.btn_delete){
int count=0;
//删除单行
//Uri uri= ContentUris.withAppendedId(UserInfoContent.CONTENT_URI,1);
//count=getContentResolver().delete(uri,null,null);
//删除多行
count=getContentResolver().delete(UserInfoContent.CONTENT_URI,"name=?",new String[]{"Jack"});
if(count>0){
ToastUtil.show(this,"删除成功");
}
}
}
}
AndroidManifest.xml文件
<queries>
<package android:name="com.example.chapter07_server" />
</queries>
public class UserInfoContent implements BaseColumns {
public static final String AUTHORITY = "com.example.chapter07_server.provider.UserInfoProvider";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/user");
public static final String USER_NAME = "name";
public static final String USER_AGE = "age";
public static final String USER_HEIGHT = "height";
public static final String USER_WEIGHT = "weight";
public static final String USER_MARRIED = "married";
}
2. 运行时动态权限管理
Android系统为了防止某些App滥用权限,从6.0开始引入了运行时权限管理机制,App在运行过程中动态检查是否拥有某些权限,一旦发现缺少某种必需的权限,则系统会自动弹出小窗提示用户去开启该权限。
动态申请权限的步骤
检查App是否开启了指定权限
- 调用ContextCompact的checkSelfPermission方法。
请求系统弹窗,以便用户选择是否开启权限
- 调用ActivityCompact的requestPermission方法,即可命令系统自动弹出权限申请窗口。
判断用户的权限选择结果
- 重写活动页面的权限请求回调方法OnRequestPermissionResult,在该方法内部处理用户的权限选择结果。
代码示例
Lazy模式
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 int REQUEST_CODE_ALL=3;
private static final String[] PERMISSIONS = new String[]{
Manifest.permission.READ_CONTACTS,
Manifest.permission.WRITE_CONTACTS,
Manifest.permission.SEND_SMS,
Manifest.permission.READ_SMS
};
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);
findViewById(R.id.btn_contact).setOnClickListener(this);
findViewById(R.id.btn_sms).setOnClickListener(this);
}
@Override
public void onClick(View view) {
if (view.getId() == R.id.btn_contact) {
PermissionUtil.checkPermission(this, PERMISSIONS_CONTACTS, REQUEST_CODE_CONTACTS);
} else if (view.getId() == R.id.btn_sms) {
PermissionUtil.checkPermission(this, PERMISSIONS_SMS, REQUEST_CODE_SMS);
}
}
@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)){
Log.d("lzk","通讯录权限获取成功");
}else{
ToastUtil.show(this,"通讯录权限获取失败");
jumpToSetting();
}
break;
case REQUEST_CODE_SMS:
break;
}
}
public void jumpToSetting(){
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.fromParts("package",getPackageName(),null));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
}
Hunger模式
public class PermissionHungryActivity extends AppCompatActivity implements View.OnClickListener {
private static final String[] PERMISSIONS_CONTACTS = new String[]{
Manifest.permission.READ_CONTACTS,
Manifest.permission.WRITE_CONTACTS
};
private static final int REQUEST_CODE_ALL=3;
private static final String[] PERMISSIONS = new String[]{
Manifest.permission.READ_CONTACTS,
Manifest.permission.WRITE_CONTACTS,
Manifest.permission.SEND_SMS,
Manifest.permission.READ_SMS
};
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);
findViewById(R.id.btn_contact).setOnClickListener(this);
findViewById(R.id.btn_sms).setOnClickListener(this);
PermissionUtil.checkPermission(this, PERMISSIONS, REQUEST_CODE_ALL);
}
@Override
public void onClick(View view) {
if (view.getId() == R.id.btn_contact) {
PermissionUtil.checkPermission(this, new String[]{PERMISSIONS[0], PERMISSIONS[1]},REQUEST_CODE_ALL);
} else if (view.getId() == R.id.btn_sms) {
PermissionUtil.checkPermission(this,new String[]{PERMISSIONS[2], PERMISSIONS[3]},REQUEST_CODE_ALL);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case REQUEST_CODE_ALL:
if(PermissionUtil.checkGrant(grantResults)){
Log.d("lzk","通权限获取成功");
}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;
}
}
}
jumpToSetting();
}
case REQUEST_CODE_CONTACTS:
if(PermissionUtil.checkGrant(grantResults)){
Log.d("lzk","通讯录权限获取成功");
}else{
ToastUtil.show(this,"通讯录权限获取失败");
jumpToSetting();
}
break;
case REQUEST_CODE_SMS:
if(PermissionUtil.checkGrant(grantResults)){
Log.d("lzk","收发短信权限获取成功");
}else{
ToastUtil.show(this,"收发短信权限获取失败");
jumpToSetting();
}
break;
}
}
public void jumpToSetting(){
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.fromParts("package",getPackageName(),null));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
}
PermissionUtil
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) {
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 grant : grantResults) {
// 未获得授权
if (grant != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
return false;
}
}
2. 案例(联系人操作)
public class ContactAddActivity extends AppCompatActivity implements View.OnClickListener {
private EditText et_contact_name;
private EditText et_contact_phone;
private EditText et_contact_email;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_contact_add);
et_contact_name = findViewById(R.id.et_contact_name);
et_contact_phone = findViewById(R.id.et_contact_phone);
et_contact_email = findViewById(R.id.et_contact_email);
findViewById(R.id.btn_add_contact).setOnClickListener(this);
findViewById(R.id.btn_read_contact).setOnClickListener(this);
}
@Override
public void onClick(View view) {
if(view.getId()==R.id.btn_add_contact){
Contact contact=new Contact();
contact.name=et_contact_name.getText().toString().trim();
contact.phone=et_contact_phone.getText().toString().trim();
contact.email=et_contact_email.getText().toString().trim();
//方式一,使用ContentResolver多次写入,每次一个字段
addContacts(getContentResolver(),contact);
//方式2,批处理方式
addFullContacts(getContentResolver(),contact);
}else if(view.getId()==R.id.btn_read_contact){
readPhoneContacts(getContentResolver());
}
}
@SuppressLint("Range")
private void readPhoneContacts(ContentResolver resolver) {
Cursor cursor = resolver.query(ContactsContract.RawContacts.CONTENT_URI, new String[]{ContactsContract.RawContacts._ID}, null, null, null, null);
while(cursor.moveToNext()) {
int rawContactId = cursor.getInt(0);
Uri uri = Uri.parse("content://com.android.contacts/contacts/" + rawContactId + "/data");
Cursor dataCursor = resolver.query(uri, new String[]{Contacts.Data.MIMETYPE, Contacts.Data.DATA1, Contacts.Data.DATA2},
null, null, null);
Contact contact=new Contact();
while(dataCursor.moveToNext()){
String data1 = dataCursor.getString(dataCursor.getColumnIndex(Contacts.Data.DATA1));
String mimeType = dataCursor.getString(dataCursor.getColumnIndex(Contacts.Data.MIMETYPE));
if(mimeType.equals(CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)){
//姓名
contact.name=data1;
}else if(mimeType.equals(CommonDataKinds.Phone.CONTENT_ITEM_TYPE)){
//电话
contact.phone=data1;
}else if(mimeType.equals(CommonDataKinds.Email.CONTENT_ITEM_TYPE)){
contact.email=data1;
}
}
dataCursor.close();
if(contact.name!=null){
Log.d("lzk",contact.toString());
}
cursor.close();
}
}
private void addFullContacts(ContentResolver resolver, Contact contact) {
// 创建一个插入联系人主记录的内容操作器
ContentProviderOperation op_main = ContentProviderOperation
.newInsert(ContactsContract.RawContacts.CONTENT_URI)
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null)
.build();
// 创建一个插入联系人姓名记录的内容操作器
ContentProviderOperation op_name = ContentProviderOperation
.newInsert(ContactsContract.Data.CONTENT_URI)
// 将第0个操作的id,即 raw_contacts 的 id 作为 data 表中的 raw_contact_id
.withValueBackReference(Contacts.Data.RAW_CONTACT_ID, 0)
.withValue(Contacts.Data.MIMETYPE, CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
.withValue(Contacts.Data.DATA2, contact.name)
.build();
// 创建一个插入联系人电话号码记录的内容操作器
ContentProviderOperation op_phone = ContentProviderOperation
.newInsert(ContactsContract.Data.CONTENT_URI)
// 将第0个操作的id,即 raw_contacts 的 id 作为 data 表中的 raw_contact_id
.withValueBackReference(Contacts.Data.RAW_CONTACT_ID, 0)
.withValue(Contacts.Data.MIMETYPE, CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
.withValue(Contacts.Data.DATA1, contact.phone)
.withValue(Contacts.Data.DATA2, CommonDataKinds.Phone.TYPE_MOBILE)
.build();
// 创建一个插入联系人电子邮箱记录的内容操作器
ContentProviderOperation op_email = ContentProviderOperation
.newInsert(ContactsContract.Data.CONTENT_URI)
// 将第0个操作的id,即 raw_contacts 的 id 作为 data 表中的 raw_contact_id
.withValueBackReference(Contacts.Data.RAW_CONTACT_ID, 0)
.withValue(Contacts.Data.MIMETYPE, CommonDataKinds.Email.CONTENT_ITEM_TYPE)
.withValue(Contacts.Data.DATA1, contact.email)
.withValue(Contacts.Data.DATA2, CommonDataKinds.Email.TYPE_WORK)
.build();
// 声明一个内容操作器的列表,并将上面四个操作器添加到该列表中
ArrayList<ContentProviderOperation> operations = new ArrayList<>();
operations.add(op_main);
operations.add(op_name);
operations.add(op_phone);
operations.add(op_email);
try {
// 批量提交四个操作
resolver.applyBatch(ContactsContract.AUTHORITY, operations);
} catch (OperationApplicationException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
}
}
private void addContacts(ContentResolver resolver, Contact contact) {
ContentValues values = new ContentValues();
// 往 raw_contacts 添加联系人记录,并获取添加后的联系人编号
Uri uri = resolver.insert(ContactsContract.RawContacts.CONTENT_URI, values);
long rawContactId = ContentUris.parseId(uri);
ContentValues name = new ContentValues();
// 关联联系人编号
name.put(ContactsContract.Contacts.Data.RAW_CONTACT_ID, rawContactId);
// “姓名”的数据类型
name.put(ContactsContract.Contacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
// 联系人的姓名
name.put(ContactsContract.Contacts.Data.DATA2, contact.name);
// 往提供器添加联系人的姓名记录
resolver.insert(ContactsContract.Data.CONTENT_URI, name);
ContentValues phone = new ContentValues();
// 关联联系人编号
phone.put(ContactsContract.Contacts.Data.RAW_CONTACT_ID, rawContactId);
// “电话号码”的数据类型
phone.put(ContactsContract.Contacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
// 联系人的电话号码
phone.put(ContactsContract.Contacts.Data.DATA1, contact.phone);
// 联系类型。1表示家庭,2表示工作
phone.put(ContactsContract.Contacts.Data.DATA2, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE);
// 往提供器添加联系人的姓名记录
resolver.insert(ContactsContract.Data.CONTENT_URI, phone);
ContentValues email = new ContentValues();
// 关联联系人编号
email.put(ContactsContract.Contacts.Data.RAW_CONTACT_ID, rawContactId);
// “电子邮箱”的数据类型
email.put(ContactsContract.Contacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE);
// 联系人的电子邮箱
email.put(ContactsContract.Contacts.Data.DATA1, contact.email);
// 联系类型。1表示家庭,2表示工作
email.put(ContactsContract.Contacts.Data.DATA2, ContactsContract.CommonDataKinds.Email.TYPE_WORK);
// 往提供器添加联系人的姓名记录
resolver.insert(ContactsContract.Data.CONTENT_URI, email);
}
}