Xposed 利用ContentProvider实现跨进程数据读取

    技术2022-07-20  72

    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

    Processed: 0.009, SQL: 9