由于Android N以后,Sharepreference的第三个参数MODE_WORLD_READABLE的被禁止,Shareperference的跨进程通信变得不可用,谷歌推荐使用ContentProvider进行通信。
但是由于ContentProvider在平时简单的使用中过于重量,需要进行数据库操作特别的麻烦,所以我找到了一个库,基于ContentProvider封装,使用和平时SharePreference基本一致。
开源库地址:MultiprocessSharedPreferences
关键库代码:MultiprocessSharedPreferences.java
/*
* 创建日期:2014年9月12日 下午0:0:02
*/
package com.android.zgj.utils;
import com.android.zgj.BuildConfig;
import android.content.BroadcastReceiver;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.UriMatcher;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.DeadObjectException;
import android.support.annotation.NonNull;
import android.util.Log;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
/**
* 使用ContentProvider实现多进程SharedPreferences读写;
* 1、ContentProvider天生支持多进程访问;
* 2、使用内部私有BroadcastReceiver实现多进程OnSharedPreferenceChangeListener监听;
*
* 使用方法:AndroidManifest.xml中添加provider申明:
*
* <provider android:name="com.android.zgj.utils.MultiprocessSharedPreferences"
* android:authorities="com.android.zgj.MultiprocessSharedPreferences"
* android:process="com.android.zgj.MultiprocessSharedPreferences"
* android:exported="false" />
* <!-- authorities属性里面最好使用包名做前缀,apk在安装时authorities同名的provider需要校验签名,否则无法安装;--!/>
*
*
* ContentProvider方式实现要注意:
* 1、当ContentProvider所在进程android.os.Process.killProcess(pid)时,会导致整个应用程序完全意外退出或者ContentProvider所在进程重启;
* 重启报错信息:Acquiring provider for user 0: existing object's process dead;
* 2、如果设备处在“安全模式”下,只有系统自带的ContentProvider才能被正常解析使用,因此put值时默认返回false,get值时默认返回null;
*
* 其他方式实现SharedPreferences的问题:
* 使用FileLock和FileObserver也可以实现多进程SharedPreferences读写,但是维护成本高,需要定期对照系统实现更新新的特性;
*
* @author zhangguojun
* @version 1.0
* @since JDK1.6
*/
public class MultiprocessSharedPreferences extends ContentProvider implements SharedPreferences {
private static final String TAG = "MultiprocessSharedPreferences";
public static final boolean DEBUG = BuildConfig.DEBUG;
private Context mContext;
private String mName;
private int mMode;
private boolean mIsSafeMode;
private static final Object CONTENT = new Object();
private WeakHashMap mListeners;
private BroadcastReceiver mReceiver;
private static String AUTHORITY;
private static volatile Uri AUTHORITY_URI;
private UriMatcher mUriMatcher;
private static final String KEY = "value";
private static final String KEY_NAME = "name";
private static final String PATH_WILDCARD = "*/";
private static final String PATH_GET_ALL = "getAll";
private static final String PATH_GET_STRING = "getString";
private static final String PATH_GET_INT = "getInt";
private static final String PATH_GET_LONG = "getLong";
private static final String PATH_GET_FLOAT = "getFloat";
private static final String PATH_GET_BOOLEAN = "getBoolean";
private static final String PATH_CONTAINS = "contains";
private static final String PATH_APPLY = "apply";
private static final String PATH_COMMIT = "commit";
private static final String PATH_REGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER = "registerOnSharedPreferenceChangeListener";
private static final String PATH_UNREGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER = "unregisterOnSharedPreferenceChangeListener";
private static final String PATH_GET_STRING_SET = "getStringSet";
private static final int GET_ALL = 1;
private static final int GET_STRING = 2;
private static final int GET_INT = 3;
private static final int GET_LONG = 4;
private static final int GET_FLOAT = 5;
private static final int GET_BOOLEAN = 6;
private static final int CONTAINS = 7;
private static final int APPLY = 8;
private static final int COMMIT = 9;
private static final int REGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER = 10;
private static final int UNREGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER = 11;
private static final int GET_STRING_SET = 12;
private HashMap mListenersCount;
private static class ReflectionUtil {
public static ContentValues contentValuesNewInstance(HashMap values) {
try {
Constructor c = ContentValues.class.getDeclaredConstructor(new Class[] { HashMap.class }); // hide
c.setAccessible(true);
return c.newInstance(values);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
}
}
public static Editor editorPutStringSet(Editor editor, String key, Set values) {
try {
Method method = editor.getClass().getDeclaredMethod("putStringSet", new Class[] { String.class, Set.class }); // Android 3.0
return (Editor) method.invoke(editor, key, values);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
@SuppressWarnings("unchecked")
public static Set sharedPreferencesGetStringSet(SharedPreferences sharedPreferences, String key, Set values) {
try {
Method method = sharedPreferences.getClass().getDeclaredMethod("getStringSet", new Class[] { String.class, Set.class }); // Android 3.0
return (Set) method.invoke(sharedPreferences, key, values);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
public static void editorApply(Editor editor) {
try {
Method method = editor.getClass().getDeclaredMethod("apply"); // Android 2.3
method.invoke(editor);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
public static String contentProvidermAuthority(ContentProvider contentProvider) {
try {
Field mAuthority = ContentProvider.class.getDeclaredField("mAuthority"); // Android 5.0
mAuthority.setAccessible(true);
return (String) mAuthority.get(contentProvider);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
// 如果设备处在“安全模式”下,只有系统自带的ContentProvider才能被正常解析使用;
private boolean isSafeMode(Context context) {
boolean isSafeMode = false;
try {
isSafeMode = context.getPackageManager().isSafeMode();
// 解决崩溃:
// java.lang.RuntimeException: Package manager has died
// at android.app.ApplicationPackageManager.isSafeMode(ApplicationPackageManager.java:820)
} catch (RuntimeException e) {
if (!isPackageManagerHasDiedException(e)) {
throw e;
}
}
return isSafeMode;
}
/**
* (可选)设置AUTHORITY,不用在初始化时遍历程序的AndroidManifest.xml文件获取android:authorities的值,减少初始化时间提高运行速度;
* @param authority
*/
public static void setAuthority(String authority) {
AUTHORITY = authority;
}
private boolean checkInitAuthority(Context context) {
if (AUTHORITY_URI == null) {
synchronized (MultiprocessSharedPreferences.this) {
if (AUTHORITY_URI == null) {
if(AUTHORITY == null) {
if (Build.VERSION.SDK_INT >= 21 && this instanceof ContentProvider) {
AUTHORITY = ReflectionUtil.contentProvidermAuthority(this);
} else {
PackageInfo packageInfos = null;
try {
packageInfos = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_PROVIDERS);
} catch (PackageManager.NameNotFoundException e) {
if (DEBUG) {
e.printStackTrace();
}
} catch (RuntimeException e) {
if (!isPackageManagerHasDiedException(e)) {
throw new RuntimeException("checkInitAuthority", e);
}
}
if (packageInfos != null && packageInfos.providers != null) {
for (ProviderInfo providerInfo : packageInfos.providers) {
if (providerInfo.name.equals(MultiprocessSharedPreferences.class.getName())) {
AUTHORITY = providerInfo.authority;
break;
}
}
}
}
}
if (DEBUG) {
if (AUTHORITY == null) {
throw new IllegalArgumentException("'AUTHORITY' initialize failed, Unable to find explicit provider class " + MultiprocessSharedPreferences.class.getName() + "; have you declared this provider in your AndroidManifest.xml?");
} else {
Log.d(TAG, "checkInitAuthority.AUTHORITY = " + AUTHORITY);
}
}
AUTHORITY_URI = Uri.parse(ContentResolver.SCHEME_CONTENT + "://" + AUTHORITY);
}
}
}
return AUTHORITY_URI != null;
}
private boolean isPackageManagerHasDiedException(Throwable e) {
// 1、packageManager.getPackageInfo
// java.lang.RuntimeException: Package manager has died
// at android.app.ApplicationPackageManager.getPackageInfo(ApplicationPackageManager.java:80)
// ...
// Caused by: android.os.DeadObjectException
// at android.os.BinderProxy.transact(Native Method)
// at android.content.pm.IPackageManager$Stub$Proxy.getPackageInfo(IPackageManager.java:1374)
// 2、contentResolver.query
// java.lang.RuntimeException: Package manager has died
// at android.app.ApplicationPackageManager.resolveContentProvider(ApplicationPackageManager.java:636)
// at android.app.ActivityThread.acquireProvider(ActivityThread.java:4750)
// at android.app.ContextImpl$ApplicationContentResolver.acquireUnstableProvider(ContextImpl.java:2234)
// at android.content.ContentResolver.acquireUnstableProvider(ContentResolver.java:1425)
// at android.content.ContentResolver.query(ContentResolver.java:445)
// at android.content.ContentResolver.query(ContentResolver.java:404)
// at com.qihoo.storager.MultiprocessSharedPreferences.getValue(AppStore:502)
// ...
// Caused by: android.os.TransactionTooLargeException
// at android.os.BinderProxy.transact(Native Method)
// at android.content.pm.IPackageManager$Stub$Proxy.resolveContentProvider(IPackageManager.java:2500)
// at android.app.ApplicationPackageManager.resolveContentProvider(ApplicationPackageManager.java:634)
if (e instanceof RuntimeException
&& e.getMessage() != null
&& e.getMessage().contains("Package manager has died")) {
Throwable cause = getLastCause(e);
if (cause instanceof DeadObjectException || cause.getClass().getName().equals("android.os.TransactionTooLargeException")) {
return true;
}
}
return false;
}
private boolean isUnstableCountException(Throwable e) {
// java.lang.RuntimeException: java.lang.IllegalStateException: unstableCount < 0: -1
// at com.qihoo.storager.MultiprocessSharedPreferences.getValue(AppStore:459)
// at com.qihoo.storager.MultiprocessSharedPreferences.getBoolean(AppStore:282)
// ...
// Caused by: java.lang.IllegalStateException: unstableCount < 0: -1
// at android.os.Parcel.readException(Parcel.java:1628)
// at android.os.Parcel.readException(Parcel.java:1573)
// at android.app.ActivityManagerProxy.refContentProvider(ActivityManagerNative.java:3680)
// at android.app.ActivityThread.releaseProvider(ActivityThread.java:5052)
// at android.app.ContextImpl$ApplicationContentResolver.releaseUnstableProvider(ContextImpl.java:2036)
// at android.content.ContentResolver.query(ContentResolver.java:534)
// at android.content.ContentResolver.query(ContentResolver.java:435)
// at com.qihoo.storager.MultiprocessSharedPreferences.a(AppStore:452)
if (e instanceof RuntimeException
&& e.getMessage() != null
&& e.getMessage().contains("unstableCount < 0: -1")) {
if (getLastCause(e) instanceof IllegalStateException) {
return true;
}
}
return false;
}
/**
* 获取异常栈中最底层的 Throwable Cause;
*
* @param tr
* @return
*/
private Throwable getLastCause(Throwable tr) {
Throwable cause = tr.getCause();
Throwable causeLast = null;
while (cause != null) {
causeLast = cause;
cause = cause.getCause();
}
if (causeLast == null) {
causeLast = new Throwable();
}
return causeLast;
}
/**
* mode不使用{@link Context#MODE_MULTI_PROCESS}特可以支持多进程了;
*
* @param mode
*
* @see Context#MODE_PRIVATE
* @see Context#MODE_WORLD_READABLE
* @see Context#MODE_WORLD_WRITEABLE
*/
public static SharedPreferences getSharedPreferences(Context context, String name, int mode) {
return new MultiprocessSharedPreferences(context, name, mode);
}
/**
* @deprecated 此默认构造函数只用于父类ContentProvider在初始化时使用;
*/
@Deprecated
public MultiprocessSharedPreferences() {
}
private MultiprocessSharedPreferences(Context context, String name, int mode) {
mContext = context;
mName = name;
mMode = mode;
mIsSafeMode = isSafeMode(mContext);
}
@SuppressWarnings("unchecked")
@Override
public Map getAll() {
Map value = (Map) getValue(PATH_GET_ALL, null, null);
return value == null ? new HashMap() : value;
}
@Override
public String getString(String key, String defValue) {
return (String) getValue(PATH_GET_STRING, key, defValue);
}
// @Override // Android 3.0
@SuppressWarnings("unchecked")
public Set getStringSet(String key, Set defValues) {
return (Set) getValue(PATH_GET_STRING_SET, key, defValues);
}
@Override
public int getInt(String key, int defValue) {
return (Integer) getValue(PATH_GET_INT, key, defValue);
}
@Override
public long getLong(String key, long defValue) {
return (Long) getValue(PATH_GET_LONG, key, defValue);
}
@Override
public float getFloat(String key, float defValue) {
return (Float) getValue(PATH_GET_FLOAT, key, defValue);
}
@Override
public boolean getBoolean(String key, boolean defValue) {
return (Boolean) getValue(PATH_GET_BOOLEAN, key, defValue);
}
@Override
public boolean contains(String key) {
return (Boolean) getValue(PATH_CONTAINS, key, false);
}
@Override
public Editor edit() {
return new EditorImpl();
}
@Override
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
synchronized (this) {
if (mListeners == null) {
mListeners = new WeakHashMap();
}
Boolean result = (Boolean) getValue(PATH_REGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER, null, false);
if (result != null && result) {
mListeners.put(listener, CONTENT);
if (mReceiver == null) {
mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String name = intent.getStringExtra(KEY_NAME);
@SuppressWarnings("unchecked")
List keysModified = (List) intent.getSerializableExtra(KEY);
if (mName.equals(name) && keysModified != null) {
Set listeners = new HashSet(mListeners.keySet());
for (int i = keysModified.size() - 1; i >= 0; i--) {
final String key = keysModified.get(i);
for (OnSharedPreferenceChangeListener listener : listeners) {
if (listener != null) {
listener.onSharedPreferenceChanged(MultiprocessSharedPreferences.this, key);
}
}
}
}
}
};
mContext.registerReceiver(mReceiver, new IntentFilter(makeAction(mName)));
}
}
}
}
@Override
public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
synchronized (this) {
getValue(PATH_UNREGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER, null, false); // WeakHashMap
if (mListeners != null) {
mListeners.remove(listener);
if (mListeners.isEmpty() && mReceiver != null) {
mContext.unregisterReceiver(mReceiver);
}
}
}
}
public final class EditorImpl implements Editor {
private final Map mModified = new HashMap();
private boolean mClear = false;
@Override
public Editor putString(String key, String value) {
synchronized (this) {
mModified.put(key, value);
return this;
}
}
// @Override // Android 3.0
public Editor putStringSet(String key, Set values) {
synchronized (this) {
mModified.put(key, (values == null) ? null : new HashSet(values));
return this;
}
}
@Override
public Editor putInt(String key, int value) {
synchronized (this) {
mModified.put(key, value);
return this;
}
}
@Override
public Editor putLong(String key, long value) {
synchronized (this) {
mModified.put(key, value);
return this;
}
}
@Override
public Editor putFloat(String key, float value) {
synchronized (this) {
mModified.put(key, value);
return this;
}
}
@Override
public Editor putBoolean(String key, boolean value) {
synchronized (this) {
mModified.put(key, value);
return this;
}
}
@Override
public Editor remove(String key) {
synchronized (this) {
mModified.put(key, null);
return this;
}
}
@Override
public Editor clear() {
synchronized (this) {
mClear = true;
return this;
}
}
@Override
public void apply() {
setValue(PATH_APPLY);
}
@Override
public boolean commit() {
return setValue(PATH_COMMIT);
}
private boolean setValue(String pathSegment) {
boolean result = false;
if (!mIsSafeMode && checkInitAuthority(mContext)) { // 如果设备处在“安全模式”,返回false;
String[] selectionArgs = new String[] { String.valueOf(mMode), String.valueOf(mClear) };
synchronized (this) {
Uri uri = Uri.withAppendedPath(Uri.withAppendedPath(AUTHORITY_URI, mName), pathSegment);
ContentValues values = ReflectionUtil.contentValuesNewInstance((HashMap) mModified);
try {
result = mContext.getContentResolver().update(uri, values, null, selectionArgs) > 0;
} catch (IllegalArgumentException e) {
// 解决ContentProvider所在进程被杀时的抛出的异常:
// java.lang.IllegalArgumentException: Unknown URI content://xxx.xxx.xxx/xxx/xxx
// at android.content.ContentResolver.update(ContentResolver.java:1312)
if (DEBUG) {
e.printStackTrace();
}
} catch (RuntimeException e) {
if (!isPackageManagerHasDiedException(e) && !isUnstableCountException(e)) {
throw new RuntimeException(e);
}
}
}
}
if (DEBUG) {
Log.d(TAG, "setValue.mName = " + mName + ", pathSegment = " + pathSegment + ", mModified.size() = " + mModified.size());
}
return result;
}
}
private Object getValue(String pathSegment, String key, Object defValue) {
Object v = null;
if (!mIsSafeMode && checkInitAuthority(mContext)) { // 如果设备处在“安全模式”,返回defValue;
Uri uri = Uri.withAppendedPath(Uri.withAppendedPath(AUTHORITY_URI, mName), pathSegment);
String[] projection = null;
if (PATH_GET_STRING_SET.equals(pathSegment) && defValue != null) {
@SuppressWarnings("unchecked")
Set set = (Set) defValue;
projection = new String[set.size()];
set.toArray(projection);
}
String[] selectionArgs = new String[] { String.valueOf(mMode), key, defValue == null ? null : String.valueOf(defValue) };
Cursor cursor = null;
try {
cursor = mContext.getContentResolver().query(uri, projection, null, selectionArgs, null);
} catch (SecurityException e) {
// 解决崩溃:
// java.lang.SecurityException: Permission Denial: reading com.qihoo.storager.MultiprocessSharedPreferences uri content://com.qihoo.appstore.MultiprocessSharedPreferences/LogUtils/getBoolean from pid=2446, uid=10116 requires the provider be exported, or grantUriPermission()
// at android.content.ContentProvider$Transport.enforceReadPermission(ContentProvider.java:332)
// ...
// at android.content.ContentResolver.query(ContentResolver.java:317)
if (DEBUG) {
e.printStackTrace();
}
} catch (RuntimeException e) {
if (!isPackageManagerHasDiedException(e) && !isUnstableCountException(e)) {
throw new RuntimeException(e);
}
}
if (cursor != null) {
Bundle bundle = null;
try {
bundle = cursor.getExtras();
} catch (RuntimeException e) {
// 解决ContentProvider所在进程被杀时的抛出的异常:
// java.lang.RuntimeException: android.os.DeadObjectException
// at android.database.BulkCursorToCursorAdaptor.getExtras(BulkCursorToCursorAdaptor.java:173)
// at android.database.CursorWrapper.getExtras(CursorWrapper.java:94)
if (DEBUG) {
e.printStackTrace();
}
}
if (bundle != null) {
v = bundle.get(KEY);
bundle.clear();
}
cursor.close();
}
}
if (DEBUG) {
Log.d(TAG, "getValue.mName = " + mName + ", pathSegment = " + pathSegment + ", key = " + key + ", defValue = " + defValue);
}
return v == null ? defValue : v;
}
private String makeAction(String name) {
return String.format("%1$s_%2$s", MultiprocessSharedPreferences.class.getName(), name);
}
@Override
public boolean onCreate() {
if (checkInitAuthority(getContext())) {
mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
mUriMatcher.addURI(AUTHORITY, PATH_WILDCARD + PATH_GET_ALL, GET_ALL);
mUriMatcher.addURI(AUTHORITY, PATH_WILDCARD + PATH_GET_STRING, GET_STRING);
mUriMatcher.addURI(AUTHORITY, PATH_WILDCARD + PATH_GET_INT, GET_INT);
mUriMatcher.addURI(AUTHORITY, PATH_WILDCARD + PATH_GET_LONG, GET_LONG);
mUriMatcher.addURI(AUTHORITY, PATH_WILDCARD + PATH_GET_FLOAT, GET_FLOAT);
mUriMatcher.addURI(AUTHORITY, PATH_WILDCARD + PATH_GET_BOOLEAN, GET_BOOLEAN);
mUriMatcher.addURI(AUTHORITY, PATH_WILDCARD + PATH_CONTAINS, CONTAINS);
mUriMatcher.addURI(AUTHORITY, PATH_WILDCARD + PATH_APPLY, APPLY);
mUriMatcher.addURI(AUTHORITY, PATH_WILDCARD + PATH_COMMIT, COMMIT);
mUriMatcher.addURI(AUTHORITY, PATH_WILDCARD + PATH_REGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER, REGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER);
mUriMatcher.addURI(AUTHORITY, PATH_WILDCARD + PATH_UNREGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER, UNREGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER);
mUriMatcher.addURI(AUTHORITY, PATH_WILDCARD + PATH_GET_STRING_SET, GET_STRING_SET);
return true;
} else {
return false;
}
}
@Override
public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
String name = uri.getPathSegments().get(0);
int mode = Integer.parseInt(selectionArgs[0]);
String key = selectionArgs[1];
String defValue = selectionArgs[2];
Bundle bundle = new Bundle();
switch (mUriMatcher.match(uri)) {
case GET_ALL:
bundle.putSerializable(KEY, (HashMap) getSystemSharedPreferences(name, mode).getAll());
break;
case GET_STRING:
bundle.putString(KEY, getSystemSharedPreferences(name, mode).getString(key, defValue));
break;
case GET_INT:
bundle.putInt(KEY, getSystemSharedPreferences(name, mode).getInt(key, Integer.parseInt(defValue)));
break;
case GET_LONG:
bundle.putLong(KEY, getSystemSharedPreferences(name, mode).getLong(key, Long.parseLong(defValue)));
break;
case GET_FLOAT:
bundle.putFloat(KEY, getSystemSharedPreferences(name, mode).getFloat(key, Float.parseFloat(defValue)));
break;
case GET_BOOLEAN:
bundle.putBoolean(KEY, getSystemSharedPreferences(name, mode).getBoolean(key, Boolean.parseBoolean(defValue)));
break;
case CONTAINS:
bundle.putBoolean(KEY, getSystemSharedPreferences(name, mode).contains(key));
break;
case REGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER: {
checkInitListenersCount();
Integer countInteger = mListenersCount.get(name);
int count = (countInteger == null ? 0 : countInteger) + 1;
mListenersCount.put(name, count);
countInteger = mListenersCount.get(name);
bundle.putBoolean(KEY, count == (countInteger == null ? 0 : countInteger));
}
break;
case UNREGISTER_ON_SHARED_PREFERENCE_CHANGE_LISTENER: {
checkInitListenersCount();
Integer countInteger = mListenersCount.get(name);
int count = (countInteger == null ? 0 : countInteger) - 1;
if (count <= 0) {
mListenersCount.remove(name);
bundle.putBoolean(KEY, !mListenersCount.containsKey(name));
} else {
mListenersCount.put(name, count);
countInteger = mListenersCount.get(name);
bundle.putBoolean(KEY, count == (countInteger == null ? 0 : countInteger));
}
}
break;
case GET_STRING_SET: {
if (Build.VERSION.SDK_INT >= 11) { // Android 3.0
Set set = null;
if (projection != null) {
set = new HashSet(Arrays.asList(projection));
}
bundle.putSerializable(KEY, (HashSet) ReflectionUtil.sharedPreferencesGetStringSet(getSystemSharedPreferences(name, mode), key, set));
}
}
default:
if (DEBUG) {
throw new IllegalArgumentException("At query, This is Unknown Uri:" + uri + ", AUTHORITY = " + AUTHORITY);
}
}
return new BundleCursor(bundle);
}
@SuppressWarnings("unchecked")
@Override
public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int result = 0;
String name = uri.getPathSegments().get(0);
int mode = Integer.parseInt(selectionArgs[0]);
SharedPreferences preferences = getSystemSharedPreferences(name, mode);
int match = mUriMatcher.match(uri);
switch (match) {
case APPLY:
case COMMIT:
boolean hasListeners = mListenersCount != null && mListenersCount.get(name) != null && mListenersCount.get(name) > 0;
ArrayList keysModified = null;
Map map = null;
if (hasListeners) {
keysModified = new ArrayList();
map = (Map) preferences.getAll();
}
Editor editor = preferences.edit();
boolean clear = Boolean.parseBoolean(selectionArgs[1]);
if (clear) {
if (hasListeners && !map.isEmpty()) {
for (Map.Entry entry : map.entrySet()) {
keysModified.add(entry.getKey());
}
}
editor.clear();
}
for (Map.Entry entry : values.valueSet()) {
String k = entry.getKey();
Object v = entry.getValue();
// Android 5.L_preview : "this" is the magic value for a removal mutation. In addition,
// setting a value to "null" for a given key is specified to be
// equivalent to calling remove on that key.
if (v instanceof EditorImpl || v == null) {
editor.remove(k);
if (hasListeners && map.containsKey(k)) {
keysModified.add(k);
}
} else {
if (hasListeners && (!map.containsKey(k) || (map.containsKey(k) && !v.equals(map.get(k))))) {
keysModified.add(k);
}
}
if (v instanceof String) {
editor.putString(k, (String) v);
} else if (v instanceof Set) {
ReflectionUtil.editorPutStringSet(editor, k, (Set) v); // Android 3.0
} else if (v instanceof Integer) {
editor.putInt(k, (Integer) v);
} else if (v instanceof Long) {
editor.putLong(k, (Long) v);
} else if (v instanceof Float) {
editor.putFloat(k, (Float) v);
} else if (v instanceof Boolean) {
editor.putBoolean(k, (Boolean) v);
}
}
if (hasListeners && keysModified.isEmpty()) {
result = 1;
} else {
switch (match) {
case APPLY:
ReflectionUtil.editorApply(editor); // Android 2.3
result = 1;
// Okay to notify the listeners before it's hit disk
// because the listeners should always get the same
// SharedPreferences instance back, which has the
// changes reflected in memory.
notifyListeners(name, keysModified);
break;
case COMMIT:
if (editor.commit()) {
result = 1;
notifyListeners(name, keysModified);
}
break;
default:
break;
}
}
values.clear();
break;
default:
if (DEBUG) {
throw new IllegalArgumentException("At update, This is Unknown Uri:" + uri + ", AUTHORITY = " + AUTHORITY);
}
}
return result;
}
@Override
public String getType(@NonNull Uri uri) {
throw new UnsupportedOperationException("No external call");
}
@Override
public Uri insert(@NonNull Uri uri, ContentValues values) {
throw new UnsupportedOperationException("No external insert");
}
@Override
public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException("No external delete");
}
private SharedPreferences getSystemSharedPreferences(String name, int mode) {
return getContext().getSharedPreferences(name, mode);
}
private void checkInitListenersCount() {
if (mListenersCount == null) {
mListenersCount = new HashMap();
}
}
private void notifyListeners(String name, ArrayList keysModified) {
if (keysModified != null && !keysModified.isEmpty()) {
Intent intent = new Intent();
intent.setAction(makeAction(name));
intent.setPackage(getContext().getPackageName());
intent.putExtra(KEY_NAME, name);
intent.putExtra(KEY, keysModified);
getContext().sendBroadcast(intent);
}
}
private static final class BundleCursor extends MatrixCursor {
private Bundle mBundle;
public BundleCursor(Bundle extras) {
super(new String[] {}, 0);
mBundle = extras;
}
@Override
public Bundle getExtras() {
return mBundle;
}
@Override
public Bundle respond(Bundle extras) {
mBundle = extras;
return mBundle;
}
}
}
使用时仅需要将此文件复制到项目中即可。
接下来看使用方法。
第一步,注册provider
name:MultiprocessSharedPreferences文件路径
authorities:provider标志,provider数据存储路径,一般以包名开头+.provider
exported:必须为true,否则不能跨进程通信
第二步:
在onCreate中设置
MultiprocessSharedPreferences.setAuthority("com.example.xposedmodule.provider");
这一步本来是不需要,因为可以直接读取到AndroidMainfest里的 android:authorities,但是我在Android N上读取到这个值是空的,所以手动设置一下。
如果有需求可以自行更改MultiprocessSharedPreferences,实现直接读取AndroidMainfest里的数据。
第三步:存储数据和读取数据
SharedPreferences sharedPreferences = MultiprocessSharedPreferences.getSharedPreferences(context, "test", Context.MODE_PRIVATE);
sharedPreferences.edit().putString("hello","world").commit();
String hello = sharedPreferences.getString("hello", "");
Log.e("hello",hello);
和SharePreference一样
07-02 16:53:33.886 2770-2770/com.example.xposedmodule E/hello: world
读取方法2:
也可以使用原生的ContentProvider方法
ContentResolver provider = getContentResolver();
Cursor cursorTid;
try {
Uri uri = Uri.parse("content://com.example.xposedmodule.provider/test/getString");//uri拼接,前面是provider地址,第二个是sharepreference的Name,由于我存入的数据是String,原作者将getString与前面的uri拼接
String[] selectionArgs = {"0","hello",""};//第一个参数是否是安全模式,一般为0。第二个参数读取的key,第三个参数defaultValue
cursorTid = provider.query(uri, null, null,selectionArgs, null);
Bundle extras = cursorTid.getExtras();
String hello1 = (String) extras.get("value");
Log.e("asdasd",hello1);
} catch (Exception e) {
Log.e("TAG",e.getMessage()+"\t"+e.toString());
e.printStackTrace();
}
得到结果
07-02 16:53:33.886 2770-2770/com.example.xposedmodule E/asdasd: world
一样的结果,只是为了方便自己测试用的
如果其他项目没有集成MultiprocessSharedPreferences,用第二个方法也是可以获取到数据的。
既然其他app能获取到数据了,Xposed的Hook就简单了
final Context[] context = new Context[1];
XposedHelpers.findAndHookMethod(Application.class, "attach", Context.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
context[0] = (Context) param.args[0];
}
});
首先先拿到Context,我始终觉得这种获取Context的方式是有问题的,应该有更简单的方式获取。但是现在初学Xposed,网上获取Context的都是这种方法,先mark一下。
然后Hook掉需要获取值的地方,我这里是getText()
findAndHookMethod("com.example.hoyn.example.MainActivity", lpparam.classLoader, "getText", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
String data ;
MultiprocessSharedPreferences.setAuthority("com.example.xposedmodule.provider");
SharedPreferences test = MultiprocessSharedPreferences.getSharedPreferences(context[0], "test", MODE_PRIVATE);
data = test.getString("hello", "");
XposedBridge.log("获取到了"+data);
if(TextUtils.isEmpty(data)){
data = "ccccc";
}
param.setResult(data);
XposedBridge.log(param.getResult().toString());
}
});
}
运行结果
如图所示,hook后的app已经获取到我们需要的值了。
假如我们在Xposed APP里的按钮事件改为按一下button就修改一次值
String text = ev.getText().toString();
SharedPreferences sharedPreferences = MultiprocessSharedPreferences.getSharedPreferences(context, "test", Context.MODE_PRIVATE);
sharedPreferences.edit().putString("hello",text).commit();
Toast.makeText(MainActivity.this, text, Toast.LENGTH_SHORT).show();
那么hook后的app就可以动态获取需要的值了。
Xposed APP:
Hook APP:
由于Xposed中,如果需要hook的app没有获取到文件读取权限,是没办法对sd卡文件进行操作从而动态获取数据的。所以对文件的读取本人卡了很久,比如在Xposed中主动给APP获取权限,assets可以读,不能写,利用反射去修改文件内容,最后也没有成功。
最后还有一种可以动态获取数据的方式就是通过网络,简单点的就是在本地搭建一个小型服务器,利用Xposed在需要Hook的App中读取这个服务器上的文件。不过感觉有点舍本逐末,没有去研究。但是在Github上有已经实现的开源项目。
该例子github:
https://github.com/adzcsx2/Xposed-ContentProvider-Example
参考:
https://github.com/seven456/MultiprocessSharedPreferences
https://www.cnblogs.com/jason207489550/p/6743409.html