天天看點

Android 跨程序資料共享

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吧!