Repository: sevenshal/oksharedprefs Branch: master Commit: 6ddebe3612eb Files: 42 Total size: 88.3 KB Directory structure: gitextract_7_weegng/ ├── .gitignore ├── README.md ├── annotations/ │ ├── .gitignore │ ├── build.gradle │ └── src/ │ └── main/ │ └── java/ │ └── cn/ │ └── framework/ │ └── oksharedpref/ │ └── annotations/ │ ├── DefaultValue.java │ ├── PreferenceType.java │ ├── SharedPreference.java │ └── Type.java ├── api/ │ ├── .gitignore │ ├── build.gradle │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ └── java/ │ └── cn/ │ └── framework/ │ └── oksharedpref/ │ ├── MPSPUtils.java │ └── OkSharedPrefContentProvider.java ├── app/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── cn/ │ │ └── framework/ │ │ └── ExampleInstrumentedTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── cn/ │ │ │ └── framework/ │ │ │ └── oksharedpref/ │ │ │ └── app/ │ │ │ ├── IMsg.java │ │ │ ├── MainActivity.java │ │ │ └── SeActivity.java │ │ └── res/ │ │ ├── layout/ │ │ │ ├── activity_main.xml │ │ │ └── content_main.xml │ │ └── values/ │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test/ │ └── java/ │ └── cn/ │ └── framework/ │ └── ExampleUnitTest.java ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── processor/ │ ├── .gitignore │ ├── build.gradle │ └── src/ │ └── main/ │ ├── java/ │ │ └── cn/ │ │ └── framework/ │ │ └── mpsharedpreferences/ │ │ └── annotations/ │ │ └── processor/ │ │ ├── Modifier.java │ │ ├── Preference.java │ │ ├── PreferenceHolder.java │ │ └── SharedPreferencesAnnotationProcessor.java │ └── resources/ │ └── META-INF/ │ └── services/ │ └── javax.annotation.processing.Processor └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.iml .gradle /local.properties /.idea .DS_Store /build /captures .externalNativeBuild ================================================ FILE: README.md ================================================ # oksharedpref ``` 通过注解生成SharedPreferences实现的工具。解决安卓SharedPreferences多进程数据访问不一致的问题。 ``` ## 简介 1. 让你告别手写包装代码管理SharedPreferences,通过注解的方式定义SharedPreferences包装类,使你可以方便的通过get/set方法操作SharedPreferences。 2. 现在看来,安卓官方基本放弃了解决SharedPreferences跨进程访问不一致这一问题了,跨进程访问数据官方更加推荐ContentProvider。 3. OkSharedPrefs将SharedPreferences和ContentProvider结合起来,让你使用SharedPreferences更加方便,并且通过ContentProvider交换数据,解决了跨进程数据访问不一致的问题。 4. 底层仍然使用系统SharedPreferences实现,所以你的应用之前没有使用oksharedprefs,你可以很方便的移植,在新版本中加入这个库,已安装用户的原有数据不会有任何影响。 ## 安装 ```groovy allprojects { repositories { maven { url 'https://jitpack.io' } } } dependencies { compile 'com.github.sevenshal.oksharedprefs:api:1.0.1' annotationProcessor 'com.github.sevenshal.oksharedprefs:processor:1.0.1' } ``` ## 用法 定一个interface类并且如下所示添加注解: ```java @SharedPreference(value = "Msg", implSharedPreference = false, preferenceName = "msg", multiProcess = false) public interface IMsg { @DefaultValue(value = "null", createDefaultGetter = false) String USERID = "userId"; @Type(PreferenceType.STRING_SET) @DefaultValue(value = "null", createDefaultGetter = false) String TOKEN = "token"; @Type(PreferenceType.LONG) @DefaultValue(value = "0", createDefaultGetter = false) String DEVICE_ID = "deviceId"; @Type(PreferenceType.BOOLEAN) @DefaultValue(value = "false", createDefaultGetter = false) String LOGIN = "hasAuth"; String NICK_NAME = "nickName"; } ``` 然后就可以直接使用生成的包装类了: ``` java MsgPrefs msgPrefs = MsgPrefs.defaultInstance(this); long deviceId = msgPrefs.getDeviceId(); String userId = msgPrefs.getUserid(); boolean login = msgPrefs.isLogin(); String nickName= msgPrefs.getNickName("未命名"); msgPrefs.prefs().registerOnSharedPreferenceChangeListener(this); Set set = new HashSet(); set.add("a"); set.add("b"); msgPrefs.edit().setDeviceId(111).setLogin(true).setUserid("userid").setToken(set).apply(); ``` ## 说明 生成的类的名称通过 @SharedPreference 的 value属性定义。生成的类名称为 value+Prefs,比如 @SharedPreference(value = "Msg") 将生成 MsgPrefs 类。 如果你不希望以Prefs结尾,可以通过preferencesSuffix属性修改。 @SharedPreference(value = "Msg", preferencesSuffix = "Preferences") 将生成 MsgPreferences类。 OkSharedPrefs生成的包装类默认实现了SharedPreferences接口, 这在key值通过变量方式存取时很方便,如果不希望生成的类实现SharedPreferences接口, 可以通过将 implSharedPreference 设置为 false,关闭该功能。此种情况下,可以通过生成的类的prefs()获取SharedPreferences接口实例。 默认的SharedPreferences文件名为default_preferences,你可以通过 preferenceName 修改。 **默认生成的包装类不支持跨进程,但是可以通过将 multiProcess 设置为true打开该功能,默认关闭该功能是出于性能考虑,减少生成不必要的代码。** 生成的包装类是单例模式的,因为安卓底层SharedPreferences也是全局单实例的,所以不会单例模式并不会带来性能问题。 考虑到在插件化系统中Context可能会做隔离的使用场景,你仍然可以通过 new MsgPrefs(context)的方式来使用。 甚至可以new MsgPrefs(context,name)来通过相同结构的包装类管理不同的属性文件,这对那种多用户数据管理的app很有用。 所有属性默认类型是String类型,通过为interface的属性添加 @Type(PreferenceType.LONG) 来修改类型。支持完整的SharedPreferences数据类型。 通过 @DefaultValue(value = "null", createDefaultGetter = false) 可以设置默认值,以及是否生成默认值取值方法。 createDefaultGetter的取值意义在于你是希望通过 msgPrefs.getNickName("自定义默认值") 还是 msgPrefs.getNickName() 获取数据。如果你在编码期间不确定默认值是什么,那需要将createDefaultGetter设为true。 ================================================ FILE: annotations/.gitignore ================================================ /build ================================================ FILE: annotations/build.gradle ================================================ /* * The MIT License (MIT) * * Copyright (c) 2015 David Medenjak * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ /* * The MIT License (MIT) * * Copyright (c) 2015 David Medenjak * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ buildscript { repositories { mavenLocal() // mavenCentral() } } apply plugin: 'java' apply plugin: 'maven' //apply plugin: 'signing' compileJava { sourceCompatibility = 1.6 targetCompatibility = 1.7 } archivesBaseName = "annotations" dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.google.android:android:4.1.1.4' } task javadocJar(type: Jar) { classifier = 'javadoc' from javadoc } task sourcesJar(type: Jar) { classifier = 'sources' from sourceSets.main.allSource } artifacts { archives javadocJar, sourcesJar } //signing { // sign configurations.archives //} def ossrhUsername = System.properties['ossrhUsername'] def ossrhPassword = System.properties['ossrhPassword'] uploadArchives { repositories { mavenDeployer { repository(url: mavenLocal().getUrl()) // beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } // // repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") { // authentication(userName: ossrhUsername, password: ossrhPassword) // } // // snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") { // authentication(userName: ossrhUsername, password: ossrhPassword) // } // // pom.project { // name 'SharedPreferences Annotations' // packaging 'jar' // // optionally artifactId can be defined here // description 'Annotations to generate Source Code ' + // 'used by the Annotation Processor in cn.framework.oksharedpref:processor' // url 'https://github.com/sevenshal/oksharedpref-api' // // scm { // connection 'scm:git:ssh://git@github.com/sevenshal/oksharedpref-api.git' // developerConnection 'scm:git:ssh://git@github.com/sevenshal/oksharedpref-api.git' // url 'https://github.com/sevenshal/oksharedpref-api' // } // // licenses { // license { // name 'MIT License' // url 'http://www.opensource.org/licenses/mit-license.php' // } // } // // developers { // developer { // id 'sevenshal' // name 'David Medenjak' // email 'davidmedenjak@gmail.com' // url 'https://github.com/sevenshal ' // roles { // role 'owner' // role 'developer' // } // timezone '+1' // } // } // } } } } ================================================ FILE: annotations/src/main/java/cn/framework/oksharedpref/annotations/DefaultValue.java ================================================ /* * The MIT License (MIT) * * Copyright (c) 2015 David Medenjak * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package cn.framework.oksharedpref.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Used to set the default value for this preference. THis value is supplied as String, because * the code is generated and the value simply inserted as is. Please be sure to support a valid * value. *

