├── app ├── .gitignore ├── src │ └── main │ │ ├── ic_launcher-web.png │ │ ├── res │ │ ├── values │ │ │ ├── strings.xml │ │ │ ├── ic_launcher_background.xml │ │ │ ├── colors.xml │ │ │ └── styles.xml │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_round.png │ │ │ └── ic_launcher_foreground.png │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_round.png │ │ │ └── ic_launcher_foreground.png │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_round.png │ │ │ └── ic_launcher_foreground.png │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_round.png │ │ │ └── ic_launcher_foreground.png │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_round.png │ │ │ └── ic_launcher_foreground.png │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ ├── layout │ │ │ ├── activity_error.xml │ │ │ └── activity_main.xml │ │ └── menu │ │ │ └── menu_main.xml │ │ ├── java │ │ └── com │ │ │ └── haohaohu │ │ │ └── cachemanagesample │ │ │ ├── Test.java │ │ │ └── MainActivity.java │ │ └── AndroidManifest.xml ├── build.gradle └── proguard-rules.pro ├── library ├── .gitignore ├── project.properties ├── src │ └── main │ │ ├── res │ │ └── values │ │ │ └── strings.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ ├── com │ │ └── haohaohu │ │ │ └── cachemanage │ │ │ ├── strategy │ │ │ ├── IEncryptStrategy.java │ │ │ ├── KeyStoreEncryptStrategy.java │ │ │ └── Des3EncryptStrategy.java │ │ │ ├── observer │ │ │ ├── IDataChangeListener.java │ │ │ └── CacheObserver.java │ │ │ ├── util │ │ │ ├── LockUtil.java │ │ │ ├── Md5Utils.java │ │ │ ├── Des3Util.java │ │ │ ├── Base64Util.java │ │ │ └── KeyStoreHelper.java │ │ │ ├── CacheUtilConfig.java │ │ │ ├── CacheUtil.java │ │ │ └── ACache.java │ │ └── mohapps │ │ └── modified │ │ └── java │ │ └── util │ │ └── Base64.java ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── gradle.properties ├── .travis.yml ├── bintrayUpload.gradle ├── gradlew.bat ├── gradlew ├── README.md └── LICENSE /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':library' 2 | -------------------------------------------------------------------------------- /library/project.properties: -------------------------------------------------------------------------------- 1 | #javadoc 2 | javadoc.name=CacheManage -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ronghao/CacheManage/HEAD/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | CacheManage 3 | 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ronghao/CacheManage/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /library/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | CacheManage 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ronghao/CacheManage/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ronghao/CacheManage/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ronghao/CacheManage/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ronghao/CacheManage/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ronghao/CacheManage/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ronghao/CacheManage/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ronghao/CacheManage/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ronghao/CacheManage/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ronghao/CacheManage/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ronghao/CacheManage/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ronghao/CacheManage/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ronghao/CacheManage/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ronghao/CacheManage/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ronghao/CacheManage/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ronghao/CacheManage/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FAFAFA 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat May 20 10:43:05 CST 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /library/src/main/java/com/haohaohu/cachemanage/strategy/IEncryptStrategy.java: -------------------------------------------------------------------------------- 1 | package com.haohaohu.cachemanage.strategy; 2 | 3 | /** 4 | * 加解密策略 5 | * 6 | * @author haohao(ronghao3508@gmail.com) on 2018/5/28 16:14 7 | * @version v1.0 8 | */ 9 | public interface IEncryptStrategy { 10 | 11 | String encrypt(String str); 12 | 13 | String decode(String str); 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_error.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | -------------------------------------------------------------------------------- /library/src/main/java/com/haohaohu/cachemanage/observer/IDataChangeListener.java: -------------------------------------------------------------------------------- 1 | package com.haohaohu.cachemanage.observer; 2 | 3 | /** 4 | * 数据变化监听接口 5 | * 6 | * @author haohao(ronghao3508@gmail.com) on 2017/12/16 下午 05:31 7 | * @version v1.0 8 | */ 9 | public interface IDataChangeListener { 10 | /** 11 | * 数据变化 12 | * 13 | * @param key 主键 14 | * @param value 值 15 | */ 16 | void onDataChange(String key, String value); 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/haohaohu/cachemanagesample/Test.java: -------------------------------------------------------------------------------- 1 | package com.haohaohu.cachemanagesample; 2 | 3 | /** 4 | * @author haohao on 2017/6/23 11:39 5 | * @version v1.0 6 | */ 7 | public class Test { 8 | private int d; 9 | private String str; 10 | 11 | Test(int d, String str) { 12 | this.d = d; 13 | this.str = str; 14 | } 15 | 16 | @Override 17 | public String toString() { 18 | return "{d:" + d + " str:" + str + "}"; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /library/src/main/java/com/haohaohu/cachemanage/util/LockUtil.java: -------------------------------------------------------------------------------- 1 | package com.haohaohu.cachemanage.util; 2 | 3 | import java.util.concurrent.locks.ReentrantReadWriteLock; 4 | 5 | /** 6 | * 公平锁单例类 7 | * 8 | * @author haohao(ronghao3508 gmail.com) on 2018/12/12 14:33 9 | * @version v1.0 10 | */ 11 | public class LockUtil { 12 | public static ReentrantReadWriteLock getInstance() { 13 | return ReentrantLockHolder.lock; 14 | } 15 | 16 | 17 | private static class ReentrantLockHolder { 18 | static ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | gradlew.bat 19 | gradlew 20 | build/ 21 | 22 | # Mirror files 23 | mirror/ 24 | # Local configuration file (sdk path, etc) 25 | local.properties 26 | 27 | # Proguard folder generated by Eclipse 28 | proguard/ 29 | 30 | # Intellij project files 31 | *.iws 32 | 33 | # Log Files 34 | *.log 35 | 36 | # Android Studio Navigation editor temp files 37 | .navigation/ 38 | 39 | # Android Studio captures folder 40 | captures/ 41 | 42 | # Intellij 43 | *.iml 44 | .idea/ 45 | 46 | 47 | # OS 48 | .DS_Store 49 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | 14 | 18 | 22 | -------------------------------------------------------------------------------- /library/src/main/java/com/haohaohu/cachemanage/util/Md5Utils.java: -------------------------------------------------------------------------------- 1 | package com.haohaohu.cachemanage.util; 2 | 3 | import java.math.BigInteger; 4 | import java.security.MessageDigest; 5 | import java.security.NoSuchAlgorithmException; 6 | 7 | /** 8 | * 使用md5的算法进行加密的Utils 9 | */ 10 | public class Md5Utils { 11 | public static String md5(String plainText) { 12 | byte[] secretBytes = null; 13 | try { 14 | secretBytes = MessageDigest.getInstance("md5").digest( 15 | plainText.getBytes()); 16 | } catch (NoSuchAlgorithmException e) { 17 | throw new RuntimeException("没有md5这个算法!"); 18 | } 19 | String md5code = new BigInteger(1, secretBytes).toString(16); 20 | for (int i = 0; i < 32 - md5code.length(); i++) { 21 | md5code = "0" + md5code; 22 | } 23 | return md5code; 24 | } 25 | } 26 | 27 | 28 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | jdk: oraclejdk8 3 | before_install: 4 | - mkdir "$ANDROID_HOME/licenses" || true 5 | - echo -e "\n8933bad161af4178b1185d1a37fbf41ea5269c55" > "$ANDROID_HOME/licenses/android-sdk-license" 6 | - echo -e "\n84831b9409646a918e30573bab4c9c91346d8abd" > "$ANDROID_HOME/licenses/android-sdk-preview-license" 7 | - chmod +x gradlew 8 | android: 9 | components: 10 | - tools 11 | - platform-tools 12 | - build-tools-26.0.2 13 | - android-26 14 | - extra-android-m2repository 15 | - extra-google-m2repository 16 | - extra-android-support 17 | - extra-google-google_play_services 18 | - extra-google-m2repository 19 | - addon-google_apis-google-26 20 | 21 | - sys-img-armeabi-v7a-android-26 22 | - sys-img-armeabi-v7a-android-17 23 | licenses: 24 | - 'android-sdk-preview-license-.+' 25 | - 'android-sdk-license-.+' 26 | - 'google-gdk-license-.+' -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion compile_sdk_version 5 | buildToolsVersion build_tools_version 6 | 7 | defaultConfig { 8 | applicationId "com.haohaohu.cachemanagesample" 9 | minSdkVersion min_sdk_version 10 | targetSdkVersion compile_sdk_version 11 | versionCode version_code 12 | versionName version_name 13 | } 14 | 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | 22 | lintOptions { 23 | abortOnError false 24 | } 25 | } 26 | 27 | dependencies { 28 | compile fileTree(include: ['*.jar'], dir: 'libs') 29 | compile project(':library') 30 | // compile 'com.github.ronghao:CacheManage:1.3.5' 31 | compile 'com.android.support:appcompat-v7:25.3.1' 32 | } 33 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\androidIDE\androidsdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /library/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\androidIDE\androidsdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion compile_sdk_version 5 | buildToolsVersion build_tools_version 6 | 7 | defaultConfig { 8 | minSdkVersion min_sdk_version 9 | targetSdkVersion compile_sdk_version 10 | versionCode version_code 11 | versionName version_name 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | 20 | lintOptions { 21 | abortOnError false 22 | } 23 | 24 | //单元测试 25 | testOptions.unitTests.all { 26 | testLogging { 27 | events 'passed', 'skipped', 'failed', 'standardOut', 'standardError' 28 | } 29 | } 30 | } 31 | 32 | dependencies { 33 | compile fileTree(dir: 'libs', include: ['*.jar']) 34 | compile 'com.android.support:support-annotations:25.3.1' 35 | compile 'com.google.code.gson:gson:2.8.1' 36 | } 37 | 38 | apply from: "../bintrayUpload.gradle" 39 | -------------------------------------------------------------------------------- /bintrayUpload.gradle: -------------------------------------------------------------------------------- 1 | def projectVersionName = android.defaultConfig.versionName 2 | 3 | // load properties 4 | Properties properties = new Properties() 5 | File localPropertiesFile = project.file("local.properties"); 6 | if (localPropertiesFile.exists()) { 7 | properties.load(localPropertiesFile.newDataInputStream()) 8 | } 9 | File projectPropertiesFile = project.file("project.properties"); 10 | if (projectPropertiesFile.exists()) { 11 | properties.load(projectPropertiesFile.newDataInputStream()) 12 | } 13 | def javadocName = properties.getProperty("javadoc.name") 14 | 15 | // This generates sources.jar 16 | task sourcesJar(type: Jar) { 17 | from android.sourceSets.main.java.srcDirs 18 | classifier = 'sources' 19 | } 20 | 21 | task javadoc(type: Javadoc) { 22 | source = android.sourceSets.main.java.srcDirs 23 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 24 | } 25 | 26 | // This generates javadoc.jar 27 | task javadocJar(type: Jar, dependsOn: javadoc) { 28 | classifier = 'javadoc' 29 | from javadoc.destinationDir 30 | } 31 | 32 | artifacts { 33 | archives javadocJar 34 | archives sourcesJar 35 | } 36 | 37 | // javadoc configuration 38 | javadoc { 39 | options { 40 | encoding "UTF-8" 41 | charSet 'UTF-8' 42 | author true 43 | version projectVersionName 44 | links "http://docs.oracle.com/javase/7/docs/api" 45 | title javadocName 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /library/src/main/java/com/haohaohu/cachemanage/strategy/KeyStoreEncryptStrategy.java: -------------------------------------------------------------------------------- 1 | package com.haohaohu.cachemanage.strategy; 2 | 3 | import android.content.Context; 4 | 5 | import com.haohaohu.cachemanage.util.KeyStoreHelper; 6 | 7 | /** 8 | * KeyStore加密策略 9 | * 10 | * @author haohao(ronghao3508@gmail.com) on 2018/5/28 16:18 11 | * @version v1.0 12 | */ 13 | public class KeyStoreEncryptStrategy implements IEncryptStrategy { 14 | 15 | private Context mContext; 16 | private String alias; 17 | 18 | public KeyStoreEncryptStrategy(Context context) { 19 | this(context, context.getPackageName()); 20 | } 21 | 22 | public KeyStoreEncryptStrategy(Context context, String alias) { 23 | this.mContext = context; 24 | this.alias = context.getPackageName() + "_" + alias; 25 | createKeyStoreSecretKey(this.alias); 26 | } 27 | 28 | @Override 29 | public String encrypt(String str) { 30 | try { 31 | return KeyStoreHelper.encrypt(mContext, alias, str); 32 | } catch (Exception e) { 33 | e.printStackTrace(); 34 | return ""; 35 | } 36 | } 37 | 38 | @Override 39 | public String decode(String str) { 40 | try { 41 | return KeyStoreHelper.decrypt(mContext, alias, str); 42 | } catch (Exception e) { 43 | e.printStackTrace(); 44 | return ""; 45 | } 46 | } 47 | 48 | private void createKeyStoreSecretKey(String alias) { 49 | try { 50 | KeyStoreHelper.createKeys(mContext, alias); 51 | } catch (Exception e) { 52 | e.printStackTrace(); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 18 | 19 | 26 | 27 | 34 | 35 | 43 | 44 | 51 | 52 | 59 | 66 | 67 | 71 | 72 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /library/src/main/java/com/haohaohu/cachemanage/util/Des3Util.java: -------------------------------------------------------------------------------- 1 | package com.haohaohu.cachemanage.util; 2 | 3 | import android.text.TextUtils; 4 | import javax.crypto.Cipher; 5 | import javax.crypto.SecretKey; 6 | import javax.crypto.SecretKeyFactory; 7 | import javax.crypto.spec.DESedeKeySpec; 8 | import javax.crypto.spec.IvParameterSpec; 9 | 10 | /** 11 | * Des3工具类 12 | * 13 | * @author haohao on 2017/6/22 18:19 14 | * @version v1.0 15 | */ 16 | public class Des3Util { 17 | public static final String DESEDE_CBC_PKCS5_PADDING = "desede/CBC/PKCS5Padding"; 18 | private static String encoding = "utf-8"; 19 | 20 | /** 21 | * 加密 22 | * 23 | * @param plainText 要加密文字 24 | * @param iv 偏移量 25 | * @param secretKey 密钥 26 | * @return 加密文字 27 | * @throws Exception 28 | */ 29 | public static String encode(String plainText, String secretKey, String iv) throws Exception { 30 | if (TextUtils.isEmpty(secretKey) || TextUtils.isEmpty(iv)) { 31 | throw new NullPointerException("u should init first"); 32 | } 33 | DESedeKeySpec spec = new DESedeKeySpec(secretKey.getBytes()); 34 | SecretKeyFactory keyfactory = SecretKeyFactory.getInstance("desede"); 35 | SecretKey deskey = keyfactory.generateSecret(spec); 36 | Cipher cipher = Cipher.getInstance(DESEDE_CBC_PKCS5_PADDING); 37 | IvParameterSpec ips = new IvParameterSpec(iv.getBytes()); 38 | cipher.init(1, deskey, ips); 39 | byte[] encryptData = cipher.doFinal(plainText.getBytes(encoding)); 40 | return Base64Util.encode(encryptData); 41 | } 42 | 43 | /*** 44 | * 解密 45 | * @param encryptText 要解密文字 46 | * @param secretKey 密钥 47 | * @param iv 偏移量 48 | * @return 解密文字 49 | * @throws Exception 50 | */ 51 | public static String decode(String encryptText, String secretKey, String iv) throws Exception { 52 | if (TextUtils.isEmpty(secretKey) || TextUtils.isEmpty(iv)) { 53 | throw new NullPointerException("u should init first"); 54 | } 55 | DESedeKeySpec spec = new DESedeKeySpec(secretKey.getBytes()); 56 | SecretKeyFactory keyfactory = SecretKeyFactory.getInstance("desede"); 57 | SecretKey deskey = keyfactory.generateSecret(spec); 58 | Cipher cipher = Cipher.getInstance(DESEDE_CBC_PKCS5_PADDING); 59 | IvParameterSpec ips = new IvParameterSpec(iv.getBytes()); 60 | cipher.init(2, deskey, ips); 61 | byte[] decryptData = cipher.doFinal(Base64Util.decode(encryptText)); 62 | return new String(decryptData, encoding); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /library/src/main/java/com/haohaohu/cachemanage/observer/CacheObserver.java: -------------------------------------------------------------------------------- 1 | package com.haohaohu.cachemanage.observer; 2 | 3 | import android.text.TextUtils; 4 | 5 | import java.util.ArrayList; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | 9 | /** 10 | * 数据监控器 11 | * 1、当数据变化时候,回调所有监听器 12 | * 13 | * @author haohao(ronghao3508@gmail.com) on 2017/12/16 下午 05:30 14 | * @version v1.0 15 | */ 16 | public class CacheObserver { 17 | 18 | private HashMap> mListenerMap = new HashMap<>(); 19 | 20 | public static CacheObserver getInstance() { 21 | return Holder.observer; 22 | } 23 | 24 | public static void addListener(String key, IDataChangeListener listener) { 25 | CacheObserver.getInstance().addObserver(key, listener); 26 | } 27 | 28 | public synchronized void addObserver(String key, IDataChangeListener listener) { 29 | if (TextUtils.isEmpty(key) || listener == null) { 30 | return; 31 | } 32 | List list = mListenerMap.get(key); 33 | if (list == null) { 34 | list = new ArrayList<>(); 35 | } 36 | list.add(listener); 37 | mListenerMap.put(key, list); 38 | } 39 | 40 | public synchronized void addObserver(String key, List listeners) { 41 | if (TextUtils.isEmpty(key) || listeners == null) { 42 | return; 43 | } 44 | List list = mListenerMap.get(key); 45 | if (list == null) { 46 | list = new ArrayList<>(); 47 | } 48 | list.addAll(listeners); 49 | mListenerMap.put(key, list); 50 | } 51 | 52 | public synchronized void removeObserver(String key, IDataChangeListener listener) { 53 | if (TextUtils.isEmpty(key) || listener == null) { 54 | return; 55 | } 56 | List list = mListenerMap.get(key); 57 | if (list == null) { 58 | return; 59 | } 60 | list.remove(listener); 61 | mListenerMap.put(key, list); 62 | } 63 | 64 | public synchronized void removeObservers(String key) { 65 | if (TextUtils.isEmpty(key)) { 66 | return; 67 | } 68 | mListenerMap.remove(key); 69 | } 70 | 71 | public void notifyDataChange(String key, String value) { 72 | if (TextUtils.isEmpty(key)) { 73 | return; 74 | } 75 | List list = mListenerMap.get(key); 76 | if (list == null) { 77 | return; 78 | } 79 | int size = list.size(); 80 | for (int i = 0; i < size; i++) { 81 | list.get(i).onDataChange(key, value); 82 | } 83 | } 84 | 85 | private static class Holder { 86 | private static CacheObserver observer = new CacheObserver(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /library/src/main/java/com/haohaohu/cachemanage/strategy/Des3EncryptStrategy.java: -------------------------------------------------------------------------------- 1 | package com.haohaohu.cachemanage.strategy; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.os.Build; 6 | import android.provider.Settings; 7 | import android.support.annotation.RequiresApi; 8 | import android.text.TextUtils; 9 | 10 | import com.haohaohu.cachemanage.util.Des3Util; 11 | import com.haohaohu.cachemanage.util.KeyStoreHelper; 12 | 13 | import java.security.InvalidAlgorithmParameterException; 14 | import java.security.NoSuchAlgorithmException; 15 | import java.security.NoSuchProviderException; 16 | 17 | /** 18 | * Des3加密策略 19 | * 20 | * @author haohao(ronghao3508@gmail.com) on 2018/5/28 16:18 21 | * @version v1.0 22 | */ 23 | public class Des3EncryptStrategy implements IEncryptStrategy { 24 | 25 | private String secretKey;//秘钥 26 | private String iv = "haohaoha";//移动位置8位 27 | private Context mContext; 28 | 29 | public Des3EncryptStrategy(Context context, String secretKey, String iv) { 30 | this.mContext = context; 31 | this.secretKey = secretKey; 32 | this.iv = iv; 33 | } 34 | 35 | @Override 36 | public String encrypt(String str) { 37 | try { 38 | return Des3Util.encode(str, this.secretKey, this.iv); 39 | } catch (Exception e) { 40 | e.printStackTrace(); 41 | return ""; 42 | } 43 | } 44 | 45 | @Override 46 | public String decode(String str) { 47 | try { 48 | return Des3Util.decode(str, this.secretKey, this.iv); 49 | } catch (Exception e) { 50 | e.printStackTrace(); 51 | return ""; 52 | } 53 | } 54 | 55 | private String createSecretKey() { 56 | String secretKey; 57 | String str = getAndroidID(); 58 | if (str.length() > 24) { 59 | secretKey = str.substring(0, 24); 60 | } else { 61 | secretKey = str + getStr(24 - str.length()); 62 | } 63 | 64 | return secretKey; 65 | } 66 | 67 | private String get24Str(String str) { 68 | if (TextUtils.isEmpty(str)) return ""; 69 | if (str.length() >= 24) { 70 | return str.substring(0, 24); 71 | } 72 | return str + getStr(24 - str.length()); 73 | } 74 | 75 | private String getStr(int num) { 76 | StringBuilder builder = new StringBuilder(); 77 | for (int i = 0; i < num; i++) { 78 | builder.append("a"); 79 | } 80 | return builder.toString(); 81 | } 82 | 83 | @SuppressLint("HardwareIds") 84 | public String getAndroidID() { 85 | return Settings.Secure.getString(mContext.getContentResolver(), Settings.Secure.ANDROID_ID); 86 | } 87 | 88 | @RequiresApi(api = Build.VERSION_CODES.KITKAT) 89 | public void createKeyStoreSecretKey() { 90 | KeyStoreHelper.createKeys(mContext, mContext.getPackageName()); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /library/src/main/java/com/haohaohu/cachemanage/CacheUtilConfig.java: -------------------------------------------------------------------------------- 1 | package com.haohaohu.cachemanage; 2 | 3 | import android.content.Context; 4 | import android.os.Build; 5 | import android.text.TextUtils; 6 | 7 | import com.haohaohu.cachemanage.strategy.IEncryptStrategy; 8 | import com.haohaohu.cachemanage.strategy.KeyStoreEncryptStrategy; 9 | 10 | import java.io.File; 11 | 12 | /** 13 | * 配置项 14 | * 15 | * @author haohao on 2017/8/24 10:37 16 | * @version v1.0 17 | */ 18 | public class CacheUtilConfig { 19 | 20 | private Context context; 21 | private boolean isKeyEncrypt; 22 | private boolean isEncrypt; 23 | private boolean memoryCache; 24 | private ACache aCache; 25 | 26 | private IEncryptStrategy mIEncryptStrategy; 27 | 28 | private CacheUtilConfig(Builder builder) { 29 | context = builder.context; 30 | isEncrypt = builder.isEncrypt; 31 | isKeyEncrypt = builder.isKeyEncrypt; 32 | memoryCache = builder.memoryCache; 33 | aCache = builder.aCache; 34 | mIEncryptStrategy = builder.iEncryptStrategy; 35 | } 36 | 37 | public static Builder builder(Context context) { 38 | return new Builder(context.getApplicationContext()); 39 | } 40 | 41 | public Context getContext() { 42 | return context; 43 | } 44 | 45 | public boolean isEncrypt() { 46 | return isEncrypt; 47 | } 48 | 49 | public boolean isKeyEncrypt() { 50 | return isKeyEncrypt; 51 | } 52 | 53 | public boolean isMemoryCache() { 54 | return memoryCache; 55 | } 56 | 57 | public ACache getACache() { 58 | return aCache; 59 | } 60 | 61 | public void setACache(ACache aCache) { 62 | this.aCache = aCache; 63 | } 64 | 65 | public IEncryptStrategy getIEncryptStrategy() { 66 | return mIEncryptStrategy; 67 | } 68 | 69 | public void setIEncryptStrategy(IEncryptStrategy mIEncryptStrategy) { 70 | this.mIEncryptStrategy = mIEncryptStrategy; 71 | } 72 | 73 | public static class Builder { 74 | public IEncryptStrategy iEncryptStrategy; 75 | private Context context; 76 | private boolean isEncrypt = true;//默认加密 77 | private boolean isKeyEncrypt = true;//key默认加密 78 | private boolean memoryCache = true;//默认保存到内存 79 | private boolean isPreventPowerDelete = false;//防止被删除 80 | private String alias;//私钥 81 | private ACache aCache;//ACache示例 82 | 83 | public Builder(Context context) { 84 | this.context = context; 85 | } 86 | 87 | public Builder allowMemoryCache(boolean memoryCache) { 88 | this.memoryCache = memoryCache; 89 | return this; 90 | } 91 | 92 | public Builder setACache(ACache aCache) { 93 | this.aCache = aCache; 94 | return this; 95 | } 96 | 97 | public Builder setIEncryptStrategy(IEncryptStrategy iEncryptStrategy) { 98 | this.iEncryptStrategy = iEncryptStrategy; 99 | return this; 100 | } 101 | 102 | public Builder allowEncrypt(boolean isEncrypt) { 103 | this.isEncrypt = isEncrypt; 104 | return this; 105 | } 106 | 107 | public Builder allowKeyEncrypt(boolean isKeyEncrypt) { 108 | this.isKeyEncrypt = isKeyEncrypt; 109 | return this; 110 | } 111 | 112 | public Builder preventPowerDelete(boolean isPreventPowerDelete) { 113 | this.isPreventPowerDelete = isPreventPowerDelete; 114 | return this; 115 | } 116 | 117 | public Builder setAlias(String alise) { 118 | this.alias = alise; 119 | return this; 120 | } 121 | 122 | public CacheUtilConfig build() { 123 | if (this.iEncryptStrategy == null) { 124 | if (TextUtils.isEmpty(alias)) { 125 | iEncryptStrategy = new KeyStoreEncryptStrategy(context); 126 | } else { 127 | iEncryptStrategy = new KeyStoreEncryptStrategy(context, alias); 128 | } 129 | } 130 | if (this.aCache == null) { 131 | if (isPreventPowerDelete) { 132 | File file = context.getDatabasePath("cachetest"); 133 | if (file == null || !file.exists()) { 134 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 135 | context.openOrCreateDatabase("cachetest", 136 | Context.MODE_ENABLE_WRITE_AHEAD_LOGGING, null); 137 | } 138 | file = context.getDatabasePath("cachetest"); 139 | } 140 | File cacheFile = new File(file.getParent(), "cachemanage/"); 141 | if (cacheFile.exists() && cacheFile.isFile()) { 142 | cacheFile.delete(); 143 | } 144 | if (!cacheFile.exists()) { 145 | cacheFile.mkdirs(); 146 | } 147 | //删除临时创建的数据库文件 148 | if (file.exists()) { 149 | file.delete(); 150 | } 151 | this.aCache = ACache.get(cacheFile); 152 | } else { 153 | File file = new File(ACache.getDiskCacheDir(context)); 154 | File cacheFile = new File(file.getParent(), "cachemanage/"); 155 | if (!cacheFile.exists()) { 156 | cacheFile.mkdirs(); 157 | } 158 | this.aCache = ACache.get(cacheFile); 159 | } 160 | } 161 | 162 | return new CacheUtilConfig(this); 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /library/src/main/java/com/haohaohu/cachemanage/util/Base64Util.java: -------------------------------------------------------------------------------- 1 | package com.haohaohu.cachemanage.util; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.ByteArrayOutputStream; 5 | import java.io.IOException; 6 | import java.io.InputStreamReader; 7 | import java.io.OutputStream; 8 | import java.io.StringBufferInputStream; 9 | import java.nio.ByteBuffer; 10 | 11 | /** 12 | * Base64编码 13 | * 14 | * @author haohao on 2017/6/22 22:40 15 | * @version v1.0 16 | */ 17 | public class Base64Util { 18 | public static final char[] BASE64_CODE = new char[]{ 19 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 20 | 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 21 | 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 22 | 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', '=' 23 | }; 24 | public static final int MAX_BUFF_SIZE = 4000000; 25 | private static final char[] legalChars = 26 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray(); 27 | 28 | public Base64Util() { 29 | } 30 | 31 | public static String encode(byte[] data) { 32 | byte start = 0; 33 | int len = data.length; 34 | StringBuffer buf = new StringBuffer(data.length * 3 / 2); 35 | int end = len - 3; 36 | int i = start; 37 | int n = 0; 38 | 39 | int d; 40 | while (i <= end) { 41 | d = (data[i] & 255) << 16 | (data[i + 1] & 255) << 8 | data[i + 2] & 255; 42 | buf.append(legalChars[d >> 18 & 63]); 43 | buf.append(legalChars[d >> 12 & 63]); 44 | buf.append(legalChars[d >> 6 & 63]); 45 | buf.append(legalChars[d & 63]); 46 | i += 3; 47 | if (n++ >= 14) { 48 | n = 0; 49 | buf.append(" "); 50 | } 51 | } 52 | 53 | if (i == start + len - 2) { 54 | d = (data[i] & 255) << 16 | (data[i + 1] & 255) << 8; 55 | buf.append(legalChars[d >> 18 & 63]); 56 | buf.append(legalChars[d >> 12 & 63]); 57 | buf.append(legalChars[d >> 6 & 63]); 58 | buf.append("="); 59 | } else if (i == start + len - 1) { 60 | d = (data[i] & 255) << 16; 61 | buf.append(legalChars[d >> 18 & 63]); 62 | buf.append(legalChars[d >> 12 & 63]); 63 | buf.append("=="); 64 | } 65 | 66 | return buf.toString(); 67 | } 68 | 69 | private static int decode(char c) { 70 | if (c >= 65 && c <= 90) { 71 | return c - 65; 72 | } else if (c >= 97 && c <= 122) { 73 | return c - 97 + 26; 74 | } else if (c >= 48 && c <= 57) { 75 | return c - 48 + 26 + 26; 76 | } else { 77 | switch (c) { 78 | case '+': 79 | return 62; 80 | case '/': 81 | return 63; 82 | case '=': 83 | return 0; 84 | default: 85 | throw new RuntimeException("unexpected persondatafragment_code: " + c); 86 | } 87 | } 88 | } 89 | 90 | public static byte[] decode(String s) { 91 | ByteArrayOutputStream bos = new ByteArrayOutputStream(); 92 | 93 | try { 94 | decode(s, bos); 95 | } catch (IOException var5) { 96 | throw new RuntimeException(); 97 | } 98 | 99 | byte[] decodedBytes = bos.toByteArray(); 100 | 101 | try { 102 | bos.close(); 103 | bos = null; 104 | } catch (IOException var4) { 105 | System.err.println("Error while decoding BASE64: " + var4.toString()); 106 | } 107 | 108 | return decodedBytes; 109 | } 110 | 111 | private static void decode(String s, OutputStream os) throws IOException { 112 | int i = 0; 113 | int len = s.length(); 114 | 115 | while (true) { 116 | while (i < len && s.charAt(i) <= 32) { 117 | ++i; 118 | } 119 | 120 | if (i == len) { 121 | break; 122 | } 123 | 124 | int tri = (decode(s.charAt(i)) << 18) + (decode(s.charAt(i + 1)) << 12) + (decode( 125 | s.charAt(i + 2)) << 6) + decode(s.charAt(i + 3)); 126 | os.write(tri >> 16 & 255); 127 | if (s.charAt(i + 2) == 61) { 128 | break; 129 | } 130 | 131 | os.write(tri >> 8 & 255); 132 | if (s.charAt(i + 3) == 61) { 133 | break; 134 | } 135 | 136 | os.write(tri & 255); 137 | i += 4; 138 | } 139 | } 140 | 141 | public static boolean check(String str) throws IOException { 142 | BufferedReader br = 143 | new BufferedReader(new InputStreamReader(new StringBufferInputStream(str))); 144 | ByteBuffer buffer = ByteBuffer.allocate(1024); 145 | String line = null; 146 | String lastLine = null; 147 | 148 | byte[] lastLineBytes; 149 | while ((line = br.readLine()) != null) { 150 | lastLine = line; 151 | lastLineBytes = line.getBytes(); 152 | tryAllocate(buffer, lastLineBytes.length); 153 | buffer.put(lastLineBytes); 154 | } 155 | 156 | lastLineBytes = lastLine.getBytes(); 157 | int equalsNum = 0; 158 | 159 | for (int src = lastLineBytes.length - 1; src >= lastLineBytes.length - 2; --src) { 160 | if (lastLineBytes[src] == 61) { 161 | ++equalsNum; 162 | } 163 | } 164 | 165 | byte[] var10 = buffer.toString().getBytes(); 166 | 167 | for (int i = 0; i < var10.length - equalsNum; ++i) { 168 | char c = (char) var10[i]; 169 | if ((c < 97 || c > 122) 170 | && (c < 65 || c > 90) 171 | && (c < 48 || c > 57) 172 | && c != 43 173 | && c != 47) { 174 | return false; 175 | } 176 | } 177 | 178 | if ((var10.length - equalsNum) % 4 != 0) { 179 | return false; 180 | } else { 181 | return true; 182 | } 183 | } 184 | 185 | public static ByteBuffer tryAllocate(ByteBuffer buffer, int length) { 186 | if (length > buffer.remaining()) { 187 | buffer.flip(); 188 | return ByteBuffer.allocate(roundup(buffer.limit() + length)).put(buffer); 189 | } else { 190 | return buffer; 191 | } 192 | } 193 | 194 | public static int roundup(int length) { 195 | if (length > 4000000) { 196 | throw new IllegalArgumentException("length too large!"); 197 | } else { 198 | int capacity; 199 | for (capacity = 16; length < capacity; capacity <<= 1) { 200 | ; 201 | } 202 | 203 | return capacity; 204 | } 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # [CacheManage](https://github.com/ronghao/CacheManage) [![GitHub version](https://badge.fury.io/gh/ronghao%2FCacheManage.svg)](https://badge.fury.io/gh/ronghao%2FCacheManage) [![](https://jitpack.io/v/ronghao/CacheManage.svg)](https://jitpack.io/#ronghao/CacheManage) [![](https://travis-ci.org/ronghao/CacheManage.svg?branch=master)](https://travis-ci.org/ronghao/CacheManage) [![GitHub license](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://raw.githubusercontent.com/ronghao/CacheManage/master/LICENSE) 3 | 4 | > android缓存管理器,分为两级缓存:内存缓存和文件缓存;先取内存数据,没有再从文件缓存中获取 5 | 6 | # 特点 7 | + 封装[ASimpleCache](https://github.com/yangfuhai/ASimpleCache),继承其所有功能 8 | + 二级缓存 9 | + 内存缓存(采用SoftReference和LruCache,防止内存溢出) 10 | + 文件缓存(由ASimpleCache实现) 11 | + 默认使用SD卡缓存 12 | + getCacheDir()获取的缓存文件较容易被删除 13 | + 大于1M的缓存文件,google建议使用getExternalCacheDir()缓存存储 14 | + 默认存储位置为app数据缓存位置 15 | + 为处理防止被删除,在数据库文件夹下创建cachemanage文件夹,数据存储在该文件夹下 16 | + 支持文件加密存储 17 | + 默认des3加密内容 18 | + 默认生成des3唯一密钥(建议使用默认生成的密钥) 19 | + 默认生成的默认密钥,每个客户端都是唯一的,互不相同 20 | + 默认密钥存储在KeyStore中,防逆向工程获取密钥 21 | + 支持基本数据类型、String、JSONObject、JSONArray、实体对象(Test类) 22 | + 不支持数据类型可转换成String存储 23 | + **缓存数据可设置过期时间,到期自动销毁** 24 | + 允许内存缓存 25 | + key值加密 26 | + 对应的本地缓存文件也加密 27 | + **添加数据监控机制** 28 | + **支持自定义加密算法** 29 | 30 | modifications by mohapps: 31 | + Implemented AES encryption instead of RSA 32 | + Managing AES key via Keystore for API>=23 33 | + Generating, Encrypting (by RSA) and saving AES key into (and loading, decrypting from) SharedPreferences in API<23 since Keystore doesn't support AES 34 | + RSA was unable to encrypt text longer than 128 bit (solved by AES implemention) 35 | + Using java.util.Base64 instead of android.util.Base64 (added class to library to use on all APIs including API<26) 36 | + Fixed an issue that caused crash on API 23 while generating keypair when app locale is Arabic, Persian,... 37 | 38 | 39 | To use AES encryption: 40 | ```java 41 | 42 | // Initialize 43 | CacheUtilConfig cc = CacheUtilConfig.builder(context) 44 | .setIEncryptStrategy(new KeyStoreEncryptStrategy(context, "cacheUtil")) 45 | .allowMemoryCache(true) 46 | .allowEncrypt(false) 47 | .build(); 48 | CacheUtil.init(cc); 49 | 50 | // Save value 51 | CacheUtil.put(key, value, isEncrypt); 52 | 53 | // Read value 54 | CacheUtil.get(key, isEncrypt); 55 | 56 | ``` 57 | 58 | # 使用方法 59 | #### 初始化配置(必须调用 activity或者application) 60 | ```java 61 | CacheUtilConfig cc = CacheUtilConfig.builder(getApplication()) 62 | .allowMemoryCache(true)//是否允许保存到内存 63 | .allowEncrypt(false)//是否允许加密 64 | .allowKeyEncrypt(true)//是否允许Key加密 65 | .preventPowerDelete(true)//强力防止删除,将缓存数据存储在app数据库目录下的cachemanage文件夹下 66 | .setACache(ACache.get(file1))//自定义ACache,file1为缓存自定义存储文件夹,设置该项,preventPowerDelete失效 67 | .setAlias("")//默认KeyStore加密算法私钥,建议设置.自定义加密算法,该功能失效 68 | .setIEncryptStrategy( 69 | new Des3EncryptStrategy(MainActivity.this, "WLIJkjdsfIlI789sd87dnu==", 70 | "haohaoha"))//自定义des3加密 71 | .build(); 72 | CacheUtil.init(cc);//初始化,必须调用 73 | ``` 74 | 75 | 76 | #### 初始化配置 77 | |默认|状态| 78 | |-|-| 79 | |默认内存缓存|缓存| 80 | |默认Key是否加密|加密| 81 | |默认value是否加密|加密| 82 | |默认加密算法|keystore加密| 83 | |默认加密私钥|包名| 84 | |强力防止删除|否,需设置| 85 | 86 | 87 | 88 | #### 保存数据 89 | ```java 90 | CacheUtil.put("key1", "测试数据1");//默认加密 91 | CacheUtil.put("key2", "测试数据2", true);//true代表加密存储 92 | CacheUtil.put("key3", "~!@#$%^&*()_+{}[];':,.<>`");//特殊字符串测试 93 | CacheUtil.put("key4", "~!@#$%^&*()_+{}[];':,.<>`", true);//加密特殊字符串测试 94 | CacheUtil.put("key5", new Test(1, "2"));//实体对象测试 95 | CacheUtil.put("key6", new Test(1, "2"), true);//加密实体对象测试 96 | CacheUtil.put("key7", jsonObject);//jsonObject对象测试 97 | CacheUtil.put("key8", jsonObject, true);//加密jsonObject对象测试 98 | CacheUtil.put("key9", jsonArray);//jsonArray对象测试 99 | CacheUtil.put("key10", jsonArray, true);//加密jsonArray对象测试 100 | CacheUtil.put("key11", 1);//jsonArray对象测试 101 | CacheUtil.put("key12", 1, true);//加密jsonArray对象测试 102 | CacheUtil.put("key13", "测试数据1", 5);//保存数据5秒 103 | CacheUtil.put("key14", new Test(1, "2"), 5);//保存对象数据5秒 104 | CacheUtil.put("key15", "测试数据1", 5, true);//加密保存数据5秒 105 | CacheUtil.put("key16", new Test(1, "2"), 5, true);//加密保存对象数据5秒 106 | CacheUtil.put(CacheUtil.translateKey("key17"), "123456", true);//key加密 107 | CacheUtil.put("key18", "测试数据18", false);//false代表不加密存储 108 | ``` 109 | #### 获取数据 110 | ```java 111 | String key1Value = CacheUtil.get("key1"); 112 | String key2Value = CacheUtil.get("key2", true); 113 | String key3Value = CacheUtil.get("key3"); 114 | String key4Value = CacheUtil.get("key4", true); 115 | Test key5Test = CacheUtil.get("key5", Test.class);//可能为null 116 | String key5Value = key5Test == null ? "" : key5Test.toString(); 117 | Test key6Test = CacheUtil.get("key6", Test.class, true); 118 | String key6Value = key6Test == null ? "" : key6Test.toString(); 119 | String key7Value = CacheUtil.get("key7"); 120 | String key8Value = CacheUtil.get("key8", true); 121 | String key9Value = CacheUtil.get("key9"); 122 | String key10Value = CacheUtil.get("key10", true); 123 | String key11Value = CacheUtil.get("key11"); 124 | String key12Value = CacheUtil.get("key12", true); 125 | String key13Value = CacheUtil.get("key13"); 126 | Test key14Test = CacheUtil.get("key14", Test.class); 127 | String key14Value = key14Test == null ? "" : key14Test.toString(); 128 | String key15Value = CacheUtil.get("key15", true); 129 | Test key16Test = CacheUtil.get("key16", Test.class, true); 130 | String key16Value = key16Test == null ? "" : key16Test.toString(); 131 | String key17Value = CacheUtil.get(CacheUtil.translateKey("key17"), true); 132 | String key18Value = CacheUtil.get("key18", false); 133 | Boolean key19Value = 134 | CacheUtil.get(CacheUtil.translateKey("null"), Boolean.class, false, true); 135 | //key19未存储的数据,返回默认值 136 | Test key20Value = CacheUtil.get(CacheUtil.translateKey("null1"), Test.class, 137 | new Test(1, "默认值"), true); 138 | //key20未存储的数据,返回默认值 139 | Test key21Value = CacheUtil.get(CacheUtil.translateKey("null1"), Test.class, true); 140 | //key21未存储的数据,且无默认构造方法,返回null 141 | ``` 142 | ```java 143 | CacheUtil.get("要查找的key") 144 | CacheUtil.get("要查找的key", 是否加密) 145 | CacheUtil.get("要查找的key",对应的实体对象) 146 | CacheUtil.get("要查找的key",对应的实体对象, 是否加密) 147 | CacheUtil.get("要查找的key",对应的实体对象, 错误情况下返回默认数据) 148 | CacheUtil.get("要查找的key",对应的实体对象, 错误情况下返回默认数据,是否加密) 149 | ``` 150 | 151 | #### 删除数据 152 | ```java 153 | CacheUtil.clearMemory("key1");//指定key内存缓存删除 154 | CacheUtil.clear("key8");//指定key内存缓存和文件缓存都删除 155 | CacheUtil.clearAllMemory();//所有内存缓存删除 156 | CacheUtil.clearAll();//所有内存缓存和文件缓存都删除 157 | ``` 158 | 159 | #### 数据监听 160 | ```java 161 | CacheObserver.getInstance().addObserver("key1", new IDataChangeListener() { 162 | @Override 163 | public void onDataChange(String str) { 164 | Toast.makeText(MainActivity.this, str, Toast.LENGTH_SHORT).show(); 165 | } 166 | }); 167 | ``` 168 | 169 | #### String与List互相转换 170 | ```java 171 | //String 转list 172 | List userList = gson.fromJson(string, new TypeToken>() {}.getType()); 173 | //List转String 174 | String string = new Gson().toJson(userList); 175 | ``` 176 | 177 | # 项目添加方法 178 | 在根 build.gradle中添加 179 | ``` 180 | allprojects { 181 | repositories { 182 | ... 183 | maven { url 'https://jitpack.io' } 184 | } 185 | } 186 | ``` 187 | 188 | 在项目build.gradle中添加 ![](https://jitpack.io/v/ronghao/CacheManage.svg) 189 | ``` 190 | dependencies { 191 | compile 'com.github.ronghao:CacheManage:1.3.6' 192 | } 193 | ``` 194 | # Java doc帮助文档 195 | [Java doc帮助文档](https://jitpack.io/com/github/ronghao/CacheManage/1.3.0/javadoc/) 196 | 197 | # 特别注意 198 | + **请混淆代码,减小库大小,默认没有混淆** 199 | + **卸载APP会清空数据** 200 | + 指定文件目录存储的,需要自行清除。卸载后重装,使用默认设置会重新生成新的密钥。旧数据无法继续使用 201 | + **文件缓存默认缓存到app缓存文件,在手机清理缓存空间时可被清理,请悉知:如不想被清理,请指定缓存位置存储** 202 | + 现在默认存在app缓存文件的父文件夹下,防止清理缓存时清理掉 203 | + 1.2.3版本后,存储在数据库文件夹下,防止被清除 204 | + 禁止传入空key 205 | + 如未存储数据时,get(key)会返回的字符串为空字符串 206 | + 如未存储数据(实体对象)时获取数据, 207 | + 如果实现了默认构造函数,会返回一个新的对象实例; 208 | + 如果未实现构造函数,会返回null 209 | + 如果存储bitmap等文件结构,可以转换为字符串存储 210 | 211 | # 版本更新说明 212 | + v1.3.5 213 | + 修改默认值方法返回null问题 214 | + v1.3.4 215 | + 修改默认值bug 216 | + v1.3.3 217 | + 添加缓存文件创建不成功判断 218 | + v1.3.2 219 | + 添加keystore判空操作 220 | + v1.3.1 221 | + 解决clear方法执行错误的问题 222 | + v1.3.0 223 | + 提高底层加密算法、增加安全性 224 | + v1.2.4 225 | + 添加线程锁,测试多线程下数据情况 226 | + v1.2.3 227 | + 修改默认缓存位置,防止被清理 228 | + v1.2.1 229 | + 添加clearAll()和clearAllMemory()方法 230 | + v1.2.0 231 | + 添加自定义加密算法,只需实现IEncryptStrategy接口,参见[Des3EncryptStrategy](https://github.com/ronghao/CacheManage/blob/master/library/src/main/java/com/haohaohu/cachemanage/strategy/Des3EncryptStrategy.java) 232 | + v1.1.2 233 | + 添加自定义ACache 234 | + 在[CacheUtilConfig](https://github.com/ronghao/CacheManage/blob/master/library/src/main/java/com/haohaohu/cachemanage/CacheUtilConfig.java)配置 235 | + v1.1.1 236 | + 添加数据监控机制 237 | + v1.1.0 238 | + 添加注解 239 | + 优化大量代码 240 | + v1.0.5 241 | + 添加KeyStore生成与存储密钥 242 | + v1.0.4 243 | + 添加配置项管理 244 | + 添加根据设备id自动生成des3秘钥 245 | + 默认des3加密 246 | 247 | 248 | # 关于 249 | + 个人博客:[www.haohaohu.com](http://www.haohaohu.com/) 250 | + 如果你也喜欢这个库,Star一下吧,欢迎Fork 251 | 252 | # License 253 | 254 | Copyright 2016 haohaohu 255 | 256 | Licensed under the Apache License, Version 2.0 (the "License"); 257 | you may not use this file except in compliance with the License. 258 | You may obtain a copy of the License at 259 | 260 | http://www.apache.org/licenses/LICENSE-2.0 261 | 262 | Unless required by applicable law or agreed to in writing, software 263 | distributed under the License is distributed on an "AS IS" BASIS, 264 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 265 | See the License for the specific language governing permissions and 266 | limitations under the License. 267 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /library/src/main/java/com/haohaohu/cachemanage/util/KeyStoreHelper.java: -------------------------------------------------------------------------------- 1 | package com.haohaohu.cachemanage.util; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.content.SharedPreferences; 6 | import android.content.res.Configuration; 7 | import android.content.res.Resources; 8 | import android.os.Build; 9 | import android.security.KeyPairGeneratorSpec; 10 | import android.security.keystore.KeyGenParameterSpec; 11 | import android.security.keystore.KeyProperties; 12 | import android.support.annotation.RequiresApi; 13 | 14 | import java.math.BigInteger; 15 | import java.nio.charset.StandardCharsets; 16 | import java.security.KeyPairGenerator; 17 | import java.security.KeyStore; 18 | import java.security.NoSuchAlgorithmException; 19 | import java.security.PrivateKey; 20 | import java.security.PublicKey; 21 | import java.security.cert.Certificate; 22 | import java.security.cert.CertificateEncodingException; 23 | import java.util.Calendar; 24 | import java.util.GregorianCalendar; 25 | import java.util.Locale; 26 | 27 | import javax.crypto.Cipher; 28 | import javax.crypto.KeyGenerator; 29 | import javax.crypto.NoSuchPaddingException; 30 | import javax.crypto.SecretKey; 31 | import javax.crypto.spec.IvParameterSpec; 32 | import javax.crypto.spec.SecretKeySpec; 33 | import javax.security.auth.x500.X500Principal; 34 | 35 | import mohapps.modified.java.util.Base64; 36 | 37 | 38 | 39 | /** 40 | * KeyStore 帮助类 用来缓存密钥,防止破解 41 | * {转自 https://gist.github.com/alphamu/cf44b2783fb2fd81cc53aca91276d481} 42 | * 43 | * @author haohao on 2017/8/24 10:37 44 | * @version v1.0 45 | */ 46 | @RequiresApi(api = Build.VERSION_CODES.KITKAT) 47 | public class KeyStoreHelper { 48 | private static final String TAG = "KeyStoreHelper"; 49 | 50 | 51 | /** 52 | * Creates a public and private key and stores it using the Android Key 53 | * Store, so that only this application will be able to access the keys. 54 | */ 55 | public static void createKeys(Context context, String alias) { 56 | if (!isSigningKey(alias)) { 57 | if (Build.VERSION.SDK_INT == Build.VERSION_CODES.M) { 58 | createKeysM(context, alias, false); 59 | } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 60 | createKeysN(alias, false); 61 | } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { 62 | createKeysJBMR2(context, alias); 63 | } 64 | } 65 | } 66 | //this is for API <=23 only 67 | private static void setLocale(Context context, Locale locale) { 68 | Resources resources = context.getResources(); 69 | Configuration configuration = resources.getConfiguration(); 70 | configuration.setLocale(locale); 71 | configuration.setLayoutDirection(locale); 72 | resources.updateConfiguration(configuration, resources.getDisplayMetrics()); 73 | } 74 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) 75 | private static void createKeysJBMR2(Context context, String alias) 76 | { 77 | try { 78 | Calendar start = new GregorianCalendar(); 79 | Calendar end = new GregorianCalendar(); 80 | end.add(Calendar.YEAR, 30); 81 | Locale initialLocale = context.getResources().getConfiguration().locale; 82 | setLocale(context, Locale.ENGLISH); 83 | KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(SecurityConstants.KEY_ALGORITHM_RSA, 84 | SecurityConstants.KEYSTORE_PROVIDER_ANDROID_KEYSTORE); 85 | keyPairGenerator.initialize(new KeyPairGeneratorSpec.Builder(context) 86 | // You'll use the alias later to retrieve the key. It's a key 87 | // for the key! 88 | .setAlias(alias) 89 | .setSubject(new X500Principal("CN=" + alias)) 90 | .setSerialNumber(BigInteger.valueOf(Math.abs(alias.hashCode()))) 91 | // Date range of validity for the generated pair. 92 | .setStartDate(start.getTime()) 93 | .setEndDate(end.getTime()) 94 | .build()); 95 | keyPairGenerator.generateKeyPair(); 96 | setLocale(context, initialLocale); 97 | getSecretKeyJBMR2(context, alias); 98 | } 99 | catch (Exception e){ 100 | throw new RuntimeException(e); 101 | } 102 | } 103 | @TargetApi(Build.VERSION_CODES.M) 104 | private static void createKeysM(Context context, String alias, boolean requireAuth) { 105 | try { 106 | //workaround to avoid crash on API 23 when app locale is Arabic, Persian,... 107 | //https://issuetracker.google.com/issues/37095309 108 | //https://stackoverflow.com/a/46602170/6305235 109 | Locale initialLocale = context.getResources().getConfiguration().locale; 110 | setLocale(context, Locale.ENGLISH); 111 | KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, SecurityConstants.KEYSTORE_PROVIDER_ANDROID_KEYSTORE); 112 | keyGenerator.init(new KeyGenParameterSpec.Builder(alias, 113 | KeyProperties.PURPOSE_ENCRYPT 114 | | KeyProperties.PURPOSE_DECRYPT) 115 | .setKeySize(256) 116 | .setBlockModes(KeyProperties.BLOCK_MODE_CBC) 117 | .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) 118 | // Only permit the private key to be used if the user authenticated 119 | // within the last five minutes. 120 | .setUserAuthenticationRequired(requireAuth) 121 | .build()); 122 | keyGenerator.generateKey(); 123 | setLocale(context, initialLocale); 124 | } catch (Exception e) { 125 | throw new RuntimeException(e); 126 | } 127 | } 128 | 129 | @TargetApi(Build.VERSION_CODES.N) 130 | private static void createKeysN(String alias, boolean requireAuth) { 131 | try { 132 | KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, SecurityConstants.KEYSTORE_PROVIDER_ANDROID_KEYSTORE); 133 | keyGenerator.init(new KeyGenParameterSpec.Builder(alias, 134 | KeyProperties.PURPOSE_ENCRYPT 135 | | KeyProperties.PURPOSE_DECRYPT) 136 | .setKeySize(256) 137 | .setBlockModes(KeyProperties.BLOCK_MODE_CBC) 138 | .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) 139 | // Only permit the private key to be used if the user authenticated 140 | // within the last five minutes. 141 | .setUserAuthenticationRequired(requireAuth) 142 | .build()); 143 | keyGenerator.generateKey(); 144 | } catch (Exception e) { 145 | throw new RuntimeException(e); 146 | } 147 | } 148 | 149 | /** 150 | * JBMR2+ If Key with the default alias exists, returns true, else false. 151 | * on pre-JBMR2 returns true always. 152 | */ 153 | private static boolean isSigningKey(String alias) { 154 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { 155 | try { 156 | KeyStore keyStore = 157 | KeyStore.getInstance(SecurityConstants.KEYSTORE_PROVIDER_ANDROID_KEYSTORE); 158 | keyStore.load(null); 159 | return keyStore.containsAlias(alias); 160 | } catch (Exception e) { 161 | return false; 162 | } 163 | } else { 164 | return false; 165 | } 166 | } 167 | 168 | /** 169 | * Returns the private key signature on JBMR2+ or else null. 170 | */ 171 | public static String getSigningKey(String alias) { 172 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 && Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { 173 | KeyStore.PrivateKeyEntry keyEntry = getPrivateKeyEntry(alias); 174 | if (keyEntry == null) { 175 | return null; 176 | } 177 | Certificate cert = keyEntry.getCertificate(); 178 | if (cert == null) { 179 | return null; 180 | } 181 | try { 182 | return Base64.getEncoder().encodeToString(cert.getEncoded()); 183 | } catch (CertificateEncodingException e) { 184 | e.printStackTrace(); 185 | return null; 186 | } 187 | } else { 188 | return null; 189 | } 190 | } 191 | 192 | private static KeyStore.PrivateKeyEntry getPrivateKeyEntry(String alias) { 193 | try { 194 | KeyStore ks = 195 | KeyStore.getInstance(SecurityConstants.KEYSTORE_PROVIDER_ANDROID_KEYSTORE); 196 | ks.load(null); 197 | KeyStore.Entry entry = ks.getEntry(alias, null); 198 | 199 | if (entry == null) { 200 | return null; 201 | } 202 | 203 | if (!(entry instanceof KeyStore.PrivateKeyEntry)) { 204 | return null; 205 | } 206 | return (KeyStore.PrivateKeyEntry) entry; 207 | } catch (Exception e) { 208 | return null; 209 | } 210 | } 211 | @RequiresApi(api = Build.VERSION_CODES.KITKAT) 212 | private static SecretKey getSecretKeyJBMR2(Context context, String alias) { 213 | SharedPreferences pref = context.getSharedPreferences(SecurityConstants.ENCRYPTION, Context.MODE_PRIVATE); 214 | String aesKey = pref.getString(SecurityConstants.AES_KEY, ""); 215 | if (aesKey.equals("")) { 216 | try { 217 | KeyGenerator keygen = KeyGenerator.getInstance(SecurityConstants.KEY_ALGORITHM_AES); 218 | keygen.init(128); 219 | SecretKey secretKey = keygen.generateKey(); 220 | SharedPreferences.Editor editor = pref.edit(); 221 | editor.putString(SecurityConstants.AES_KEY, encryptKey(alias, new String(secretKey.getEncoded(), StandardCharsets.ISO_8859_1))); 222 | editor.apply(); 223 | return secretKey; 224 | } catch (NoSuchAlgorithmException e) { 225 | e.printStackTrace(); 226 | } 227 | } else { 228 | return new SecretKeySpec(decryptKey(alias, aesKey).getBytes(StandardCharsets.ISO_8859_1), 0, 16, SecurityConstants.KEY_ALGORITHM_AES); 229 | } 230 | return null; 231 | } 232 | @TargetApi(Build.VERSION_CODES.M) 233 | private static SecretKey getSecretKeyM(String alias) { 234 | try { 235 | KeyStore ks = 236 | KeyStore.getInstance(SecurityConstants.KEYSTORE_PROVIDER_ANDROID_KEYSTORE); 237 | ks.load(null); 238 | KeyStore.Entry entry = ks.getEntry(alias, null); 239 | 240 | if (entry == null) { 241 | return null; 242 | } 243 | 244 | if (!(entry instanceof KeyStore.SecretKeyEntry)) { 245 | return null; 246 | } 247 | return ((KeyStore.SecretKeyEntry) entry).getSecretKey(); 248 | } catch (Exception e) { 249 | return null; 250 | } 251 | } 252 | 253 | 254 | private static String encryptKey(String alias, String aesKey) { 255 | try { 256 | KeyStore.PrivateKeyEntry keyEntry = getPrivateKeyEntry(alias); 257 | if (keyEntry == null) { 258 | return ""; 259 | } 260 | PublicKey publicKey = keyEntry.getCertificate().getPublicKey(); 261 | Cipher cipher = getCipherRSA(); 262 | cipher.init(Cipher.ENCRYPT_MODE, publicKey); 263 | return Base64.getEncoder().encodeToString(cipher.doFinal(aesKey.getBytes(StandardCharsets.UTF_8))); 264 | } catch (Exception e) { 265 | throw new RuntimeException(e); 266 | } 267 | } 268 | private static String decryptKey(String alias, String aesKeyCipher) { 269 | try { 270 | KeyStore.PrivateKeyEntry keyEntry = getPrivateKeyEntry(alias); 271 | if (keyEntry == null) { 272 | return ""; 273 | } 274 | PrivateKey privateKey = keyEntry.getPrivateKey(); 275 | Cipher cipher = getCipherRSA(); 276 | cipher.init(Cipher.DECRYPT_MODE, privateKey); 277 | return new String(cipher.doFinal(Base64.getDecoder().decode(aesKeyCipher.getBytes(StandardCharsets.UTF_8)))); 278 | } catch (Exception e) { 279 | throw new RuntimeException(e); 280 | } 281 | } 282 | public static String encrypt(Context context, String alias, String plainText) { 283 | try { 284 | SecretKey secretKey = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)?getSecretKeyM(alias):getSecretKeyJBMR2(context, alias); 285 | if (secretKey == null) { 286 | return ""; 287 | } 288 | Cipher cipher = getCipherAES(); 289 | cipher.init(Cipher.ENCRYPT_MODE, secretKey); 290 | byte[] plainTextBytes = plainText.getBytes(StandardCharsets.UTF_8); 291 | byte[] encryptedTextBytes = cipher.doFinal(plainTextBytes); 292 | IvParameterSpec ivParams = cipher.getParameters().getParameterSpec(IvParameterSpec.class); 293 | byte[] iv = ivParams.getIV(); 294 | byte[] cb = new byte[iv.length + encryptedTextBytes.length]; 295 | for (int i = 0; i < cb.length; ++i) { 296 | cb[i] = i < iv.length ? iv[i] : encryptedTextBytes[i - iv.length]; 297 | } 298 | return Base64.getEncoder().encodeToString(cb); 299 | } catch (Exception e) { 300 | throw new RuntimeException(e); 301 | } 302 | 303 | } 304 | 305 | 306 | public static String decrypt(Context context, String alias, String savedText) { 307 | try { 308 | SecretKey secretKey = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)?getSecretKeyM(alias):getSecretKeyJBMR2(context, alias); 309 | if (secretKey == null) { 310 | return ""; 311 | } 312 | Cipher cipher = getCipherAES(); 313 | byte[] savedTextBytes = Base64.getDecoder().decode(savedText.getBytes(StandardCharsets.UTF_8)); 314 | byte[] iv = new byte[16]; 315 | byte[] encryptedTextBytes = new byte[savedTextBytes.length - iv.length]; 316 | for (int i = 0; i < savedTextBytes.length; i++) { 317 | if (i < iv.length) { 318 | iv[i] = savedTextBytes[i]; 319 | } else { 320 | encryptedTextBytes[i-iv.length] = savedTextBytes[i]; 321 | } 322 | } 323 | IvParameterSpec ivParams = new IvParameterSpec(iv); 324 | cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParams); 325 | return new String(cipher.doFinal(encryptedTextBytes)); 326 | } catch (Exception e) { 327 | throw new RuntimeException(e); 328 | } 329 | } 330 | 331 | private static Cipher getCipherRSA() throws NoSuchPaddingException, NoSuchAlgorithmException { 332 | return Cipher.getInstance(String.format("%s/%s/%s", SecurityConstants.KEY_ALGORITHM_RSA, 333 | SecurityConstants.BLOCK_MODE_ECB, SecurityConstants.ENCRYPTION_PADDING_RSA_PKCS1)); 334 | } 335 | private static Cipher getCipherAES() throws NoSuchPaddingException, NoSuchAlgorithmException { 336 | return Cipher.getInstance(String.format("%s/%s/%s", SecurityConstants.KEY_ALGORITHM_AES, 337 | SecurityConstants.BLOCK_MODE_CBC, SecurityConstants.ENCRYPTION_PADDING_PKCS7)); 338 | } 339 | 340 | public interface SecurityConstants { 341 | String KEYSTORE_PROVIDER_ANDROID_KEYSTORE = "AndroidKeyStore"; 342 | 343 | public static final String KEY_ALGORITHM_RSA = "RSA"; 344 | public static final String KEY_ALGORITHM_EC = "EC"; 345 | public static final String KEY_ALGORITHM_AES = "AES"; 346 | 347 | public static final String ENCRYPTION_PADDING_NONE = "NoPadding"; 348 | public static final String ENCRYPTION_PADDING_PKCS7 = "PKCS7Padding"; 349 | public static final String ENCRYPTION_PADDING_RSA_PKCS1 = "PKCS1Padding"; 350 | public static final String ENCRYPTION_PADDING_RSA_OAEP = "OAEPPadding"; 351 | 352 | public static final String BLOCK_MODE_ECB = "ECB"; 353 | public static final String BLOCK_MODE_CBC = "CBC"; 354 | public static final String BLOCK_MODE_CTR = "CTR"; 355 | public static final String BLOCK_MODE_GCM = "GCM"; 356 | 357 | public static final String ENCRYPTION = "ENCRIPTION"; 358 | public static final String AES_KEY = "AES_KEY"; 359 | 360 | String SIGNATURE_SHA256withRSA = "SHA256withRSA"; 361 | String SIGNATURE_SHA512withRSA = "SHA512withRSA"; 362 | } 363 | } 364 | -------------------------------------------------------------------------------- /app/src/main/java/com/haohaohu/cachemanagesample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.haohaohu.cachemanagesample; 2 | 3 | import android.os.Bundle; 4 | import android.os.Environment; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.util.Log; 7 | import android.view.Menu; 8 | import android.view.MenuInflater; 9 | import android.view.MenuItem; 10 | import android.view.View; 11 | import android.widget.TextView; 12 | import android.widget.Toast; 13 | import com.haohaohu.cachemanage.ACache; 14 | import com.haohaohu.cachemanage.CacheUtil; 15 | import com.haohaohu.cachemanage.CacheUtilConfig; 16 | import com.haohaohu.cachemanage.observer.CacheObserver; 17 | import com.haohaohu.cachemanage.observer.IDataChangeListener; 18 | import com.haohaohu.cachemanage.strategy.Des3EncryptStrategy; 19 | import java.io.File; 20 | import java.io.IOException; 21 | import java.security.KeyStore; 22 | import java.security.KeyStoreException; 23 | import java.security.NoSuchAlgorithmException; 24 | import java.security.cert.CertificateException; 25 | import java.util.concurrent.CountDownLatch; 26 | import java.util.concurrent.locks.ReentrantLock; 27 | import javax.crypto.KeyGenerator; 28 | import org.json.JSONArray; 29 | import org.json.JSONException; 30 | import org.json.JSONObject; 31 | 32 | /** 33 | * 测试Activity 34 | * 35 | * @author ME 36 | */ 37 | public class MainActivity extends AppCompatActivity { 38 | 39 | private TextView mTextView; 40 | private JSONArray jsonArray; 41 | private JSONObject jsonObject; 42 | private Integer index = 0; 43 | private CountDownLatch countDownLatch = new CountDownLatch(10); 44 | private ReentrantLock lock = new ReentrantLock(true); 45 | 46 | @Override 47 | protected void onCreate(Bundle savedInstanceState) { 48 | super.onCreate(savedInstanceState); 49 | setContentView(R.layout.activity_main); 50 | initData(); 51 | init(); 52 | } 53 | 54 | private void initData() { 55 | jsonObject = new JSONObject(); 56 | try { 57 | jsonObject.put("11", "11"); 58 | } catch (JSONException e) { 59 | e.printStackTrace(); 60 | } 61 | 62 | jsonArray = new JSONArray(); 63 | jsonArray.put(jsonObject); 64 | } 65 | 66 | @Override 67 | public boolean onCreateOptionsMenu(Menu menu) { 68 | MenuInflater inflater = getMenuInflater(); 69 | inflater.inflate(R.menu.menu_main, menu); 70 | return true; 71 | } 72 | 73 | @Override 74 | public boolean onOptionsItemSelected(MenuItem item) { 75 | switch (item.getItemId()) { 76 | case R.id.action_default: 77 | initCacheConfigDefault(); 78 | clearMemory(); 79 | case R.id.action_config1: 80 | initCacheConfig1(); 81 | clearMemory(); 82 | break; 83 | case R.id.action_config2: 84 | initCacheConfig2(); 85 | clearMemory(); 86 | break; 87 | case R.id.action_config3: 88 | initCacheConfig3(); 89 | clearMemory(); 90 | break; 91 | default: 92 | break; 93 | } 94 | return true; 95 | } 96 | 97 | @Override 98 | protected void onResume() { 99 | super.onResume(); 100 | } 101 | 102 | private void init() { 103 | mTextView = (TextView) findViewById(R.id.main_text3); 104 | initCacheConfig1(); 105 | initEvent(); 106 | initObserver(); 107 | } 108 | 109 | private void initObserver() { 110 | CacheObserver.getInstance().addObserver("key1", new IDataChangeListener() { 111 | @Override 112 | public void onDataChange(String key, String str) { 113 | Toast.makeText(MainActivity.this, key + ":" + str, Toast.LENGTH_SHORT).show(); 114 | } 115 | }); 116 | 117 | CacheObserver.addListener("key2", new IDataChangeListener() { 118 | @Override 119 | public void onDataChange(String key, String str) { 120 | Toast.makeText(MainActivity.this, key + ":" + str, Toast.LENGTH_SHORT).show(); 121 | } 122 | }); 123 | } 124 | 125 | private void initEvent() { 126 | findViewById(R.id.main_text).setOnClickListener(new View.OnClickListener() { 127 | @Override 128 | public void onClick(View v) { 129 | 130 | String key1Value = CacheUtil.get("key1"); 131 | String key2Value = CacheUtil.get("key2", true); 132 | String key3Value = CacheUtil.get("key3", false); 133 | String key4Value = CacheUtil.get("key4", true); 134 | Test key5Test = CacheUtil.get("key5", Test.class);//可能为null 135 | String key5Value = key5Test == null ? "" : key5Test.toString(); 136 | Test key6Test = CacheUtil.get("key6", Test.class, true); 137 | String key6Value = key6Test == null ? "" : key6Test.toString(); 138 | String key7Value = CacheUtil.get("key7"); 139 | String key8Value = CacheUtil.get("key8", true); 140 | String key9Value = CacheUtil.get("key9"); 141 | String key10Value = CacheUtil.get("key10", true); 142 | String key11Value = CacheUtil.get("key11"); 143 | String key12Value = CacheUtil.get("key12", true); 144 | String key13Value = CacheUtil.get("key13"); 145 | Test key14Test = CacheUtil.get("key14", Test.class); 146 | String key14Value = key14Test == null ? "" : key14Test.toString(); 147 | String key15Value = CacheUtil.get("key15", true); 148 | Test key16Test = CacheUtil.get("key16", Test.class, true); 149 | String key16Value = key16Test == null ? "" : key16Test.toString(); 150 | String key17Value = CacheUtil.get(CacheUtil.translateKey("key17"), true); 151 | String key18Value = CacheUtil.get("key18", false); 152 | Boolean key19Value = 153 | CacheUtil.get(CacheUtil.translateKey("null"), Boolean.class, false, true); 154 | //key19未存储的数据,返回默认值 155 | Test key20Value = CacheUtil.get(CacheUtil.translateKey("null1"), Test.class, 156 | new Test(1, "默认值"), true); 157 | //key20未存储的数据,返回默认值 158 | Test key21Value = CacheUtil.get(CacheUtil.translateKey("null1"), Test.class, true); 159 | //key21未存储的数据,且无默认构造方法,返回null 160 | int key22Value = CacheUtil.get("key22", int.class, 100,true); 161 | //key21已存储的数据,返回默认值 162 | 163 | String value = "测试:\n" 164 | + "字符串(默认方式):" 165 | + check("测试数据1", key1Value) 166 | + "\n" 167 | + "字符串(加密):" 168 | + check("测试数据2", key2Value) 169 | + "\n" 170 | + "字符串(不加密):" 171 | + check("测试数据18", key18Value) 172 | + "\n" 173 | + "特殊字符串(不加密):" 174 | + check("~!@#$%^&*()_+{}[];':,.<>`", key3Value) 175 | + "\n" 176 | + "特殊字符串(加密):" 177 | + check("~!@#$%^&*()_+{}[];':,.<>`", key4Value) 178 | + "\n" 179 | + "实体对象[Test类](不加密):" 180 | + check(new Test(1, "2").toString(), key5Value) 181 | + "\n" 182 | + "实体对象[Test类](加密):" 183 | + check(new Test(1, "2").toString(), key6Value) 184 | + "\n" 185 | + "jsonObject对象(不加密):" 186 | + check(jsonObject.toString(), key7Value) 187 | + "\n" 188 | + "jsonObject对象(加密):" 189 | + check(jsonObject.toString(), key8Value) 190 | + "\n" 191 | + "jsonArray对象(不加密):" 192 | + check(jsonArray.toString(), key9Value) 193 | + "\n" 194 | + "jsonArray对象(加密):" 195 | + check(jsonArray.toString(), key10Value) 196 | + "\n" 197 | + "数字(不加密):" 198 | + check(1 + "", key11Value) 199 | + "\n" 200 | + "数字(加密):" 201 | + check(1 + "", key12Value) 202 | + "\n" 203 | + "字符串(5秒):" 204 | + check("测试数据1", key13Value) 205 | + "\n" 206 | + "实体对象[Test类](5秒):" 207 | + check(new Test(1, "2").toString(), key14Value) 208 | + "\n" 209 | + "字符串(5秒)(加密):" 210 | + check("测试数据1", key15Value) 211 | + "\n" 212 | + "实体对象(5秒)(加密):" 213 | + check(new Test(1, "2").toString(), key16Value) 214 | + "\n" 215 | + "对key加密:" 216 | + check("123456", key17Value) 217 | + "\n" 218 | + "未保存数据(Boolean类型): 默认值" 219 | + key19Value 220 | + "\n" 221 | + "未保存实体对象[Test类](有默认返回对象):" 222 | + (key20Value != null ? key20Value.toString() : null) 223 | + "\n" 224 | + "未保存实体对象[Test类](无默认返回对象):" 225 | + (key21Value != null ? key21Value.toString() : "null") 226 | + "\n" 227 | + "保存实体对象[Test类](有默认返回对象):" 228 | + (String.valueOf(key22Value)) 229 | + "\n"; 230 | mTextView.setText(value); 231 | } 232 | }); 233 | 234 | findViewById(R.id.main_text1).setOnClickListener(new View.OnClickListener() { 235 | @Override 236 | public void onClick(View v) { 237 | CacheUtil.put("key1", "测试数据1");//默认加密状态 238 | CacheUtil.put("key2", "测试数据2", true);//true代表加密存储 239 | CacheUtil.put("key18", "测试数据18", false);//false代表不加密存储 240 | CacheUtil.put("key3", "~!@#$%^&*()_+{}[];':,.<>`");//特殊字符串测试 241 | CacheUtil.put("key4", "~!@#$%^&*()_+{}[];':,.<>`", true);//加密特殊字符串测试 242 | CacheUtil.put("key5", new Test(1, "2"));//实体对象测试 243 | CacheUtil.put("key6", new Test(1, "2"), true);//加密实体对象测试 244 | CacheUtil.put("key7", jsonObject);//jsonObject对象测试 245 | CacheUtil.put("key8", jsonObject, true);//加密jsonObject对象测试 246 | CacheUtil.put("key9", jsonArray);//jsonArray对象测试 247 | CacheUtil.put("key10", jsonArray, true);//加密jsonArray对象测试 248 | CacheUtil.put("key11", 1);//jsonArray对象测试 249 | CacheUtil.put("key12", 1, true);//加密jsonArray对象测试 250 | CacheUtil.put("key13", "测试数据1", 5);//保存数据5秒 251 | CacheUtil.put("key14", new Test(1, "2"), 5);//保存对象数据5秒 252 | CacheUtil.put("key15", "测试数据1", 5, true);//加密保存数据5秒 253 | CacheUtil.put("key16", new Test(1, "2"), 5, true);//加密保存对象数据5秒 254 | CacheUtil.put(CacheUtil.translateKey("key17"), "123456", true);//key加密 255 | CacheUtil.put("key22", 1);//测试默认值返回 256 | Toast.makeText(MainActivity.this, "保存成功", Toast.LENGTH_SHORT).show(); 257 | } 258 | }); 259 | 260 | findViewById(R.id.main_text2).setOnClickListener(new View.OnClickListener() { 261 | @Override 262 | public void onClick(View v) { 263 | index = 0; 264 | countDownLatch = new CountDownLatch(10); 265 | Toast.makeText(MainActivity.this, "压力测试,看Log", Toast.LENGTH_SHORT).show(); 266 | Thread thread = new Thread(new Runnable() { 267 | @Override 268 | public void run() { 269 | doTask(); 270 | } 271 | }); 272 | thread.start(); 273 | } 274 | }); 275 | 276 | findViewById(R.id.main_text4). 277 | setOnClickListener(new View.OnClickListener() { 278 | @Override 279 | public void onClick(View v) { 280 | clearMemory(); 281 | Toast.makeText(MainActivity.this, "清理内存成功", Toast.LENGTH_SHORT).show(); 282 | } 283 | }); 284 | 285 | findViewById(R.id.main_text5). 286 | setOnClickListener(new View.OnClickListener() { 287 | @Override 288 | public void onClick(View v) { 289 | CacheUtil.clearAllMemory(); 290 | Toast.makeText(MainActivity.this, "清理所有内存缓存成功", Toast.LENGTH_SHORT).show(); 291 | } 292 | }); 293 | 294 | findViewById(R.id.main_text6). 295 | 296 | setOnClickListener(new View.OnClickListener() { 297 | @Override 298 | public void onClick(View v) { 299 | CacheUtil.clearAll(); 300 | Toast.makeText(MainActivity.this, "清理缓存成功", Toast.LENGTH_SHORT).show(); 301 | } 302 | }); 303 | 304 | findViewById(R.id.main_text7). 305 | 306 | setOnClickListener(new View.OnClickListener() { 307 | @Override 308 | public void onClick(View v) { 309 | CacheUtil.clear("key1"); 310 | CacheUtil.clear("key2"); 311 | CacheUtil.clear("key18"); 312 | CacheUtil.clear("key3"); 313 | CacheUtil.clear("key4"); 314 | Toast.makeText(MainActivity.this, "清理缓存成功", Toast.LENGTH_SHORT).show(); 315 | } 316 | }); 317 | } 318 | 319 | private void doTask() { 320 | long startTime = System.currentTimeMillis(); //获取开始时间 321 | for (int i = 0; i < 10; i++) { 322 | Thread thread = new Thread(new Runnable() { 323 | @Override 324 | public void run() { 325 | synchronized (MainActivity.this) { 326 | index++; 327 | } 328 | CacheUtil.put("key0", "" + (index));//默认加密状态 329 | countDownLatch.countDown(); 330 | } 331 | }); 332 | thread.start(); 333 | } 334 | try { 335 | countDownLatch.await(); 336 | } catch (InterruptedException e) { 337 | e.printStackTrace(); 338 | } 339 | String str1 = CacheUtil.get("key0"); 340 | Log.e("time", str1); 341 | long endTime = System.currentTimeMillis(); //获取结束时间 342 | Log.e("time", "程序运行时间: " + (endTime - startTime) + "ms"); 343 | } 344 | 345 | private void initCacheConfigDefault() { 346 | CacheUtilConfig cc = 347 | CacheUtilConfig.builder(getApplication()).allowMemoryCache(true)//是否允许保存到内存 348 | .allowEncrypt(false)//是否允许加密 349 | .build(); 350 | CacheUtil.init(cc);//初始化,必须调用 351 | } 352 | 353 | private void initCacheConfig1() { 354 | CacheUtilConfig cc = 355 | CacheUtilConfig.builder(getApplication()).allowMemoryCache(true)//是否允许保存到内存 356 | .allowEncrypt(false)//是否允许加密 357 | .allowKeyEncrypt(true)//是否允许Key加密 358 | //.preventPowerDelete(true)//强力防止删除,将缓存数据存储在app数据库目录下的cachemanage文件夹下 359 | // .setACache(ACache.get(file1))//自定义ACache,file1为缓存自定义存储文件夹,设置该项,preventPowerDelete失效 360 | .setAlias("")//默认KeyStore加密算法私钥,建议设置.自定义加密算法,该功能失效 361 | .setIEncryptStrategy(new Des3EncryptStrategy(MainActivity.this, 362 | "WLIJkjdsfIlI789sd87dnu==", "haohaoha"))//自定义des3加密 363 | .build(); 364 | CacheUtil.init(cc);//初始化,必须调用 365 | } 366 | 367 | private void initCacheConfig2() { 368 | CacheUtilConfig cc = CacheUtilConfig.builder(MainActivity.this) 369 | .setIEncryptStrategy( 370 | new Des3EncryptStrategy(MainActivity.this, "WLIJkjdsfIlI789sd87dnu==", 371 | "haohaoha")) 372 | .allowMemoryCache(true)//是否允许保存到内存 373 | .allowEncrypt(true)//是否允许加密 374 | .build(); 375 | CacheUtil.init(cc);//初始化,必须调用 376 | KeyGenerator xd; 377 | try { 378 | xd = KeyGenerator.getInstance("DES"); 379 | String str = xd.generateKey().toString(); 380 | Log.e("str", str); 381 | } catch (NoSuchAlgorithmException e) { 382 | e.printStackTrace(); 383 | } 384 | 385 | try { 386 | KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); 387 | keyStore.load(null); 388 | } catch (KeyStoreException e) { 389 | e.printStackTrace(); 390 | } catch (CertificateException e) { 391 | e.printStackTrace(); 392 | } catch (NoSuchAlgorithmException e) { 393 | e.printStackTrace(); 394 | } catch (IOException e) { 395 | e.printStackTrace(); 396 | } 397 | } 398 | 399 | private void initCacheConfig3() { 400 | File file = new File(ACache.getDiskCacheDir(MainActivity.this)); 401 | File file1 = new File(file.getParent(), "cachemanage"); 402 | CacheUtilConfig cc = 403 | CacheUtilConfig.builder(getApplication()).allowMemoryCache(true)//是否允许保存到内存 404 | .allowEncrypt(false)//是否允许加密 405 | .setACache(ACache.get(file1))//自定义ACache 406 | .build(); 407 | CacheUtil.init(cc);//初始化,必须调用 408 | } 409 | 410 | private void initCacheConfig4() { 411 | File file1 = new File(Environment.getExternalStorageDirectory(), "cache"); 412 | CacheUtilConfig cc = 413 | CacheUtilConfig.builder(getApplication()).allowMemoryCache(true)//是否允许保存到内存 414 | .allowEncrypt(false)//是否允许加密 415 | // .preventPowerDelete(true)//强力防止删除,将缓存数据存储在app数据库目录下的cachemanage文件夹下 416 | .setACache(ACache.get(file1))//自定义ACache 417 | .build(); 418 | CacheUtil.init(cc);//初始化,必须调用 419 | } 420 | 421 | public void clearMemory() { 422 | CacheUtil.clearMemory("key1"); 423 | CacheUtil.clearMemory("key2"); 424 | CacheUtil.clearMemory("key18"); 425 | CacheUtil.clearMemory("key3"); 426 | CacheUtil.clearMemory("key4"); 427 | CacheUtil.clearMemory("key5"); 428 | CacheUtil.clearMemory("key6"); 429 | CacheUtil.clearMemory("key7"); 430 | CacheUtil.clearMemory("key8"); 431 | CacheUtil.clearMemory("key9"); 432 | CacheUtil.clearMemory("key10"); 433 | CacheUtil.clearMemory("key11"); 434 | CacheUtil.clearMemory("key12"); 435 | CacheUtil.clearMemory("key13"); 436 | CacheUtil.clearMemory("key14"); 437 | CacheUtil.clearMemory("key15"); 438 | CacheUtil.clearMemory("key16"); 439 | CacheUtil.clearMemory(CacheUtil.translateKey("key17")); 440 | } 441 | 442 | public String check(String str, String str1) { 443 | return str.equals(str1) ? "ok" : "fail"; 444 | } 445 | } 446 | -------------------------------------------------------------------------------- /library/src/main/java/com/haohaohu/cachemanage/CacheUtil.java: -------------------------------------------------------------------------------- 1 | package com.haohaohu.cachemanage; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.graphics.Canvas; 7 | import android.graphics.PixelFormat; 8 | import android.graphics.drawable.BitmapDrawable; 9 | import android.graphics.drawable.Drawable; 10 | import android.support.annotation.NonNull; 11 | import android.support.annotation.Nullable; 12 | import android.text.TextUtils; 13 | import android.util.LruCache; 14 | import com.google.gson.Gson; 15 | import com.haohaohu.cachemanage.observer.CacheObserver; 16 | import com.haohaohu.cachemanage.util.Base64Util; 17 | import com.haohaohu.cachemanage.util.LockUtil; 18 | import com.haohaohu.cachemanage.util.Md5Utils; 19 | import java.io.ByteArrayOutputStream; 20 | import java.lang.ref.SoftReference; 21 | import org.json.JSONArray; 22 | import org.json.JSONObject; 23 | 24 | /** 25 | * 数据缓存类,包括内存缓存和文件缓存 26 | * 先取内存数据,没有再从文件缓存中取 27 | * 28 | * @author haohao on 2017/6/9 14:56 29 | * @version v1.1 30 | */ 31 | public class CacheUtil { 32 | 33 | private CacheUtilConfig mConfig; 34 | private SoftReference> mLuCache = 35 | new SoftReference<>(new LruCache(50)); 36 | 37 | private static CacheUtil getInstance() { 38 | return CacheUtilHolder.mInstance; 39 | } 40 | 41 | /** 42 | * 初始化工具类,使用之前调用 43 | * 44 | * @param config 配置 45 | */ 46 | public static void init(CacheUtilConfig config) { 47 | if (config == null) { 48 | throw new NullPointerException("u should Builder first"); 49 | } 50 | getInstance().mConfig = config; 51 | } 52 | 53 | protected static CacheUtilConfig getConfig() { 54 | if (getInstance().mConfig == null) { 55 | throw new NullPointerException("u should Builder first"); 56 | } 57 | return getInstance().mConfig; 58 | } 59 | 60 | /** 61 | * 获取ApplicationContext 62 | * 63 | * @return ApplicationContext 64 | */ 65 | private static Context getContext() { 66 | if (getConfig().getContext() != null) { 67 | return getConfig().getContext(); 68 | } 69 | throw new NullPointerException("u should init first"); 70 | } 71 | 72 | /** 73 | * 获取LruCache,防止弱引用销毁 74 | */ 75 | private static LruCache getLruCache() { 76 | LruCache lruCache = getInstance().mLuCache.get(); 77 | if (lruCache == null) { 78 | getInstance().mLuCache = new SoftReference<>(new LruCache(50)); 79 | lruCache = getInstance().mLuCache.get(); 80 | } 81 | return lruCache; 82 | } 83 | 84 | /** 85 | * 保存key和value到内存缓存和文件缓存 86 | * 87 | * @param key 保存的key 88 | * @param value 保存的value 89 | */ 90 | public static void put(String key, @NonNull String value) { 91 | put(key, value, getConfig().isEncrypt()); 92 | } 93 | 94 | /** 95 | * 保存key和value到内存缓存和文件缓存 96 | * 97 | * @param key 保存的key 98 | * @param value 保存的value 99 | * @param isEncrypt 是否加密 100 | */ 101 | public static void put(String key, @NonNull String value, boolean isEncrypt) { 102 | if (TextUtils.isEmpty(key) || TextUtils.isEmpty(value)) { 103 | return; 104 | } 105 | LockUtil.getInstance().writeLock().lock(); 106 | if (getConfig().isKeyEncrypt()) { 107 | key = translateSecretKey(key); 108 | } 109 | if (getConfig().isMemoryCache()) { 110 | getLruCache().put(key, value); 111 | } 112 | getConfig().getACache().put(key, value, isEncrypt); 113 | CacheObserver.getInstance().notifyDataChange(key, value); 114 | LockUtil.getInstance().writeLock().unlock(); 115 | } 116 | 117 | /** 118 | * 保存key和value到内存缓存和文件缓存 119 | * 120 | * @param key 保存的key 121 | * @param value 保存的value 122 | * @param time 过期时间 123 | */ 124 | public static void put(String key, @NonNull String value, int time) { 125 | put(key, value, time, getConfig().isEncrypt()); 126 | } 127 | 128 | /** 129 | * 保存key和value到内存缓存和文件缓存 130 | * 131 | * @param key 保存的key 132 | * @param value 保存的value 133 | * @param time 过期时间 134 | * @param isEncrypt 是否加密 135 | */ 136 | public static void put(String key, @NonNull String value, int time, boolean isEncrypt) { 137 | if (TextUtils.isEmpty(key) || TextUtils.isEmpty(value)) { 138 | return; 139 | } 140 | LockUtil.getInstance().writeLock().lock(); 141 | if (getConfig().isKeyEncrypt()) { 142 | key = translateSecretKey(key); 143 | } 144 | if (getConfig().isMemoryCache()) { 145 | getLruCache().put(key, Utils.newStringWithDateInfo(time, value)); 146 | } 147 | getConfig().getACache().put(key, value, time, isEncrypt); 148 | CacheObserver.getInstance().notifyDataChange(key, value); 149 | LockUtil.getInstance().writeLock().unlock(); 150 | } 151 | 152 | /** 153 | * 保存key和value到内存缓存和文件缓存 154 | * 155 | * @param key 保存的key 156 | * @param value 保存的value 157 | * @param 对应的实体对象 158 | */ 159 | public static void put(String key, @NonNull T value) { 160 | put(key, value, getConfig().isEncrypt()); 161 | } 162 | 163 | /** 164 | * 保存key和value到内存缓存和文件缓存 165 | * 166 | * @param 对应的实体对象 167 | * @param key 保存的key 168 | * @param value 保存的value 169 | * @param isEncrypt 是否加密 170 | */ 171 | public static void put(String key, @NonNull T value, boolean isEncrypt) { 172 | if (TextUtils.isEmpty(key)) { 173 | return; 174 | } 175 | Gson gson = new Gson(); 176 | String date; 177 | if (value instanceof JSONObject) { 178 | date = value.toString(); 179 | } else if (value instanceof JSONArray) { 180 | date = value.toString(); 181 | } else { 182 | date = gson.toJson(value); 183 | } 184 | 185 | put(key, date, isEncrypt); 186 | } 187 | 188 | /** 189 | * 保存key和value到内存缓存和文件缓存 190 | * 191 | * @param 对应的实体对象 192 | * @param key 保存的key 193 | * @param value 保存的value 194 | * @param time 过期时间 秒 195 | */ 196 | public static void put(String key, @NonNull T value, int time) { 197 | put(key, value, time, getConfig().isEncrypt()); 198 | } 199 | 200 | /** 201 | * 保存key和value到内存缓存和文件缓存 202 | * 203 | * @param 对应的实体对象 204 | * @param key 保存的key 205 | * @param value 保存的value 206 | * @param time 过期时间 秒 207 | * @param isEncrypt 是否加密 208 | */ 209 | public static void put(String key, @NonNull T value, int time, boolean isEncrypt) { 210 | if (TextUtils.isEmpty(key)) { 211 | return; 212 | } 213 | Gson gson = new Gson(); 214 | String date; 215 | if (value instanceof JSONObject) { 216 | date = value.toString(); 217 | } else if (value instanceof JSONArray) { 218 | date = value.toString(); 219 | } else { 220 | date = gson.toJson(value); 221 | } 222 | 223 | put(key, date, time, isEncrypt); 224 | } 225 | 226 | /** 227 | * 根据key获取保存的value 228 | * 先从内存缓存提取,取不到再从文件缓存中获取 229 | * 230 | * @param key 要查找的key 231 | * @return 保存的value 232 | */ 233 | @Nullable 234 | public static String get(String key) { 235 | return get(key, getConfig().isEncrypt()); 236 | } 237 | 238 | /** 239 | * 根据key获取保存的value 240 | * 先从内存缓存提取,取不到再从文件缓存中获取 241 | * 242 | * @param key 要查找的key 243 | * @param isEncrypt 是否加密 244 | * @return 保存的value 245 | */ 246 | @Nullable 247 | public static String get(String key, boolean isEncrypt) { 248 | if (TextUtils.isEmpty(key)) { 249 | return ""; 250 | } 251 | try { 252 | LockUtil.getInstance().readLock().lock(); 253 | if (getConfig().isKeyEncrypt()) { 254 | key = translateSecretKey(key); 255 | } 256 | String value; 257 | if (getConfig().isMemoryCache()) { 258 | value = getLruCache().get(key); 259 | if (!TextUtils.isEmpty(value)) { 260 | if (!Utils.isDue(value)) { 261 | return Utils.clearDateInfo(value); 262 | } else { 263 | getLruCache().remove(key); 264 | return ""; 265 | } 266 | } 267 | } 268 | 269 | value = getConfig().getACache().getAsString(key, isEncrypt); 270 | if (!TextUtils.isEmpty(value)) { 271 | if (getConfig().isMemoryCache()) { 272 | getLruCache().put(key, 273 | getConfig().getACache().getAsStringHasDate(key, isEncrypt)); 274 | } 275 | return value; 276 | } 277 | return ""; 278 | } catch (Exception e) { 279 | return ""; 280 | } finally { 281 | LockUtil.getInstance().readLock().unlock(); 282 | } 283 | } 284 | 285 | /** 286 | * 根据key获取对象 287 | * 先从内存缓存提取,取不到再从文件缓存中获取 288 | * 289 | * @param key 查找的key 290 | * @param classOfT 对应的实体对象 291 | * @param 对应的实体对象 292 | * @return 实体对象 293 | */ 294 | @Nullable 295 | public static T get(String key, Class classOfT) { 296 | return get(key, classOfT, getConfig().isEncrypt()); 297 | } 298 | 299 | /** 300 | * 根据key获取对象 301 | * 先从内存缓存提取,取不到再从文件缓存中获取 302 | * 303 | * @param 对应的实体对象 304 | * @param key 查找的key 305 | * @param classOfT 对应的实体对象 306 | * @param isEncrypt 是否加密 307 | * @return 实体对象 308 | */ 309 | @Nullable 310 | public static T get(String key, Class classOfT, boolean isEncrypt) { 311 | if (TextUtils.isEmpty(key) || classOfT == null) { 312 | return null; 313 | } 314 | try { 315 | LockUtil.getInstance().readLock().lock(); 316 | if (getConfig().isKeyEncrypt()) { 317 | key = translateSecretKey(key); 318 | } 319 | Gson gson = new Gson(); 320 | String value; 321 | if (getConfig().isMemoryCache()) { 322 | value = getLruCache().get(key); 323 | if (!TextUtils.isEmpty(value)) { 324 | if (!Utils.isDue(value)) { 325 | return gson.fromJson(Utils.clearDateInfo(value), classOfT); 326 | } else { 327 | getLruCache().remove(key); 328 | try { 329 | return classOfT.newInstance(); 330 | } catch (InstantiationException e) { 331 | return null; 332 | } catch (IllegalAccessException e) { 333 | e.printStackTrace(); 334 | return null; 335 | } 336 | } 337 | } 338 | } 339 | value = getConfig().getACache().getAsString(key, isEncrypt); 340 | 341 | if (!TextUtils.isEmpty(value)) { 342 | if (getConfig().isMemoryCache()) { 343 | getLruCache().put(key, 344 | getConfig().getACache().getAsStringHasDate(key, isEncrypt)); 345 | } 346 | return gson.fromJson(value, classOfT); 347 | } 348 | return classOfT.newInstance(); 349 | } catch (Exception e) { 350 | return null; 351 | } finally { 352 | LockUtil.getInstance().readLock().unlock(); 353 | } 354 | } 355 | 356 | /** 357 | * 根据key获取对象 358 | * 先从内存缓存提取,取不到再从文件缓存中获取 359 | * 360 | * @param 对应的实体对象 361 | * @param key 查找的key 362 | * @param classOfT 对应的实体对象 363 | * @param t 错误情况下返回数据 364 | * @return 实体对象 365 | */ 366 | public static T get(String key, Class classOfT, T t) { 367 | return get(key, classOfT, t, getConfig().isEncrypt()); 368 | } 369 | 370 | /** 371 | * 根据key获取对象 372 | * 先从内存缓存提取,取不到再从文件缓存中获取 373 | * 374 | * @param 对应的实体对象 375 | * @param key 查找的key 376 | * @param classOfT 对应的实体对象 377 | * @param t 错误情况下返回数据 378 | * @param isEncrypt 是否加密 379 | * @return 实体对象 380 | */ 381 | public static T get(String key, Class classOfT, @NonNull T t, boolean isEncrypt) { 382 | if (TextUtils.isEmpty(key) || classOfT == null) { 383 | return t; 384 | } 385 | try { 386 | LockUtil.getInstance().readLock().lock(); 387 | Gson gson = new Gson(); 388 | String value; 389 | if (getConfig().isKeyEncrypt()) { 390 | key = translateSecretKey(key); 391 | } 392 | if (getConfig().isMemoryCache()) { 393 | value = getLruCache().get(key); 394 | if (!TextUtils.isEmpty(value)) { 395 | if (!Utils.isDue(value)) { 396 | return gson.fromJson(Utils.clearDateInfo(value), classOfT); 397 | } else { 398 | getLruCache().remove(key); 399 | return t; 400 | } 401 | } 402 | } 403 | value = getConfig().getACache().getAsString(key, isEncrypt); 404 | 405 | if (!TextUtils.isEmpty(value)) { 406 | if (getConfig().isMemoryCache()) { 407 | getLruCache().put(key, 408 | getConfig().getACache().getAsStringHasDate(key, isEncrypt)); 409 | } 410 | return gson.fromJson(value, classOfT); 411 | } 412 | return t; 413 | } catch (Exception e) { 414 | return t; 415 | } finally { 416 | LockUtil.getInstance().readLock().unlock(); 417 | } 418 | } 419 | 420 | /** 421 | * 根据key清理内存缓存和文件缓存 422 | * 423 | * @param key 要删除的key 424 | */ 425 | public static void clear(String key) { 426 | if (TextUtils.isEmpty(key)) { 427 | return; 428 | } 429 | if (getConfig().isKeyEncrypt()) { 430 | key = translateSecretKey(key); 431 | } 432 | LockUtil.getInstance().writeLock().lock(); 433 | getLruCache().remove(key); 434 | getConfig().getACache().remove(key); 435 | LockUtil.getInstance().writeLock().unlock(); 436 | } 437 | 438 | /** 439 | * 根据key清理内存缓存 440 | * 441 | * @param key 要删除的key 442 | */ 443 | 444 | public static void clearMemory(@NonNull String key) { 445 | if (TextUtils.isEmpty(key)) { 446 | return; 447 | } 448 | if (getConfig().isKeyEncrypt()) { 449 | key = translateSecretKey(key); 450 | } 451 | getLruCache().remove(key); 452 | } 453 | 454 | /** 455 | * 清理所有内存缓存 456 | */ 457 | public static void clearAllMemory() { 458 | getLruCache().evictAll(); 459 | } 460 | 461 | /** 462 | * 清理所有缓存 463 | */ 464 | public static void clearAll() { 465 | getLruCache().evictAll(); 466 | getConfig().getACache().clear(); 467 | } 468 | 469 | /** 470 | * 以.开头的文件默认不显示 471 | */ 472 | public static String translateKey(@NonNull String key) { 473 | return "." + Base64Util.encode(key.getBytes()); 474 | } 475 | 476 | /** 477 | * keyMd5编码 478 | */ 479 | public static String translateSecretKey(@NonNull String key) { 480 | return Md5Utils.md5(key); 481 | } 482 | 483 | private static class CacheUtilHolder { 484 | private static CacheUtil mInstance = new CacheUtil(); 485 | } 486 | 487 | /** 488 | * 时间计算工具类 489 | * 490 | * @author 杨福海(michael) 491 | * @version 1.0 492 | */ 493 | private static class Utils { 494 | 495 | private static final char SEPARATOR = ' '; 496 | 497 | /** 498 | * 判断缓存的String数据是否到期 499 | * 500 | * @param str 保存的str 501 | * @return true:到期了 false:还没有到期 502 | */ 503 | private static boolean isDue(String str) { 504 | return isDue(str.getBytes()); 505 | } 506 | 507 | /** 508 | * 判断缓存的byte数据是否到期 509 | * 510 | * @param data 保存的data 511 | * @return true:到期了 false:还没有到期 512 | */ 513 | private static boolean isDue(byte[] data) { 514 | String[] strs = getDateInfoFromDate(data); 515 | if (strs != null && strs.length == 2) { 516 | String saveTimeStr = strs[0]; 517 | while (saveTimeStr.startsWith("0")) { 518 | saveTimeStr = saveTimeStr.substring(1, saveTimeStr.length()); 519 | } 520 | long saveTime = Long.valueOf(saveTimeStr); 521 | long deleteAfter = Long.valueOf(strs[1]); 522 | if (System.currentTimeMillis() > saveTime + deleteAfter * 1000) { 523 | return true; 524 | } 525 | } 526 | return false; 527 | } 528 | 529 | private static String newStringWithDateInfo(int second, String strInfo) { 530 | return createDateInfo(second) + strInfo; 531 | } 532 | 533 | private static byte[] newByteArrayWithDateInfo(int second, byte[] data2) { 534 | byte[] data1 = createDateInfo(second).getBytes(); 535 | byte[] retdata = new byte[data1.length + data2.length]; 536 | System.arraycopy(data1, 0, retdata, 0, data1.length); 537 | System.arraycopy(data2, 0, retdata, data1.length, data2.length); 538 | return retdata; 539 | } 540 | 541 | private static String clearDateInfo(String strInfo) { 542 | if (strInfo != null && hasDateInfo(strInfo.getBytes())) { 543 | strInfo = strInfo.substring(strInfo.indexOf(SEPARATOR) + 1, strInfo.length()); 544 | } 545 | return strInfo; 546 | } 547 | 548 | private static byte[] clearDateInfo(byte[] data) { 549 | if (hasDateInfo(data)) { 550 | return copyOfRange(data, indexOf(data, SEPARATOR) + 1, data.length); 551 | } 552 | return data; 553 | } 554 | 555 | private static boolean hasDateInfo(byte[] data) { 556 | return data != null 557 | && data.length > 15 558 | && data[13] == '-' 559 | && indexOf(data, SEPARATOR) > 14; 560 | } 561 | 562 | private static String[] getDateInfoFromDate(byte[] data) { 563 | if (hasDateInfo(data)) { 564 | String saveDate = new String(copyOfRange(data, 0, 13)); 565 | String deleteAfter = new String(copyOfRange(data, 14, indexOf(data, SEPARATOR))); 566 | return new String[] { saveDate, deleteAfter }; 567 | } 568 | return null; 569 | } 570 | 571 | private static int indexOf(byte[] data, char c) { 572 | for (int i = 0; i < data.length; i++) { 573 | if (data[i] == c) { 574 | return i; 575 | } 576 | } 577 | return -1; 578 | } 579 | 580 | private static byte[] copyOfRange(byte[] original, int from, int to) { 581 | int newLength = to - from; 582 | if (newLength < 0) { 583 | throw new IllegalArgumentException(from + " > " + to); 584 | } 585 | byte[] copy = new byte[newLength]; 586 | System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength)); 587 | return copy; 588 | } 589 | 590 | private static String createDateInfo(int second) { 591 | StringBuilder currentTime = new StringBuilder(System.currentTimeMillis() + ""); 592 | while (currentTime.length() < 13) { 593 | currentTime.insert(0, "0"); 594 | } 595 | return currentTime + "-" + second + SEPARATOR; 596 | } 597 | 598 | /** 599 | * Bitmap → byte[] 600 | * 601 | * @param bm 图片bitmap 602 | */ 603 | private static byte[] bitmap2Bytes(Bitmap bm) { 604 | if (bm == null) { 605 | return null; 606 | } 607 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 608 | bm.compress(Bitmap.CompressFormat.PNG, 100, baos); 609 | return baos.toByteArray(); 610 | } 611 | 612 | /** 613 | * byte[] → Bitmap 614 | */ 615 | private static Bitmap bytes2Bimap(byte[] b) { 616 | if (b.length == 0) { 617 | return null; 618 | } 619 | return BitmapFactory.decodeByteArray(b, 0, b.length); 620 | } 621 | 622 | /** 623 | * Drawable → Bitmap 624 | */ 625 | private static Bitmap drawable2Bitmap(Drawable drawable) { 626 | if (drawable == null) { 627 | return null; 628 | } 629 | // 取 drawable 的长宽 630 | int w = drawable.getIntrinsicWidth(); 631 | int h = drawable.getIntrinsicHeight(); 632 | // 取 drawable 的颜色格式 633 | Bitmap.Config config = 634 | drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 635 | : Bitmap.Config.RGB_565; 636 | // 建立对应 bitmap 637 | Bitmap bitmap = Bitmap.createBitmap(w, h, config); 638 | // 建立对应 bitmap 的画布 639 | Canvas canvas = new Canvas(bitmap); 640 | drawable.setBounds(0, 0, w, h); 641 | // 把 drawable 内容画到画布中 642 | drawable.draw(canvas); 643 | return bitmap; 644 | } 645 | 646 | /** 647 | * Bitmap → Drawable 648 | */ 649 | @SuppressWarnings("deprecation") 650 | private static Drawable bitmap2Drawable(Bitmap bm) { 651 | if (bm == null) { 652 | return null; 653 | } 654 | return new BitmapDrawable(bm); 655 | } 656 | } 657 | } 658 | -------------------------------------------------------------------------------- /library/src/main/java/com/haohaohu/cachemanage/ACache.java: -------------------------------------------------------------------------------- 1 | package com.haohaohu.cachemanage; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.graphics.Canvas; 7 | import android.graphics.PixelFormat; 8 | import android.graphics.drawable.BitmapDrawable; 9 | import android.graphics.drawable.Drawable; 10 | import android.os.Environment; 11 | import android.widget.ImageView; 12 | 13 | import org.json.JSONArray; 14 | import org.json.JSONObject; 15 | 16 | import java.io.BufferedReader; 17 | import java.io.BufferedWriter; 18 | import java.io.ByteArrayInputStream; 19 | import java.io.ByteArrayOutputStream; 20 | import java.io.File; 21 | import java.io.FileOutputStream; 22 | import java.io.FileReader; 23 | import java.io.FileWriter; 24 | import java.io.IOException; 25 | import java.io.ObjectInputStream; 26 | import java.io.ObjectOutputStream; 27 | import java.io.RandomAccessFile; 28 | import java.io.Serializable; 29 | import java.util.Arrays; 30 | import java.util.Collections; 31 | import java.util.HashMap; 32 | import java.util.Map; 33 | import java.util.Map.Entry; 34 | import java.util.Set; 35 | import java.util.concurrent.atomic.AtomicInteger; 36 | import java.util.concurrent.atomic.AtomicLong; 37 | 38 | public class ACache { 39 | public static final int TIME_HOUR = 60 * 60; 40 | public static final int TIME_DAY = TIME_HOUR * 24; 41 | // 500 mb 42 | private static final int MAX_SIZE = 1000 * 1000 * 50; 43 | //不限制存放数据的数量 44 | private static final int MAX_COUNT = Integer.MAX_VALUE; 45 | private static Map mInstanceMap = new HashMap<>(); 46 | private ACacheManager mCache; 47 | 48 | private ACache(File cacheDir, long max_size, int max_count) { 49 | if (!cacheDir.exists() && !cacheDir.mkdirs()) { 50 | throw new RuntimeException("can't make dirs in " + cacheDir.getAbsolutePath()); 51 | } 52 | mCache = new ACacheManager(cacheDir, max_size, max_count); 53 | } 54 | 55 | public static ACache get(Context ctx) { 56 | return get(ctx, "ACache"); 57 | } 58 | 59 | public static ACache get(Context ctx, String cacheName) { 60 | File f = new File(getDiskCacheDir(ctx), cacheName); 61 | return get(f, MAX_SIZE, MAX_COUNT); 62 | } 63 | 64 | public static ACache get(File cacheDir) { 65 | return get(cacheDir, MAX_SIZE, MAX_COUNT); 66 | } 67 | 68 | public static ACache get(Context ctx, long max_zise, int max_count) { 69 | File f = new File(getDiskCacheDir(ctx), "ACache"); 70 | return get(f, max_zise, max_count); 71 | } 72 | 73 | public static ACache get(File cacheDir, long max_zise, int max_count) { 74 | ACache manager = mInstanceMap.get(cacheDir.getAbsoluteFile() + myPid()); 75 | if (manager == null) { 76 | manager = new ACache(cacheDir, max_zise, max_count); 77 | mInstanceMap.put(cacheDir.getAbsolutePath() + myPid(), manager); 78 | } 79 | return manager; 80 | } 81 | 82 | private static String myPid() { 83 | return "_" + android.os.Process.myPid(); 84 | } 85 | 86 | // ======================================= 87 | // ============ String数据 读写 ============== 88 | // ======================================= 89 | 90 | /** 91 | * 获取缓存文件目录,优先获取SD卡缓存目录 92 | */ 93 | public static String getDiskCacheDir(Context context) { 94 | String cachePath; 95 | boolean contentCachePath = 96 | context.getExternalCacheDir() != null && (Environment.MEDIA_MOUNTED.equals( 97 | Environment.getExternalStorageState()) 98 | || !Environment.isExternalStorageRemovable()); 99 | if (contentCachePath) { 100 | cachePath = context.getExternalCacheDir().getPath(); 101 | } else { 102 | cachePath = context.getCacheDir().getPath(); 103 | } 104 | return cachePath; 105 | } 106 | 107 | /** 108 | * 保存 String数据 到 缓存中 109 | * 110 | * @param key 保存的key 111 | * @param value 保存的String数据 112 | */ 113 | public void put(String key, String value) { 114 | File file = mCache.newFile(key); 115 | BufferedWriter out = null; 116 | try { 117 | out = new BufferedWriter(new FileWriter(file), 1024); 118 | out.write(value); 119 | } catch (IOException e) { 120 | e.printStackTrace(); 121 | } finally { 122 | if (out != null) { 123 | try { 124 | out.flush(); 125 | out.close(); 126 | } catch (IOException e) { 127 | e.printStackTrace(); 128 | } 129 | } 130 | mCache.put(file); 131 | } 132 | } 133 | 134 | /** 135 | * 保存 String数据 到 缓存中 136 | * 137 | * @param key 保存的key 138 | * @param value 保存的String数据 139 | * @param saveTime 保存的时间,单位:秒 140 | */ 141 | public void put(String key, String value, int saveTime) { 142 | put(key, Utils.newStringWithDateInfo(saveTime, value)); 143 | } 144 | 145 | // ======================================= 146 | // ============= JSONObject 数据 读写 ============== 147 | // ======================================= 148 | 149 | /** 150 | * 读取 String数据 151 | * 152 | * @param key 保存的key 153 | * 154 | * @return String 数据 155 | */ 156 | public String getAsString(String key) { 157 | File file = mCache.get(key); 158 | if (!file.exists()) { 159 | return null; 160 | } 161 | boolean removeFile = false; 162 | BufferedReader in = null; 163 | try { 164 | in = new BufferedReader(new FileReader(file)); 165 | String readString = ""; 166 | String currentLine; 167 | while ((currentLine = in.readLine()) != null) { 168 | readString += currentLine; 169 | } 170 | if (!Utils.isDue(readString)) { 171 | return Utils.clearDateInfo(readString); 172 | } else { 173 | removeFile = true; 174 | return null; 175 | } 176 | } catch (IOException e) { 177 | e.printStackTrace(); 178 | return null; 179 | } finally { 180 | if (in != null) { 181 | try { 182 | in.close(); 183 | } catch (IOException e) { 184 | e.printStackTrace(); 185 | } 186 | } 187 | if (removeFile) { 188 | remove(key); 189 | } 190 | } 191 | } 192 | 193 | /** 194 | * 保存 JSONObject数据 到 缓存中 195 | * 196 | * @param key 保存的key 197 | * @param value 保存的JSON数据 198 | */ 199 | public void put(String key, JSONObject value) { 200 | put(key, value.toString()); 201 | } 202 | 203 | /** 204 | * 保存 JSONObject数据 到 缓存中 205 | * 206 | * @param key 保存的key 207 | * @param value 保存的JSONObject数据 208 | * @param saveTime 保存的时间,单位:秒 209 | */ 210 | public void put(String key, JSONObject value, int saveTime) { 211 | put(key, value.toString(), saveTime); 212 | } 213 | 214 | // ======================================= 215 | // ============ JSONArray 数据 读写 ============= 216 | // ======================================= 217 | 218 | /** 219 | * 读取JSONObject数据 220 | * 221 | * @param key 保存的key 222 | * 223 | * @return JSONObject数据 224 | */ 225 | public JSONObject getAsJSONObject(String key) { 226 | String mJSONString = getAsString(key); 227 | try { 228 | return new JSONObject(mJSONString); 229 | } catch (Exception e) { 230 | e.printStackTrace(); 231 | return null; 232 | } 233 | } 234 | 235 | /** 236 | * 保存 JSONArray数据 到 缓存中 237 | * 238 | * @param key 保存的key 239 | * @param value 保存的JSONArray数据 240 | */ 241 | public void put(String key, JSONArray value) { 242 | put(key, value.toString()); 243 | } 244 | 245 | /** 246 | * 保存 JSONArray数据 到 缓存中 247 | * 248 | * @param key 保存的key 249 | * @param value 保存的JSONArray数据 250 | * @param saveTime 保存的时间,单位:秒 251 | */ 252 | public void put(String key, JSONArray value, int saveTime) { 253 | put(key, value.toString(), saveTime); 254 | } 255 | 256 | // ======================================= 257 | // ============== byte 数据 读写 ============= 258 | // ======================================= 259 | 260 | /** 261 | * 读取JSONArray数据 262 | * 263 | * @param key 保存的key 264 | * 265 | * @return JSONArray数据 266 | */ 267 | public JSONArray getAsJSONArray(String key) { 268 | String mJSONString = getAsString(key); 269 | try { 270 | return new JSONArray(mJSONString); 271 | } catch (Exception e) { 272 | e.printStackTrace(); 273 | return null; 274 | } 275 | } 276 | 277 | /** 278 | * 保存 byte数据 到 缓存中 279 | * 280 | * @param key 保存的key 281 | * @param value 保存的数据 282 | */ 283 | public void put(String key, byte[] value) { 284 | File file = mCache.newFile(key); 285 | FileOutputStream out = null; 286 | try { 287 | out = new FileOutputStream(file); 288 | out.write(value); 289 | } catch (Exception e) { 290 | e.printStackTrace(); 291 | } finally { 292 | if (out != null) { 293 | try { 294 | out.flush(); 295 | out.close(); 296 | } catch (IOException e) { 297 | e.printStackTrace(); 298 | } 299 | } 300 | mCache.put(file); 301 | } 302 | } 303 | 304 | /** 305 | * 保存 byte数据 到 缓存中 306 | * 307 | * @param key 保存的key 308 | * @param value 保存的数据 309 | * @param saveTime 保存的时间,单位:秒 310 | */ 311 | public void put(String key, byte[] value, int saveTime) { 312 | put(key, Utils.newByteArrayWithDateInfo(saveTime, value)); 313 | } 314 | 315 | // ======================================= 316 | // ============= 序列化 数据 读写 =============== 317 | // ======================================= 318 | 319 | /** 320 | * 获取 byte 数据 321 | * 322 | * @param key 保存的key 323 | * 324 | * @return byte 数据 325 | */ 326 | public byte[] getAsBinary(String key) { 327 | RandomAccessFile mRAFile = null; 328 | boolean removeFile = false; 329 | try { 330 | File file = mCache.get(key); 331 | if (!file.exists()) { 332 | return null; 333 | } 334 | mRAFile = new RandomAccessFile(file, "r"); 335 | byte[] byteArray = new byte[(int) mRAFile.length()]; 336 | mRAFile.read(byteArray); 337 | if (!Utils.isDue(byteArray)) { 338 | return Utils.clearDateInfo(byteArray); 339 | } else { 340 | removeFile = true; 341 | return null; 342 | } 343 | } catch (Exception e) { 344 | e.printStackTrace(); 345 | return null; 346 | } finally { 347 | if (mRAFile != null) { 348 | try { 349 | mRAFile.close(); 350 | } catch (IOException e) { 351 | e.printStackTrace(); 352 | } 353 | } 354 | if (removeFile) { 355 | remove(key); 356 | } 357 | } 358 | } 359 | 360 | /** 361 | * 保存 Serializable数据 到 缓存中 362 | * 363 | * @param key 保存的key 364 | * @param value 保存的value 365 | */ 366 | public void put(String key, Serializable value) { 367 | put(key, value, -1); 368 | } 369 | 370 | /** 371 | * 保存 Serializable数据到 缓存中 372 | * 373 | * @param key 保存的key 374 | * @param value 保存的value 375 | * @param saveTime 保存的时间,单位:秒 376 | */ 377 | public void put(String key, Serializable value, int saveTime) { 378 | ByteArrayOutputStream baos; 379 | ObjectOutputStream oos = null; 380 | try { 381 | baos = new ByteArrayOutputStream(); 382 | oos = new ObjectOutputStream(baos); 383 | oos.writeObject(value); 384 | byte[] data = baos.toByteArray(); 385 | if (saveTime != -1) { 386 | put(key, data, saveTime); 387 | } else { 388 | put(key, data); 389 | } 390 | } catch (Exception e) { 391 | e.printStackTrace(); 392 | } finally { 393 | try { 394 | if (oos != null) { 395 | oos.close(); 396 | } 397 | } catch (IOException ignored) { 398 | } 399 | } 400 | } 401 | 402 | // ======================================= 403 | // ============== bitmap 数据 读写 ============= 404 | // ======================================= 405 | 406 | /** 407 | * 读取 Serializable数据 408 | * 409 | * @param key 保存的key 410 | * 411 | * @return Serializable 数据 412 | */ 413 | public Object getAsObject(String key) { 414 | byte[] data = getAsBinary(key); 415 | if (data != null) { 416 | ByteArrayInputStream bais = null; 417 | ObjectInputStream ois = null; 418 | try { 419 | bais = new ByteArrayInputStream(data); 420 | ois = new ObjectInputStream(bais); 421 | return ois.readObject(); 422 | } catch (Exception e) { 423 | e.printStackTrace(); 424 | return null; 425 | } finally { 426 | try { 427 | if (bais != null) { 428 | bais.close(); 429 | } 430 | } catch (IOException e) { 431 | e.printStackTrace(); 432 | } 433 | try { 434 | if (ois != null) { 435 | ois.close(); 436 | } 437 | } catch (IOException e) { 438 | e.printStackTrace(); 439 | } 440 | } 441 | } 442 | return null; 443 | } 444 | 445 | /** 446 | * 保存 bitmap 到 缓存中 447 | * 448 | * @param key 保存的key 449 | * @param value 保存的bitmap数据 450 | */ 451 | public void put(String key, Bitmap value) { 452 | put(key, Utils.bitmap2Bytes(value)); 453 | } 454 | 455 | /** 456 | * 保存 bitmap 到 缓存中 457 | * 458 | * @param key 保存的key 459 | * @param value 保存的 bitmap 数据 460 | * @param saveTime 保存的时间,单位:秒 461 | */ 462 | public void put(String key, Bitmap value, int saveTime) { 463 | put(key, Utils.bitmap2Bytes(value), saveTime); 464 | } 465 | 466 | /** 467 | * 读取 bitmap 数据 468 | * 469 | * @param key 保存的key 470 | * 471 | * @return bitmap 数据 472 | */ 473 | public Bitmap getAsBitmap(String key) { 474 | if (getAsBinary(key) == null) { 475 | return null; 476 | } 477 | return Utils.bytes2Bimap(getAsBinary(key)); 478 | } 479 | 480 | // ======================================= 481 | // ============= drawable 数据 读写 ============= 482 | // ======================================= 483 | 484 | public void getBitmap(final ImageView imageView, String key) { 485 | if (getAsBinary(key) == null) { 486 | return; 487 | } 488 | imageView.setImageBitmap(Utils.bytes2Bimap(getAsBinary(key))); 489 | } 490 | 491 | /** 492 | * 保存 drawable 到 缓存中 493 | * 494 | * @param key 保存的key 495 | * @param value 保存的drawable数据 496 | */ 497 | public void put(String key, Drawable value) { 498 | put(key, Utils.drawable2Bitmap(value)); 499 | } 500 | 501 | /** 502 | * 保存 drawable 到 缓存中 503 | * 504 | * @param key 保存的key 505 | * @param value 保存的 drawable 数据 506 | * @param saveTime 保存的时间,单位:秒 507 | */ 508 | public void put(String key, Drawable value, int saveTime) { 509 | put(key, Utils.drawable2Bitmap(value), saveTime); 510 | } 511 | 512 | /** 513 | * 读取 Drawable 数据 514 | * 515 | * @param key 保存的key 516 | * 517 | * @return Drawable 数据 518 | */ 519 | public Drawable getAsDrawable(String key) { 520 | if (getAsBinary(key) == null) { 521 | return null; 522 | } 523 | return Utils.bitmap2Drawable(Utils.bytes2Bimap(getAsBinary(key))); 524 | } 525 | 526 | /** 527 | * 获取缓存文件 528 | * 529 | * @param key 保存的key 530 | * 531 | * @return value 缓存的文件 532 | */ 533 | public File file(String key) { 534 | File f = mCache.newFile(key); 535 | if (f.exists()) { 536 | return f; 537 | } 538 | return null; 539 | } 540 | 541 | /** 542 | * 移除某个key 543 | * 544 | * @param key 保存的key 545 | * 546 | * @return 是否移除成功 547 | */ 548 | public boolean remove(String key) { 549 | return mCache.remove(key); 550 | } 551 | 552 | /** 553 | * 清除所有数据 554 | */ 555 | public void clear() { 556 | mCache.clear(); 557 | } 558 | 559 | /** 560 | * 保存 String数据 到 缓存中 561 | * 562 | * @param key 保存的key 563 | * @param value 保存的String数据 564 | * @param isEncrypt 是否加密 565 | */ 566 | public void put(String key, String value, boolean isEncrypt) { 567 | File file = mCache.newFile(key); 568 | BufferedWriter out = null; 569 | try { 570 | out = new BufferedWriter(new FileWriter(file), 1024); 571 | if (isEncrypt) { 572 | value = CacheUtil.getConfig().getIEncryptStrategy().encrypt(value); 573 | } 574 | out.write(value); 575 | } catch (IOException e) { 576 | e.printStackTrace(); 577 | } catch (Exception e) { 578 | e.printStackTrace(); 579 | } finally { 580 | if (out != null) { 581 | try { 582 | out.flush(); 583 | out.close(); 584 | } catch (IOException e) { 585 | e.printStackTrace(); 586 | } 587 | } 588 | mCache.put(file); 589 | } 590 | } 591 | 592 | /** 593 | * 保存 String数据 到 缓存中 594 | * 595 | * @param key 保存的key 596 | * @param value 保存的String数据 597 | * @param saveTime 保存的时间,单位:秒 598 | */ 599 | public void put(String key, String value, int saveTime, boolean isEncrypt) { 600 | put(key, Utils.newStringWithDateInfo(saveTime, value), isEncrypt); 601 | } 602 | 603 | /** 604 | * 读取 String数据 605 | * 606 | * @param key 保存的key 607 | * @param isEncrypt 是否加密 608 | * 609 | * @return String 数据 610 | */ 611 | public String getAsString(String key, boolean isEncrypt) { 612 | File file = mCache.get(key); 613 | if (!file.exists()) { 614 | return null; 615 | } 616 | boolean removeFile = false; 617 | BufferedReader in = null; 618 | try { 619 | in = new BufferedReader(new FileReader(file)); 620 | StringBuilder readString = new StringBuilder(); 621 | String currentLine; 622 | while ((currentLine = in.readLine()) != null) { 623 | readString.append(currentLine); 624 | } 625 | if (isEncrypt) { 626 | readString = new StringBuilder( 627 | CacheUtil.getConfig().getIEncryptStrategy().decode(readString.toString())); 628 | } 629 | if (!Utils.isDue(readString.toString())) { 630 | return Utils.clearDateInfo(readString.toString()); 631 | } else { 632 | removeFile = true; 633 | return null; 634 | } 635 | } catch (IOException e) { 636 | e.printStackTrace(); 637 | return null; 638 | } catch (Exception e) { 639 | e.printStackTrace(); 640 | return null; 641 | } finally { 642 | if (in != null) { 643 | try { 644 | in.close(); 645 | } catch (IOException e) { 646 | e.printStackTrace(); 647 | } 648 | } 649 | if (removeFile) { 650 | remove(key); 651 | } 652 | } 653 | } 654 | 655 | /** 656 | * 读取 String数据 ,返回数据没有去除时间 657 | * 658 | * @param key 保存的key 659 | * @param isEncrypt 是否加密 660 | * 661 | * @return String 数据 662 | */ 663 | public String getAsStringHasDate(String key, boolean isEncrypt) { 664 | File file = mCache.get(key); 665 | if (!file.exists()) { 666 | return null; 667 | } 668 | boolean removeFile = false; 669 | BufferedReader in = null; 670 | try { 671 | in = new BufferedReader(new FileReader(file)); 672 | StringBuilder readString = new StringBuilder(); 673 | String currentLine; 674 | while ((currentLine = in.readLine()) != null) { 675 | readString.append(currentLine); 676 | } 677 | if (isEncrypt) { 678 | readString = new StringBuilder( 679 | CacheUtil.getConfig().getIEncryptStrategy().decode(readString.toString())); 680 | } 681 | if (!Utils.isDue(readString.toString())) { 682 | return readString.toString(); 683 | } else { 684 | removeFile = true; 685 | return null; 686 | } 687 | } catch (IOException e) { 688 | e.printStackTrace(); 689 | return null; 690 | } catch (Exception e) { 691 | e.printStackTrace(); 692 | return null; 693 | } finally { 694 | if (in != null) { 695 | try { 696 | in.close(); 697 | } catch (IOException e) { 698 | e.printStackTrace(); 699 | } 700 | } 701 | if (removeFile) { 702 | remove(key); 703 | } 704 | } 705 | } 706 | 707 | public void put(String key, JSONObject value, boolean isEncrypt) { 708 | put(key, value.toString(), isEncrypt); 709 | } 710 | 711 | public void put(String key, JSONObject value, int saveTime, boolean isEncrypt) { 712 | put(key, value.toString(), saveTime, isEncrypt); 713 | } 714 | 715 | public JSONObject getAsJSONObject(String key, boolean isEncrypt) { 716 | String mJSONString = getAsString(key); 717 | 718 | try { 719 | if (isEncrypt) { 720 | mJSONString = CacheUtil.getConfig().getIEncryptStrategy().decode(mJSONString); 721 | } 722 | return new JSONObject(mJSONString); 723 | } catch (Exception e) { 724 | e.printStackTrace(); 725 | return null; 726 | } 727 | } 728 | 729 | public void put(String key, JSONArray value, boolean isEncrypt) { 730 | put(key, value.toString(), isEncrypt); 731 | } 732 | 733 | public void put(String key, JSONArray value, int saveTime, boolean isEncrypt) { 734 | put(key, value.toString(), saveTime, isEncrypt); 735 | } 736 | 737 | public JSONArray getAsJSONArray(String key, boolean isEncrypt) { 738 | String mJSONString = getAsString(key); 739 | try { 740 | if (isEncrypt) { 741 | mJSONString = CacheUtil.getConfig().getIEncryptStrategy().decode(mJSONString); 742 | } 743 | return new JSONArray(mJSONString); 744 | } catch (Exception e) { 745 | e.printStackTrace(); 746 | return null; 747 | } 748 | } 749 | 750 | public void put(String key, byte[] value, boolean isEncrypt) { 751 | File file = mCache.newFile(key); 752 | FileOutputStream out = null; 753 | try { 754 | out = new FileOutputStream(file); 755 | if (isEncrypt) { 756 | value = CacheUtil.getConfig() 757 | .getIEncryptStrategy() 758 | .encrypt(new String(value)) 759 | .getBytes(); 760 | } 761 | out.write(value); 762 | } catch (Exception e) { 763 | e.printStackTrace(); 764 | } finally { 765 | if (out != null) { 766 | try { 767 | out.flush(); 768 | out.close(); 769 | } catch (IOException e) { 770 | e.printStackTrace(); 771 | } 772 | } 773 | mCache.put(file); 774 | } 775 | } 776 | 777 | public void put(String key, byte[] value, int saveTime, boolean isEncrypt) { 778 | put(key, Utils.newByteArrayWithDateInfo(saveTime, value), isEncrypt); 779 | } 780 | 781 | public byte[] getAsBinary(String key, boolean isEncrypt) { 782 | RandomAccessFile mRAFile = null; 783 | boolean removeFile = false; 784 | try { 785 | File file = mCache.get(key); 786 | if (!file.exists()) { 787 | return null; 788 | } 789 | mRAFile = new RandomAccessFile(file, "r"); 790 | byte[] byteArray = new byte[(int) mRAFile.length()]; 791 | mRAFile.read(byteArray); 792 | if (!Utils.isDue(byteArray)) { 793 | if (isEncrypt) { 794 | byteArray = CacheUtil.getConfig() 795 | .getIEncryptStrategy() 796 | .decode(Arrays.toString(byteArray)) 797 | .getBytes(); 798 | return Utils.clearDateInfo(byteArray); 799 | } else { 800 | return Utils.clearDateInfo(byteArray); 801 | } 802 | } else { 803 | removeFile = true; 804 | return null; 805 | } 806 | } catch (Exception e) { 807 | e.printStackTrace(); 808 | return null; 809 | } finally { 810 | if (mRAFile != null) { 811 | try { 812 | mRAFile.close(); 813 | } catch (IOException e) { 814 | e.printStackTrace(); 815 | } 816 | } 817 | if (removeFile) { 818 | remove(key); 819 | } 820 | } 821 | } 822 | 823 | /** 824 | * 时间计算工具类 825 | * 826 | * @author 杨福海(michael) 827 | * @version 1.0 828 | */ 829 | private static class Utils { 830 | 831 | private static final char M_SEPARATOR = ' '; 832 | 833 | /** 834 | * 判断缓存的String数据是否到期 835 | * 836 | * @param str 保存的str 837 | * 838 | * @return true:到期了 false:还没有到期 839 | */ 840 | private static boolean isDue(String str) { 841 | return isDue(str.getBytes()); 842 | } 843 | 844 | /** 845 | * 判断缓存的byte数据是否到期 846 | * 847 | * @param data 保存的data 848 | * 849 | * @return true:到期了 false:还没有到期 850 | */ 851 | private static boolean isDue(byte[] data) { 852 | String[] strs = getDateInfoFromDate(data); 853 | if (strs != null && strs.length == 2) { 854 | String saveTimeStr = strs[0]; 855 | while (saveTimeStr.startsWith("0")) { 856 | saveTimeStr = saveTimeStr.substring(1, saveTimeStr.length()); 857 | } 858 | long saveTime = Long.valueOf(saveTimeStr); 859 | long deleteAfter = Long.valueOf(strs[1]); 860 | if (System.currentTimeMillis() > saveTime + deleteAfter * 1000) { 861 | return true; 862 | } 863 | } 864 | return false; 865 | } 866 | 867 | private static String newStringWithDateInfo(int second, String strInfo) { 868 | return createDateInfo(second) + strInfo; 869 | } 870 | 871 | private static byte[] newByteArrayWithDateInfo(int second, byte[] data2) { 872 | byte[] data1 = createDateInfo(second).getBytes(); 873 | byte[] retdata = new byte[data1.length + data2.length]; 874 | System.arraycopy(data1, 0, retdata, 0, data1.length); 875 | System.arraycopy(data2, 0, retdata, data1.length, data2.length); 876 | return retdata; 877 | } 878 | 879 | private static String clearDateInfo(String strInfo) { 880 | if (strInfo != null && hasDateInfo(strInfo.getBytes())) { 881 | strInfo = strInfo.substring(strInfo.indexOf(M_SEPARATOR) + 1, strInfo.length()); 882 | } 883 | return strInfo; 884 | } 885 | 886 | private static byte[] clearDateInfo(byte[] data) { 887 | if (hasDateInfo(data)) { 888 | return copyOfRange(data, indexOf(data, M_SEPARATOR) + 1, data.length); 889 | } 890 | return data; 891 | } 892 | 893 | private static boolean hasDateInfo(byte[] data) { 894 | return data != null 895 | && data.length > 15 896 | && data[13] == '-' 897 | && indexOf(data, M_SEPARATOR) > 14; 898 | } 899 | 900 | private static String[] getDateInfoFromDate(byte[] data) { 901 | if (hasDateInfo(data)) { 902 | String saveDate = new String(copyOfRange(data, 0, 13)); 903 | String deleteAfter = new String(copyOfRange(data, 14, indexOf(data, M_SEPARATOR))); 904 | return new String[]{saveDate, deleteAfter}; 905 | } 906 | return null; 907 | } 908 | 909 | private static int indexOf(byte[] data, char c) { 910 | for (int i = 0; i < data.length; i++) { 911 | if (data[i] == c) { 912 | return i; 913 | } 914 | } 915 | return -1; 916 | } 917 | 918 | private static byte[] copyOfRange(byte[] original, int from, int to) { 919 | int newLength = to - from; 920 | if (newLength < 0) { 921 | throw new IllegalArgumentException(from + " > " + to); 922 | } 923 | byte[] copy = new byte[newLength]; 924 | System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength)); 925 | return copy; 926 | } 927 | 928 | private static String createDateInfo(int second) { 929 | StringBuilder currentTime = new StringBuilder(System.currentTimeMillis() + ""); 930 | while (currentTime.length() < 13) { 931 | currentTime.insert(0, "0"); 932 | } 933 | return currentTime + "-" + second + M_SEPARATOR; 934 | } 935 | 936 | /** 937 | * Bitmap → byte[] 938 | */ 939 | private static byte[] bitmap2Bytes(Bitmap bm) { 940 | if (bm == null) { 941 | return null; 942 | } 943 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 944 | bm.compress(Bitmap.CompressFormat.PNG, 100, baos); 945 | return baos.toByteArray(); 946 | } 947 | 948 | /** 949 | * byte[] → Bitmap 950 | */ 951 | private static Bitmap bytes2Bimap(byte[] b) { 952 | if (b.length == 0) { 953 | return null; 954 | } 955 | return BitmapFactory.decodeByteArray(b, 0, b.length); 956 | } 957 | 958 | /** 959 | * Drawable → Bitmap 960 | */ 961 | private static Bitmap drawable2Bitmap(Drawable drawable) { 962 | if (drawable == null) { 963 | return null; 964 | } 965 | // 取 drawable 的长宽 966 | int w = drawable.getIntrinsicWidth(); 967 | int h = drawable.getIntrinsicHeight(); 968 | // 取 drawable 的颜色格式 969 | Bitmap.Config config = 970 | drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 971 | : Bitmap.Config.RGB_565; 972 | // 建立对应 bitmap 973 | Bitmap bitmap = Bitmap.createBitmap(w, h, config); 974 | // 建立对应 bitmap 的画布 975 | Canvas canvas = new Canvas(bitmap); 976 | drawable.setBounds(0, 0, w, h); 977 | // 把 drawable 内容画到画布中 978 | drawable.draw(canvas); 979 | return bitmap; 980 | } 981 | 982 | /** 983 | * Bitmap → Drawable 984 | */ 985 | @SuppressWarnings("deprecation") 986 | private static Drawable bitmap2Drawable(Bitmap bm) { 987 | if (bm == null) { 988 | return null; 989 | } 990 | return new BitmapDrawable(bm); 991 | } 992 | } 993 | 994 | /** 995 | * 缓存管理器 996 | * 997 | * @author 杨福海(michael) 998 | * @version 1.0 999 | */ 1000 | public class ACacheManager { 1001 | private final AtomicLong cacheSize; 1002 | private final AtomicInteger cacheCount; 1003 | private final long sizeLimit; 1004 | private final int countLimit; 1005 | private final Map lastUsageDates = 1006 | Collections.synchronizedMap(new HashMap()); 1007 | protected File cacheDir; 1008 | 1009 | private ACacheManager(File cacheDir, long sizeLimit, int countLimit) { 1010 | this.cacheDir = cacheDir; 1011 | this.sizeLimit = sizeLimit; 1012 | this.countLimit = countLimit; 1013 | cacheSize = new AtomicLong(); 1014 | cacheCount = new AtomicInteger(); 1015 | calculateCacheSizeAndCacheCount(); 1016 | } 1017 | 1018 | /** 1019 | * 计算 cacheSize和cacheCount 1020 | */ 1021 | private void calculateCacheSizeAndCacheCount() { 1022 | new Thread(new Runnable() { 1023 | @Override 1024 | public void run() { 1025 | int size = 0; 1026 | int count = 0; 1027 | File[] cachedFiles = cacheDir.listFiles(); 1028 | if (cachedFiles != null) { 1029 | for (File cachedFile : cachedFiles) { 1030 | size += calculateSize(cachedFile); 1031 | count += 1; 1032 | lastUsageDates.put(cachedFile, cachedFile.lastModified()); 1033 | } 1034 | cacheSize.set(size); 1035 | cacheCount.set(count); 1036 | } 1037 | } 1038 | }).start(); 1039 | } 1040 | 1041 | private void put(File file) { 1042 | int curCacheCount = cacheCount.get(); 1043 | while (curCacheCount + 1 > countLimit) { 1044 | long freedSize = removeNext(); 1045 | cacheSize.addAndGet(-freedSize); 1046 | 1047 | curCacheCount = cacheCount.addAndGet(-1); 1048 | } 1049 | cacheCount.addAndGet(1); 1050 | 1051 | long valueSize = calculateSize(file); 1052 | long curCacheSize = cacheSize.get(); 1053 | while (curCacheSize + valueSize > sizeLimit) { 1054 | long freedSize = removeNext(); 1055 | curCacheSize = cacheSize.addAndGet(-freedSize); 1056 | } 1057 | cacheSize.addAndGet(valueSize); 1058 | 1059 | Long currentTime = System.currentTimeMillis(); 1060 | file.setLastModified(currentTime); 1061 | lastUsageDates.put(file, currentTime); 1062 | } 1063 | 1064 | public File get(String key) { 1065 | File file = newFile(key); 1066 | Long currentTime = System.currentTimeMillis(); 1067 | file.setLastModified(currentTime); 1068 | lastUsageDates.put(file, currentTime); 1069 | 1070 | return file; 1071 | } 1072 | 1073 | private File newFile(String key) { 1074 | return new File(cacheDir, key); 1075 | } 1076 | 1077 | private boolean remove(String key) { 1078 | File image = get(key); 1079 | return image.delete(); 1080 | } 1081 | 1082 | private void clear() { 1083 | lastUsageDates.clear(); 1084 | cacheSize.set(0); 1085 | File[] files = cacheDir.listFiles(); 1086 | if (files != null) { 1087 | for (File f : files) { 1088 | if (f.exists()) { 1089 | f.delete(); 1090 | } 1091 | } 1092 | } 1093 | } 1094 | 1095 | /** 1096 | * 移除旧的文件 1097 | */ 1098 | private long removeNext() { 1099 | if (lastUsageDates.isEmpty()) { 1100 | return 0; 1101 | } 1102 | 1103 | Long oldestUsage = null; 1104 | File mostLongUsedFile = null; 1105 | Set> entries = lastUsageDates.entrySet(); 1106 | synchronized (lastUsageDates) { 1107 | for (Entry entry : entries) { 1108 | if (mostLongUsedFile == null) { 1109 | mostLongUsedFile = entry.getKey(); 1110 | oldestUsage = entry.getValue(); 1111 | } else { 1112 | Long lastValueUsage = entry.getValue(); 1113 | if (lastValueUsage < oldestUsage) { 1114 | oldestUsage = lastValueUsage; 1115 | mostLongUsedFile = entry.getKey(); 1116 | } 1117 | } 1118 | } 1119 | } 1120 | 1121 | long fileSize = calculateSize(mostLongUsedFile); 1122 | if (mostLongUsedFile != null && mostLongUsedFile.delete()) { 1123 | lastUsageDates.remove(mostLongUsedFile); 1124 | } 1125 | return fileSize; 1126 | } 1127 | 1128 | private long calculateSize(File file) { 1129 | return file.length(); 1130 | } 1131 | } 1132 | } 1133 | -------------------------------------------------------------------------------- /library/src/main/java/mohapps/modified/java/util/Base64.java: -------------------------------------------------------------------------------- 1 | package mohapps.modified.java.util; 2 | /* 3 | * Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved. 4 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 5 | * 6 | * This code is free software; you can redistribute it and/or modify it 7 | * under the terms of the GNU General Public License version 2 only, as 8 | * published by the Free Software Foundation. Oracle designates this 9 | * particular file as subject to the "Classpath" exception as provided 10 | * by Oracle in the LICENSE file that accompanied this code. 11 | * 12 | * This code is distributed in the hope that it will be useful, but WITHOUT 13 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 15 | * version 2 for more details (a copy is included in the LICENSE file that 16 | * accompanied this code). 17 | * 18 | * You should have received a copy of the GNU General Public License version 19 | * 2 along with this work; if not, write to the Free Software Foundation, 20 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 21 | * 22 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 23 | * or visit www.oracle.com if you need additional information or have any 24 | * questions. 25 | */ 26 | 27 | //added by mohapps to use on Android API<26 28 | //java.util.Base64 was only available for API>=26 29 | 30 | 31 | import java.io.FilterOutputStream; 32 | import java.io.IOException; 33 | import java.io.InputStream; 34 | import java.io.OutputStream; 35 | import java.nio.ByteBuffer; 36 | import java.nio.charset.StandardCharsets; 37 | import java.util.Arrays; 38 | import java.util.Objects; 39 | 40 | /** 41 | * This class consists exclusively of static methods for obtaining 42 | * encoders and decoders for the Base64 encoding scheme. The 43 | * implementation of this class supports the following types of Base64 44 | * as specified in 45 | * RFC 4648 and 46 | * RFC 2045. 47 | * 48 | *
    49 | *
  • Basic 50 | *

    Uses "The Base64 Alphabet" as specified in Table 1 of 51 | * RFC 4648 and RFC 2045 for encoding and decoding operation. 52 | * The encoder does not add any line feed (line separator) 53 | * character. The decoder rejects data that contains characters 54 | * outside the base64 alphabet.

  • 55 | * 56 | *
  • URL and Filename safe 57 | *

    Uses the "URL and Filename safe Base64 Alphabet" as specified 58 | * in Table 2 of RFC 4648 for encoding and decoding. The 59 | * encoder does not add any line feed (line separator) character. 60 | * The decoder rejects data that contains characters outside the 61 | * base64 alphabet.

  • 62 | * 63 | *
  • MIME 64 | *

    Uses the "The Base64 Alphabet" as specified in Table 1 of 65 | * RFC 2045 for encoding and decoding operation. The encoded output 66 | * must be represented in lines of no more than 76 characters each 67 | * and uses a carriage return {@code '\r'} followed immediately by 68 | * a linefeed {@code '\n'} as the line separator. No line separator 69 | * is added to the end of the encoded output. All line separators 70 | * or other characters not found in the base64 alphabet table are 71 | * ignored in decoding operation.

  • 72 | *
