Android 開發過程中,基於功能隔離、進程安全、進程保活等等考慮,我們經常需要為應用划分進程,然后不得不面臨跨進程通信和跨進程共享數據的挑戰。
跨進程通信
相對來說,跨進程通信比較簡單,常用的方式有:
1.全局廣播
廣播是最簡潔的跨進程通信方式,發送——接收廣播即可完成異步通信。
2.AIDL
使用AIDL進行跨進程調用、通信是不錯的選擇,能夠支持更復雜的接口調用,通信是同步完成的。但是實現上需要與其他進程的Service建立連接,然后通過AIDL定義的接口進行調用,實現上稍顯復雜。
筆者經常使用的是這兩種方式,具體使用哪種看場景決定,沒有最好的方案,只有最適合的方案。如果小伙伴們有更多方式,歡迎留言交流,讓我也學習學習。
跨進程共享數據
跨進程共享數據也是很常見的需求,一份應用數據,各個進程都需要讀取、更新使用,我們要做的就是,在各進程都可以訪問到這份數據的前提下,保證數據的同步。
常用的方式有:
1.SharedPreferences
使用存儲模式為MODE_MULTI_PROCESS的SharedPreferences來實現數據存儲,各個進程都需要建立自己的SharedPreference實例,通過它來訪問數據,系統機制保證數據的同步。不過這種方式不完全可靠,已經被官方棄用,新的Android 版本已經不再支持。
2.ContentProvider
ContentProvider是官方推薦應用間共享數據的方式,也是被大家最廣泛使用的方式,由系統來保證進程間數據同步的安全性和可靠性,穩定可靠。ContentProvider提供了增刪改查的接口,與數據庫結合,相當於為其他進程提供了一個遠程數據庫,功能強大,只是實現上相當於定義了一套遠程訪問數據庫的接口協議,稍顯復雜。
3.第三方框架
常見的有github上的Tray,作為SharedPreferences跨進程版本的替代方案,使用者的反饋是不錯的,不過我還沒有使用研究過它:D
同樣的,沒有最好的方案,只有最適合的方案,使用哪一種方案,需要根據實際場景,分析各自的優點和局限性,作出合理的選擇。就如同在選擇使用Sqlite還是SharedPreferences的時候,對於需要復雜操作的數據,比如大量且需要復雜的增刪改查的數據,應該使用Sqlite等數據庫實現來存儲,跨進程時就用數據庫作支持的ContentProvider,對於簡單的只需要簡單查詢,讀寫的數據,用SharedPreferences足矣,跨進程時,使用SharedPreferences作支持的ContentProvider足矣。
在跨進程共享簡單數據,如配置、用戶信息等等的時候,我常用的做法就是利用ContentProvider的跨進程安全特性,以SharedPreferences作為支撐,實現跨進程的SharedPreferences:
import android.content.ContentProvider; import android.content.ContentValues; import android.content.Context; import android.content.SharedPreferences; import android.database.Cursor; import android.database.MatrixCursor; import android.net.Uri; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.TextUtils; import java.util.Iterator; /** * Created by Irwin on 2017/8/29. */ public class GlobalProvider extends ContentProvider { public static final Uri AUTHORITY_URI = Uri.parse("content://[YOUR PACKAGE NAME]"); public static final Uri CONTENT_URI = AUTHORITY_URI; public static final String PARAM_KEY = "key"; public static final String PARAM_VALUE = "value"; private final String DB_NAME = "global.sp"; private SharedPreferences mStore; public static Cursor query(Context context, String... keys) { return context.getContentResolver().query(CONTENT_URI, keys, null, null, null); } public static String getString(Context context, String key) { return getString(context, key, null); } public static String getString(Context context, String key, String defValue) { Cursor cursor = query(context, key); String ret = defValue; if (cursor.moveToNext()) { ret = cursor.getString(0); if (TextUtils.isEmpty(ret)) { ret = defValue; } } cursor.close(); return ret; } public static int getInt(Context context, String key, int defValue) { Cursor cursor = query(context, key); int ret = defValue; if (cursor.moveToNext()) { try { ret = cursor.getInt(0); } catch (Exception e) { } } cursor.close(); return ret; } public static Uri save(Context context, ContentValues values) { return context.getContentResolver().insert(GlobalProvider.CONTENT_URI, values); } public static Uri save(Context context, String key, String value) { ContentValues values = new ContentValues(1); values.put(key, value); return save(context, values); } public static Uri remove(Context context, String key) { return save(context, key, null); } @Override public boolean onCreate() { mStore = getContext().getSharedPreferences(DB_NAME, Context.MODE_PRIVATE); return true; } @Nullable @Override public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) { int size = projection == null ? 0 : projection.length; if (size > 0) { String[] values = new String[size]; for (int i = 0; i < size; i++) { values[i] = getValue(projection[i], null); } return createCursor(projection, values); } String key = uri.getQueryParameter(PARAM_KEY); String value = null; if (!TextUtils.isEmpty(key)) { value = getValue(key, null); } return createSingleCursor(key, value); } protected Cursor createSingleCursor(String key, String value) { MatrixCursor cursor = new MatrixCursor(new String[]{key}, 1); if (!TextUtils.isEmpty(value)) { cursor.addRow(new Object[]{value}); } return cursor; } protected Cursor createCursor(String[] keys, String[] values) { MatrixCursor cursor = new MatrixCursor(keys, 1); cursor.addRow(values); return cursor; } @Nullable @Override public String getType(@NonNull Uri uri) { return ""; } @Nullable @Override public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { if (values != null && values.size() > 0) { save(values); } else { String key = uri.getQueryParameter(PARAM_KEY); String value = uri.getQueryParameter(PARAM_VALUE); if (!TextUtils.isEmpty(key)) { save(key, value); } } return null; } @Override public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) { String key = selection == null ? selection : uri.getQueryParameter(PARAM_KEY); if (!TextUtils.isEmpty(key)) { remove(key); return 1; } return 0; } @Override public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) { if (values != null && values.size() > 0) { save(values); return values.size(); } String key = uri.getQueryParameter(PARAM_KEY); String value = uri.getQueryParameter(PARAM_VALUE); if (!TextUtils.isEmpty(key)) { save(key, value); return 1; } return 0; } protected String getValue(String key, String defValue) { return mStore.getString(key, defValue); } protected void save(ContentValues values) { String key; String value; Iterator<String> iterator = values.keySet().iterator(); SharedPreferences.Editor editor = mStore.edit(); while (iterator.hasNext()) { key = iterator.next(); value = values.getAsString(key); if (!TextUtils.isEmpty(key)) { if (value != null) { editor.putString(key, value); } else { editor.remove(key); } } } editor.commit(); } protected void save(String key, String value) { SharedPreferences.Editor editor = mStore.edit(); if (value != null) { editor.putString(key, value); } else { editor.remove(key); } editor.commit(); } protected void remove(String key) { SharedPreferences.Editor editor = mStore.edit(); editor.remove(key); editor.commit(); } }
根據需要添加快捷訪問的方法,使用起來簡單方便:
//讀取共享 GlobalProvider.getInt(context,PARAMETER_KEY,DEFAULT_VALUE_WHILE_NULL); //寫入共享 GlobalProvider.save(context, PARAMETER_KEY, PARAMETER_VALUE);
當然,對於需要復雜操作的共享數據,還是乖乖滴基於數據庫根據自己的業務需求實現完整的ContentProvider吧!
