Xposed 利用ContentProvider實現跨進程數據讀取
由於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讀寫;<br>
* 1、ContentProvider天生支持多進程訪問;<br>
* 2、使用內部私有BroadcastReceiver實現多進程OnSharedPreferenceChangeListener監聽;<br>
*
* 使用方法:AndroidManifest.xml中添加provider申明:<br>
* <pre>
* <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需要校驗簽名,否則無法安裝;--!/><br>
* </pre>
*
* ContentProvider方式實現要注意:<br>
* 1、當ContentProvider所在進程android.os.Process.killProcess(pid)時,會導致整個應用程序完全意外退出或者ContentProvider所在進程重啓;<br>
* 重啓報錯信息:Acquiring provider <processName> for user 0: existing object's process dead;<br>
* 2、如果設備處在“安全模式”下,只有系統自帶的ContentProvider才能被正常解析使用,因此put值時默認返回false,get值時默認返回null;<br>
*
* 其他方式實現SharedPreferences的問題:<br>
* 使用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<OnSharedPreferenceChangeListener, Object> 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<String, Integer> mListenersCount;
private static class ReflectionUtil {
public static ContentValues contentValuesNewInstance(HashMap<String, Object> values) {
try {
Constructor<ContentValues> 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<String> 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<String> sharedPreferencesGetStringSet(SharedPreferences sharedPreferences, String key, Set<String> values) {
try {
Method method = sharedPreferences.getClass().getDeclaredMethod("getStringSet", new Class[] { String.class, Set.class }); // Android 3.0
return (Set<String>) 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<String, ?> getAll() {
Map<String, ?> value = (Map<String, ?>) getValue(PATH_GET_ALL, null, null);
return value == null ? new HashMap<String, Object>() : 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<String> getStringSet(String key, Set<String> defValues) {
return (Set<String>) 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<OnSharedPreferenceChangeListener, Object>();
}
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<String> keysModified = (List<String>) intent.getSerializableExtra(KEY);
if (mName.equals(name) && keysModified != null) {
Set<OnSharedPreferenceChangeListener> listeners = new HashSet<OnSharedPreferenceChangeListener>(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<String, Object> mModified = new HashMap<String, Object>();
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<String> values) {
synchronized (this) {
mModified.put(key, (values == null) ? null : new HashSet<String>(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<String, Object>) 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<String> set = (Set<String>) 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<String, ?>) 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<String> set = null;
if (projection != null) {
set = new HashSet<String>(Arrays.asList(projection));
}
bundle.putSerializable(KEY, (HashSet<String>) 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<String> keysModified = null;
Map<String, Object> map = null;
if (hasListeners) {
keysModified = new ArrayList<String>();
map = (Map<String, Object>) preferences.getAll();
}
Editor editor = preferences.edit();
boolean clear = Boolean.parseBoolean(selectionArgs[1]);
if (clear) {
if (hasListeners && !map.isEmpty()) {
for (Map.Entry<String, Object> entry : map.entrySet()) {
keysModified.add(entry.getKey());
}
}
editor.clear();
}
for (Map.Entry<String, Object> 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<String>) 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<String, Integer>();
}
}
private void notifyListeners(String name, ArrayList<String> 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
<provider
android:name="com.example.xposedmodule.MultiprocessSharedPreferences"
android:authorities="com.example.xposedmodule.provider"
android:exported="true" />
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