主目錄見:Android高級進階知識(這是總目錄索引)
?我們都知道sharedpreference在使用的時候是不支持多進程操作數(shù)據(jù)的,不同進程間操作數(shù)據(jù)的讀取,存取或者并發(fā)操作數(shù)據(jù)都會出現(xiàn)問題,所以我們需要自己去控制跨進程操作,現(xiàn)在我們看看官方文檔對shareprefrence中MODE_MULTI_PROCESS的描述:
int MODE_MULTI_PROCESS
This constant was deprecated in API level 23.
MODE_MULTI_PROCESS does not work reliably in some versions of Android, and furthermore does not provide any mechanism for reconciling concurrent modifications across processes. Applications should not attempt to use it. Instead, they should use an explicit cross-process data management approach such as ContentProvider.
這段英文的意思就是這個常量在api 23就已經(jīng)廢棄了,MODE_MULTI_PROCESS在一些android版本中不能穩(wěn)定運行,而且不提供任何機制來記錄跨進程的修改,不建議應(yīng)用使用他。作為替代,應(yīng)該使用更加明確的跨進程數(shù)據(jù)管理辦法比如ContentProvider。所以這里我們必須尋求一個更好的解決這個問題的辦法,我們今天這里選擇ContentProvider+sharedpreference的辦法即框架[DPreference],當然還有框架ContentProvider+sqLite的[Tray],但是看驗證性能不理想,而且需要升級相關(guān)問題,所以我們這里選擇DPreference來講解,好啦,看了這么多放松一下。。。
一.目標
?今天這篇文章也是我在用跨進程框架時候遇到需要存儲數(shù)據(jù),然后多個進程操作的時候遇到的問題,其實有很多種辦法解決,但是這種方法我覺得是比較方便的。學(xué)習(xí)今天的文章有幾個目標:
1.復(fù)習(xí)ContentProvider的使用,因為這個平常用的確實沒有很多;
2.可以多一個多進程存取數(shù)據(jù)的解決方案。
二.源碼分析
首先還是跟其他源碼的入手點一樣,我們先來看看最基本的使用方法,其實這個使用方法跟shareprefrence是一模一樣的:
DPreference dPreference = new DPreference(context, "default");
dPreference.setPrefString( "key", "value");
DPreference dPreference = new DPreference(context, "default");
dPreference.getPrefString( "key");
我們看到確實用法跟shareprefrence無異,甚至更簡單有沒有,不用commit。現(xiàn)在我們先從存儲開始講解。
1.存儲 setPrefString
首先我們從DPreference的構(gòu)造函數(shù)開始看:
public DPreference(Context context, String name) {
this.mContext = context;
this.mName = name;
}
我們看到構(gòu)造函數(shù)啥也沒有,只是簡單地賦值一下,那么我們來看此類的setPrefString方法吧:
public void setPrefString(final String key, final String value) {
PrefAccessor.setString(mContext, mName, key, value);
}
這個方法里面又調(diào)用了PrefAccessor類的setString方法,我們跟進去看下:
public static void setString(Context context, String name, String key, String value) {
Uri URI = PreferenceProvider.buildUri(name, key, PreferenceProvider.PREF_STRING);
ContentValues cv = new ContentValues();
cv.put(PreferenceProvider.PREF_KEY, key);
cv.put(PreferenceProvider.PREF_VALUE, value);
context.getContentResolver().update(URI, cv, null, null);
}
我們看到這里面是典型的訪問ContentProvider的方法,這里我們看下PreferenceProvider中的buildUri方法。PreferenceProvider是個ContentProvider對象,我們首先看下Uri是什么:
private static final String AUTHORITY = "me.dozen.dpreference.PreferenceProvider";
public static final String CONTENT_PREF_BOOLEAN_URI = "content://" + AUTHORITY + "/boolean/";
public static final String CONTENT_PREF_STRING_URI = "content://" + AUTHORITY + "/string/";
public static final String CONTENT_PREF_INT_URI = "content://" + AUTHORITY + "/integer/";
public static final String CONTENT_PREF_LONG_URI = "content://" + AUTHORITY + "/long/";
public static final String PREF_KEY = "key";
public static final String PREF_VALUE = "value";
public static final int PREF_BOOLEAN = 1;
public static final int PREF_STRING = 2;
public static final int PREF_INT = 3;
public static final int PREF_LONG = 4;
private static final UriMatcher sUriMatcher;
static {
sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
sUriMatcher.addURI(AUTHORITY, "boolean/*/*", PREF_BOOLEAN);
sUriMatcher.addURI(AUTHORITY, "string/*/*", PREF_STRING);
sUriMatcher.addURI(AUTHORITY, "integer/*/*", PREF_INT);
sUriMatcher.addURI(AUTHORITY, "long/*/*", PREF_LONG);
}
我們看到這個就是我們ContentProvider典型的做法,暴露幾個Uri給外部訪問,具體的細節(jié)相信大家知道了,如果不知道我這里推薦一篇文章[Android開發(fā)之內(nèi)容提供者——創(chuàng)建自己的ContentProvider(詳解) ],相信看了大家就會明白的。然后我們來看buildUri方法:
public static Uri buildUri(String name, String key, int type) {
return Uri.parse(getUriByType(type) + name + "/" + key);
}
private static String getUriByType(int type) {
switch (type) {
case PreferenceProvider.PREF_BOOLEAN:
return PreferenceProvider.CONTENT_PREF_BOOLEAN_URI;
case PreferenceProvider.PREF_INT:
return PreferenceProvider.CONTENT_PREF_INT_URI;
case PreferenceProvider.PREF_LONG:
return PreferenceProvider.CONTENT_PREF_LONG_URI;
case PreferenceProvider.PREF_STRING:
return PreferenceProvider.CONTENT_PREF_STRING_URI;
}
throw new IllegalStateException("unsupport preftype : " + type);
}
我們看到這里代碼會根據(jù)type來獲取對應(yīng)的Uri,到這里我們已經(jīng)獲取到了Uri,我們就可以用ContentResolver調(diào)用ContentProvider里面相應(yīng)的方法了,我們看到我們這里的setString()方法最后調(diào)用了ContentProvider的update()方法,所以我們進一步就是看這個PreferenceProvider的update()方法:
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
PrefModel model = getPrefModelByUri(uri);
if(model == null) {
throw new IllegalArgumentException("update prefModel is null");
}
switch (sUriMatcher.match(uri)) {
case PREF_BOOLEAN:
persistBoolean(model.getName(), values);
break;
case PREF_LONG:
persistLong(model.getName(), values);
break;
case PREF_STRING:
persistString(model.getName(), values);
break;
case PREF_INT:
persistInt(model.getName(), values);
break;
default:
throw new IllegalStateException("update unsupported uri : " + uri);
}
return 0;
}
這個方法其實就是要保存我們設(shè)置進來的value,我們先看這個方法最開始會查找PrefModel的對象,所以我們看下這個getPrefModelByUri()方法:
private PrefModel getPrefModelByUri(Uri uri) {
if (uri == null || uri.getPathSegments().size() != 3) {
throw new IllegalArgumentException("getPrefModelByUri uri is wrong : " + uri);
}
String name = uri.getPathSegments().get(1);
String key = uri.getPathSegments().get(2);
return new PrefModel(name, key);
}
其中g(shù)etPathSegments方法的getPathSegments得到uri的path部分,并拆分,去掉"/",取到第一個元素(從第0個開始),
//比如:
content://"+FirstProvierMetaData.AUTHORIY+"/users /1"
//getPathSegments()得到的是users 和1,get(1)會得到1
所以這里的get(1)得到的name(就是對應(yīng)于DPreference(context, "default")這里面的default,也就是這個DPreference的名字),get(2)就是獲取到key就是setString()方法里面的key,然后會匹配Uri調(diào)用相應(yīng)方法,我們字符串會匹配到persistString()方法:
private void persistString(String name, ContentValues values) {
if (values == null) {
throw new IllegalArgumentException(" values is null!!!");
}
String kString = values.getAsString(PREF_KEY);
String vString = values.getAsString(PREF_VALUE);
getDPreference(name).setPrefString(kString, vString);
}
我們這里看到會根據(jù)name來獲取到一個對應(yīng)的shareprefrence,具體我們看getDPreference方法:
private IPrefImpl getDPreference(String name) {
if (TextUtils.isEmpty(name)) {
throw new IllegalArgumentException("getDPreference name is null!!!");
}
if (sPreferences.get(name) == null) {
IPrefImpl pref = new PreferenceImpl(getContext(), name);
sPreferences.put(name, pref);
}
return sPreferences.get(name);
}
我們看到這里會先在sPreferences(private static Map<String, IPrefImpl> sPreferences = new ArrayMap<>())中獲取到這個name對應(yīng)的PreferenceImpl對象,如果不存在則保存,然后返回PreferenceImpl對象,接著就是調(diào)用setPrefString()方法,那么這個方法就是PreferenceImpl的setPrefString方法:
public void setPrefString(final String key, final String value) {
final SharedPreferences settings =
mContext.getSharedPreferences(mPrefName, Context.MODE_PRIVATE);
settings.edit().putString(key, value).apply();
}
我們看到方法最終還是調(diào)用了sharedpreference的方法,非常的簡單,到這里我們的存儲方法已經(jīng)完畢了,是不是很簡單,其實的確是很簡單。
2.獲取 getPrefString
上面已經(jīng)講完怎么存了,就是利用ContentProvider機制,但是最終還是用shareprefrence進行存儲,那么我們這里取肯定最終也是從shareprefrence中獲取的嘛,我們開始驗證:
public String getPrefString(final String key, final String defaultValue) {
return PrefAccessor.getString(mContext, mName, key, defaultValue);
}
這里套路是一樣的,也是調(diào)用的PrefAccessor里面的getString方法:
public static String getString(Context context, String name, String key, String defaultValue) {
Uri URI = PreferenceProvider.buildUri(name, key, PreferenceProvider.PREF_STRING);
String value = defaultValue;
Cursor cursor = context.getContentResolver().query(URI, null, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
value = cursor.getString(cursor.getColumnIndex(PreferenceProvider.PREF_VALUE));
}
IOUtils.closeQuietly(cursor);
return value;
}
我們看到第一句也是獲取Uri,這個跟前面講過的是一樣的,這里就不贅述,然后我們看利用ContentResolver調(diào)用了query方法,我們就看ContentProvider的query方法到底做了啥:
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
MatrixCursor cursor = null;
PrefModel model = getPrefModelByUri(uri);
switch (sUriMatcher.match(uri)) {
case PREF_BOOLEAN:
if (getDPreference(model.getName()).hasKey(model.getKey())) {
cursor = preferenceToCursor(getDPreference(model.getName()).getPrefBoolean(model.getKey(), false) ? 1 : 0);
}
break;
case PREF_STRING:
if (getDPreference(model.getName()).hasKey(model.getKey())) {
cursor = preferenceToCursor(getDPreference(model.getName()).getPrefString(model.getKey(), ""));
}
break;
case PREF_INT:
if (getDPreference(model.getName()).hasKey(model.getKey())) {
cursor = preferenceToCursor(getDPreference(model.getName()).getPrefInt(model.getKey(), -1));
}
break;
case PREF_LONG:
if (getDPreference(model.getName()).hasKey(model.getKey())) {
cursor = preferenceToCursor(getDPreference(model.getName()).getPrefLong(model.getKey(), -1));
}
break;
}
return cursor;
}
首先看上面這段代碼之前,我們需要知道一個概念,那就是MatrixCursor類的作用,如果想得到一個Cursor, 而此時又沒有數(shù)據(jù)庫返回一個Cursor,此時可以通過MatrixCursor來返回一個Cursor,因為我們這里底層是用sharedpreference,不是用的sqlite,所以我們要返回搜索的結(jié)果Cursor,我們就只能用這個類了。我們程序匹配的時候還是會調(diào)用到preferenceToCursor(當然在調(diào)用之前是會判斷key在不在的這個name對應(yīng)的sharedpreference中),我們首先看到preferenceToCursor里面的參數(shù)就是getDPreference(model.getName()).getPrefString(model.getKey(), "")獲取得到的,我們首先看下PreferenceImpl的getPrefString方法:
public String getPrefString(final String key,
final String defaultValue) {
final SharedPreferences settings =
mContext.getSharedPreferences(mPrefName, Context.MODE_PRIVATE);
return settings.getString(key, defaultValue);
}
我們看到這里調(diào)用了sharedpreference來獲取值,然后傳給preferenceToCursor方法:
private <T> MatrixCursor preferenceToCursor(T value) {
MatrixCursor matrixCursor = new MatrixCursor(PREFERENCE_COLUMNS, 1);
MatrixCursor.RowBuilder builder = matrixCursor.newRow();
builder.add(value);
return matrixCursor;
}
然后我們看到這個value被添加進MatrixCursor 的對象中,然后返回這個Cursor,就可以像操作數(shù)據(jù)庫返回的Cursor那樣了。到這里我們的數(shù)據(jù)也就取完畢了。整個流程是比較容易的,就是ContentProvider的基本知識。
總結(jié):這篇文章主要就是利用ContentProvider來包裝SharedPereference來操作key和value的值,整體來說是比較簡單,但是這是一個解決問題的辦法,還是很不錯的,希望大家有g(shù)et到技能。