/ 今日科技快讯 /
美国国家航空航天局(NASA)坚称,被困在国际空间站的女宇航员苏尼塔·威廉姆斯(Sunita Williams)安全且健康。10月28日,白宫在排灯节招待会上播放了美国宇航员威廉姆斯的致辞,她看上去精神良好。然而一张9月24日的照片显示威廉姆斯的脸颊凹陷成“锥子脸”,引发公众担忧。
/ 作者简介 /
本篇文章转载自萌娃瑜宝爸比的博客,文章主要分享了Android 跨进程+解耦的数据持久化方案,相信会对大家有所帮助!
原文地址:
https://juejin.cn/post/7372396200861499442
/ 前言 /
如果提到跨进程你肯定会想到 AIDL ,没错我们确实是频繁使用到 AIDL 去 bind 服来完成跨进程通信。但是 AIDL 有个弊端是如果是跨两个应用之间我们需要互相知道对方的 AIDL 文件,这样我们在 bind 成功后才能知道 Binder 是什么类型有哪些接口:
bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.e(TAG, "onServiceConnected: ");
//here "IBinder"
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e(TAG, "onServiceDisconnected: ");
}
}, Context.BIND_AUTO_CREATE);
另外如果是一些持久化到本地的数据,bind service 的方式也不是最优解。这种解耦的支持跨进程的持久化存储方案:今天带来 ContentProvider+Room 来给大家出个示例。
ContentProvider 是基于 Uri 的,天然就算是解耦的,不需要有任何的 sdk 或者 aidl 文件依赖就能达到耦合和跨进程。
content://com.xxx.xxx/xxx
是不是有点类似 http:// 或者是 ws:// 闲话不多说直接看示例。
/ 正文 /
数据库
ContentProvider 可以理解为接口,所有的增删改查都需要有具体的数据库实现,数据库的选型大家可以随意,你可以用原生 sqlite 也可以用 ORM 映射框架。因为笔者曾经对 room 写过一篇文章 Android ROOM 数据库高手秘籍,索性这里我们就用 ROOM。
def room_version = "2.5.1"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
@Entity(tableName = "user_table")
public class User {
@PrimaryKey(autoGenerate = true)
private int id;
@ColumnInfo
private String name;
@ColumnInfo
private int age;
// Getters and setters...
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
// Convert ContentValues to User object
public static User fromContentValues(ContentValues values) {
User user = new User();
if (values.containsKey("id")) {
user.setId(values.getAsInteger("id"));
}
if (values.containsKey("name")) {
user.setName(values.getAsString("name"));
}
if (values.containsKey("age")) {
user.setAge(values.getAsInteger("age"));
}
return user;
}
}
@Dao
public interface UserDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
long insert(User user);
@Update
int update(User user);
@Delete
int delete(User user);
@Query("DELETE FROM user_table")
int deleteAll();
@Query("DELETE FROM user_table WHERE id = :userId")
int deleteById(int userId);
@Query("SELECT * FROM user_table WHERE id = :userId")
User getUserById(int userId);
@Query("SELECT * FROM user_table")
List getAllUsers();
@Query("SELECT * FROM user_table WHERE id = :userId")
Cursor getUserByIdCursor(int userId);
@Query("SELECT * FROM user_table")
Cursor getAllUsersCursor();
@RawQuery
int checkpoint(SupportSQLiteQuery supportSQLiteQuery);
}
@Database(entities = {Car.class, User.class}, version = 1, exportSchema = false)
public abstract class DiagnoseDatabase extends RoomDatabase {
private static volatile DiagnoseDatabase INSTANCE;
public abstract CarDao carDao();
public abstract UserDao userDao();
public static DiagnoseDatabase getInstance(Context context) {
if (INSTANCE == null) {
synchronized (DiagnoseDatabase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
DiagnoseDatabase.class, "diagnose.db")
.fallbackToDestructiveMigration()
.build();
}
}
}
return INSTANCE;
}
}
ContentProvider
android:authorities="com.xxx.diagnose"
android:name="com.xxx.diagnose.provider.DiagnoseContentProvider"
android:exported="true"/>
public class DiagnoseContentProvider extends ContentProvider {
public static final String TAG = DiagnoseContentProvider.class.getSimpleName();
private DiagnoseDatabase db;
public static final String AUTHORITY = "com.xxx.diagnose";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/users");
private static final int USERS = 1;
private static final int USER_ID = 2;
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
uriMatcher.addURI(AUTHORITY, "users", USERS);
uriMatcher.addURI(AUTHORITY, "users/#", USER_ID);
}
private Handler daoHandler;
@Override
public boolean onCreate() {
initDB();
HandlerThread handlerThread = new HandlerThread("DaoHandler");
handlerThread.start();
daoHandler = new Handler(handlerThread.getLooper());
return true;
}
private void initDB() {
Log.e(TAG, "initDB");
db = DiagnoseDatabase.getInstance(getContext());
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
final BlockingQueue cursorBlockingQueue = new ArrayBlockingQueue<>(1);
daoHandler.post(() -> {
Cursor cursor = null;
switch (uriMatcher.match(uri)) {
case USERS:
cursor = db.userDao().getAllUsersCursor();
break;
case USER_ID:
long id = ContentUris.parseId(uri);
cursor = db.userDao().getUserByIdCursor((int) id);
break;
}
try {
cursorBlockingQueue.put(cursor);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
Log.e(TAG, "Interrupted while waiting to put cursor in queue", e);
}
});
try {
return cursorBlockingQueue.poll(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
Log.e(TAG, "Interrupted while waiting to take cursor from queue", e);
return null;
}
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
switch (uriMatcher.match(uri)) {
case USERS:
return "vnd.android.cursor.dir/vnd." + AUTHORITY + ".users";
case USER_ID:
return "vnd.android.cursor.item/vnd." + AUTHORITY + ".users";
default:
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
if (uriMatcher.match(uri) != USERS) {
throw new IllegalArgumentException("Unsupported URI for insertion: " + uri);
}
final BlockingQueue resultBlockingQueue = new ArrayBlockingQueue<>(1);
daoHandler.post(() -> {
long id = db.userDao().insert(User.fromContentValues(values));
Uri resultUri = ContentUris.withAppendedId(uri, id);
try {
resultBlockingQueue.put(resultUri);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
Log.e(TAG, "Interrupted while waiting to put result in queue", e);
}
});
try {
return resultBlockingQueue.poll(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
Log.e(TAG, "Interrupted while waiting to take result from queue", e);
return null;
}
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
final BlockingQueue resultBlockingQueue = new ArrayBlockingQueue<>(1);
daoHandler.post(() -> {
int rowsDeleted = 0;
switch (uriMatcher.match(uri)) {
case USERS:
rowsDeleted = db.userDao().deleteAll();
break;
case USER_ID:
long id = ContentUris.parseId(uri);
rowsDeleted = db.userDao().deleteById((int) id);
break;
}
try {
resultBlockingQueue.put(rowsDeleted);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
Log.e(TAG, "Interrupted while waiting to put result in queue", e);
}
});
try {
return resultBlockingQueue.poll(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
Log.e(TAG, "Interrupted while waiting to take result from queue", e);
return 0;
}
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
if (uriMatcher.match(uri) != USER_ID) {
throw new IllegalArgumentException("Unsupported URI for update: " + uri);
}
long id = ContentUris.parseId(uri);
User user = User.fromContentValues(values);
user.setId((int) id);
final BlockingQueue resultBlockingQueue = new ArrayBlockingQueue<>(1);
daoHandler.post(() -> {
int rowsUpdated = db.userDao().update(user);
try {
resultBlockingQueue.put(rowsUpdated);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
Log.e(TAG, "Interrupted while waiting to put result in queue", e);
}
});
try {
return resultBlockingQueue.poll(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
Log.e(TAG, "Interrupted while waiting to take result from queue", e);
return 0;
}
}
}
外部使用
public static final String TAG = MainActivity.class.getSimpleName();
private Uri newUserUri;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.insert).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ContentValues values = new ContentValues();
values.put("name", "John Doe");
values.put("age", 30);
newUserUri = getContentResolver().insert(DiagnoseContentProvider.CONTENT_URI, values);
Log.e(TAG,"insert : " + newUserUri);
}
});
findViewById(R.id.query_all).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Query all users
Cursor cursor = getContentResolver().query(DiagnoseContentProvider.CONTENT_URI, null, null, null, null);
if (cursor != null) {
while (cursor.moveToNext()) {
String name = cursor.getString(cursor.getColumnIndexOrThrow("name"));
int age = cursor.getInt(cursor.getColumnIndexOrThrow("age"));
int id = cursor.getInt(cursor.getColumnIndexOrThrow("id"));
// Do something with the user data
Log.e(TAG," name : " + name + " age : " + age + " id : " + id);
}
cursor.close();
}
}
});
findViewById(R.id.update).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ContentValues updateValues = new ContentValues();
updateValues.put("name", "Jane Doe");
updateValues.put("age", 25);
getContentResolver().update(newUserUri, updateValues, null, null);
}
});
findViewById(R.id.delete).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Delete a user
getContentResolver().delete(newUserUri, null, null);
}
});
findViewById(R.id.query_by_id).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Query a user by ID
int userId = 1; // 假设要查询的用户 ID 为 1
Uri queryUri = ContentUris.withAppendedId(DiagnoseContentProvider.CONTENT_URI, userId);
Cursor cursor = getContentResolver().query(queryUri, null, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
String name = cursor.getString(cursor.getColumnIndexOrThrow("name"));
int age = cursor.getInt(cursor.getColumnIndexOrThrow("age"));
// Do something with the user data
Log.e(TAG, " name : " + name + " age : " + age + " id : " + userId);
cursor.close();
} else {
Log.e(TAG, "User not found with ID: " + userId);
}
}
});
findViewById(R.id.detele_all).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Delete all users
int rowsDeleted = getContentResolver().delete(DiagnoseContentProvider.CONTENT_URI, null, null);
Log.e(TAG, "Deleted " + rowsDeleted + " users");
}
});
}
效果
FQA
Q:为什么我跨进程使用 getContentResolver 无法访问到数据,已经检查了 Uri 没有错误?
A:
推荐阅读:
我的新书,《第一行代码 第3版》已出版!
原创:写给初学者的Jetpack Compose教程,edge-to-edge全面屏体验
Github Copilot 近期的一次重要更新
欢迎关注我的公众号
学习技术或投稿
长按上图,识别图中二维码即可关注