By setting {@link #createDefaultGetter} to false you * can suppress the additional getter of the form get(Type defaultValue).

* * @author sevenshal * @version 1.0 */ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.FIELD) public @interface DefaultValue { /** * Supplies the default value to use for a preference in String format. e.g. "3.0f", "hi", ... * @return the value to use as String. */ String value(); /** * Used to specify the creation of the default getter of the form get(Type defaultValue). * @return default true, to create the default getter nonetheless. */ boolean createDefaultGetter() default true; } ================================================ FILE: annotations/src/main/java/cn/framework/oksharedpref/annotations/PreferenceType.java ================================================ /* * The MIT License (MIT) * * Copyright (c) 2015 David Medenjak * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package cn.framework.oksharedpref.annotations; /** * The usable types of the properties in the preferences. Used to set the return type * and parameters. *

* {@link #BOOLEAN}

* {@link #FLOAT}

* {@link #INTEGER}

* {@link #LONG}

* {@link #STRING}

* {@link #STRING_SET}

* * @author sevenshal * @version 1.0 */ public enum PreferenceType { /** * Standard java boolean. * * @see Boolean */ BOOLEAN("boolean", "Boolean"), /** * Standard java floating point number. * * @see Float */ FLOAT("float", "Float"), /** * Standard java number. * * @see Integer */ INTEGER("int", "Int"), /** * Standard java long. * * @see Long */ LONG("long", "Long"), /** * Standard java String. * * @see String */ STRING("String", "String"), /** * A set of Strings. * * @see String * @see java.util.Set */ STRING_SET("Set", "StringSet"); private String returnType; private String fullName; PreferenceType(String returnType, String fullName) { this.returnType = returnType; this.fullName = fullName; } /** * Method to supply the spelling for the type as a return type. * * @return the type as String usable for method declarations. */ public String getReturnType() { return returnType; } /** * Method to supply the type as a String used for the getter methods. e.g. getString() * * @return the type as String, CamelCase. */ public String getFullName() { return fullName; } } ================================================ FILE: annotations/src/main/java/cn/framework/oksharedpref/annotations/SharedPreference.java ================================================ /* * The MIT License (MIT) * * Copyright (c) 2015 David Medenjak * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package cn.framework.oksharedpref.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** *

Annotation to generate a default wrapper class for the annotated interface. * Supply a String value * to change the name of the genereated class to valuePrefs and valueEditor. *

*

By not specifying a value DefaultPrefs and DefaultEditor will be generated.

*

*

Additionally you may change the class name suffixes by setting {@link #preferencesSuffix()} * or {@link #editorSuffix()}.

*/ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) public @interface SharedPreference { /** * the prefix for the generated preferences and editor. * * @return the prefix, the name of the annotated interface by default. */ String value() default ""; /** * the suffix for the preferences. * * @return the suffix, "Prefs" by default. */ String preferencesSuffix() default "Prefs"; /** * preferences name which used by getSharedPreferences(preferenceName). * @return the name, "default_preferences" by default. */ String preferenceName() default "default_preferences"; /** * Set the default type for all the contained properties in the interface if not specified. * To use a different type for a single property * annotate the properties with {@link Type}. * @return the default type, PreferenceType.STRING by default. */ PreferenceType defaultPreferenceType() default PreferenceType.STRING; /** * the suffix for the editor. * * @return the suffix, "Editor" by default. */ String editorSuffix() default "Editor"; /** * implement interface of SharedPreference if true * * @return true */ boolean implSharedPreference() default true; /** * the sharedpreference can be used in multiprocess safely if true. * @return */ boolean multiProcess() default false; } ================================================ FILE: annotations/src/main/java/cn/framework/oksharedpref/annotations/Type.java ================================================ /* * The MIT License (MIT) * * Copyright (c) 2015 David Medenjak * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package cn.framework.oksharedpref.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** *

The preferences type of this single property. * The getter and setter will be genereated with the supplied type.

* Supported types are of {@link PreferenceType} * * @author sevenshal * @version 1.0 */ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.FIELD) public @interface Type { /** * the type of the preference element. * * @return the type */ PreferenceType value(); /** * if the type is a boolean, you can set the getter prefix here. * * @return the prefix for the getter, "is" by default. */ String booleanPrefix() default "is"; } ================================================ FILE: api/.gitignore ================================================ /build ================================================ FILE: api/build.gradle ================================================ /* * The MIT License (MIT) * * Copyright (c) 2015 David Medenjak * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ /* * The MIT License (MIT) * * Copyright (c) 2015 David Medenjak * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ buildscript { repositories { mavenLocal() // mavenCentral() } } apply plugin: 'com.android.library' apply plugin: 'com.github.dcendents.android-maven' apply plugin: 'maven' android { compileSdkVersion 26 buildToolsVersion '26.0.2' defaultConfig { minSdkVersion 11 targetSdkVersion 26 versionCode 1 versionName "1.0.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } archivesBaseName = "api" dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile project(':annotations') } //signing { // sign configurations.archives //} def ossrhUsername = System.properties['ossrhUsername'] def ossrhPassword = System.properties['ossrhPassword'] uploadArchives { repositories { mavenDeployer { repository(url: mavenLocal().getUrl()) } } } ================================================ FILE: api/src/main/AndroidManifest.xml ================================================ ================================================ FILE: api/src/main/java/cn/framework/oksharedpref/MPSPUtils.java ================================================ package cn.framework.oksharedpref; import android.content.ComponentName; import android.content.Context; import android.content.SharedPreferences; import android.content.pm.ProviderInfo; import android.os.Process; import android.text.TextUtils; import java.io.BufferedReader; import java.io.FileReader; import java.util.Locale; /** * Created by sevenshal on 2017/10/26. */ public class MPSPUtils { private static Boolean isProviderProcess; private static boolean isProviderProcess(Context ctx) { if (isProviderProcess != null) { return isProviderProcess; } String processCmdPath = String.format(Locale.getDefault(), "/proc/%d/cmdline", Process.myPid()); BufferedReader inputStream = null; try { inputStream = new BufferedReader(new FileReader( processCmdPath)); String processName = inputStream.readLine().trim(); ProviderInfo providerInfo = ctx.getPackageManager().getProviderInfo(new ComponentName(ctx, OkSharedPrefContentProvider.class), 0); isProviderProcess = TextUtils.equals(providerInfo.processName, processName); return isProviderProcess; } catch (Exception e) { throw new RuntimeException(e); } finally { try { if (inputStream != null) { inputStream.close(); } } catch (Throwable e) { e.printStackTrace(); } } } public static SharedPreferences getSharedPref(Context ctx, String name) { SharedPreferences preferences; if (MPSPUtils.isProviderProcess(ctx)) { preferences = ctx.getSharedPreferences(name, Context.MODE_PRIVATE); } else { preferences = OkSharedPrefContentProvider.getSharedPreferences(ctx, name, Context.MODE_PRIVATE); } return preferences; } } ================================================ FILE: api/src/main/java/cn/framework/oksharedpref/OkSharedPrefContentProvider.java ================================================ package cn.framework.oksharedpref; import android.content.ContentProvider; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.SharedPreferences; import android.database.ContentObserver; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; /** * 使用ContentProvider实现多进程SharedPreferences读写 */ public class OkSharedPrefContentProvider extends ContentProvider { private static final String TAG = "MPSP"; private static String AUTHORITY; private static volatile Uri AUTHORITY_URI; private static final String METHOD_MP = "multi_process"; private static final String KEY = "key"; private static final String KEY_TYPE = "type"; private static final String KEY_MODE = "mode"; private static final String KEY_VALUE = "value"; private static final String KEY_CLEAR = "clear"; 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 GET_STRING_SET = 7; private static final int CONTAINS = 8; private static final int APPLY = 9; private static final int COMMIT = 10; private HashMap listenerHashMap; private static Bundle handle(SharedPreferences sp, Bundle extras) { String key = extras.getString(KEY); int type = extras.getInt(KEY_TYPE); Bundle bundle = new Bundle(); switch (type) { case GET_ALL: bundle.putSerializable(KEY_VALUE, new HashMap(sp.getAll())); break; case GET_STRING: bundle.putString(KEY_VALUE, sp.getString(key, extras.getString(KEY_VALUE))); break; case GET_INT: bundle.putInt(KEY_VALUE, sp.getInt(key, extras.getInt(KEY_VALUE))); break; case GET_LONG: bundle.putLong(KEY_VALUE, sp.getLong(key, extras.getLong(KEY_VALUE))); break; case GET_FLOAT: bundle.putFloat(KEY_VALUE, sp.getFloat(key, extras.getFloat(KEY_VALUE))); break; case GET_BOOLEAN: bundle.putBoolean(KEY_VALUE, sp.getBoolean(key, extras.getBoolean(KEY_VALUE))); break; case GET_STRING_SET: bundle.putSerializable(KEY_VALUE, wrapperSet( sp.getStringSet(key, (Set) extras.getSerializable(KEY_VALUE)))); break; case CONTAINS: bundle.putBoolean(KEY_VALUE, sp.contains(key)); break; case APPLY: case COMMIT: boolean clear = extras.getBoolean(KEY_CLEAR, false); SharedPreferences.Editor editor = sp.edit(); if (clear) { editor.clear(); } HashMap values = (HashMap) extras.getSerializable(KEY_VALUE); for (Map.Entry entry : values.entrySet()) { String k = (String) entry.getKey(); Object v = entry.getValue(); if (v == null) { editor.remove(k); } else if (v instanceof String) { editor.putString(k, (String) v); } else if (v instanceof Set) { editor.putStringSet(k, (Set) v); } 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 (type == APPLY) { editor.apply(); bundle.putBoolean(KEY_VALUE, true); } else { bundle.putBoolean(KEY_VALUE, editor.commit()); } break; default: break; } return bundle; } @Override public Bundle call(String method, String name, Bundle extras) { if (!method.equals(METHOD_MP)) { return null; } int mode = extras.getInt(KEY_MODE); SharedPreferences sp = getContext().getSharedPreferences(name, mode); registListener(sp, name); return handle(sp, extras); } void registListener(SharedPreferences pref, final String name) { if (listenerHashMap == null || listenerHashMap.get(name) == null) { synchronized (MPSPUtils.class) { if (listenerHashMap == null) { listenerHashMap = new HashMap<>(); } if (listenerHashMap.get(name) == null) { SharedPreferences.OnSharedPreferenceChangeListener listener = new SharedPreferences .OnSharedPreferenceChangeListener() { @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { Uri uri = Uri.withAppendedPath(OkSharedPrefContentProvider.getAuthorityUri(getContext(), name), key); getContext().getContentResolver().notifyChange(uri, null); } }; pref.registerOnSharedPreferenceChangeListener(listener); listenerHashMap.put(name, listener); } } } } /** * 如果设备处在“安全模式”下,只有系统自带的ContentProvider才能被正常解析使用; */ private static 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) { e.printStackTrace(); } return isSafeMode; } public static Uri getAuthorityUri(Context context, String name) { if (AUTHORITY_URI == null) { synchronized (OkSharedPrefContentProvider.class) { if (AUTHORITY_URI == null) { if (AUTHORITY == null) { AUTHORITY = context.getPackageName() + ".oksharedpref"; } AUTHORITY_URI = Uri.parse(ContentResolver.SCHEME_CONTENT + "://" + AUTHORITY); } } } return Uri.withAppendedPath(AUTHORITY_URI, name); } /** * 获取异常栈中最底层的 Throwable Cause; * * @param tr * @return */ private static 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 isSafeMode(context) ? context.getSharedPreferences(name, mode) : new SharedPreferencesImpl(context, name, mode); } /** * @deprecated 此默认构造函数只用于父类ContentProvider在初始化时使用; */ @Deprecated public OkSharedPrefContentProvider() { } private static final class SharedPreferencesImpl implements SharedPreferences { private static Handler uiHandler = new Handler(Looper.getMainLooper()); private Context mContext; private String mName; private int mMode; private WeakHashMap mListeners; private SharedPreferencesImpl(Context context, String name, int mode) { mContext = context; mName = name; mMode = mode; } @SuppressWarnings("unchecked") @Override public Map getAll() { Map value = (Map) call(null, GET_ALL, new Bundle()); return value == null ? new HashMap() : value; } @Override public String getString(String key, String defValue) { Bundle arg = new Bundle(); arg.putString(KEY_VALUE, defValue); return (String) call(key, GET_STRING, arg); } @Override @SuppressWarnings("unchecked") public Set getStringSet(String key, Set defValues) { Bundle arg = new Bundle(); arg.putSerializable(KEY_VALUE, wrapperSet(defValues)); return (Set) call(key, GET_STRING_SET, arg); } @Override public int getInt(String key, int defValue) { Bundle arg = new Bundle(); arg.putInt(KEY_VALUE, defValue); return (Integer) call(key, GET_INT, arg); } @Override public long getLong(String key, long defValue) { Bundle arg = new Bundle(); arg.putLong(KEY_VALUE, defValue); return (Long) call(key, GET_LONG, arg); } @Override public float getFloat(String key, float defValue) { Bundle arg = new Bundle(); arg.putFloat(KEY_VALUE, defValue); return (Float) call(key, GET_FLOAT, arg); } @Override public boolean getBoolean(String key, boolean defValue) { Bundle arg = new Bundle(); arg.putBoolean(KEY_VALUE, defValue); return (Boolean) call(key, GET_BOOLEAN, arg); } @Override public boolean contains(String key) { return (Boolean) call(key, CONTAINS, new Bundle()); } @Override public Editor edit() { return new EditorImpl(); } @Override public void registerOnSharedPreferenceChangeListener(final OnSharedPreferenceChangeListener listener) { Uri uri = getAuthorityUri(mContext, mName); ContentObserverImplHolder observer = new ContentObserverImplHolder(listener); mContext.getContentResolver() .registerContentObserver(uri, true, observer.observer); getListeners().put(listener, observer); } @Override public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { ContentObserverImplHolder holder = getListeners().remove(listener); if (holder != null) { holder.observer.destroy(); } } private Map getListeners() { if (mListeners == null) { synchronized (this) { if (mListeners == null) { mListeners = new WeakHashMap<>(); } } } return mListeners; } private class ContentObserverImplHolder { ContentObserverImpl observer; ContentObserverImplHolder(OnSharedPreferenceChangeListener listener) { observer = new ContentObserverImpl(listener); } @Override protected void finalize() throws Throwable { try { observer.destroy(); } catch (Throwable e) { e.printStackTrace(); } super.finalize(); } } private class ContentObserverImpl extends ContentObserver { private WeakReference listenerRef; private boolean destroy; private ContentObserverImpl(OnSharedPreferenceChangeListener listener) { super(uiHandler); this.listenerRef = new WeakReference(listener); } public void destroy() { if (!destroy) { synchronized (this) { if (!destroy) { mContext.getContentResolver() .unregisterContentObserver(this); destroy = true; } } } } @Override public void onChange(boolean selfChange, Uri uri) { OnSharedPreferenceChangeListener listener = listenerRef.get(); if (listener != null) { listener.onSharedPreferenceChanged(SharedPreferencesImpl.this, uri.getLastPathSegment()); } else { destroy(); } } } private Object call(String key, int type, Bundle arg) { Uri uri = getAuthorityUri(mContext, mName); arg.putInt(KEY_MODE, mMode); arg.putString(KEY, key); arg.putInt(KEY_TYPE, type); try { Bundle ret = mContext.getContentResolver().call(uri, METHOD_MP, mName, arg); return ret.get(KEY_VALUE); } catch (Throwable e) { e.printStackTrace(); return handle(mContext.getSharedPreferences(mName, mMode), arg).get(KEY_VALUE); } } public final class EditorImpl implements Editor { private final HashMap mModified = new HashMap(); private boolean mClear = false; @Override public Editor putString(String key, String value) { synchronized (this) { mModified.put(key, value); return this; } } @Override public Editor putStringSet(String key, Set values) { synchronized (this) { mModified.put(key, 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) { mModified.clear(); mClear = true; return this; } } @Override public void apply() { setValue(APPLY); } @Override public boolean commit() { return setValue(COMMIT); } private boolean setValue(int type) { Bundle extras = new Bundle(); extras.putSerializable(KEY_VALUE, mModified); extras.putBoolean(KEY_CLEAR, mClear); return (Boolean) call(null, type, extras); } } } private static HashSet wrapperSet(Set set) { return set == null ? null : (set instanceof HashMap ? (HashSet) set : new HashSet(set)); } private static String makeAction(String name) { return String.format("%1$s_%2$s", OkSharedPrefContentProvider.class.getName(), name); } @Override public boolean onCreate() { return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { throw new UnsupportedOperationException(" No external query"); } @SuppressWarnings("unchecked") @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { throw new UnsupportedOperationException(" No external update"); } @Override public String getType(Uri uri) { throw new UnsupportedOperationException("No external call"); } @Override public Uri insert(Uri uri, ContentValues values) { throw new UnsupportedOperationException("No external insert"); } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { throw new UnsupportedOperationException("No external delete"); } } ================================================ FILE: app/.gitignore ================================================ /build ================================================ FILE: app/build.gradle ================================================ apply plugin: 'com.android.application' android { compileSdkVersion 26 buildToolsVersion '26.0.2' defaultConfig { applicationId "cn.framework.sharedpref.app" minSdkVersion 11 targetSdkVersion 26 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support.constraint:constraint-layout:1.0.2' testCompile 'junit:junit:4.12' compile 'com.github.sevenshal.oksharedprefs:api:1.0.1' annotationProcessor 'com.github.sevenshal.oksharedprefs:processor:1.0.1' // compile project(':api') // annotationProcessor project(':processor') } ================================================ FILE: app/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /Users/sevenshal/Library/Android/sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile ================================================ FILE: app/src/androidTest/java/cn/framework/ExampleInstrumentedTest.java ================================================ package cn.framework; import android.content.Context; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import static org.junit.Assert.*; /** * Instrumentation test, which will execute on an Android device. * * @see Testing documentation */ @RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest { @Test public void useAppContext() throws Exception { // Context of the app under test. Context appContext = InstrumentationRegistry.getTargetContext(); assertEquals("cn.framework", appContext.getPackageName()); } } ================================================ FILE: app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: app/src/main/java/cn/framework/oksharedpref/app/IMsg.java ================================================ package cn.framework.oksharedpref.app; import cn.framework.oksharedpref.annotations.DefaultValue; import cn.framework.oksharedpref.annotations.PreferenceType; import cn.framework.oksharedpref.annotations.SharedPreference; import cn.framework.oksharedpref.annotations.Type; /** * Created by sevenshal on 2016/11/28. */ @SharedPreference(value = "Msg", implSharedPreference = false, preferenceName = "msg", multiProcess = false) public interface IMsg { @DefaultValue(value = "null", createDefaultGetter = false) String USERID = "userId"; @Type(PreferenceType.STRING_SET) @DefaultValue(value = "null", createDefaultGetter = false) String TOKEN = "token"; @Type(PreferenceType.LONG) @DefaultValue(value = "0", createDefaultGetter = false) String DEVICE_ID = "deviceId"; @Type(PreferenceType.BOOLEAN) @DefaultValue(value = "false", createDefaultGetter = false) String LOGIN = "hasAuth"; String NICK_NAME = "nickName"; } ================================================ FILE: app/src/main/java/cn/framework/oksharedpref/app/MainActivity.java ================================================ package cn.framework.oksharedpref.app; import android.app.Activity; import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; import android.util.Log; import android.view.View; import java.util.HashSet; import java.util.Set; import cn.framework.R; public class MainActivity extends Activity implements SharedPreferences.OnSharedPreferenceChangeListener { MsgPrefs msgPrefs; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { startActivity(new Intent(MainActivity.this, SeActivity.class)); } }); msgPrefs = MsgPrefs.defaultInstance(this); Log.i("MPM", "deviceId:" + msgPrefs.getDeviceId() + ";userId:" + msgPrefs.getUserid() + ";isLogin:" + msgPrefs.isLogin() + ";token:" + msgPrefs.getToken()); msgPrefs.prefs().registerOnSharedPreferenceChangeListener(this); Set set = new HashSet(); set.add("a"); set.add("b"); msgPrefs.edit().setDeviceId(111).setLogin(true).setUserid("userid").setToken(set).apply(); } @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { Log.i("MP", "main_onSharedPreferenceChanged:" + key); Log.i("MPM", "deviceId:" + msgPrefs.getDeviceId() + ";userId:" + msgPrefs.getUserid() + ";isLogin:" + msgPrefs.isLogin() + ";token:" + msgPrefs.getToken()); } } ================================================ FILE: app/src/main/java/cn/framework/oksharedpref/app/SeActivity.java ================================================ package cn.framework.oksharedpref.app; import android.app.Activity; import android.content.SharedPreferences; import android.os.Bundle; import android.util.Log; import java.util.HashSet; import java.util.Set; /** * Created by sevenshal on 2017/10/23. */ public class SeActivity extends Activity implements SharedPreferences.OnSharedPreferenceChangeListener { MsgPrefs msgPrefs; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); msgPrefs = MsgPrefs.defaultInstance(this); Log.i("MPM", "deviceId:" + msgPrefs.getDeviceId() + ";userId:" + msgPrefs.getUserid() + ";isLogin:" + msgPrefs.isLogin() + ";token:" + msgPrefs.getToken()); Set set = new HashSet(); set.add("a2"); set.add("b2"); msgPrefs.prefs().registerOnSharedPreferenceChangeListener(this); msgPrefs.edit().setToken(set).setUserid("userid2").setLogin(false).setDeviceId(222).apply(); } @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { Log.i("MP", "onSharedPreferenceChanged:" + key); Log.i("MPM", "deviceId:" + msgPrefs.getDeviceId() + ";userId:" + msgPrefs.getUserid() + ";isLogin:" + msgPrefs.isLogin() + ";token:" + msgPrefs.getToken()); } @Override protected void onDestroy() { super.onDestroy(); msgPrefs.prefs().unregisterOnSharedPreferenceChangeListener(this); } } ================================================ FILE: app/src/main/res/layout/activity_main.xml ================================================