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讀寫;<br>
 * 1、ContentProvider天生支持多進程訪問;<br>
 * 2、使用內部私有BroadcastReceiver實現多進程OnSharedPreferenceChangeListener監聽;<br>
 * 
 * 使用方法:AndroidManifest.xml中添加provider申明:<br>
 * <pre>
 * &lt;provider android:name="com.android.zgj.utils.MultiprocessSharedPreferences"
 * android:authorities="com.android.zgj.MultiprocessSharedPreferences"
 * android:process="com.android.zgj.MultiprocessSharedPreferences"
 * android:exported="false" /&gt;
 * &lt;!-- authorities屬性裏面最好使用包名做前綴,apk在安裝時authorities同名的provider需要校驗簽名,否則無法安裝;--!/&gt;<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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章