73 | * 74 | *

Unless otherwise noted, passing a {@code null} argument to a 75 | * method of this class will cause a {@link java.lang.NullPointerException 76 | * NullPointerException} to be thrown. 77 | * 78 | * @author Xueming Shen 79 | * @since 1.8 80 | */ 81 | 82 | public class Base64 { 83 | 84 | private Base64() {} 85 | 86 | /** 87 | * Returns a {@link Encoder} that encodes using the 88 | * Basic type base64 encoding scheme. 89 | * 90 | * @return A Base64 encoder. 91 | */ 92 | public static Encoder getEncoder() { 93 | return Encoder.RFC4648; 94 | } 95 | 96 | /** 97 | * Returns a {@link Encoder} that encodes using the 98 | * URL and Filename safe type base64 99 | * encoding scheme. 100 | * 101 | * @return A Base64 encoder. 102 | */ 103 | public static Encoder getUrlEncoder() { 104 | return Encoder.RFC4648_URLSAFE; 105 | } 106 | 107 | /** 108 | * Returns a {@link Encoder} that encodes using the 109 | * MIME type base64 encoding scheme. 110 | * 111 | * @return A Base64 encoder. 112 | */ 113 | public static Encoder getMimeEncoder() { 114 | return Encoder.RFC2045; 115 | } 116 | 117 | /** 118 | * Returns a {@link Encoder} that encodes using the 119 | * MIME type base64 encoding scheme 120 | * with specified line length and line separators. 121 | * 122 | * @param lineLength 123 | * the length of each output line (rounded down to nearest multiple 124 | * of 4). If {@code lineLength <= 0} the output will not be separated 125 | * in lines 126 | * @param lineSeparator 127 | * the line separator for each output line 128 | * 129 | * @return A Base64 encoder. 130 | * 131 | * @throws IllegalArgumentException if {@code lineSeparator} includes any 132 | * character of "The Base64 Alphabet" as specified in Table 1 of 133 | * RFC 2045. 134 | */ 135 | public static Encoder getMimeEncoder(int lineLength, byte[] lineSeparator) { 136 | Objects.requireNonNull(lineSeparator); 137 | int[] base64 = Decoder.fromBase64; 138 | for (byte b : lineSeparator) { 139 | if (base64[b & 0xff] != -1) 140 | throw new IllegalArgumentException( 141 | "Illegal base64 line separator character 0x" + Integer.toString(b, 16)); 142 | } 143 | if (lineLength <= 0) { 144 | return Encoder.RFC4648; 145 | } 146 | return new Encoder(false, lineSeparator, lineLength >> 2 << 2, true); 147 | } 148 | 149 | /** 150 | * Returns a {@link Decoder} that decodes using the 151 | * Basic type base64 encoding scheme. 152 | * 153 | * @return A Base64 decoder. 154 | */ 155 | public static Decoder getDecoder() { 156 | return Decoder.RFC4648; 157 | } 158 | 159 | /** 160 | * Returns a {@link Decoder} that decodes using the 161 | * URL and Filename safe type base64 162 | * encoding scheme. 163 | * 164 | * @return A Base64 decoder. 165 | */ 166 | public static Decoder getUrlDecoder() { 167 | return Decoder.RFC4648_URLSAFE; 168 | } 169 | 170 | /** 171 | * Returns a {@link Decoder} that decodes using the 172 | * MIME type base64 decoding scheme. 173 | * 174 | * @return A Base64 decoder. 175 | */ 176 | public static Decoder getMimeDecoder() { 177 | return Decoder.RFC2045; 178 | } 179 | 180 | /** 181 | * This class implements an encoder for encoding byte data using 182 | * the Base64 encoding scheme as specified in RFC 4648 and RFC 2045. 183 | * 184 | *

Instances of {@link Encoder} class are safe for use by 185 | * multiple concurrent threads. 186 | * 187 | *

Unless otherwise noted, passing a {@code null} argument to 188 | * a method of this class will cause a 189 | * {@link java.lang.NullPointerException NullPointerException} to 190 | * be thrown. 191 | * 192 | * @see Decoder 193 | * @since 1.8 194 | */ 195 | public static class Encoder { 196 | 197 | private final byte[] newline; 198 | private final int linemax; 199 | private final boolean isURL; 200 | private final boolean doPadding; 201 | 202 | private Encoder(boolean isURL, byte[] newline, int linemax, boolean doPadding) { 203 | this.isURL = isURL; 204 | this.newline = newline; 205 | this.linemax = linemax; 206 | this.doPadding = doPadding; 207 | } 208 | 209 | /** 210 | * This array is a lookup table that translates 6-bit positive integer 211 | * index values into their "Base64 Alphabet" equivalents as specified 212 | * in "Table 1: The Base64 Alphabet" of RFC 2045 (and RFC 4648). 213 | */ 214 | private static final char[] toBase64 = { 215 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 216 | 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 217 | 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 218 | 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 219 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' 220 | }; 221 | 222 | /** 223 | * It's the lookup table for "URL and Filename safe Base64" as specified 224 | * in Table 2 of the RFC 4648, with the '+' and '/' changed to '-' and 225 | * '_'. This table is used when BASE64_URL is specified. 226 | */ 227 | private static final char[] toBase64URL = { 228 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 229 | 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 230 | 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 231 | 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 232 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_' 233 | }; 234 | 235 | private static final int MIMELINEMAX = 76; 236 | private static final byte[] CRLF = new byte[] {'\r', '\n'}; 237 | 238 | static final Encoder RFC4648 = new Encoder(false, null, -1, true); 239 | static final Encoder RFC4648_URLSAFE = new Encoder(true, null, -1, true); 240 | static final Encoder RFC2045 = new Encoder(false, CRLF, MIMELINEMAX, true); 241 | 242 | private final int outLength(int srclen) { 243 | int len = 0; 244 | if (doPadding) { 245 | len = 4 * ((srclen + 2) / 3); 246 | } else { 247 | int n = srclen % 3; 248 | len = 4 * (srclen / 3) + (n == 0 ? 0 : n + 1); 249 | } 250 | if (linemax > 0) // line separators 251 | len += (len - 1) / linemax * newline.length; 252 | return len; 253 | } 254 | 255 | /** 256 | * Encodes all bytes from the specified byte array into a newly-allocated 257 | * byte array using the {@link java.util.Base64} encoding scheme. The returned byte 258 | * array is of the length of the resulting bytes. 259 | * 260 | * @param src 261 | * the byte array to encode 262 | * @return A newly-allocated byte array containing the resulting 263 | * encoded bytes. 264 | */ 265 | public byte[] encode(byte[] src) { 266 | int len = outLength(src.length); // dst array size 267 | byte[] dst = new byte[len]; 268 | int ret = encode0(src, 0, src.length, dst); 269 | if (ret != dst.length) 270 | return Arrays.copyOf(dst, ret); 271 | return dst; 272 | } 273 | 274 | /** 275 | * Encodes all bytes from the specified byte array using the 276 | * {@link java.util.Base64} encoding scheme, writing the resulting bytes to the 277 | * given output byte array, starting at offset 0. 278 | * 279 | *

It is the responsibility of the invoker of this method to make 280 | * sure the output byte array {@code dst} has enough space for encoding 281 | * all bytes from the input byte array. No bytes will be written to the 282 | * output byte array if the output byte array is not big enough. 283 | * 284 | * @param src 285 | * the byte array to encode 286 | * @param dst 287 | * the output byte array 288 | * @return The number of bytes written to the output byte array 289 | * 290 | * @throws IllegalArgumentException if {@code dst} does not have enough 291 | * space for encoding all input bytes. 292 | */ 293 | public int encode(byte[] src, byte[] dst) { 294 | int len = outLength(src.length); // dst array size 295 | if (dst.length < len) 296 | throw new IllegalArgumentException( 297 | "Output byte array is too small for encoding all input bytes"); 298 | return encode0(src, 0, src.length, dst); 299 | } 300 | 301 | /** 302 | * Encodes the specified byte array into a String using the {@link java.util.Base64} 303 | * encoding scheme. 304 | * 305 | *

This method first encodes all input bytes into a base64 encoded 306 | * byte array and then constructs a new String by using the encoded byte 307 | * array and the {@link java.nio.charset.StandardCharsets#ISO_8859_1 308 | * ISO-8859-1} charset. 309 | * 310 | *

In other words, an invocation of this method has exactly the same 311 | * effect as invoking 312 | * {@code new String(encode(src), StandardCharsets.ISO_8859_1)}. 313 | * 314 | * @param src 315 | * the byte array to encode 316 | * @return A String containing the resulting Base64 encoded characters 317 | */ 318 | @SuppressWarnings("deprecation") 319 | public String encodeToString(byte[] src) { 320 | byte[] encoded = encode(src); 321 | return new String(encoded, 0, 0, encoded.length); 322 | } 323 | 324 | /** 325 | * Encodes all remaining bytes from the specified byte buffer into 326 | * a newly-allocated ByteBuffer using the {@link java.util.Base64} encoding 327 | * scheme. 328 | * 329 | * Upon return, the source buffer's position will be updated to 330 | * its limit; its limit will not have been changed. The returned 331 | * output buffer's position will be zero and its limit will be the 332 | * number of resulting encoded bytes. 333 | * 334 | * @param buffer 335 | * the source ByteBuffer to encode 336 | * @return A newly-allocated byte buffer containing the encoded bytes. 337 | */ 338 | public ByteBuffer encode(ByteBuffer buffer) { 339 | int len = outLength(buffer.remaining()); 340 | byte[] dst = new byte[len]; 341 | int ret = 0; 342 | if (buffer.hasArray()) { 343 | ret = encode0(buffer.array(), 344 | buffer.arrayOffset() + buffer.position(), 345 | buffer.arrayOffset() + buffer.limit(), 346 | dst); 347 | buffer.position(buffer.limit()); 348 | } else { 349 | byte[] src = new byte[buffer.remaining()]; 350 | buffer.get(src); 351 | ret = encode0(src, 0, src.length, dst); 352 | } 353 | if (ret != dst.length) 354 | dst = Arrays.copyOf(dst, ret); 355 | return ByteBuffer.wrap(dst); 356 | } 357 | 358 | /** 359 | * Wraps an output stream for encoding byte data using the {@link java.util.Base64} 360 | * encoding scheme. 361 | * 362 | *

It is recommended to promptly close the returned output stream after 363 | * use, during which it will flush all possible leftover bytes to the underlying 364 | * output stream. Closing the returned output stream will close the underlying 365 | * output stream. 366 | * 367 | * @param os 368 | * the output stream. 369 | * @return the output stream for encoding the byte data into the 370 | * specified Base64 encoded format 371 | */ 372 | public OutputStream wrap(OutputStream os) { 373 | Objects.requireNonNull(os); 374 | return new EncOutputStream(os, isURL ? toBase64URL : toBase64, 375 | newline, linemax, doPadding); 376 | } 377 | 378 | /** 379 | * Returns an encoder instance that encodes equivalently to this one, 380 | * but without adding any padding character at the end of the encoded 381 | * byte data. 382 | * 383 | *

The encoding scheme of this encoder instance is unaffected by 384 | * this invocation. The returned encoder instance should be used for 385 | * non-padding encoding operation. 386 | * 387 | * @return an equivalent encoder that encodes without adding any 388 | * padding character at the end 389 | */ 390 | public Encoder withoutPadding() { 391 | if (!doPadding) 392 | return this; 393 | return new Encoder(isURL, newline, linemax, false); 394 | } 395 | 396 | private int encode0(byte[] src, int off, int end, byte[] dst) { 397 | char[] base64 = isURL ? toBase64URL : toBase64; 398 | int sp = off; 399 | int slen = (end - off) / 3 * 3; 400 | int sl = off + slen; 401 | if (linemax > 0 && slen > linemax / 4 * 3) 402 | slen = linemax / 4 * 3; 403 | int dp = 0; 404 | while (sp < sl) { 405 | int sl0 = Math.min(sp + slen, sl); 406 | for (int sp0 = sp, dp0 = dp ; sp0 < sl0; ) { 407 | int bits = (src[sp0++] & 0xff) << 16 | 408 | (src[sp0++] & 0xff) << 8 | 409 | (src[sp0++] & 0xff); 410 | dst[dp0++] = (byte)base64[(bits >>> 18) & 0x3f]; 411 | dst[dp0++] = (byte)base64[(bits >>> 12) & 0x3f]; 412 | dst[dp0++] = (byte)base64[(bits >>> 6) & 0x3f]; 413 | dst[dp0++] = (byte)base64[bits & 0x3f]; 414 | } 415 | int dlen = (sl0 - sp) / 3 * 4; 416 | dp += dlen; 417 | sp = sl0; 418 | if (dlen == linemax && sp < end) { 419 | for (byte b : newline){ 420 | dst[dp++] = b; 421 | } 422 | } 423 | } 424 | if (sp < end) { // 1 or 2 leftover bytes 425 | int b0 = src[sp++] & 0xff; 426 | dst[dp++] = (byte)base64[b0 >> 2]; 427 | if (sp == end) { 428 | dst[dp++] = (byte)base64[(b0 << 4) & 0x3f]; 429 | if (doPadding) { 430 | dst[dp++] = '='; 431 | dst[dp++] = '='; 432 | } 433 | } else { 434 | int b1 = src[sp++] & 0xff; 435 | dst[dp++] = (byte)base64[(b0 << 4) & 0x3f | (b1 >> 4)]; 436 | dst[dp++] = (byte)base64[(b1 << 2) & 0x3f]; 437 | if (doPadding) { 438 | dst[dp++] = '='; 439 | } 440 | } 441 | } 442 | return dp; 443 | } 444 | } 445 | 446 | /** 447 | * This class implements a decoder for decoding byte data using the 448 | * Base64 encoding scheme as specified in RFC 4648 and RFC 2045. 449 | * 450 | *

The Base64 padding character {@code '='} is accepted and 451 | * interpreted as the end of the encoded byte data, but is not 452 | * required. So if the final unit of the encoded byte data only has 453 | * two or three Base64 characters (without the corresponding padding 454 | * character(s) padded), they are decoded as if followed by padding 455 | * character(s). If there is a padding character present in the 456 | * final unit, the correct number of padding character(s) must be 457 | * present, otherwise {@code IllegalArgumentException} ( 458 | * {@code IOException} when reading from a Base64 stream) is thrown 459 | * during decoding. 460 | * 461 | *

Instances of {@link Decoder} class are safe for use by 462 | * multiple concurrent threads. 463 | * 464 | *

Unless otherwise noted, passing a {@code null} argument to 465 | * a method of this class will cause a 466 | * {@link java.lang.NullPointerException NullPointerException} to 467 | * be thrown. 468 | * 469 | * @see Encoder 470 | * @since 1.8 471 | */ 472 | public static class Decoder { 473 | 474 | private final boolean isURL; 475 | private final boolean isMIME; 476 | 477 | private Decoder(boolean isURL, boolean isMIME) { 478 | this.isURL = isURL; 479 | this.isMIME = isMIME; 480 | } 481 | 482 | /** 483 | * Lookup table for decoding unicode characters drawn from the 484 | * "Base64 Alphabet" (as specified in Table 1 of RFC 2045) into 485 | * their 6-bit positive integer equivalents. Characters that 486 | * are not in the Base64 alphabet but fall within the bounds of 487 | * the array are encoded to -1. 488 | * 489 | */ 490 | private static final int[] fromBase64 = new int[256]; 491 | static { 492 | Arrays.fill(fromBase64, -1); 493 | for (int i = 0; i < Encoder.toBase64.length; i++) 494 | fromBase64[Encoder.toBase64[i]] = i; 495 | fromBase64['='] = -2; 496 | } 497 | 498 | /** 499 | * Lookup table for decoding "URL and Filename safe Base64 Alphabet" 500 | * as specified in Table2 of the RFC 4648. 501 | */ 502 | private static final int[] fromBase64URL = new int[256]; 503 | 504 | static { 505 | Arrays.fill(fromBase64URL, -1); 506 | for (int i = 0; i < Encoder.toBase64URL.length; i++) 507 | fromBase64URL[Encoder.toBase64URL[i]] = i; 508 | fromBase64URL['='] = -2; 509 | } 510 | 511 | static final Decoder RFC4648 = new Decoder(false, false); 512 | static final Decoder RFC4648_URLSAFE = new Decoder(true, false); 513 | static final Decoder RFC2045 = new Decoder(false, true); 514 | 515 | /** 516 | * Decodes all bytes from the input byte array using the {@link java.util.Base64} 517 | * encoding scheme, writing the results into a newly-allocated output 518 | * byte array. The returned byte array is of the length of the resulting 519 | * bytes. 520 | * 521 | * @param src 522 | * the byte array to decode 523 | * 524 | * @return A newly-allocated byte array containing the decoded bytes. 525 | * 526 | * @throws IllegalArgumentException 527 | * if {@code src} is not in valid Base64 scheme 528 | */ 529 | public byte[] decode(byte[] src) { 530 | byte[] dst = new byte[outLength(src, 0, src.length)]; 531 | int ret = decode0(src, 0, src.length, dst); 532 | if (ret != dst.length) { 533 | dst = Arrays.copyOf(dst, ret); 534 | } 535 | return dst; 536 | } 537 | 538 | /** 539 | * Decodes a Base64 encoded String into a newly-allocated byte array 540 | * using the {@link java.util.Base64} encoding scheme. 541 | * 542 | *

An invocation of this method has exactly the same effect as invoking 543 | * {@code decode(src.getBytes(StandardCharsets.ISO_8859_1))} 544 | * 545 | * @param src 546 | * the string to decode 547 | * 548 | * @return A newly-allocated byte array containing the decoded bytes. 549 | * 550 | * @throws IllegalArgumentException 551 | * if {@code src} is not in valid Base64 scheme 552 | */ 553 | public byte[] decode(String src) { 554 | return decode(src.getBytes(StandardCharsets.ISO_8859_1)); 555 | } 556 | 557 | /** 558 | * Decodes all bytes from the input byte array using the {@link java.util.Base64} 559 | * encoding scheme, writing the results into the given output byte array, 560 | * starting at offset 0. 561 | * 562 | *

It is the responsibility of the invoker of this method to make 563 | * sure the output byte array {@code dst} has enough space for decoding 564 | * all bytes from the input byte array. No bytes will be be written to 565 | * the output byte array if the output byte array is not big enough. 566 | * 567 | *

If the input byte array is not in valid Base64 encoding scheme 568 | * then some bytes may have been written to the output byte array before 569 | * IllegalargumentException is thrown. 570 | * 571 | * @param src 572 | * the byte array to decode 573 | * @param dst 574 | * the output byte array 575 | * 576 | * @return The number of bytes written to the output byte array 577 | * 578 | * @throws IllegalArgumentException 579 | * if {@code src} is not in valid Base64 scheme, or {@code dst} 580 | * does not have enough space for decoding all input bytes. 581 | */ 582 | public int decode(byte[] src, byte[] dst) { 583 | int len = outLength(src, 0, src.length); 584 | if (dst.length < len) 585 | throw new IllegalArgumentException( 586 | "Output byte array is too small for decoding all input bytes"); 587 | return decode0(src, 0, src.length, dst); 588 | } 589 | 590 | /** 591 | * Decodes all bytes from the input byte buffer using the {@link java.util.Base64} 592 | * encoding scheme, writing the results into a newly-allocated ByteBuffer. 593 | * 594 | *

Upon return, the source buffer's position will be updated to 595 | * its limit; its limit will not have been changed. The returned 596 | * output buffer's position will be zero and its limit will be the 597 | * number of resulting decoded bytes 598 | * 599 | *

{@code IllegalArgumentException} is thrown if the input buffer 600 | * is not in valid Base64 encoding scheme. The position of the input 601 | * buffer will not be advanced in this case. 602 | * 603 | * @param buffer 604 | * the ByteBuffer to decode 605 | * 606 | * @return A newly-allocated byte buffer containing the decoded bytes 607 | * 608 | * @throws IllegalArgumentException 609 | * if {@code src} is not in valid Base64 scheme. 610 | */ 611 | public ByteBuffer decode(ByteBuffer buffer) { 612 | int pos0 = buffer.position(); 613 | try { 614 | byte[] src; 615 | int sp, sl; 616 | if (buffer.hasArray()) { 617 | src = buffer.array(); 618 | sp = buffer.arrayOffset() + buffer.position(); 619 | sl = buffer.arrayOffset() + buffer.limit(); 620 | buffer.position(buffer.limit()); 621 | } else { 622 | src = new byte[buffer.remaining()]; 623 | buffer.get(src); 624 | sp = 0; 625 | sl = src.length; 626 | } 627 | byte[] dst = new byte[outLength(src, sp, sl)]; 628 | return ByteBuffer.wrap(dst, 0, decode0(src, sp, sl, dst)); 629 | } catch (IllegalArgumentException iae) { 630 | buffer.position(pos0); 631 | throw iae; 632 | } 633 | } 634 | 635 | /** 636 | * Returns an input stream for decoding {@link java.util.Base64} encoded byte stream. 637 | * 638 | *

The {@code read} methods of the returned {@code InputStream} will 639 | * throw {@code IOException} when reading bytes that cannot be decoded. 640 | * 641 | *

Closing the returned input stream will close the underlying 642 | * input stream. 643 | * 644 | * @param is 645 | * the input stream 646 | * 647 | * @return the input stream for decoding the specified Base64 encoded 648 | * byte stream 649 | */ 650 | public InputStream wrap(InputStream is) { 651 | Objects.requireNonNull(is); 652 | return new DecInputStream(is, isURL ? fromBase64URL : fromBase64, isMIME); 653 | } 654 | 655 | private int outLength(byte[] src, int sp, int sl) { 656 | int[] base64 = isURL ? fromBase64URL : fromBase64; 657 | int paddings = 0; 658 | int len = sl - sp; 659 | if (len == 0) 660 | return 0; 661 | if (len < 2) { 662 | if (isMIME && base64[0] == -1) 663 | return 0; 664 | throw new IllegalArgumentException( 665 | "Input byte[] should at least have 2 bytes for base64 bytes"); 666 | } 667 | if (isMIME) { 668 | // scan all bytes to fill out all non-alphabet. a performance 669 | // trade-off of pre-scan or Arrays.copyOf 670 | int n = 0; 671 | while (sp < sl) { 672 | int b = src[sp++] & 0xff; 673 | if (b == '=') { 674 | len -= (sl - sp + 1); 675 | break; 676 | } 677 | if ((b = base64[b]) == -1) 678 | n++; 679 | } 680 | len -= n; 681 | } else { 682 | if (src[sl - 1] == '=') { 683 | paddings++; 684 | if (src[sl - 2] == '=') 685 | paddings++; 686 | } 687 | } 688 | if (paddings == 0 && (len & 0x3) != 0) 689 | paddings = 4 - (len & 0x3); 690 | return 3 * ((len + 3) / 4) - paddings; 691 | } 692 | 693 | private int decode0(byte[] src, int sp, int sl, byte[] dst) { 694 | int[] base64 = isURL ? fromBase64URL : fromBase64; 695 | int dp = 0; 696 | int bits = 0; 697 | int shiftto = 18; // pos of first byte of 4-byte atom 698 | while (sp < sl) { 699 | int b = src[sp++] & 0xff; 700 | if ((b = base64[b]) < 0) { 701 | if (b == -2) { // padding byte '=' 702 | // = shiftto==18 unnecessary padding 703 | // x= shiftto==12 a dangling single x 704 | // x to be handled together with non-padding case 705 | // xx= shiftto==6&&sp==sl missing last = 706 | // xx=y shiftto==6 last is not = 707 | if (shiftto == 6 && (sp == sl || src[sp++] != '=') || 708 | shiftto == 18) { 709 | throw new IllegalArgumentException( 710 | "Input byte array has wrong 4-byte ending unit"); 711 | } 712 | break; 713 | } 714 | if (isMIME) // skip if for rfc2045 715 | continue; 716 | else 717 | throw new IllegalArgumentException( 718 | "Illegal base64 character " + 719 | Integer.toString(src[sp - 1], 16)); 720 | } 721 | bits |= (b << shiftto); 722 | shiftto -= 6; 723 | if (shiftto < 0) { 724 | dst[dp++] = (byte)(bits >> 16); 725 | dst[dp++] = (byte)(bits >> 8); 726 | dst[dp++] = (byte)(bits); 727 | shiftto = 18; 728 | bits = 0; 729 | } 730 | } 731 | // reached end of byte array or hit padding '=' characters. 732 | if (shiftto == 6) { 733 | dst[dp++] = (byte)(bits >> 16); 734 | } else if (shiftto == 0) { 735 | dst[dp++] = (byte)(bits >> 16); 736 | dst[dp++] = (byte)(bits >> 8); 737 | } else if (shiftto == 12) { 738 | // dangling single "x", incorrectly encoded. 739 | throw new IllegalArgumentException( 740 | "Last unit does not have enough valid bits"); 741 | } 742 | // anything left is invalid, if is not MIME. 743 | // if MIME, ignore all non-base64 character 744 | while (sp < sl) { 745 | if (isMIME && base64[src[sp++]] < 0) 746 | continue; 747 | throw new IllegalArgumentException( 748 | "Input byte array has incorrect ending byte at " + sp); 749 | } 750 | return dp; 751 | } 752 | } 753 | 754 | /* 755 | * An output stream for encoding bytes into the Base64. 756 | */ 757 | private static class EncOutputStream extends FilterOutputStream { 758 | 759 | private int leftover = 0; 760 | private int b0, b1, b2; 761 | private boolean closed = false; 762 | 763 | private final char[] base64; // byte->base64 mapping 764 | private final byte[] newline; // line separator, if needed 765 | private final int linemax; 766 | private final boolean doPadding;// whether or not to pad 767 | private int linepos = 0; 768 | 769 | EncOutputStream(OutputStream os, char[] base64, 770 | byte[] newline, int linemax, boolean doPadding) { 771 | super(os); 772 | this.base64 = base64; 773 | this.newline = newline; 774 | this.linemax = linemax; 775 | this.doPadding = doPadding; 776 | } 777 | 778 | @Override 779 | public void write(int b) throws IOException { 780 | byte[] buf = new byte[1]; 781 | buf[0] = (byte)(b & 0xff); 782 | write(buf, 0, 1); 783 | } 784 | 785 | private void checkNewline() throws IOException { 786 | if (linepos == linemax) { 787 | out.write(newline); 788 | linepos = 0; 789 | } 790 | } 791 | 792 | @Override 793 | public void write(byte[] b, int off, int len) throws IOException { 794 | if (closed) 795 | throw new IOException("Stream is closed"); 796 | if (off < 0 || len < 0 || len > b.length - off) 797 | throw new ArrayIndexOutOfBoundsException(); 798 | if (len == 0) 799 | return; 800 | if (leftover != 0) { 801 | if (leftover == 1) { 802 | b1 = b[off++] & 0xff; 803 | len--; 804 | if (len == 0) { 805 | leftover++; 806 | return; 807 | } 808 | } 809 | b2 = b[off++] & 0xff; 810 | len--; 811 | checkNewline(); 812 | out.write(base64[b0 >> 2]); 813 | out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]); 814 | out.write(base64[(b1 << 2) & 0x3f | (b2 >> 6)]); 815 | out.write(base64[b2 & 0x3f]); 816 | linepos += 4; 817 | } 818 | int nBits24 = len / 3; 819 | leftover = len - (nBits24 * 3); 820 | while (nBits24-- > 0) { 821 | checkNewline(); 822 | int bits = (b[off++] & 0xff) << 16 | 823 | (b[off++] & 0xff) << 8 | 824 | (b[off++] & 0xff); 825 | out.write(base64[(bits >>> 18) & 0x3f]); 826 | out.write(base64[(bits >>> 12) & 0x3f]); 827 | out.write(base64[(bits >>> 6) & 0x3f]); 828 | out.write(base64[bits & 0x3f]); 829 | linepos += 4; 830 | } 831 | if (leftover == 1) { 832 | b0 = b[off++] & 0xff; 833 | } else if (leftover == 2) { 834 | b0 = b[off++] & 0xff; 835 | b1 = b[off++] & 0xff; 836 | } 837 | } 838 | 839 | @Override 840 | public void close() throws IOException { 841 | if (!closed) { 842 | closed = true; 843 | if (leftover == 1) { 844 | checkNewline(); 845 | out.write(base64[b0 >> 2]); 846 | out.write(base64[(b0 << 4) & 0x3f]); 847 | if (doPadding) { 848 | out.write('='); 849 | out.write('='); 850 | } 851 | } else if (leftover == 2) { 852 | checkNewline(); 853 | out.write(base64[b0 >> 2]); 854 | out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]); 855 | out.write(base64[(b1 << 2) & 0x3f]); 856 | if (doPadding) { 857 | out.write('='); 858 | } 859 | } 860 | leftover = 0; 861 | out.close(); 862 | } 863 | } 864 | } 865 | 866 | /* 867 | * An input stream for decoding Base64 bytes 868 | */ 869 | private static class DecInputStream extends InputStream { 870 | 871 | private final InputStream is; 872 | private final boolean isMIME; 873 | private final int[] base64; // base64 -> byte mapping 874 | private int bits = 0; // 24-bit buffer for decoding 875 | private int nextin = 18; // next available "off" in "bits" for input; 876 | // -> 18, 12, 6, 0 877 | private int nextout = -8; // next available "off" in "bits" for output; 878 | // -> 8, 0, -8 (no byte for output) 879 | private boolean eof = false; 880 | private boolean closed = false; 881 | 882 | DecInputStream(InputStream is, int[] base64, boolean isMIME) { 883 | this.is = is; 884 | this.base64 = base64; 885 | this.isMIME = isMIME; 886 | } 887 | 888 | private byte[] sbBuf = new byte[1]; 889 | 890 | @Override 891 | public int read() throws IOException { 892 | return read(sbBuf, 0, 1) == -1 ? -1 : sbBuf[0] & 0xff; 893 | } 894 | 895 | @Override 896 | public int read(byte[] b, int off, int len) throws IOException { 897 | if (closed) 898 | throw new IOException("Stream is closed"); 899 | if (eof && nextout < 0) // eof and no leftover 900 | return -1; 901 | if (off < 0 || len < 0 || len > b.length - off) 902 | throw new IndexOutOfBoundsException(); 903 | int oldOff = off; 904 | if (nextout >= 0) { // leftover output byte(s) in bits buf 905 | do { 906 | if (len == 0) 907 | return off - oldOff; 908 | b[off++] = (byte)(bits >> nextout); 909 | len--; 910 | nextout -= 8; 911 | } while (nextout >= 0); 912 | bits = 0; 913 | } 914 | while (len > 0) { 915 | int v = is.read(); 916 | if (v == -1) { 917 | eof = true; 918 | if (nextin != 18) { 919 | if (nextin == 12) 920 | throw new IOException("Base64 stream has one un-decoded dangling byte."); 921 | // treat ending xx/xxx without padding character legal. 922 | // same logic as v == '=' below 923 | b[off++] = (byte)(bits >> (16)); 924 | len--; 925 | if (nextin == 0) { // only one padding byte 926 | if (len == 0) { // no enough output space 927 | bits >>= 8; // shift to lowest byte 928 | nextout = 0; 929 | } else { 930 | b[off++] = (byte) (bits >> 8); 931 | } 932 | } 933 | } 934 | if (off == oldOff) 935 | return -1; 936 | else 937 | return off - oldOff; 938 | } 939 | if (v == '=') { // padding byte(s) 940 | // = shiftto==18 unnecessary padding 941 | // x= shiftto==12 dangling x, invalid unit 942 | // xx= shiftto==6 && missing last '=' 943 | // xx=y or last is not '=' 944 | if (nextin == 18 || nextin == 12 || 945 | nextin == 6 && is.read() != '=') { 946 | throw new IOException("Illegal base64 ending sequence:" + nextin); 947 | } 948 | b[off++] = (byte)(bits >> (16)); 949 | len--; 950 | if (nextin == 0) { // only one padding byte 951 | if (len == 0) { // no enough output space 952 | bits >>= 8; // shift to lowest byte 953 | nextout = 0; 954 | } else { 955 | b[off++] = (byte) (bits >> 8); 956 | } 957 | } 958 | eof = true; 959 | break; 960 | } 961 | if ((v = base64[v]) == -1) { 962 | if (isMIME) // skip if for rfc2045 963 | continue; 964 | else 965 | throw new IOException("Illegal base64 character " + 966 | Integer.toString(v, 16)); 967 | } 968 | bits |= (v << nextin); 969 | if (nextin == 0) { 970 | nextin = 18; // clear for next 971 | nextout = 16; 972 | while (nextout >= 0) { 973 | b[off++] = (byte)(bits >> nextout); 974 | len--; 975 | nextout -= 8; 976 | if (len == 0 && nextout >= 0) { // don't clean "bits" 977 | return off - oldOff; 978 | } 979 | } 980 | bits = 0; 981 | } else { 982 | nextin -= 6; 983 | } 984 | } 985 | return off - oldOff; 986 | } 987 | 988 | @Override 989 | public int available() throws IOException { 990 | if (closed) 991 | throw new IOException("Stream is closed"); 992 | return is.available(); // TBD: 993 | } 994 | 995 | @Override 996 | public void close() throws IOException { 997 | if (!closed) { 998 | closed = true; 999 | is.close(); 1000 | } 1001 | } 1002 | } 1003 | } 1004 | 1005 | --------------------------------------------------------------------------------