在Android應用程序中很多地方會使用SharedPreferences
來保存配置文件,這樣你就會出現很多下面的寫法:java
// 程序配置文件 public class MyAppConfig { SharedPreferences mSharedPreferences; public MyAppConfig(Context context) { mSharedPreferences = context.getSharedPreferences("AppConfig", Context.MODE_PRIVATE); } /** * 設置配置1 */ public void setConfig1(String value) { mSharedPreferences.edit().putString("config1", value).apply(); } /** * 獲取配置1 */ public String getConfig1() { return mSharedPreferences.getString("config1", ""); } // ... 省略其餘更多的配置項 } 複製代碼
這樣一來咱們就須要寫不少跟SharedPreferences
打交道的代碼,其實咱們關注的只有SET方法
和GET方法
兩個方法罷了。那有沒有辦法,我只須要定義好配置的接口就直接獲取到值呢?這就是本篇文章要討論的啦。其實咱們理想效果應該是這樣的:git
// 定義一個配置接口 public interface ITestConfig { // 定義獲取配置的Get、Set方法 void setName(String name); String getName(); } // 實際操做中的調用 private void runTestMethod() { // 能實現這樣的效果就完美了 ITestConfig config = XXX.create(context, ITestConfig.class); Log.i("rae", "結果:" + config.getName()); } 複製代碼
探索github
不妨思考一下,這種需求好像咱們在哪一個地方見過呢?沒錯,就是咱們常常用到的Retrofit
框架中就有用到,我能對public <T> T create(final Class<T> service){}
這個方法應該很熟悉了,咱們好奇它爲何傳入一個接口它就能構造出接口的實例呢?讓咱們走進源碼看看:json
public <T> T create(final Class<T> service) { Utils.validateServiceInterface(service); if (validateEagerly) { eagerlyValidateMethods(service); } // 關鍵的地方來了:Proxy,動態代理! return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() { private final Platform platform = Platform.get(); private final Object[] emptyArgs = new Object[0]; @Override public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable { // If the method is a method from Object then defer to normal invocation. if (method.getDeclaringClass() == Object.class) { return method.invoke(this, args); } if (platform.isDefaultMethod(method)) { return platform.invokeDefaultMethod(method, service, proxy, args); } return loadServiceMethod(method).invoke(args != null ? args : emptyArgs); } }); } 複製代碼
就是這個神奇的動態代理。具體什麼是動態代理這裏就不作解析了,有興趣的同窗能夠看看這裏介紹的Java動態代理。markdown
動手前操做app
知道大概的原理以後,咱們就開始動手操做了。理一理我們的思路,定義一個動態代理實現如下功能:框架
SharedPreferences
保存配置項clear()
方法清除配置remove(String key)
方法移除配置項json
字符串來實現)動態代理的類ide
/** * 應用程序配置註解 * Created by rae on 2020-02-20. * Copyright (c) https://github.com/raedev All rights reserved. */ @Documented @Retention(RUNTIME) public @interface Config { /** * 程序配置名稱 */ String value(); } /** * 應用程序代理類 * Created by rae on 2020-02-20. * Copyright (c) https://github.com/raedev All rights reserved. */ public final class AppConfigHandler { private AppConfigHandler() { } /** * 建立程序配置代理類 * * @param cls 類的Class */ @SuppressWarnings("unchecked") public static <T> T create(Context context, Class<T> cls) { Config config = cls.getAnnotation(Config.class); if (config == null) { throw new RuntimeException("請在配置類標註@Config()"); } if (!cls.isInterface()) { throw new RuntimeException("配置類必須是接口"); } String configName = config.value(); if (TextUtils.isEmpty(configName)) { configName = cls.getName(); } SharedPreferences preferences = context.getSharedPreferences(configName, Context.MODE_PRIVATE); // 建立動態代理 return (T) Proxy.newProxyInstance(cls.getClassLoader(), new Class<?>[]{cls}, new ConfigProxy(preferences)); } private static class ConfigProxy implements InvocationHandler { private final SharedPreferences mPreference; private final Gson mGson = new Gson(); private ConfigProxy(SharedPreferences preference) { this.mPreference = preference; } @Override public Object invoke(Object proxy, Method method, Object[] args) { String methodName = method.getName().toUpperCase(); // 清除配置文件 if (methodName.equalsIgnoreCase("clear")) { mPreference.edit().clear().apply(); } // 移除配置項處理 else if (methodName.equalsIgnoreCase("remove") && args != null) { String key = args[0].toString().toUpperCase(); mPreference.edit().remove(key).apply(); } // Get方法處理 else if (methodName.startsWith("SET")) { setValue(methodName.replace("SET", ""), method, args); } // Set方法處理 else if (methodName.startsWith("GET")) { return getValue(methodName.replace("GET", ""), method, args); } // Is方法處理,好比:isLogin()、isVip(),這類的布爾值 else if (methodName.startsWith("IS")) { boolean value = mPreference.getBoolean(methodName.replace("IS", ""), false); return value; } return null; } /** * 設置配置值 */ private void setValue(String name, Method method, Object[] args) { if (args.length != 1) throw new IllegalArgumentException("set方法的方法參數只容許一個"); Class<?>[] parameterTypes = method.getParameterTypes(); Class<?> parameterType = parameterTypes[0]; Object arg = args[0]; SharedPreferences.Editor editor = mPreference.edit(); if (parameterType == String.class) { editor.putString(name, (String) arg); } else if (parameterType == int.class) { editor.putInt(name, (int) arg); } else if (parameterType == boolean.class) { editor.putBoolean(name, (boolean) arg); } else if (parameterType == float.class) { editor.putFloat(name, (float) arg); } else if (parameterType == long.class) { editor.putLong(name, (long) arg); } else { // 其餘值默認使用Json字符串 String json = mGson.toJson(arg); editor.putString(name, json); } editor.apply(); } /** * 獲取配置值 */ private Object getValue(String name, Method method, Object[] args) { Class<?> type = method.getReturnType(); Object defaultValue = args == null ? null : args[0]; if (type == String.class) { return mPreference.getString(name, (String) defaultValue); } else if (type == int.class) { return mPreference.getInt(name, defaultValue == null ? 0 : (int) defaultValue); } else if (type == boolean.class) { return mPreference.getBoolean(name, defaultValue != null && (boolean) defaultValue); } else if (type == float.class) { return mPreference.getFloat(name, defaultValue == null ? 0 : (float) defaultValue); } else if (type == long.class) { return mPreference.getLong(name, defaultValue == null ? 0 : (long) defaultValue); } else { // 其餘值默認使用Json字符串 String json = mPreference.getString(name, null); return mGson.fromJson(json, type); } } } } 複製代碼
實踐操做oop
最終咱們定義一個接口,就能夠輕鬆實現讀取配置文件啦!this
/** * 程序配置 * Created by rae on 2020/2/22. * Copyright (c) https://github.com/raedev All rights reserved. */ @Config("YourAppConfig") public interface IAppConfig { void setUserName(String name); String getUserName(String defaultValue); void setVip(boolean isVip); boolean isVip(); void setVersion(int version); int getVersion(); void clear(); void remove(String key); } 複製代碼
方法調用
private void runTestMethod() { IAppConfig config = AppConfigHandler.create(getApplicationContext(), IAppConfig.class); config.clear(); config.setUserName("RAE"); config.remove("UserName"); Log.i("Rae", "username is " + config.getUserName("DefaultValue")); config.setVip(true); Log.i("Rae", "is vip: " + config.isVip()); config.setVersion(10); Log.i("Rae", "version is " + config.getVersion()); } 複製代碼
輸出結果
I/Rae: username is DefaultValue I/Rae: is vip: true I/Rae: version is 10 複製代碼