Content Provider Implemantation
ContentProvider offers a standardized external interface for applications to access internal data, enabling data sharing between different apps. Client applications can communicate with Server applications through ContentProvider for cross-process data exchange.
Server Implemantation
Database Helper Class
public class UserDBHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "user.db";
public static final String TABLE_USERS = "user_info";
private static final int DATABASE_VERSION = 1;
private static UserDBHelper instance = null;
private UserDBHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
public static synchronized UserDBHelper getInstance(Context context) {
if (instance == null) {
instance = new UserDBHelper(context.getApplicationContext());
}
return instance;
}
@Override
public void onCreate(SQLiteDatabase db) {
String createTableSQL = "CREATE TABLE IF NOT EXISTS " + TABLE_USERS + " (" +
"_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +
" username VARCHAR NOT NULL," +
" user_age INTEGER NOT NULL," +
" height_cm LONG NOT NULL," +
" weight_kg FLOAT NOT NULL," +
" is_married INTEGER NOT NULL);";
db.execSQL(createTableSQL);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// Handle database upgrades here
}
}
Content Provider Class
public class UserContentProvider extends ContentProvider {
private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
private static final int USER_ITEMS = 1;
private static final int USER_ITEM = 2;
static {
URI_MATCHER.addURI(UserContract.AUTHORITY, "users", USER_ITEMS);
URI_MATCHER.addURI(UserContract.AUTHORITY, "users/#", USER_ITEM);
}
private UserDBHelper dbHelper;
@Override
public boolean onCreate() {
dbHelper = UserDBHelper.getInstance(getContext());
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor cursor;
switch (URI_MATCHER.match(uri)) {
case USER_ITEMS:
cursor = db.query(TABLE_USERS, projection, selection,
selectionArgs, null, null, sortOrder);
break;
case USER_ITEM:
String userId = uri.getLastPathSegment();
cursor = db.query(TABLE_USERS, projection, "_id=?",
new String[]{userId}, null, null, sortOrder);
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
@Override
public String getType(Uri uri) {
switch (URI_MATCHER.match(uri)) {
case USER_ITEMS:
return "vnd.android.cursor.dir/vnd." + UserContract.AUTHORITY + ".users";
case USER_ITEM:
return "vnd.android.cursor.item/vnd." + UserContract.AUTHORITY + ".user";
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
}
@Override
public Uri insert(Uri uri, ContentValues values) {
if (URI_MATCHER.match(uri) != USER_ITEMS) {
throw new IllegalArgumentException("Insert URI not supported: " + uri);
}
SQLiteDatabase db = dbHelper.getWritableDatabase();
long id = db.insert(TABLE_USERS, null, values);
if (id > 0) {
Uri newUri = ContentUris.withAppendedId(uri, id);
getContext().getContentResolver().notifyChange(newUri, null);
return newUri;
}
throw new SQLException("Failed to insert row into " + uri);
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
int count;
switch (URI_MATCHER.match(uri)) {
case USER_ITEMS:
count = db.delete(TABLE_USERS, selection, selectionArgs);
break;
case USER_ITEM:
String userId = uri.getLastPathSegment();
count = db.delete(TABLE_USERS, "_id=?", new String[]{userId});
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
if (count > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return count;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
int count;
switch (URI_MATCHER.match(uri)) {
case USER_ITEMS:
count = db.update(TABLE_USERS, values, selection, selectionArgs);
break;
case USER_ITEM:
String userId = uri.getLastPathSegment();
count = db.update(TABLE_USERS, values, "_id=?", new String[]{userId});
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
if (count > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return count;
}
}
Manifest Declaration
<provider
android:name=".provider.UserContentProvider"
android:authorities="com.example.contentproviderdemo.provider.UserContentProvider"
android:exported="true"
android:enabled="true"/>
Client Implementation
Content Resolver Activity
public class DataOperationActivity extends AppCompatActivity implements View.OnClickListener {
private EditText nameInput, ageInput, heightInput, weightInput;
private CheckBox marriedCheckbox;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_data_operation);
nameInput = findViewById(R.id.et_name);
ageInput = findViewById(R.id.et_age);
heightInput = findViewById(R.id.et_height);
weightInput = findViewById(R.id.et_weight);
marriedCheckbox = 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(UserContract.USER_NAME, nameInput.getText().toString());
values.put(UserContract.USER_AGE, Integer.parseInt(ageInput.getText().toString()));
values.put(UserContract.USER_HEIGHT, Integer.parseInt(heightInput.getText().toString()));
values.put(UserContract.USER_WEIGHT, Float.parseFloat(weightInput.getText().toString()));
values.put(UserContract.USER_MARRIED, marriedCheckbox.isChecked() ? 1 : 0);
getContentResolver().insert(UserContract.CONTENT_URI, values);
Toast.makeText(this, "保存成功", Toast.LENGTH_SHORT).show();
} else if (v.getId() == R.id.btn_read) {
Cursor cursor = getContentResolver().query(UserContract.CONTENT_URI, null, null, null, null);
if (cursor != null) {
while (cursor.moveToNext()) {
User user = new User();
user.id = cursor.getInt(cursor.getColumnIndex(UserContract._ID));
user.name = cursor.getString(cursor.getColumnIndex(UserContract.USER_NAME));
user.age = cursor.getInt(cursor.getColumnIndex(UserContract.USER_AGE));
user.height = cursor.getInt(cursor.getColumnIndex(UserContract.USER_HEIGHT));
user.weight = cursor.getFloat(cursor.getColumnIndex(UserContract.USER_WEIGHT));
user.married = cursor.getInt(cursor.getColumnIndex(UserContract.USER_MARRIED)) == 1;
Log.d("User", user.toString());
}
cursor.close();
}
} else if (v.getId() == R.id.btn_delete) {
int count = getContentResolver().delete(UserContract.CONTENT_URI,
"username=?", new String[]{"Jack"});
if (count > 0) {
Toast.makeText(this, "删除成功", Toast.LENGTH_SHORT).show();
}
}
}
}
Content Contract
public class UserContract implements BaseColumns {
public static final String AUTHORITY = "com.example.contentproviderdemo.provider.UserContentProvider";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/users");
public static final String USER_NAME = "username";
public static final String USER_AGE = "user_age";
public static final String USER_HEIGHT = "height_cm";
public static final String USER_WEIGHT = "weight_kg";
public static final String USER_MARRIED = "is_married";
}
Manifest Queries
<queries>
<package android:name="com.example.contentproviderdemo" />
</queries>
Runtime Permission Management
Android introduced runtime permissions in version 6.0 to prevent apps from misusing permissions. Apps must now check and request permissions at runtime.
Permission Request Implementation
Lazy Permission Request
public class LazyPermissionActivity extends AppCompatActivity implements View.OnClickListener {
private static final String[] CONTACT_PERMISSIONS = {
Manifest.permission.READ_CONTACTS,
Manifest.permission.WRITE_CONTACTS
};
private static final String[] SMS_PERMISSIONS = {
Manifest.permission.SEND_SMS,
Manifest.permission.RECEIVE_SMS
};
private static final int REQUEST_CONTACTS = 1;
private static final int REQUEST_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) {
checkPermissions(CONTACT_PERMISSIONS, REQUEST_CONTACTS);
} else if (view.getId() == R.id.btn_sms) {
checkPermissions(SMS_PERMISSIONS, REQUEST_SMS);
}
}
private void checkPermissions(String[] permissions, int requestCode) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
List<string> deniedPermissions = new ArrayList<>();
for (String permission : permissions) {
if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
deniedPermissions.add(permission);
}
}
if (!deniedPermissions.isEmpty()) {
requestPermissions(deniedPermissions.toArray(new String[0]), requestCode);
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
boolean allGranted = true;
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
allGranted = false;
break;
}
}
if (allGranted) {
Log.d("Permission", "All permissions granted");
} else {
Toast.makeText(this, "部分权限未授予", Toast.LENGTH_SHORT).show();
openAppSettings();
}
}
private void openAppSettings() {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.fromParts("package", getPackageName(), null));
startActivity(intent);
}
}
</string>
Hungry Permission Request
public class HungryPermissionActivity extends AppCompatActivity implements View.OnClickListener {
private static final String[] ALL_PERMISSIONS = {
Manifest.permission.READ_CONTACTS,
Manifest.permission.WRITE_CONTACTS,
Manifest.permission.SEND_SMS,
Manifest.permission.RECEIVE_SMS
};
private static final int REQUEST_ALL = 0;
@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);
checkPermissions(ALL_PERMISSIONS, REQUEST_ALL);
}
private void checkPermissions(String[] permissions, int requestCode) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
List<string> deniedPermissions = new ArrayList<>();
for (String permission : permissions) {
if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
deniedPermissions.add(permission);
}
}
if (!deniedPermissions.isEmpty()) {
requestPermissions(deniedPermissions.toArray(new String[0]), requestCode);
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_ALL) {
for (int i = 0; i < grantResults.length; i++) {
if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
String permission = permissions[i];
Log.e("Permission", "Denied: " + permission);
Toast.makeText(this, "需要权限: " + permission, Toast.LENGTH_LONG).show();
openAppSettings();
break;
}
}
}
}
private void openAppSettings() {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.fromParts("package", getPackageName(), null));
startActivity(intent);
}
}
</string>
Contact Operations Example
public class ContactManagerActivity extends AppCompatActivity implements View.OnClickListener {
private EditText nameInput, phoneInput, emailInput;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_contact_manager);
nameInput = findViewById(R.id.et_contact_name);
phoneInput = findViewById(R.id.et_contact_phone);
emailInput = 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 = nameInput.getText().toString().trim();
contact.phone = phoneInput.getText().toString().trim();
contact.email = emailInput.getText().toString().trim();
addContact(getContentResolver(), contact);
} else if (view.getId() == R.id.btn_read_contact) {
readContacts(getContentResolver());
}
}
@SuppressLint("Range")
private void readContacts(ContentResolver resolver) {
Cursor cursor = resolver.query(ContactsContract.RawContacts.CONTENT_URI,
new String[]{ContactsContract.RawContacts._ID}, null, null, null);
if (cursor != 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();
if (dataCursor != null) {
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("Contact", contact.toString());
}
}
cursor.close();
}
}
private void addContact(ContentResolver resolver, Contact contact) {
ContentValues values = new ContentValues();
Uri uri = resolver.insert(ContactsContract.RawContacts.CONTENT_URI, values);
long rawContactId = ContentUris.parseId(uri);
// Add name
ContentValues nameValues = new ContentValues();
nameValues.put(ContactsContract.Contacts.Data.RAW_CONTACT_ID, rawContactId);
nameValues.put(ContactsContract.Contacts.Data.MIMETYPE,
CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
nameValues.put(CommonDataKinds.StructuredName.DISPLAY_NAME, contact.name);
resolver.insert(ContactsContract.Data.CONTENT_URI, nameValues);
// Add phone
ContentValues phoneValues = new ContentValues();
phoneValues.put(ContactsContract.Contacts.Data.RAW_CONTACT_ID, rawContactId);
phoneValues.put(ContactsContract.Contacts.Data.MIMETYPE,
CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
phoneValues.put(CommonDataKinds.Phone.NUMBER, contact.phone);
phoneValues.put(CommonDataKinds.Phone.TYPE, CommonDataKinds.Phone.TYPE_MOBILE);
resolver.insert(ContactsContract.Data.CONTENT_URI, phoneValues);
// Add email
ContentValues emailValues = new ContentValues();
emailValues.put(ContactsContract.Contacts.Data.RAW_CONTACT_ID, rawContactId);
emailValues.put(ContactsContract.Contacts.Data.MIMETYPE,
CommonDataKinds.Email.CONTENT_ITEM_TYPE);
emailValues.put(CommonDataKinds.Email.ADDRESS, contact.email);
emailValues.put(CommonDataKinds.Email.TYPE, CommonDataKinds.Email.TYPE_WORK);
resolver.insert(ContactsContract.Data.CONTENT_URI, emailValues);
}
}