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

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读写;
* 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());
                }
            });
        }

运行结果
Xposed 利用ContentProvider实现跨进程数据读取_第1张图片
如图所示,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:
Xposed 利用ContentProvider实现跨进程数据读取_第2张图片
Hook APP:
Xposed 利用ContentProvider实现跨进程数据读取_第3张图片
由于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

你可能感兴趣的:(Android,Xposed,Hook)