diff --git a/AppFrame/.gitignore b/AppFrame/.gitignore new file mode 100644 index 00000000..18a2228d --- /dev/null +++ b/AppFrame/.gitignore @@ -0,0 +1,4 @@ +/build +mvvm2.iml +#jcenter.gradle +gradle.properties diff --git a/AppFrame/LICENSE b/AppFrame/LICENSE new file mode 100644 index 00000000..8dada3ed --- /dev/null +++ b/AppFrame/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/AppFrame/build.gradle b/AppFrame/build.gradle new file mode 100644 index 00000000..e75d329e --- /dev/null +++ b/AppFrame/build.gradle @@ -0,0 +1,39 @@ +apply plugin: 'com.android.library' +//apply plugin: 'com.neenbedankt.android-apt' +android { + compileSdkVersion rootProject.ext.compileSdkVersion + buildToolsVersion rootProject.ext.buildToolsVersion + + defaultConfig { + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + versionCode 61 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + dataBinding { + enabled = true + } +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + testImplementation 'junit:junit:4.12' + implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}" + implementation 'com.google.code.gson:gson:2.7' + // compile 'com.squareup.retrofit2:retrofit:2.1.0' + // compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0' + // compile 'com.squareup.retrofit2:converter-gson:2.1.0' + implementation 'com.squareup.okhttp3:okhttp:3.2.0' + // compile 'com.squareup.okhttp3:logging-interceptor:3.2.0' + // compile 'com.google.dagger:dagger:2.5' + // apt 'com.google.dagger:dagger-compiler:2.5' + // provided 'javax.annotation:jsr250-api:1.0' +} + +//apply from: 'jcenter.gradle' diff --git a/AppFrame/jcenter.gradle b/AppFrame/jcenter.gradle new file mode 100644 index 00000000..3d3197d1 --- /dev/null +++ b/AppFrame/jcenter.gradle @@ -0,0 +1,168 @@ +group = PROJ_GROUP +version = PROJ_VERSION +project.archivesBaseName = PROJ_ARTIFACTID +apply plugin: 'com.jfrog.bintray' +apply plugin: "com.jfrog.artifactory" +apply plugin: 'maven-publish' + +task sourcesJar(type: Jar) { + from android.sourceSets.main.java.srcDirs + classifier = 'sources' +} + +task javadoc(type: Javadoc) { + source = android.sourceSets.main.java.srcDirs + classpath += configurations.compile + classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) +} + +task javadocJar(type: Jar, dependsOn: javadoc) { + classifier = 'javadoc' + from javadoc.destinationDir +} + +javadoc { + options { + encoding "UTF-8" + charSet 'UTF-8' + author true + version true + links "http://docs.oracle.com/javase/7/docs/api" + title PROJ_ARTIFACTID + } +} + +//添加以下信息避免JAVADOC打包时引用其它类库而出现问题,比如出现以下错误 +// xxxx.java:20: 错误: 找不到符号 +// public static T create(JsonElement json, Class classOfModel) { +// ^ +// 符号: 类 JsonElement +// 位置: 类 xxxx +android.libraryVariants.all { variant -> + println variant.javaCompile.classpath.files + if (variant.name == 'release') { + //我们只需 release 的 javadoc + task("generate${variant.name.capitalize()}Javadoc", type: Javadoc) { + // title = '' + // description = '' + source = variant.javaCompile.source + classpath = files(variant.javaCompile.classpath.files, project.android.getBootClasspath()) + options { + encoding "utf-8" + links "http://docs.oracle.com/javase/7/docs/api/" + linksOffline "http://d.android.com/reference", "${android.sdkDirectory}/docs/reference" + } + exclude '**/BuildConfig.java' + exclude '**/R.java' + } + task("javadoc${variant.name.capitalize()}Jar", type: Jar, + dependsOn: "generate${variant.name.capitalize()}Javadoc") { + classifier = 'javadoc' + from tasks.getByName("generate${variant.name.capitalize()}Javadoc").destinationDir + } + artifacts { + archives tasks.getByName("javadoc${variant.name.capitalize()}Jar") + } + } +} + +artifacts { + archives javadocJar + archives sourcesJar +} + + +def pomConfig = { + scm { + connection PROJ_VCSURL + developerConnection PROJ_VCSURL + url PROJ_WEBSITEURL + } + licenses { + license { + name "The Apache Software License, Version 2.0" + url "http://www.apache.org/licenses/LICENSE-2.0.txt" + distribution "repo" + } + } + developers { + developer { + id DEVELOPER_ID + name DEVELOPER_NAME + email DEVELOPER_EMAIL + } + } + "dependencies " { + // dependency { + // groupId "com.alibaba" + // artifactId "fastjson" + // "version " "1.2.6" + // //同dependencies + // } + // } + } +} + +publishing { + publications { + mavenJava(MavenPublication) { + artifactId PROJ_ARTIFACTID + artifact javadocJar + artifact sourcesJar + + pom { + packaging 'aar' + } + pom.withXml { + def root = asNode() + root.appendNode('description', PROJ_DESCRIPTION) + root.children().last() + pomConfig + } + } + } +} + +afterEvaluate { + publishing.publications.mavenJava.artifact(bundleRelease) +} + +bintray { + user = hasProperty("bintrayUser") ? getProperty("bintrayUser") : getProperty("BINTRAY_USER") + key = hasProperty("bintrayKey") ? getProperty("bintrayKey") : getProperty("BINTRAY_KEY") + + configurations = ['archives'] + publications = ['mavenJava'] + publish = true + + pkg { + repo = 'maven' + name = PROJ_NAME + desc = PROJ_DESCRIPTION + websiteUrl = PROJ_WEBSITEURL + issueTrackerUrl = PROJ_ISSUETRACKERURL + vcsUrl = PROJ_VCSURL + licenses = ['Apache-2.0'] + publicDownloadNumbers = true + } +} + +artifactory { + contextUrl = 'http://oss.jfrog.org/artifactory' + resolve { + repository { + repoKey = 'libs-release' + } + } + publish { + repository { + repoKey = 'oss-snapshot-local' //The Artifactory repository key to publish to + username = bintray.user + password = bintray.key + maven = true + } + defaults { + publications('mavenJava') + publishArtifacts = true + } + } +} \ No newline at end of file diff --git a/AppFrame/proguard-rules.pro b/AppFrame/proguard-rules.pro new file mode 100644 index 00000000..e1c786ed --- /dev/null +++ b/AppFrame/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in E:\sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/AppFrame/src/main/AndroidManifest.xml b/AppFrame/src/main/AndroidManifest.xml new file mode 100644 index 00000000..20730e69 --- /dev/null +++ b/AppFrame/src/main/AndroidManifest.xml @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/AppFrame/src/main/java/com/arialyy/frame/cache/AbsCache.java b/AppFrame/src/main/java/com/arialyy/frame/cache/AbsCache.java new file mode 100644 index 00000000..a9936f09 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/cache/AbsCache.java @@ -0,0 +1,367 @@ +package com.arialyy.frame.cache; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.v4.util.LruCache; +import android.text.TextUtils; +import com.arialyy.frame.cache.diskcache.DiskLruCache; +import com.arialyy.frame.util.AndroidUtils; +import com.arialyy.frame.util.AppUtils; +import com.arialyy.frame.util.StreamUtil; +import com.arialyy.frame.util.StringUtil; +import com.arialyy.frame.util.show.FL; +import com.arialyy.frame.util.show.L; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Created by “AriaLyy@outlook.com” on 2015/4/9. + * 缓存抽象类,封装了缓存的读写操作 + */ +abstract class AbsCache implements CacheParam { + private static final String TAG = "AbsCache"; + private static final Object LOCK = new Object(); + /** + * 磁盘缓存工具 + */ + private DiskLruCache mDiskLruCache = null; + /** + * 内存缓存工具 + */ + private LruCache mMemoryCache = null; + /** + * 是否使用内存缓存 + */ + private boolean useMemory = false; + /** + * 是否使用磁盘缓存 + */ + private boolean useDisk = false; + /** + * 最大的内存 + */ + private int mMaxMemoryCacheSize; + /** + * 最大的磁盘大小 + */ + private long mMaxDiskCacheSize; + private Context mContext; + + /** + * 默认使用默认路径 + */ + protected AbsCache(Context context) { + this(context, DEFAULT_DIR); + } + + /** + * 指定缓存文件夹 + * + * @param cacheDir 缓存文件夹 + */ + AbsCache(Context context, @NonNull String cacheDir) { + this.mContext = context; + init(cacheDir, 1, SMALL_DISK_CACHE_CAPACITY); + } + + private void init(String cacheDir, int valueCount, long cacheSize) { + initDiskCache(cacheDir, valueCount, cacheSize); + initMemoryCache(); + } + + /** + * 初始化磁盘缓存 + */ + private void initDiskCache(String cacheDir, int valueCount, long cacheSize) { + try { + File dir = getDiskCacheDir(mContext, cacheDir); + if (!dir.exists()) { + dir.mkdirs(); + } + mDiskLruCache = + DiskLruCache.open(dir, AppUtils.getVersionNumber(mContext), valueCount, cacheSize); + } catch (IOException e) { + FL.e(this, "createCacheFailed\n" + FL.getExceptionString(e)); + } + } + + /** + * 初始化内存缓存 + */ + private void initMemoryCache() { + if (!useMemory) return; + // 获取应用程序最大可用内存 + mMaxMemoryCacheSize = (int) Runtime.getRuntime().maxMemory(); + // 设置图片缓存大小为程序最大可用内存的1/8 + mMemoryCache = new LruCache<>(mMaxMemoryCacheSize / 8); + } + + /** + * 是否使用内存缓存 + */ + void setUseMemory(boolean useMemory) { + this.useMemory = useMemory; + } + + /** + * 是否使用磁盘缓存 + */ + void setUseDisk(boolean useDisk) { + this.useDisk = useDisk; + } + + /** + * 设置内存缓存大小 + */ + void setMemoryCacheSize(int size) { + if (useMemory && mMemoryCache != null) { + mMemoryCache.resize(size); + } + } + + /** + * 打开某个目录下的缓存 + * + * @param cacheDir 缓存目录,只需填写文件夹名,不需要写路径 + * @param valueCount 指定同一个key可以对应多少个缓存文件,基本都是传1 + * @param cacheSize 缓存大小 + * @see CacheParam + */ + void openDiskCache(@NonNull String cacheDir, int valueCount, long cacheSize) { + synchronized (LOCK) { + if (mDiskLruCache != null && mDiskLruCache.isClosed()) { + try { + File dir = getDiskCacheDir(mContext, cacheDir); + if (!dir.exists()) { + dir.mkdirs(); + } + mDiskLruCache = + DiskLruCache.open(dir, AppUtils.getVersionNumber(mContext), valueCount, cacheSize); + } catch (IOException e) { + FL.e(this, "createCacheFailed\n" + FL.getExceptionString(e)); + } + } + } + } + + /** + * 把缓存写入磁盘 + * + * @param key 缓存的key,通过该key来读写缓存,一般是URL + * @param data 缓存的数据 + */ + void writeDiskCache(@NonNull String key, @NonNull byte[] data) { + if (TextUtils.isEmpty(key)) { + L.e(TAG, "key 不能为null"); + return; + } + String hashKey = StringUtil.keyToHashKey(key); + if (useMemory && mMemoryCache != null) { + mMemoryCache.put(hashKey, data); + } + if (useDisk) { + synchronized (LOCK) { + if (mDiskLruCache != null) { + L.i(TAG, "缓存数据到磁盘[key:" + key + ",hashKey:" + hashKey + "]"); + OutputStream out = null; + try { + DiskLruCache.Editor editor = mDiskLruCache.edit(hashKey); + out = editor.newOutputStream(DISK_CACHE_INDEX); + out.write(data, 0, data.length); + editor.commit(); + out.flush(); + out.close(); + } catch (IOException e) { + FL.e(this, "writeDiskFailed[key:" + + key + + ",hashKey:" + + hashKey + + "]\n" + + FL.getExceptionString(e)); + } finally { + if (out != null) { + try { + out.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + } + } + } + + /** + * 从磁盘读取缓存 + * + * @param key 缓存的key,一般是原来的url + * @return 缓存数据 + */ + byte[] readDiskCache(@NonNull String key) { + if (TextUtils.isEmpty(key)) { + return null; + } + String hashKey = StringUtil.keyToHashKey(key); + if (useMemory && mMemoryCache != null) { + final byte[] data = mMemoryCache.get(hashKey); + if (data != null && data.length != 0) { + return data; + } + } + if (useDisk) { + synchronized (LOCK) { + byte[] data = null; + L.i(TAG, "读取磁盘缓存数据[key:" + key + ",hashKey:" + hashKey + "]"); + InputStream inputStream = null; + try { + DiskLruCache.Snapshot snapshot = mDiskLruCache.get(hashKey); + if (snapshot != null) { + inputStream = snapshot.getInputStream(DISK_CACHE_INDEX); + data = StreamUtil.readStream(inputStream); + return data; + } + } catch (Exception e) { + FL.e(this, "readDiskCacheFailed[key:" + + key + + ",hashKey:" + + hashKey + + "]\n" + + FL.getExceptionString(e)); + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + } + return null; + } + + /** + * 删除一条缓存 + * + * @param key 该缓存的key + */ + protected void removeCache(@NonNull String key) { + String hashKey = StringUtil.keyToHashKey(key); + if (mMemoryCache != null) { + mMemoryCache.remove(hashKey); + } + synchronized (LOCK) { + if (mDiskLruCache != null) { + try { + mDiskLruCache.remove(hashKey); + } catch (IOException e) { + FL.e(this, "removeCacheFailed[key:" + + key + + ",hashKey:" + + hashKey + + "]\n" + + FL.getExceptionString(e)); + } + } + } + } + + /** + * 关闭内存缓存 + */ + void closeMemoryCache() { + if (mMemoryCache != null) { + mMemoryCache.evictAll(); + } + } + + /** + * 清除所有缓存 + */ + void clearCache() { + if (mMemoryCache != null) { + mMemoryCache.evictAll(); + } + synchronized (LOCK) { + if (mDiskLruCache != null) { + try { + mDiskLruCache.delete(); + } catch (IOException e) { + FL.e(this, "clearCacheFailed" + FL.getExceptionString(e)); + } + } + } + } + + /** + * 关闭磁盘缓存,注意: + * 这个方法用于将DiskLruCache关闭掉,是和open()方法对应的一个方法。 + * 关闭掉了之后就不能再调用DiskLruCache中任何操作缓存数据的方法, + * 通常只应该在Activity的onDestroy()方法中去调用close()方法。 + */ + void closeDiskCache() { + synchronized (LOCK) { + if (mDiskLruCache != null) { + try { + mDiskLruCache.close(); + } catch (IOException e) { + FL.e(this, "closeDiskCacheFailed" + FL.getExceptionString(e)); + } + } + } + } + + /** + * 同步内存中的缓存操作记录到日志文件(也就是journal文件) + * 注意:在写入缓存时需要flush同步一次,并不是每次写入缓存都要调用一次flush()方法的,频繁地调用并不会带来任何好处, + * 只会额外增加同步journal文件的时间。比较标准的做法就是在Activity的onPause()方法中去调用一次flush()方法就可以了 + */ + void flushDiskCache() { + synchronized (LOCK) { + if (mDiskLruCache != null) { + try { + mDiskLruCache.flush(); + } catch (IOException e) { + FL.e(this, "flushDiskCacheFailed" + FL.getExceptionString(e)); + } + } + } + } + + /** + * 获取缓存大小 + */ + protected long getCacheSize() { + return mDiskLruCache.size(); + } + + /** + * 生成缓存文件夹 + * + * @param uniqueName 缓存文件夹名 + * @return 缓存文件夹 + */ + public static File getDiskCacheDir(Context context, String uniqueName) { + return new File(AndroidUtils.getDiskCacheDir(context) + File.separator + uniqueName); + } + + /** + * 转换byte数组为String + */ + private static String bytesToHexString(byte[] bytes) { + // http://stackoverflow.com/questions/332079 + StringBuilder sb = new StringBuilder(); + for (byte aByte : bytes) { + String hex = Integer.toHexString(0xFF & aByte); + if (hex.length() == 1) { + sb.append('0'); + } + sb.append(hex); + } + return sb.toString(); + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/cache/CacheHelp.java b/AppFrame/src/main/java/com/arialyy/frame/cache/CacheHelp.java new file mode 100644 index 00000000..8ba889fc --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/cache/CacheHelp.java @@ -0,0 +1,81 @@ +package com.arialyy.frame.cache; + +import android.text.TextUtils; +import com.arialyy.frame.util.show.L; +import java.util.Map; +import java.util.Set; + +/** + * Created by “AriaLyy@outlook.com” on 2015/4/9. + */ +public class CacheHelp { + + /** + * 设置缓存有效时间 + */ + public static void setCacheAvailableTime(long availableTime) { + CacheObj.CACHE_INTERVAL = availableTime; + } + + /** + * 储存缓存 + */ + public static void saveCache(CacheUtil util, String key, String value) { + if (TextUtils.isEmpty(value)) { + L.e("缓存数据位null"); + return; + } + CacheObj cache = util.getObjectCache(CacheObj.class, key); + if (cache == null) { + cache = new CacheObj(); + } + cache.setCacheTime(System.currentTimeMillis()); + cache.setAvailableNum(cache.getAvailableNum() + 1); + cache.setData(value); + util.putObjectCache(CacheObj.class, key, cache); + } + + /** + * 获取有效的缓存 + */ + public static CacheObj getAvailableCache(CacheUtil util, String url, Map param) { + if (TextUtils.isEmpty(url)) { + return null; + } + String key = getKey(url, param); + CacheObj cache = util.getObjectCache(CacheObj.class, key); + if (cache == null) { + return null; + } + if (cache.isAvailable()) { + return cache; + } + return null; + } + + /** + * 获取缓存,有效或无效 + */ + public static CacheObj getCache(CacheUtil util, String url, Map param) { + if (TextUtils.isEmpty(url)) { + return null; + } + String key = getKey(url, param); + return util.getObjectCache(CacheObj.class, key); + } + + /** + * 通过url和请求参数获取key + */ + public static String getKey(String url, Map param) { + if (param == null || param.size() == 0) { + return url; + } + String p = ""; + for (Map.Entry entry : param.entrySet()) { + p += "[" + entry.getKey() + "," + entry.getValue() + "];"; + } + url += ";" + p; + return url; + } +} \ No newline at end of file diff --git a/AppFrame/src/main/java/com/arialyy/frame/cache/CacheObj.java b/AppFrame/src/main/java/com/arialyy/frame/cache/CacheObj.java new file mode 100644 index 00000000..68fd469b --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/cache/CacheObj.java @@ -0,0 +1,56 @@ +package com.arialyy.frame.cache; + +/** + * Created by “AriaLyy@outlook.com” on 2015/4/9. + * 缓存对象 + */ +class CacheObj { + /** + * 缓存有效请求时间 + */ + public static long CACHE_INTERVAL = 120 * 1000; + /** + * 缓存有效请求次数 + */ + public static final int CACHE_NUM_INTERVAL = 5; + private long cacheTime = 0; + private long availableNum = 0; + + private String data; + + /** + * 缓存是否有效 + */ + public boolean isAvailable() { + if (System.currentTimeMillis() - cacheTime > CACHE_INTERVAL) { + return false; + } else if (availableNum >= CACHE_NUM_INTERVAL) { + return false; + } + return true; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public long getCacheTime() { + return cacheTime; + } + + public void setCacheTime(long cacheTime) { + this.cacheTime = cacheTime; + } + + public long getAvailableNum() { + return availableNum; + } + + public void setAvailableNum(long availableNum) { + this.availableNum = availableNum; + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/cache/CacheParam.java b/AppFrame/src/main/java/com/arialyy/frame/cache/CacheParam.java new file mode 100644 index 00000000..c243b642 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/cache/CacheParam.java @@ -0,0 +1,37 @@ +package com.arialyy.frame.cache; + +/** + * Created by “AriaLyy@outlook.com” on 2015/4/9. + * 缓存参数 + */ +interface CacheParam { + + /** + * 磁盘缓存 + */ + public static final int DISK_CACHE = 0; + /** + * 默认缓存目录文件夹名 + */ + public static final String DEFAULT_DIR = "defaultDir"; + /** + * 内存缓存 + */ + public static final int MEMORY_CACHE_SIZE = 4 * 1024 * 1024; + /** + * 小容量磁盘缓存 + */ + public static final long SMALL_DISK_CACHE_CAPACITY = 4 * 1024 * 1024; + /** + * 中型容量磁盘缓存 + */ + public static final long NORMAL_DISK_CACHE_CAPACITY = 10 * 1024 * 1024; + /** + * 大容量磁盘缓存 + */ + public static final long LARGER_DISKCACHE_CAPACITY = 20 * 1024 * 1024; + /** + * 缓存index + */ + public static final int DISK_CACHE_INDEX = 0; +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/cache/CacheUtil.java b/AppFrame/src/main/java/com/arialyy/frame/cache/CacheUtil.java new file mode 100644 index 00000000..84873664 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/cache/CacheUtil.java @@ -0,0 +1,262 @@ +package com.arialyy.frame.cache; + +import android.content.Context; +import android.graphics.Bitmap; +import android.support.annotation.NonNull; +import com.arialyy.frame.util.DrawableUtil; +import com.arialyy.frame.util.show.L; +import com.google.gson.Gson; +import java.io.UnsupportedEncodingException; + +/** + * Created by “AriaLyy@outlook.com” on 2015/4/9. + * 缓存工具 + */ +public class CacheUtil extends AbsCache { + private static final String TAG = "CacheUtil"; + + /** + * 默认使用默认路径 + */ + private CacheUtil(Context context) { + this(context, DEFAULT_DIR); + } + + /** + * 指定缓存文件夹 + * + * @param cacheDir 缓存文件夹名 + */ + private CacheUtil(Context context, @NonNull String cacheDir) { + super(context, cacheDir); + } + + /** + * 是否使用内存缓存 + */ + private void setUseMemoryCache(boolean openMemoryCache) { + setUseMemory(openMemoryCache); + } + + /** + * 是否使用磁场缓存 + */ + private void setUseDiskCache(boolean openDiskCache) { + setUseMemory(openDiskCache); + } + + /** + * 打开某个目录下的缓存 + * + * @param cacheDir 缓存目录,只需填写文件夹名,不需要写路径 + * @param valueCount 指定同一个key可以对应多少个缓存文件,基本都是传1 + * @param cacheSize 缓存大小 + * @see CacheParam + */ + public void openCache(String cacheDir, int valueCount, long cacheSize) { + openDiskCache(cacheDir, valueCount, cacheSize); + } + + /** + * 写入Bitmap类型缓存,注意:需要在特定的时候flush,一般在onPause()里面写,onDestroy() + * + * @param key 键值,一般是url + * @param bitmap 需要写入的数据 + */ + public void putBitmapCache(String key, Bitmap bitmap) { + byte[] data = DrawableUtil.getBitmapByte(bitmap); + putByteCache(key, data); + } + + /** + * 获取缓存中的bitmap + */ + public Bitmap getBitmapCache(String key) { + byte[] data = getByteCache(key); + return DrawableUtil.getBitmapFromByte(data); + } + + /** + * 写入String类型缓存,注意:需要在特定的时候flush,一般在onPause()里面写,onDestroy() + * + * @param key 键值,一般是url + * @param data 需要写入的数据 + */ + public void putStringCache(String key, String data) { + try { + putByteCache(key, data.getBytes("utf-8")); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } + + /** + * 读取字符串缓存 + */ + public String getStringCache(String key) { + byte[] data = getByteCache(key); + String str = ""; + if (data != null) { + try { + str = new String(data, "utf-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } + return str; + } + + /** + * 写入byte类型缓存,注意:需要在特定的时候flush,一般在onPause()里面写,onDestroy() + * + * @param key 键值,一般是url + * @param data 需要写入的数据 + */ + public void putByteCache(String key, byte[] data) { + writeDiskCache(key, data); + } + + /** + * 读取byte类型缓存 + * + * @param key 缓存的key + */ + public byte[] getByteCache(String key) { + return readDiskCache(key); + } + + /** + * 写入对象缓存后,注意:需要在特定的时候flush,一般在onPause()里面写,onDestroy() + * + * @param clazz 对象类型 + * @param key 缓存键值 + * @param object 对象 + */ + public void putObjectCache(Class clazz, String key, Object object) { + String json = new Gson().toJson(object, clazz); + try { + writeDiskCache(key, json.getBytes("utf-8")); + } catch (UnsupportedEncodingException e) { + L.e(TAG, "编码转换错误", e); + } + } + + /** + * 读取对象缓存 + * + * @param clazz 对象类型 + * @param key 缓存键值 + */ + public T getObjectCache(Class clazz, String key) { + T object = null; + try { + byte[] data = readDiskCache(key); + if (data == null) { + return null; + } + object = new Gson().fromJson(new String(data, "utf-8"), clazz); + } catch (UnsupportedEncodingException e) { + L.e(TAG, "编码转换错误", e); + } + return object; + } + + /** + * 同步记录,不同步则提取不了缓存 + */ + public void flush() { + flushDiskCache(); + } + + /** + * 删除一条缓存 + * + * @param key 缓存键值 + */ + public void remove(String key) { + readDiskCache(key); + } + + /** + * 删除所有缓存 + */ + public void clearCache() { + super.clearCache(); + } + + /** + * 关闭磁盘缓存 + */ + public void close() { + closeMemoryCache(); + closeDiskCache(); + } + + /** + * 获取缓存大小 + */ + public long getCacheSize() { + return super.getCacheSize(); + } + + public static class Builder { + boolean openDiskCache = false; + boolean openMemoryCache = false; + String cacheDirName = DEFAULT_DIR; + long diskCacheSize = NORMAL_DISK_CACHE_CAPACITY; + int memoryCacheSize = MEMORY_CACHE_SIZE; + Context context; + + public Builder(Context context) { + this.context = context; + } + + /** + * 打开磁盘缓存 + */ + public Builder openDiskCache() { + openDiskCache = true; + return this; + } + + /** + * 打开内存缓存 + */ + public Builder openMemoryCache() { + openMemoryCache = true; + return this; + } + + /** + * 缓存文件夹名,只需要写文件夹名 + */ + public Builder setCacheDirName(String cacheDirName) { + this.cacheDirName = cacheDirName; + return this; + } + + /** + * 设置磁盘缓存大小 + */ + public Builder setDiskCacheSize(long cacheSize) { + this.diskCacheSize = cacheSize; + return this; + } + + /** + * 设置内存缓存大小 + */ + public Builder setMemoryCacheSize(int cacheSize) { + this.memoryCacheSize = cacheSize; + return this; + } + + public CacheUtil build() { + CacheUtil util = new CacheUtil(context); + util.setUseMemoryCache(openMemoryCache); + util.setUseDiskCache(openDiskCache); + util.setMemoryCacheSize(memoryCacheSize); + return new CacheUtil(context, cacheDirName); + } + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/cache/diskcache/DiskLruCache.java b/AppFrame/src/main/java/com/arialyy/frame/cache/diskcache/DiskLruCache.java new file mode 100644 index 00000000..1ef48fd1 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/cache/diskcache/DiskLruCache.java @@ -0,0 +1,951 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.arialyy.frame.cache.diskcache; + +import java.io.BufferedWriter; +import java.io.Closeable; +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A cache that uses a bounded amount of space on a filesystem. Each cache + * entry has a string key and a fixed number of values. Each key must match + * the regex [a-z0-9_-]{1,120}. Values are byte sequences, + * accessible as streams or files. Each value must be between {@code 0} and + * {@code Integer.MAX_VALUE} bytes in length. + * + *

The cache stores its data in a directory on the filesystem. This + * directory must be exclusive to the cache; the cache may delete or overwrite + * files from its directory. It is an error for multiple processes to use the + * same cache directory at the same time. + * + *

This cache limits the number of bytes that it will store on the + * filesystem. When the number of stored bytes exceeds the limit, the cache will + * remove entries in the background until the limit is satisfied. The limit is + * not strict: the cache may temporarily exceed it while waiting for files to be + * deleted. The limit does not include filesystem overhead or the cache + * journal so space-sensitive applications should set a conservative limit. + * + *

Clients call {@link #edit} to create or update the values of an entry. An + * entry may have only one editor at one time; if a value is not available to be + * edited then {@link #edit} will return null. + *

    + *
  • When an entry is being created it is necessary to + * supply a full set of values; the empty value should be used as a + * placeholder if necessary. + *
  • When an entry is being edited, it is not necessary + * to supply data for every value; values default to their previous + * value. + *
+ * Every {@link #edit} call must be matched by a call to {@link Editor#commit} + * or {@link Editor#abort}. Committing is atomic: a read observes the full set + * of values as they were before or after the commit, but never a mix of values. + * + *

Clients call {@link #get} to read a snapshot of an entry. The read will + * observe the value at the time that {@link #get} was called. Updates and + * removals after the call do not impact ongoing reads. + * + *

This class is tolerant of some I/O errors. If files are missing from the + * filesystem, the corresponding entries will be dropped from the cache. If + * an error occurs while writing a cache value, the edit will fail silently. + * Callers should handle other problems by catching {@code IOException} and + * responding appropriately. + */ +public final class DiskLruCache implements Closeable { + static final String JOURNAL_FILE = "journal"; + static final String JOURNAL_FILE_TEMP = "journal.tmp"; + static final String JOURNAL_FILE_BACKUP = "journal.bkp"; + static final String MAGIC = "libcore.io.DiskLruCache"; + static final String VERSION_1 = "1"; + static final long ANY_SEQUENCE_NUMBER = -1; + static final String STRING_KEY_PATTERN = "[a-z0-9_-]{1,120}"; + static final Pattern LEGAL_KEY_PATTERN = Pattern.compile(STRING_KEY_PATTERN); + private static final String CLEAN = "CLEAN"; + private static final String DIRTY = "DIRTY"; + private static final String REMOVE = "REMOVE"; + private static final String READ = "READ"; + + /* + * This cache uses a journal file named "journal". A typical journal file + * looks like this: + * libcore.io.DiskLruCache + * 1 + * 100 + * 2 + * + * CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054 + * DIRTY 335c4c6028171cfddfbaae1a9c313c52 + * CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342 + * REMOVE 335c4c6028171cfddfbaae1a9c313c52 + * DIRTY 1ab96a171faeeee38496d8b330771a7a + * CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234 + * READ 335c4c6028171cfddfbaae1a9c313c52 + * READ 3400330d1dfc7f3f7f4b8d4d803dfcf6 + * + * The first five lines of the journal form its header. They are the + * constant string "libcore.io.DiskLruCache", the disk cache's version, + * the application's version, the value count, and a blank line. + * + * Each of the subsequent lines in the file is a record of the state of a + * cache entry. Each line contains space-separated values: a state, a key, + * and optional state-specific values. + * o DIRTY lines track that an entry is actively being created or updated. + * Every successful DIRTY action should be followed by a CLEAN or REMOVE + * action. DIRTY lines without a matching CLEAN or REMOVE indicate that + * temporary files may need to be deleted. + * o CLEAN lines track a cache entry that has been successfully published + * and may be read. A publish line is followed by the lengths of each of + * its values. + * o READ lines track accesses for LRU. + * o REMOVE lines track entries that have been deleted. + * + * The journal file is appended to as cache operations occur. The journal may + * occasionally be compacted by dropping redundant lines. A temporary file named + * "journal.tmp" will be used during compaction; that file should be deleted if + * it exists when the cache is opened. + */ + + private final File directory; + private final File journalFile; + private final File journalFileTmp; + private final File journalFileBackup; + private final int appVersion; + private long maxSize; + private final int valueCount; + private long size = 0; + private Writer journalWriter; + private final LinkedHashMap lruEntries = + new LinkedHashMap(0, 0.75f, true); + private int redundantOpCount; + + /** + * To differentiate between old and current snapshots, each entry is given + * a sequence number each time an edit is committed. A snapshot is stale if + * its sequence number is not equal to its entry's sequence number. + */ + private long nextSequenceNumber = 0; + + /** This cache uses a single background thread to evict entries. */ + final ThreadPoolExecutor executorService = + new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue()); + private final Callable cleanupCallable = new Callable() { + public Void call() throws Exception { + synchronized (DiskLruCache.this) { + if (journalWriter == null) { + return null; // Closed. + } + trimToSize(); + if (journalRebuildRequired()) { + rebuildJournal(); + redundantOpCount = 0; + } + } + return null; + } + }; + + private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) { + this.directory = directory; + this.appVersion = appVersion; + this.journalFile = new File(directory, JOURNAL_FILE); + this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP); + this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP); + this.valueCount = valueCount; + this.maxSize = maxSize; + } + + /** + * Opens the cache in {@code directory}, creating a cache if none exists + * there. + * + * @param directory a writable directory + * @param valueCount the number of values per cache entry. Must be positive. + * @param maxSize the maximum number of bytes this cache should use to store + * @throws IOException if reading or writing the cache directory fails + */ + public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize) + throws IOException { + if (maxSize <= 0) { + throw new IllegalArgumentException("maxSize <= 0"); + } + if (valueCount <= 0) { + throw new IllegalArgumentException("valueCount <= 0"); + } + + // If a bkp file exists, use it instead. + File backupFile = new File(directory, JOURNAL_FILE_BACKUP); + if (backupFile.exists()) { + File journalFile = new File(directory, JOURNAL_FILE); + // If journal file also exists just delete backup file. + if (journalFile.exists()) { + backupFile.delete(); + } else { + renameTo(backupFile, journalFile, false); + } + } + + // Prefer to pick up where we left off. + DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize); + if (cache.journalFile.exists()) { + try { + cache.readJournal(); + cache.processJournal(); + return cache; + } catch (IOException journalIsCorrupt) { + System.out.println("DiskLruCache " + + directory + + " is corrupt: " + + journalIsCorrupt.getMessage() + + ", removing"); + cache.delete(); + } + } + + // Create a new empty cache. + directory.mkdirs(); + cache = new DiskLruCache(directory, appVersion, valueCount, maxSize); + cache.rebuildJournal(); + return cache; + } + + private void readJournal() throws IOException { + StrictLineReader reader = + new StrictLineReader(new FileInputStream(journalFile), DiskLruCacheUtil.US_ASCII); + try { + String magic = reader.readLine(); + String version = reader.readLine(); + String appVersionString = reader.readLine(); + String valueCountString = reader.readLine(); + String blank = reader.readLine(); + if (!MAGIC.equals(magic) || !VERSION_1.equals(version) || !Integer.toString(appVersion) + .equals(appVersionString) || !Integer.toString(valueCount).equals(valueCountString) || !"" + .equals(blank)) { + throw new IOException("unexpected journal header: [" + + magic + + ", " + + version + + ", " + + valueCountString + + ", " + + blank + + "]"); + } + + int lineCount = 0; + while (true) { + try { + readJournalLine(reader.readLine()); + lineCount++; + } catch (EOFException endOfJournal) { + break; + } + } + redundantOpCount = lineCount - lruEntries.size(); + + // If we ended on a truncated line, rebuild the journal before appending to it. + if (reader.hasUnterminatedLine()) { + rebuildJournal(); + } else { + journalWriter = new BufferedWriter( + new OutputStreamWriter(new FileOutputStream(journalFile, true), + DiskLruCacheUtil.US_ASCII)); + } + } finally { + DiskLruCacheUtil.closeQuietly(reader); + } + } + + private void readJournalLine(String line) throws IOException { + int firstSpace = line.indexOf(' '); + if (firstSpace == -1) { + throw new IOException("unexpected journal line: " + line); + } + + int keyBegin = firstSpace + 1; + int secondSpace = line.indexOf(' ', keyBegin); + final String key; + if (secondSpace == -1) { + key = line.substring(keyBegin); + if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) { + lruEntries.remove(key); + return; + } + } else { + key = line.substring(keyBegin, secondSpace); + } + + Entry entry = lruEntries.get(key); + if (entry == null) { + entry = new Entry(key); + lruEntries.put(key, entry); + } + + if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) { + String[] parts = line.substring(secondSpace + 1).split(" "); + entry.readable = true; + entry.currentEditor = null; + entry.setLengths(parts); + } else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) { + entry.currentEditor = new Editor(entry); + } else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) { + // This work was already done by calling lruEntries.get(). + } else { + throw new IOException("unexpected journal line: " + line); + } + } + + /** + * Computes the initial size and collects garbage as a part of opening the + * cache. Dirty entries are assumed to be inconsistent and will be deleted. + */ + private void processJournal() throws IOException { + deleteIfExists(journalFileTmp); + for (Iterator i = lruEntries.values().iterator(); i.hasNext(); ) { + Entry entry = i.next(); + if (entry.currentEditor == null) { + for (int t = 0; t < valueCount; t++) { + size += entry.lengths[t]; + } + } else { + entry.currentEditor = null; + for (int t = 0; t < valueCount; t++) { + deleteIfExists(entry.getCleanFile(t)); + deleteIfExists(entry.getDirtyFile(t)); + } + i.remove(); + } + } + } + + /** + * Creates a new journal that omits redundant information. This replaces the + * current journal if it exists. + */ + private synchronized void rebuildJournal() throws IOException { + if (journalWriter != null) { + journalWriter.close(); + } + + Writer writer = new BufferedWriter( + new OutputStreamWriter(new FileOutputStream(journalFileTmp), DiskLruCacheUtil.US_ASCII)); + try { + writer.write(MAGIC); + writer.write("\n"); + writer.write(VERSION_1); + writer.write("\n"); + writer.write(Integer.toString(appVersion)); + writer.write("\n"); + writer.write(Integer.toString(valueCount)); + writer.write("\n"); + writer.write("\n"); + + for (Entry entry : lruEntries.values()) { + if (entry.currentEditor != null) { + writer.write(DIRTY + ' ' + entry.key + '\n'); + } else { + writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n'); + } + } + } finally { + writer.close(); + } + + if (journalFile.exists()) { + renameTo(journalFile, journalFileBackup, true); + } + renameTo(journalFileTmp, journalFile, false); + journalFileBackup.delete(); + + journalWriter = new BufferedWriter( + new OutputStreamWriter(new FileOutputStream(journalFile, true), DiskLruCacheUtil.US_ASCII)); + } + + private static void deleteIfExists(File file) throws IOException { + if (file.exists() && !file.delete()) { + throw new IOException(); + } + } + + private static void renameTo(File from, File to, boolean deleteDestination) throws IOException { + if (deleteDestination) { + deleteIfExists(to); + } + if (!from.renameTo(to)) { + throw new IOException(); + } + } + + /** + * Returns a snapshot of the entry named {@code key}, or null if it doesn't + * exist is not currently readable. If a value is returned, it is moved to + * the head of the LRU queue. + */ + public synchronized Snapshot get(String key) throws IOException { + checkNotClosed(); + validateKey(key); + Entry entry = lruEntries.get(key); + if (entry == null) { + return null; + } + + if (!entry.readable) { + return null; + } + + // Open all streams eagerly to guarantee that we see a single published + // snapshot. If we opened streams lazily then the streams could come + // from different edits. + InputStream[] ins = new InputStream[valueCount]; + try { + for (int i = 0; i < valueCount; i++) { + ins[i] = new FileInputStream(entry.getCleanFile(i)); + } + } catch (FileNotFoundException e) { + // A file must have been deleted manually! + for (int i = 0; i < valueCount; i++) { + if (ins[i] != null) { + DiskLruCacheUtil.closeQuietly(ins[i]); + } else { + break; + } + } + return null; + } + + redundantOpCount++; + journalWriter.append(READ + ' ' + key + '\n'); + if (journalRebuildRequired()) { + executorService.submit(cleanupCallable); + } + + return new Snapshot(key, entry.sequenceNumber, ins, entry.lengths); + } + + /** + * Returns an editor for the entry named {@code key}, or null if another + * edit is in progress. + */ + public Editor edit(String key) throws IOException { + return edit(key, ANY_SEQUENCE_NUMBER); + } + + private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException { + checkNotClosed(); + validateKey(key); + Entry entry = lruEntries.get(key); + if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null + || entry.sequenceNumber != expectedSequenceNumber)) { + return null; // Snapshot is stale. + } + if (entry == null) { + entry = new Entry(key); + lruEntries.put(key, entry); + } else if (entry.currentEditor != null) { + return null; // Another edit is in progress. + } + + Editor editor = new Editor(entry); + entry.currentEditor = editor; + + // Flush the journal before creating files to prevent file leaks. + journalWriter.write(DIRTY + ' ' + key + '\n'); + journalWriter.flush(); + return editor; + } + + /** Returns the directory where this cache stores its data. */ + public File getDirectory() { + return directory; + } + + /** + * Returns the maximum number of bytes that this cache should use to store + * its data. + */ + public synchronized long getMaxSize() { + return maxSize; + } + + /** + * Changes the maximum number of bytes the cache can store and queues a job + * to trim the existing store, if necessary. + */ + public synchronized void setMaxSize(long maxSize) { + this.maxSize = maxSize; + executorService.submit(cleanupCallable); + } + + /** + * Returns the number of bytes currently being used to store the values in + * this cache. This may be greater than the max size if a background + * deletion is pending. + */ + public synchronized long size() { + return size; + } + + private synchronized void completeEdit(Editor editor, boolean success) throws IOException { + Entry entry = editor.entry; + if (entry.currentEditor != editor) { + throw new IllegalStateException(); + } + + // If this edit is creating the entry for the first time, every index must have a value. + if (success && !entry.readable) { + for (int i = 0; i < valueCount; i++) { + if (!editor.written[i]) { + editor.abort(); + throw new IllegalStateException("Newly created entry didn't create value for index " + i); + } + if (!entry.getDirtyFile(i).exists()) { + editor.abort(); + return; + } + } + } + + for (int i = 0; i < valueCount; i++) { + File dirty = entry.getDirtyFile(i); + if (success) { + if (dirty.exists()) { + File clean = entry.getCleanFile(i); + dirty.renameTo(clean); + long oldLength = entry.lengths[i]; + long newLength = clean.length(); + entry.lengths[i] = newLength; + size = size - oldLength + newLength; + } + } else { + deleteIfExists(dirty); + } + } + + redundantOpCount++; + entry.currentEditor = null; + if (entry.readable | success) { + entry.readable = true; + journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n'); + if (success) { + entry.sequenceNumber = nextSequenceNumber++; + } + } else { + lruEntries.remove(entry.key); + journalWriter.write(REMOVE + ' ' + entry.key + '\n'); + } + journalWriter.flush(); + + if (size > maxSize || journalRebuildRequired()) { + executorService.submit(cleanupCallable); + } + } + + /** + * We only rebuild the journal when it will halve the size of the journal + * and eliminate at least 2000 ops. + */ + private boolean journalRebuildRequired() { + final int redundantOpCompactThreshold = 2000; + return redundantOpCount >= redundantOpCompactThreshold // + && redundantOpCount >= lruEntries.size(); + } + + /** + * Drops the entry for {@code key} if it exists and can be removed. Entries + * actively being edited cannot be removed. + * + * @return true if an entry was removed. + */ + public synchronized boolean remove(String key) throws IOException { + checkNotClosed(); + validateKey(key); + Entry entry = lruEntries.get(key); + if (entry == null || entry.currentEditor != null) { + return false; + } + + for (int i = 0; i < valueCount; i++) { + File file = entry.getCleanFile(i); + if (file.exists() && !file.delete()) { + throw new IOException("failed to delete " + file); + } + size -= entry.lengths[i]; + entry.lengths[i] = 0; + } + + redundantOpCount++; + journalWriter.append(REMOVE + ' ' + key + '\n'); + lruEntries.remove(key); + + if (journalRebuildRequired()) { + executorService.submit(cleanupCallable); + } + + return true; + } + + /** Returns true if this cache has been closed. */ + public synchronized boolean isClosed() { + return journalWriter == null; + } + + private void checkNotClosed() { + if (journalWriter == null) { + throw new IllegalStateException("cache is closed"); + } + } + + /** Force buffered operations to the filesystem. */ + public synchronized void flush() throws IOException { + checkNotClosed(); + trimToSize(); + journalWriter.flush(); + } + + /** Closes this cache. Stored values will remain on the filesystem. */ + public synchronized void close() throws IOException { + if (journalWriter == null) { + return; // Already closed. + } + for (Entry entry : new ArrayList(lruEntries.values())) { + if (entry.currentEditor != null) { + entry.currentEditor.abort(); + } + } + trimToSize(); + journalWriter.close(); + journalWriter = null; + } + + private void trimToSize() throws IOException { + while (size > maxSize) { + Map.Entry toEvict = lruEntries.entrySet().iterator().next(); + remove(toEvict.getKey()); + } + } + + /** + * Closes the cache and deletes all of its stored values. This will delete + * all files in the cache directory including files that weren't created by + * the cache. + */ + public void delete() throws IOException { + close(); + DiskLruCacheUtil.deleteContents(directory); + } + + private void validateKey(String key) { + Matcher matcher = LEGAL_KEY_PATTERN.matcher(key); + if (!matcher.matches()) { + throw new IllegalArgumentException( + "keys must match regex " + STRING_KEY_PATTERN + ": \"" + key + "\""); + } + } + + private static String inputStreamToString(InputStream in) throws IOException { + return DiskLruCacheUtil.readFully(new InputStreamReader(in, DiskLruCacheUtil.UTF_8)); + } + + /** A snapshot of the values for an entry. */ + public final class Snapshot implements Closeable { + private final String key; + private final long sequenceNumber; + private final InputStream[] ins; + private final long[] lengths; + + private Snapshot(String key, long sequenceNumber, InputStream[] ins, long[] lengths) { + this.key = key; + this.sequenceNumber = sequenceNumber; + this.ins = ins; + this.lengths = lengths; + } + + /** + * Returns an editor for this snapshot's entry, or null if either the + * entry has changed since this snapshot was created or if another edit + * is in progress. + */ + public Editor edit() throws IOException { + return DiskLruCache.this.edit(key, sequenceNumber); + } + + /** Returns the unbuffered stream with the value for {@code index}. */ + public InputStream getInputStream(int index) { + return ins[index]; + } + + /** Returns the string value for {@code index}. */ + public String getString(int index) throws IOException { + return inputStreamToString(getInputStream(index)); + } + + /** Returns the byte length of the value for {@code index}. */ + public long getLength(int index) { + return lengths[index]; + } + + public void close() { + for (InputStream in : ins) { + DiskLruCacheUtil.closeQuietly(in); + } + } + } + + private static final OutputStream NULL_OUTPUT_STREAM = new OutputStream() { + @Override public void write(int b) throws IOException { + // Eat all writes silently. Nom nom. + } + }; + + /** Edits the values for an entry. */ + public final class Editor { + private final Entry entry; + private final boolean[] written; + private boolean hasErrors; + private boolean committed; + + private Editor(Entry entry) { + this.entry = entry; + this.written = (entry.readable) ? null : new boolean[valueCount]; + } + + /** + * Returns an unbuffered input stream to read the last committed value, + * or null if no value has been committed. + */ + public InputStream newInputStream(int index) throws IOException { + synchronized (DiskLruCache.this) { + if (entry.currentEditor != this) { + throw new IllegalStateException(); + } + if (!entry.readable) { + return null; + } + try { + return new FileInputStream(entry.getCleanFile(index)); + } catch (FileNotFoundException e) { + return null; + } + } + } + + /** + * Returns the last committed value as a string, or null if no value + * has been committed. + */ + public String getString(int index) throws IOException { + InputStream in = newInputStream(index); + return in != null ? inputStreamToString(in) : null; + } + + /** + * Returns a new unbuffered output stream to write the value at + * {@code index}. If the underlying output stream encounters errors + * when writing to the filesystem, this edit will be aborted when + * {@link #commit} is called. The returned output stream does not throw + * IOExceptions. + */ + public OutputStream newOutputStream(int index) throws IOException { + if (index < 0 || index >= valueCount) { + throw new IllegalArgumentException("Expected index " + + index + + " to " + + "be greater than 0 and less than the maximum value count " + + "of " + + valueCount); + } + synchronized (DiskLruCache.this) { + if (entry.currentEditor != this) { + throw new IllegalStateException(); + } + if (!entry.readable) { + written[index] = true; + } + File dirtyFile = entry.getDirtyFile(index); + FileOutputStream outputStream; + try { + outputStream = new FileOutputStream(dirtyFile); + } catch (FileNotFoundException e) { + // Attempt to recreate the cache directory. + directory.mkdirs(); + try { + outputStream = new FileOutputStream(dirtyFile); + } catch (FileNotFoundException e2) { + // We are unable to recover. Silently eat the writes. + return NULL_OUTPUT_STREAM; + } + } + return new FaultHidingOutputStream(outputStream); + } + } + + /** Sets the value at {@code index} to {@code value}. */ + public void set(int index, String value) throws IOException { + Writer writer = null; + try { + writer = new OutputStreamWriter(newOutputStream(index), DiskLruCacheUtil.UTF_8); + writer.write(value); + } finally { + DiskLruCacheUtil.closeQuietly(writer); + } + } + + /** + * Commits this edit so it is visible to readers. This releases the + * edit lock so another edit may be started on the same key. + */ + public void commit() throws IOException { + if (hasErrors) { + completeEdit(this, false); + remove(entry.key); // The previous entry is stale. + } else { + completeEdit(this, true); + } + committed = true; + } + + /** + * Aborts this edit. This releases the edit lock so another edit may be + * started on the same key. + */ + public void abort() throws IOException { + completeEdit(this, false); + } + + public void abortUnlessCommitted() { + if (!committed) { + try { + abort(); + } catch (IOException ignored) { + } + } + } + + private class FaultHidingOutputStream extends FilterOutputStream { + private FaultHidingOutputStream(OutputStream out) { + super(out); + } + + @Override public void write(int oneByte) { + try { + out.write(oneByte); + } catch (IOException e) { + hasErrors = true; + } + } + + @Override public void write(byte[] buffer, int offset, int length) { + try { + out.write(buffer, offset, length); + } catch (IOException e) { + hasErrors = true; + } + } + + @Override public void close() { + try { + out.close(); + } catch (IOException e) { + hasErrors = true; + } + } + + @Override public void flush() { + try { + out.flush(); + } catch (IOException e) { + hasErrors = true; + } + } + } + } + + private final class Entry { + private final String key; + + /** Lengths of this entry's files. */ + private final long[] lengths; + + /** True if this entry has ever been published. */ + private boolean readable; + + /** The ongoing edit or null if this entry is not being edited. */ + private Editor currentEditor; + + /** The sequence number of the most recently committed edit to this entry. */ + private long sequenceNumber; + + private Entry(String key) { + this.key = key; + this.lengths = new long[valueCount]; + } + + public String getLengths() throws IOException { + StringBuilder result = new StringBuilder(); + for (long size : lengths) { + result.append(' ').append(size); + } + return result.toString(); + } + + /** Set lengths using decimal numbers like "10123". */ + private void setLengths(String[] strings) throws IOException { + if (strings.length != valueCount) { + throw invalidLengths(strings); + } + + try { + for (int i = 0; i < strings.length; i++) { + lengths[i] = Long.parseLong(strings[i]); + } + } catch (NumberFormatException e) { + throw invalidLengths(strings); + } + } + + private IOException invalidLengths(String[] strings) throws IOException { + throw new IOException("unexpected journal line: " + java.util.Arrays.toString(strings)); + } + + public File getCleanFile(int i) { + return new File(directory, key + "." + i); + } + + public File getDirtyFile(int i) { + return new File(directory, key + "." + i + ".tmp"); + } + } +} \ No newline at end of file diff --git a/AppFrame/src/main/java/com/arialyy/frame/cache/diskcache/DiskLruCacheUtil.java b/AppFrame/src/main/java/com/arialyy/frame/cache/diskcache/DiskLruCacheUtil.java new file mode 100644 index 00000000..e44f77cc --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/cache/diskcache/DiskLruCacheUtil.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.arialyy.frame.cache.diskcache; + +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.io.Reader; +import java.io.StringWriter; +import java.nio.charset.Charset; + +/** Junk drawer of utility methods. */ +final class DiskLruCacheUtil { + static final Charset US_ASCII = Charset.forName("US-ASCII"); + static final Charset UTF_8 = Charset.forName("UTF-8"); + + private DiskLruCacheUtil() { + } + + static String readFully(Reader reader) throws IOException { + try { + StringWriter writer = new StringWriter(); + char[] buffer = new char[1024]; + int count; + while ((count = reader.read(buffer)) != -1) { + writer.write(buffer, 0, count); + } + return writer.toString(); + } finally { + reader.close(); + } + } + + /** + * Deletes the contents of {@code dir}. Throws an IOException if any file + * could not be deleted, or if {@code dir} is not a readable directory. + */ + static void deleteContents(File dir) throws IOException { + File[] files = dir.listFiles(); + if (files == null) { + throw new IOException("not a readable directory: " + dir); + } + for (File file : files) { + if (file.isDirectory()) { + deleteContents(file); + } + if (!file.delete()) { + throw new IOException("failed to delete file: " + file); + } + } + } + + static void closeQuietly(/*Auto*/Closeable closeable) { + if (closeable != null) { + try { + closeable.close(); + } catch (RuntimeException rethrown) { + throw rethrown; + } catch (Exception ignored) { + } + } + } +} \ No newline at end of file diff --git a/AppFrame/src/main/java/com/arialyy/frame/cache/diskcache/StrictLineReader.java b/AppFrame/src/main/java/com/arialyy/frame/cache/diskcache/StrictLineReader.java new file mode 100644 index 00000000..8dbd351a --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/cache/diskcache/StrictLineReader.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.arialyy.frame.cache.diskcache; + +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; + +/** + * Buffers input from an {@link InputStream} for reading lines. + * + *

This class is used for buffered reading of lines. For purposes of this class, a line ends + * with "\n" or "\r\n". End of input is reported by throwing {@code EOFException}. Unterminated + * line at end of input is invalid and will be ignored, the caller may use {@code + * hasUnterminatedLine()} to detect it after catching the {@code EOFException}. + * + *

This class is intended for reading input that strictly consists of lines, such as line-based + * cache entries or cache journal. Unlike the {@link java.io.BufferedReader} which in conjunction + * with {@link java.io.InputStreamReader} provides similar functionality, this class uses different + * end-of-input reporting and a more restrictive definition of a line. + * + *

This class supports only charsets that encode '\r' and '\n' as a single byte with value 13 + * and 10, respectively, and the representation of no other character contains these values. + * We currently check in constructor that the charset is one of US-ASCII, UTF-8 and ISO-8859-1. + * The default charset is US_ASCII. + */ +class StrictLineReader implements Closeable { + private static final byte CR = (byte) '\r'; + private static final byte LF = (byte) '\n'; + + private final InputStream in; + private final Charset charset; + + /* + * Buffered data is stored in {@code buf}. As long as no exception occurs, 0 <= pos <= end + * and the data in the range [pos, end) is buffered for reading. At end of input, if there is + * an unterminated line, we set end == -1, otherwise end == pos. If the underlying + * {@code InputStream} throws an {@code IOException}, end may remain as either pos or -1. + */ + private byte[] buf; + private int pos; + private int end; + + /** + * Constructs a new {@code LineReader} with the specified charset and the default capacity. + * + * @param in the {@code InputStream} to read data from. + * @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are + * supported. + * @throws NullPointerException if {@code in} or {@code charset} is null. + * @throws IllegalArgumentException if the specified charset is not supported. + */ + public StrictLineReader(InputStream in, Charset charset) { + this(in, 8192, charset); + } + + /** + * Constructs a new {@code LineReader} with the specified capacity and charset. + * + * @param in the {@code InputStream} to read data from. + * @param capacity the capacity of the buffer. + * @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are + * supported. + * @throws NullPointerException if {@code in} or {@code charset} is null. + * @throws IllegalArgumentException if {@code capacity} is negative or zero + * or the specified charset is not supported. + */ + public StrictLineReader(InputStream in, int capacity, Charset charset) { + if (in == null || charset == null) { + throw new NullPointerException(); + } + if (capacity < 0) { + throw new IllegalArgumentException("capacity <= 0"); + } + if (!(charset.equals(DiskLruCacheUtil.US_ASCII))) { + throw new IllegalArgumentException("Unsupported encoding"); + } + + this.in = in; + this.charset = charset; + buf = new byte[capacity]; + } + + /** + * Closes the reader by closing the underlying {@code InputStream} and + * marking this reader as closed. + * + * @throws IOException for errors when closing the underlying {@code InputStream}. + */ + public void close() throws IOException { + synchronized (in) { + if (buf != null) { + buf = null; + in.close(); + } + } + } + + /** + * Reads the next line. A line ends with {@code "\n"} or {@code "\r\n"}, + * this end of line marker is not included in the result. + * + * @return the next line from the input. + * @throws IOException for underlying {@code InputStream} errors. + * @throws EOFException for the end of source stream. + */ + public String readLine() throws IOException { + synchronized (in) { + if (buf == null) { + throw new IOException("LineReader is closed"); + } + + // Read more data if we are at the end of the buffered data. + // Though it's an error to read after an exception, we will let {@code fillBuf()} + // throw again if that happens; thus we need to handle end == -1 as well as end == pos. + if (pos >= end) { + fillBuf(); + } + // Try to find LF in the buffered data and return the line if successful. + for (int i = pos; i != end; ++i) { + if (buf[i] == LF) { + int lineEnd = (i != pos && buf[i - 1] == CR) ? i - 1 : i; + String res = new String(buf, pos, lineEnd - pos, charset.name()); + pos = i + 1; + return res; + } + } + + // Let's anticipate up to 80 characters on top of those already read. + ByteArrayOutputStream out = new ByteArrayOutputStream(end - pos + 80) { + @Override public String toString() { + int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count; + try { + return new String(buf, 0, length, charset.name()); + } catch (UnsupportedEncodingException e) { + throw new AssertionError(e); // Since we control the charset this will never happen. + } + } + }; + + while (true) { + out.write(buf, pos, end - pos); + // Mark unterminated line in case fillBuf throws EOFException or IOException. + end = -1; + fillBuf(); + // Try to find LF in the buffered data and return the line if successful. + for (int i = pos; i != end; ++i) { + if (buf[i] == LF) { + if (i != pos) { + out.write(buf, pos, i - pos); + } + pos = i + 1; + return out.toString(); + } + } + } + } + } + + public boolean hasUnterminatedLine() { + return end == -1; + } + + /** + * Reads new input data into the buffer. Call only with pos == end or end == -1, + * depending on the desired outcome if the function throws. + */ + private void fillBuf() throws IOException { + int result = in.read(buf, 0, buf.length); + if (result == -1) { + throw new EOFException(); + } + pos = 0; + end = result; + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/core/AbsActivity.java b/AppFrame/src/main/java/com/arialyy/frame/core/AbsActivity.java new file mode 100644 index 00000000..f2272ba4 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/core/AbsActivity.java @@ -0,0 +1,242 @@ +package com.arialyy.frame.core; + +import android.content.Intent; +import android.databinding.DataBindingUtil; +import android.databinding.ViewDataBinding; +import android.os.Bundle; +import android.os.Handler; +import android.support.annotation.NonNull; +import android.support.v7.app.AppCompatActivity; +import android.view.View; +import com.arialyy.frame.module.AbsModule; +import com.arialyy.frame.module.IOCProxy; +import com.arialyy.frame.temp.AbsTempView; +import com.arialyy.frame.temp.OnTempBtClickListener; +import com.arialyy.frame.temp.TempView; +import com.arialyy.frame.util.StringUtil; +import com.arialyy.frame.util.show.T; + +/** + * Created by “AriaLyy@outlook.com” on 2015/11/3. + * 所有的 Activity都应该继承这个类 + */ +public abstract class AbsActivity extends AppCompatActivity + implements OnTempBtClickListener { + protected String TAG = ""; + protected AbsFrame mAm; + protected View mRootView; + protected AbsTempView mTempView; + protected boolean useTempView = true; + private VB mBind; + private IOCProxy mProxy; + /** + * 第一次点击返回的系统时间 + */ + private long mFirstClickTime = 0; + private ModuleFactory mModuleF; + + @Override protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + initialization(); + init(savedInstanceState); + } + + private void initialization() { + mAm = AbsFrame.getInstance(); + mAm.addActivity(this); + mBind = DataBindingUtil.setContentView(this, setLayoutId()); + mProxy = IOCProxy.newInstance(this); + TAG = StringUtil.getClassName(this); + mModuleF = ModuleFactory.newInstance(); + mRootView = mBind.getRoot(); + if (useTempView) { + mTempView = new TempView(this); + mTempView.setBtListener(this); + } + } + + protected void reNewModule() { + if (mModuleF == null) { + mModuleF = ModuleFactory.newInstance(); + } + } + + /** + * 获取填充View + */ + protected AbsTempView getTempView() { + return mTempView; + } + + /** + * 是否使用填充界面 + */ + protected void setUseTempView(boolean useTempView) { + this.useTempView = useTempView; + } + + /** + * 设置自定义的TempView + */ + protected void setCustomTempView(AbsTempView tempView) { + mTempView = tempView; + mTempView.setBtListener(this); + } + + /** + * 显示占位布局 + * + * @param type {@link TempView#ERROR} + * {@link TempView#DATA_NULL} + * {@link TempView#LOADING} + */ + protected void showTempView(int type) { + if (mTempView == null || !useTempView) { + return; + } + mTempView.setVisibility(View.VISIBLE); + mTempView.setType(type); + setContentView(mTempView); + } + + public ModuleFactory getModuleFactory() { + return mModuleF; + } + + /** + * 关闭占位布局 + */ + protected void hintTempView() { + hintTempView(0); + } + + /** + * 延时关闭占位布局 + */ + protected void hintTempView(int delay) { + new Handler().postDelayed(new Runnable() { + @Override public void run() { + if (mTempView == null || !useTempView) { + return; + } + mTempView.clearFocus(); + mTempView.setVisibility(View.GONE); + setContentView(mRootView); + } + }, delay); + } + + @Override public void onBtTempClick(View view, int type) { + + } + + @Override protected void onDestroy() { + super.onDestroy(); + } + + protected void init(Bundle savedInstanceState) { + + } + + @Override public void finish() { + super.finish(); + mAm.removeActivity(this); + } + + public View getRootView() { + return mRootView; + } + + /** + * 设置资源布局 + */ + protected abstract int setLayoutId(); + + /** + * 获取binding对象 + */ + protected VB getBinding() { + return mBind; + } + + /** + * 获取Module + * + * @param clazz {@link AbsModule} + */ + protected M getModule(@NonNull Class clazz) { + M module = mModuleF.getModule(this, clazz); + module.setHost(this); + mProxy.changeModule(module); + return module; + } + + /** + * 获取Module + * + * @param clazz Module class0 + * @param callback Module回调函数 + * @param {@link AbsModule} + */ + protected M getModule(@NonNull Class clazz, + @NonNull AbsModule.OnCallback callback) { + M module = mModuleF.getModule(this, clazz); + module.setCallback(callback); + mProxy.changeModule(module); + return module; + } + + /** + * 数据回调 + */ + protected abstract void dataCallback(int result, Object data); + + /** + * 双击退出 + */ + private boolean onDoubleClickExit(long timeSpace) { + long currentTimeMillis = System.currentTimeMillis(); + if (currentTimeMillis - mFirstClickTime > timeSpace) { + T.showShort(this, "再按一次退出"); + mFirstClickTime = currentTimeMillis; + return false; + } else { + return true; + } + } + + /** + * 双击退出,间隔时间为2000ms + */ + public boolean onDoubleClickExit() { + return onDoubleClickExit(2000); + } + + /** + * 退出应用程序 + * + * @param isBackground 是否开开启后台运行,如果为true则为后台运行 + */ + public void exitApp(Boolean isBackground) { + mAm.exitApp(isBackground); + } + + /** + * 退出应用程序 + */ + public void exitApp() { + mAm.exitApp(false); + } + + @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, + @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + PermissionHelp.getInstance().handlePermissionCallback(requestCode, permissions, grantResults); + } + + @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + PermissionHelp.getInstance() + .handleSpecialPermissionCallback(this, requestCode, resultCode, data); + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/core/AbsAlertDialog.java b/AppFrame/src/main/java/com/arialyy/frame/core/AbsAlertDialog.java new file mode 100644 index 00000000..2d854a00 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/core/AbsAlertDialog.java @@ -0,0 +1,112 @@ +package com.arialyy.frame.core; + +import android.app.Dialog; +import android.content.Intent; +import android.os.Bundle; + +import android.support.annotation.NonNull; +import android.support.v4.app.DialogFragment; +import com.arialyy.frame.module.AbsModule; +import com.arialyy.frame.module.IOCProxy; +import com.arialyy.frame.util.StringUtil; + +/** + * Created by lyy on 2015/11/4. + * AlertDialog基类,具有5.0效果,需要配合 AlertDialog.Builder使用 + */ +public abstract class AbsAlertDialog extends DialogFragment { + protected String TAG = ""; + + private Object mObj; //被观察者 + private IOCProxy mProxy; + private DialogSimpleModule mSimpleModule; + private Dialog mDialog; + private ModuleFactory mModuleF; + + public AbsAlertDialog() { + this(null); + } + + /** + * @param obj 被观察的对象 + */ + public AbsAlertDialog(Object obj) { + mObj = obj; + } + + @Override public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + initDialog(); + } + + @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { + return mDialog; + } + + /** + * 创建AlertDialog + * 建议使用AppCompatDialog,该Dialog具有5.0的效果 + */ + public abstract Dialog initAlertDialog(); + + private void initDialog() { + TAG = StringUtil.getClassName(this); + mProxy = IOCProxy.newInstance(this); + if (mObj != null) { + mSimpleModule = new DialogSimpleModule(getContext()); + IOCProxy.newInstance(mObj, mSimpleModule); + } + mModuleF = ModuleFactory.newInstance(); + mDialog = initAlertDialog(); + } + + /** + * 获取Module + * + * @param clazz {@link AbsModule} + */ + protected M getModule(Class clazz) { + M module = mModuleF.getModule(getContext(), clazz); + mProxy.changeModule(module); + return module; + } + + /** + * 获取Module + * + * @param clazz Module class0 + * @param callback Module回调函数 + * @param {@link AbsModule} + */ + protected M getModule(@NonNull Class clazz, + @NonNull AbsModule.OnCallback callback) { + M module = mModuleF.getModule(getContext(), clazz); + module.setCallback(callback); + mProxy.changeModule(module); + return module; + } + + /** + * 获取简单打Moduel回调,这个一般用于回调数据给寄主 + */ + protected DialogSimpleModule getSimplerModule() { + if (mObj == null) { + throw new NullPointerException("必须设置寄主对象"); + } + return mSimpleModule; + } + + protected abstract void dataCallback(int result, Object obj); + + @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, + @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + PermissionHelp.getInstance().handlePermissionCallback(requestCode, permissions, grantResults); + } + + @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + PermissionHelp.getInstance() + .handleSpecialPermissionCallback(getContext(), requestCode, resultCode, data); + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/core/AbsDialog.java b/AppFrame/src/main/java/com/arialyy/frame/core/AbsDialog.java new file mode 100644 index 00000000..7ac41cfc --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/core/AbsDialog.java @@ -0,0 +1,96 @@ +package com.arialyy.frame.core; + +import android.app.Dialog; +import android.content.Context; +import android.support.annotation.IdRes; +import android.support.annotation.NonNull; +import android.view.View; +import android.view.Window; + +import com.arialyy.frame.module.AbsModule; +import com.arialyy.frame.module.IOCProxy; +import com.arialyy.frame.util.StringUtil; + + +/** + * Created by lyy on 2015/11/4. + * 继承Dialog + */ +public abstract class AbsDialog extends Dialog { + protected String TAG = ""; + private Object mObj; //被观察者 + private IOCProxy mProxy; + private DialogSimpleModule mSimpleModule; + private ModuleFactory mModuleF; + + public AbsDialog(Context context) { + this(context, null); + } + + /** + * @param obj Dialog的寄主 + */ + public AbsDialog(Context context, Object obj) { + super(context); + mObj = obj; + requestWindowFeature(Window.FEATURE_NO_TITLE); + setContentView(setLayoutId()); + initDialog(); + } + + private void initDialog() { + TAG = StringUtil.getClassName(this); + mProxy = IOCProxy.newInstance(this); + mModuleF = ModuleFactory.newInstance(); + if (mObj != null) { + mSimpleModule = new DialogSimpleModule(getContext()); + IOCProxy.newInstance(mObj, mSimpleModule); + } + } + + /** + * 获取简单打Moduel回调,这个一般用于回调数据给寄主 + */ + protected DialogSimpleModule getSimplerModule() { + if (mObj == null) { + throw new NullPointerException("必须设置寄主对象"); + } + return mSimpleModule; + } + + /** + * 获取Module + * + * @param clazz {@link AbsModule} + */ + protected M getModule(Class clazz) { + M module = mModuleF.getModule(getContext(), clazz); + mProxy.changeModule(module); + return module; + } + + /** + * 获取Module + * + * @param clazz Module class0 + * @param callback Module回调函数 + * @param {@link AbsModule} + */ + protected M getModule(@NonNull Class clazz, + @NonNull AbsModule.OnCallback callback) { + M module = mModuleF.getModule(getContext(), clazz); + module.setCallback(callback); + mProxy.changeModule(module); + return module; + } + + /** + * 设置资源布局 + */ + protected abstract int setLayoutId(); + + /** + * 数据回调 + */ + protected abstract void dataCallback(int result, Object obj); +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/core/AbsDialogFragment.java b/AppFrame/src/main/java/com/arialyy/frame/core/AbsDialogFragment.java new file mode 100644 index 00000000..3e056023 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/core/AbsDialogFragment.java @@ -0,0 +1,160 @@ +package com.arialyy.frame.core; + +import android.app.Activity; +import android.content.Intent; +import android.databinding.DataBindingUtil; +import android.databinding.ViewDataBinding; +import android.os.Bundle; +import android.support.annotation.IdRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.DialogFragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.arialyy.frame.module.AbsModule; +import com.arialyy.frame.module.IOCProxy; +import com.arialyy.frame.util.StringUtil; +import com.lyy.frame.R; + +/** + * Created by lyy on 2015/11/4. + * DialogFragment + */ +public abstract class AbsDialogFragment extends DialogFragment { + protected String TAG = ""; + private VB mBind; + protected Object mObj; + protected View mRootView; + protected IOCProxy mProxy; + protected DialogSimpleModule mSimpleModule; + protected AbsActivity mActivity; + private ModuleFactory mModuleF; + + public AbsDialogFragment() { + this(null); + } + + /** + * @param obj 被观察的对象 + */ + public AbsDialogFragment(Object obj) { + this(STYLE_NO_TITLE, R.style.MyDialog, obj); + } + + /** + * @param style DialogFragment.STYLE_NO_TITLE , STYLE_NO_FRAME; STYLE_NO_FRAME | STYLE_NO_TITLE + * @param theme Dialog风格 + * @param obj 被观察的对象 + */ + private AbsDialogFragment(int style, int theme, Object obj) { + setStyle(style, theme); + mObj = obj; + } + + @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + mBind = DataBindingUtil.inflate(inflater, setLayoutId(), container, false); + mRootView = mBind.getRoot(); + initFragment(); + return mRootView; + } + + public V findViewById(@IdRes int id) { + return mRootView.findViewById(id); + } + + @Override public void onAttach(Activity activity) { + super.onAttach(activity); + if (activity instanceof AbsActivity) { + mActivity = (AbsActivity) activity; + } + } + + private void initFragment() { + TAG = StringUtil.getClassName(this); + mProxy = IOCProxy.newInstance(this); + mModuleF = ModuleFactory.newInstance(); + if (mObj != null) { + mSimpleModule = new DialogSimpleModule(getContext()); + IOCProxy.newInstance(mObj, mSimpleModule); + } + } + + @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + init(savedInstanceState); + } + + protected abstract void init(Bundle savedInstanceState); + + /** + * 获取简单打Moduel回调,这个一般用于回调数据给寄主 + */ + protected DialogSimpleModule getSimplerModule() { + if (mObj == null) { + throw new NullPointerException("必须设置寄主对象"); + } + return mSimpleModule; + } + + /** + * 设置资源布局 + */ + protected abstract int setLayoutId(); + + /** + * 获取binding对象 + */ + protected VB getBinding() { + return mBind; + } + + /** + * 获取Module + * + * @param clazz {@link AbsModule} + */ + protected M getModule(Class clazz) { + M module = mModuleF.getModule(getContext(), clazz); + module.setHost(this); + mProxy.changeModule(module); + return module; + } + + /** + * 获取Module + * + * @param clazz Module class0 + * @param callback Module回调函数 + * @param {@link AbsModule} + */ + protected M getModule(@NonNull Class clazz, + @NonNull AbsModule.OnCallback callback) { + M module = mModuleF.getModule(getContext(), clazz); + module.setCallback(callback); + mProxy.changeModule(module); + return module; + } + + /** + * 统一的回调接口 + * + * @param result 返回码,用来判断是哪个接口进行回调 + * @param data 回调数据 + */ + protected abstract void dataCallback(int result, Object data); + + @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, + @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + PermissionHelp.getInstance().handlePermissionCallback(requestCode, permissions, grantResults); + } + + @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + PermissionHelp.getInstance() + .handleSpecialPermissionCallback(getContext(), requestCode, resultCode, data); + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/core/AbsFragment.java b/AppFrame/src/main/java/com/arialyy/frame/core/AbsFragment.java new file mode 100644 index 00000000..42f20852 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/core/AbsFragment.java @@ -0,0 +1,274 @@ +package com.arialyy.frame.core; + +import android.content.Context; +import android.content.Intent; +import android.databinding.DataBindingUtil; +import android.databinding.ViewDataBinding; +import android.os.Bundle; +import android.os.Handler; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentContainer; +import android.support.v4.view.ViewPager; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; + +import com.arialyy.frame.module.AbsModule; +import com.arialyy.frame.module.IOCProxy; +import com.arialyy.frame.temp.AbsTempView; +import com.arialyy.frame.temp.OnTempBtClickListener; +import com.arialyy.frame.temp.TempView; +import com.arialyy.frame.util.ReflectionUtil; +import com.arialyy.frame.util.StringUtil; +import com.arialyy.frame.util.show.L; + +import java.lang.reflect.Field; + + +/** + * Created by lyy on 2015/11/4. + * 基础Fragment + */ +public abstract class AbsFragment extends Fragment + implements OnTempBtClickListener { + protected String TAG = ""; + private VB mBind; + private IOCProxy mProxy; + protected View mRootView; + protected AbsActivity mActivity; + private ModuleFactory mModuleF; + protected boolean isInit; + protected AbsTempView mTempView; + protected boolean useTempView = true; + private ViewGroup mParent; + + @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + mBind = DataBindingUtil.inflate(inflater, setLayoutId(), container, false); + initFragment(); + mRootView = mBind.getRoot(); + return mRootView; + } + + private void initFragment() { + TAG = StringUtil.getClassName(this); + mProxy = IOCProxy.newInstance(this); + mModuleF = ModuleFactory.newInstance(); + if (useTempView) { + mTempView = new TempView(getContext()); + mTempView.setBtListener(this); + } + } + + @Override public void onAttach(Context context) { + super.onAttach(context); + if (context instanceof AbsActivity) { + mActivity = (AbsActivity) context; + } + } + + @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + Field field = ReflectionUtil.getField(getClass(), "mContainerId"); + Field containerField = ReflectionUtil.getField(getFragmentManager().getClass(), "mContainer"); + try { + int id = (int) field.get(this); + FragmentContainer container = (FragmentContainer) containerField.get(getFragmentManager()); + mParent = (ViewGroup) container.onFindViewById(id); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + init(savedInstanceState); + isInit = true; + if (getUserVisibleHint()) { + onDelayLoad(); + } + } + + @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + Field field = ReflectionUtil.getField(getClass(), "mContainer"); + try { + mParent = (ViewGroup) field.get(this); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + @Override public void setUserVisibleHint(boolean isVisibleToUser) { + super.setUserVisibleHint(isVisibleToUser); + if (isVisibleToUser && isInit) { + isInit = false; + onDelayLoad(); + } + } + + protected abstract void init(Bundle savedInstanceState); + + /** + * 是否使用填充界面 + */ + protected void setUseTempView(boolean useTempView) { + this.useTempView = useTempView; + } + + /** + * 设置自定义的TempView + */ + protected void setCustomTempView(AbsTempView tempView) { + mTempView = tempView; + mTempView.setBtListener(this); + } + + /** + * 显示占位布局 + * + * @param type {@link TempView#ERROR} + * {@link TempView#DATA_NULL} + * {@link TempView#LOADING} + */ + protected void showTempView(int type) { + if (mTempView == null || !useTempView) { + return; + } + mTempView.setType(type); + if (mParent != null) { + int size = ViewGroup.LayoutParams.MATCH_PARENT; + ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(size, size); + if (mParent instanceof ViewPager) { + // ViewPager vp = (ViewPager) mParent; + // int position = vp.getCurrentItem(); + // View child = vp.getChildAt(position); + // L.d(TAG, "hashcode ==> " + child.hashCode()); + View child = mRootView; + if (child != null) { + if (child instanceof LinearLayout) { + LinearLayout ll = (LinearLayout) child; + ll.removeView(mTempView); + ll.addView(mTempView, 0, lp); + } else if (child instanceof RelativeLayout || child instanceof FrameLayout) { + ViewGroup vg = (ViewGroup) child; + vg.removeView(mTempView); + vg.addView(mTempView, lp); + } else { + L.e(TAG, "框架的填充只支持,LinearLayout、RelativeLayout、FrameLayout"); + } + } + } else { + mParent.removeView(mTempView); + mParent.addView(mTempView, lp); + } + } + } + + /** + * 关闭占位布局 + */ + protected void hintTempView() { + hintTempView(0); + } + + /** + * 延时关闭占位布局 + */ + protected void hintTempView(int delay) { + new Handler().postDelayed(new Runnable() { + @Override public void run() { + if (mTempView == null || !useTempView) { + return; + } + mTempView.clearFocus(); + if (mParent != null) { + if (mParent instanceof ViewPager) { + // ViewPager vp = (ViewPager) mParent; + // int position = vp.getCurrentItem(); + // View child = vp.getChildAt(position); + View child = mRootView; + ViewGroup vg = (ViewGroup) child; + if (vg != null) { + vg.removeView(mTempView); + } + } else { + mParent.removeView(mTempView); + } + } + } + }, delay); + } + + @Override public void onBtTempClick(View view, int type) { + } + + /** + * 获取填充View + */ + protected AbsTempView getTempView() { + return mTempView; + } + + /** + * 获取binding对象 + */ + protected VB getBinding() { + return mBind; + } + + /** + * 获取Module + * + * @param clazz {@link AbsModule} + */ + protected M getModule(@NonNull Class clazz) { + M module = mModuleF.getModule(getContext(), clazz); + module.setHost(this); + mProxy.changeModule(module); + return module; + } + + /** + * 获取Module + * + * @param clazz Module class0 + * @param callback Module回调函数 + * @param {@link AbsModule} + */ + protected M getModule(@NonNull Class clazz, + @NonNull AbsModule.OnCallback callback) { + M module = mModuleF.getModule(getContext(), clazz); + module.setCallback(callback); + mProxy.changeModule(module); + return module; + } + + /** + * 延时加载 + */ + protected abstract void onDelayLoad(); + + /** + * 设置资源布局 + */ + protected abstract int setLayoutId(); + + /** + * 数据回调 + */ + protected abstract void dataCallback(int result, Object obj); + + @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, + @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + PermissionHelp.getInstance().handlePermissionCallback(requestCode, permissions, grantResults); + } + + @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + PermissionHelp.getInstance() + .handleSpecialPermissionCallback(getContext(), requestCode, resultCode, data); + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/core/AbsFrame.java b/AppFrame/src/main/java/com/arialyy/frame/core/AbsFrame.java new file mode 100644 index 00000000..39486428 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/core/AbsFrame.java @@ -0,0 +1,203 @@ +package com.arialyy.frame.core; + +import android.app.ActivityManager; +import android.content.Context; +import android.os.Process; +import com.arialyy.frame.util.show.FL; +import java.io.File; +import java.util.Iterator; +import java.util.Stack; + +/** + * Created by “AriaLyy@outlook.com” on 2015/11/4. + * APP生命周期管理类管理 + */ +public class AbsFrame { + private static final String TAG = "AbsFrame"; + private static final Object LOCK = new Object(); + private volatile static AbsFrame mManager = null; + private Context mContext; + private Stack mActivityStack = new Stack<>(); + + private AbsFrame() { + + } + + private AbsFrame(Context context) { + mContext = context; + } + + /** + * 初始化框架 + */ + public static AbsFrame init(Context applicationContext) { + if (mManager == null) { + synchronized (LOCK) { + if (mManager == null) { + mManager = new AbsFrame(applicationContext); + } + } + } + return mManager; + } + + /** + * 获取AppManager管流程实例 + */ + public static AbsFrame getInstance() { + if (mManager == null) { + throw new NullPointerException("请在application 的 onCreate 方法里面使用MVVMFrame.init()方法进行初始化操作"); + } + return mManager; + } + + /** + * activity 是否存在 + */ + public boolean activityExists(Class clazz) { + for (AbsActivity activity : mActivityStack) { + if (activity.getClass().getName().equals(clazz.getName())) { + return true; + } + } + return false; + } + + /** + * 获取Activity栈 + */ + public Stack getActivityStack() { + return mActivityStack; + } + + /** + * 开启异常捕获 + * 日志文件位于/data/data/Package Name/cache//crash/2016.10.26_AbsExceptionFile.crash + */ + public void openCrashHandler() { + openCrashHandler("", ""); + } + + /** + * 开启异常捕获 + * 需要网络权限,get请求,异常参数,需要下面两个网络权限 + * android:name="android.permission.INTERNET" + * android:name="android.permission.ACCESS_NETWORK_STATE" + * + * @param serverHost 服务器地址 + * @param key 数据传输键值 + */ + public AbsFrame openCrashHandler(String serverHost, String key) { + CrashHandler handler = CrashHandler.getInstance(mContext); + handler.setServerHost(serverHost, key); + Thread.setDefaultUncaughtExceptionHandler(handler); + return this; + } + + /** + * 堆栈大小 + */ + public int getActivitySize() { + return mActivityStack.size(); + } + + /** + * 获取指定的Activity + */ + public AbsActivity getActivity(int location) { + return mActivityStack.get(location); + } + + /** + * 添加Activity到堆栈 + */ + public void addActivity(AbsActivity activity) { + if (mActivityStack == null) { + mActivityStack = new Stack<>(); + } + mActivityStack.add(activity); + } + + /** + * 获取当前Activity(堆栈中最后一个压入的) + */ + public AbsActivity getCurrentActivity() { + return mActivityStack.lastElement(); + } + + /** + * 结束当前Activity(堆栈中最后一个压入的) + */ + public void finishActivity() { + finishActivity(mActivityStack.lastElement()); + } + + /** + * 结束指定的Activity + */ + public void finishActivity(AbsActivity activity) { + if (activity != null) { + activity.finish(); + } + } + + /** + * 移除指定的Activity + */ + public void removeActivity(AbsActivity activity) { + if (activity != null) { + int i = mActivityStack.search(activity); + if (i != -1) { + mActivityStack.remove(activity); + } + } + } + + /** + * 结束指定类名的Activity + */ + public void finishActivity(Class cls) { + Iterator iter = mActivityStack.iterator(); + while (iter.hasNext()) { + AbsActivity activity = iter.next(); + if (activity.getClass().getName().equals(cls.getName())) { + iter.remove(); + activity.finish(); + } + } + } + + /** + * 结束所有Activity + */ + public void finishAllActivity() { + for (int i = 0, size = mActivityStack.size(); i < size; i++) { + if (mActivityStack.get(i) != null && mActivityStack.size() > 0) { + mActivityStack.get(i).finish(); + } + } + mActivityStack.clear(); + } + + /** + * 退出应用程序 + * + * @param isBackground 是否开开启后台运行 + */ + public void exitApp(Boolean isBackground) { + try { + finishAllActivity(); + //ActivityManager activityMgr = + // (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); + //activityMgr.restartPackage(mContext.getPackageName()); + } catch (Exception e) { + FL.e(TAG, FL.getExceptionString(e)); + } finally { + // 注意,如果您有后台程序运行,请不要支持此句子 + if (!isBackground) { + System.exit(0); + //Process.killProcess(Process.myPid()); + } + } + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/core/AbsPopupWindow.java b/AppFrame/src/main/java/com/arialyy/frame/core/AbsPopupWindow.java new file mode 100644 index 00000000..7715d50e --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/core/AbsPopupWindow.java @@ -0,0 +1,133 @@ +package com.arialyy.frame.core; + +import android.content.Context; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.PopupWindow; + +import com.arialyy.frame.module.AbsModule; +import com.arialyy.frame.module.IOCProxy; +import com.arialyy.frame.util.StringUtil; + + +/** + * Created by lyy on 2015/12/3. + * 抽象的Popupwindow悬浮框 + */ +public abstract class AbsPopupWindow extends PopupWindow { + + protected String TAG; + private static Context mContext; + private Drawable mBackground; + protected View mView; + private Object mObj; + protected IOCProxy mProxy; + protected DialogSimpleModule mSimpleModule; + private ModuleFactory mModuleF; + + public AbsPopupWindow(Context context) { + this(context, null); + } + + public AbsPopupWindow(Context context, Drawable background) { + this(context, background, null); + } + + public AbsPopupWindow(Context context, Drawable background, Object obj) { + mContext = context; + mBackground = background; + initPopupWindow(); + mProxy = IOCProxy.newInstance(this); + if (obj != null) { + mObj = obj; + mSimpleModule = new DialogSimpleModule(getContext()); + IOCProxy.newInstance(mObj, mSimpleModule); + } + mModuleF = ModuleFactory.newInstance(); + init(); + } + + protected void init() { + + } + + private void initPopupWindow() { + mView = LayoutInflater.from(mContext).inflate(setLayoutId(), null); + setContentView(mView); + TAG = StringUtil.getClassName(this); + // 设置SelectPicPopupWindow弹出窗体的宽 + setWidth(ViewGroup.LayoutParams.MATCH_PARENT); + // 设置SelectPicPopupWindow弹出窗体的高 + setHeight(ViewGroup.LayoutParams.MATCH_PARENT); + setFocusable(true); + // 设置SelectPicPopupWindow弹出窗体动画效果 + // setAnimationStyle(R.style.wisdom_anim_style); + // 实例化一个ColorDrawable颜色为半透明 + if (mBackground == null) { + mBackground = new ColorDrawable(Color.parseColor("#7f000000")); + } + // 设置SelectPicPopupWindow弹出窗体的背景 + setBackgroundDrawable(mBackground); + } + + protected T getViewWithTag(Object tag) { + T result = (T) mView.findViewWithTag(tag); + if (result == null) throw new NullPointerException("没有找到tag为【" + tag + "】的控件"); + return result; + } + + /** + * 获取Module + * + * @param clazz {@link AbsModule} + */ + protected M getModule(Class clazz) { + M module = mModuleF.getModule(getContext(), clazz); + mProxy.changeModule(module); + return module; + } + + /** + * 获取Module + * + * @param clazz Module class0 + * @param callback Module回调函数 + * @param {@link AbsModule} + */ + protected M getModule(@NonNull Class clazz, + @NonNull AbsModule.OnCallback callback) { + M module = mModuleF.getModule(getContext(), clazz); + module.setCallback(callback); + mProxy.changeModule(module); + return module; + } + + /** + * 获取简单打Moduel回调,这个一般用于回调数据给寄主 + */ + protected DialogSimpleModule getSimplerModule() { + if (mObj == null) { + throw new NullPointerException("必须设置寄主对象"); + } + return mSimpleModule; + } + + public Context getContext() { + return mContext; + } + + /** + * 设置资源布局 + */ + protected abstract int setLayoutId(); + + /** + * 数据回调 + */ + protected abstract void dataCallback(int result, Object obj); +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/core/BindingFactory.java b/AppFrame/src/main/java/com/arialyy/frame/core/BindingFactory.java new file mode 100644 index 00000000..578f6b28 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/core/BindingFactory.java @@ -0,0 +1,60 @@ +package com.arialyy.frame.core; + + +import android.databinding.ViewDataBinding; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Created by lyy on 2016/9/16. + * Binding工厂 + */ +public class BindingFactory { + private final String TAG = "BindingFactory"; + + private Map mBindings = new ConcurrentHashMap<>(); + + private BindingFactory() { + + } + + /** + * 需要保证每个对象都有独立的享元工厂 + */ + public static BindingFactory newInstance() { + return new BindingFactory(); + } + + /** + * 获取Binding + * + * @param obj Module寄主 + */ + public VB getBinding(Object obj, Class clazz) { + VB vb = (VB) mBindings.get(clazz.hashCode()); + if (vb == null) { + vb = loadBind(obj, clazz); + } + return vb; + } + + /** + * 从其它组件中加载binding + * + * @param obj Module寄主 + * @param clazz 具体的Binding + * @param ViewDataBinding + */ + private VB loadBind(Object obj, Class clazz) { + VB vb = null; + if (obj instanceof AbsActivity) { + vb = (VB) ((AbsActivity) obj).getBinding(); + } else if (obj instanceof AbsFragment) { + vb = (VB) ((AbsFragment) obj).getBinding(); + } else if (obj instanceof AbsDialogFragment) { + vb = (VB) ((AbsDialogFragment) obj).getBinding(); + } + mBindings.put(clazz.hashCode(), vb); + return vb; + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/core/CrashHandler.java b/AppFrame/src/main/java/com/arialyy/frame/core/CrashHandler.java new file mode 100644 index 00000000..2b02d561 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/core/CrashHandler.java @@ -0,0 +1,167 @@ +package com.arialyy.frame.core; + +import android.Manifest; +import android.content.Context; +import android.os.Build; +import android.os.Looper; +import android.text.TextUtils; + +import com.arialyy.frame.http.HttpUtil; +import com.arialyy.frame.util.AndroidUtils; +import com.arialyy.frame.util.CalendarUtils; +import com.arialyy.frame.util.FileUtil; +import com.arialyy.frame.util.NetUtils; +import com.arialyy.frame.util.show.FL; +import com.arialyy.frame.util.show.L; +import com.arialyy.frame.util.show.T; +import com.google.gson.Gson; + +import java.io.File; +import java.io.FileWriter; +import java.io.PrintWriter; +import java.util.Map; +import java.util.WeakHashMap; + +/** + * Created by lyy on 2016/4/21. + * 异常捕获 + */ +final class CrashHandler implements Thread.UncaughtExceptionHandler { + private static final Object LOCK = new Object(); + private static volatile CrashHandler INSTANCE = null; + private static final String TAG = "CrashHandler"; + private Thread.UncaughtExceptionHandler mDefaultHandler; + private Context mContext; + private String mServerHost, mPramKey; + private String mExceptionFileName = "AbsExceptionFile.crash"; + + /** + * 获取CrashHandler实例 ,单例模式 + */ + public static CrashHandler getInstance(Context context) { + if (INSTANCE == null) { + synchronized (LOCK) { + INSTANCE = new CrashHandler(context); + } + } + return INSTANCE; + } + + private CrashHandler(Context context) { + mContext = context; + mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler(); + } + + /** + * 开启异常捕获 + * 需要网络权限,get请求,异常参数 + * + * @param serverHost 服务器地址 + * @param key 数据传输键值 + */ + public void setServerHost(String serverHost, String key) { + mServerHost = serverHost; + mPramKey = key; + } + + @Override public void uncaughtException(Thread thread, Throwable ex) { + if (!handleException(ex) && mDefaultHandler != null) { + mDefaultHandler.uncaughtException(thread, ex); + } else { + // Sleep一会后结束程序 + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + //不要把线程都杀了,否则连日志都看不了 + // android.os.Process.killProcess(android.os.Process.myPid()); + //如果把这句话注释掉,有异常都不会退出 + System.exit(10); + } + } + + /** + * 处理捕获到的异常 + */ + private boolean handleException(Throwable ex) { + if (ex == null) { + return false; + } + //在这里处理崩溃逻辑,将不再显示FC对话框 + new Thread() { + @Override public void run() { + Looper.prepare(); + T.showLong(mContext, "很抱歉,程序出现异常,即将退出"); + Looper.loop(); + } + }.start(); + sendExceptionInfo(ex); + return true; + } + + /** + * 发送异常数据给服务器 + */ + private void sendExceptionInfo(final Throwable ex) { + ExceptionInfo info = new ExceptionInfo(); + info.time = CalendarUtils.getNowDataTime(); + info.versionCode = AndroidUtils.getVersionCode(mContext); + info.versionName = AndroidUtils.getVersionName(mContext); + info.systemVersionCode = Build.VERSION.SDK_INT; + info.phoneModel = Build.MODEL; + info.exceptionMsg = FL.getExceptionString(ex); + if (AndroidUtils.checkPermission(mContext, Manifest.permission.INTERNET) + && AndroidUtils.checkPermission(mContext, Manifest.permission.ACCESS_NETWORK_STATE)) { + if (NetUtils.isConnected(mContext) && !TextUtils.isEmpty(mServerHost) && !TextUtils.isEmpty( + mPramKey)) { + String objStr = new Gson().toJson(info); + HttpUtil util = HttpUtil.getInstance(mContext); + Map params = new WeakHashMap<>(); + params.put(mPramKey, objStr); + util.get(mServerHost, params, new HttpUtil.AbsResponse()); + } + } else { + L.e(TAG, + "请在manifest文件定义android.permission.INTERNET和android.permission.ACCESS_NETWORK_STATE权限"); + return; + } + File file = new File(mContext.getCacheDir().getPath() + "/crash/" + mExceptionFileName); + if (!file.exists()) { + FileUtil.createFile(file.getPath()); + } + writeExceptionToFile(info.exceptionMsg, file); + } + + /** + * 将异常日志写入文件 + */ + private void writeExceptionToFile(String message, File crashFile) { + StringBuffer stringBuffer = new StringBuffer(); + stringBuffer.append(CalendarUtils.getNowDataTime()); + stringBuffer.append("\n"); + stringBuffer.append(message); + stringBuffer.append("\n"); + PrintWriter writer = null; + try { + writer = new PrintWriter(new FileWriter(crashFile, true)); + writer.append(stringBuffer); + writer.flush(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (writer != null) { + writer.close(); + } + } + } + + private class ExceptionInfo { + int versionCode; + String versionName; + int systemVersionCode; + String exceptionMsg; + String phoneModel; + String time; + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/core/DialogSimpleModule.java b/AppFrame/src/main/java/com/arialyy/frame/core/DialogSimpleModule.java new file mode 100644 index 00000000..b0af6322 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/core/DialogSimpleModule.java @@ -0,0 +1,40 @@ +package com.arialyy.frame.core; + +import android.content.Context; +import android.os.Bundle; + +import com.arialyy.frame.module.AbsModule; + +/** + * Created by AriaLyy on 2015/2/9. + * 框架提供的默认的对话框的Module + */ +public class DialogSimpleModule extends AbsModule { + + public DialogSimpleModule(Context context) { + super(context); + } + + /** + * 统一的回调 + */ + public void onDialog(int result, Object data) { + callback(result, data); + } + + /** + * 可设置参数和回调名的回调函数 + */ + @Deprecated public void onDialog(String methodName, Class param, Object data) { + callback(methodName, param, data); + } + + /** + * Dialog的默认回调 + * + * @param b 需要回调的数据 + */ + @Deprecated public void onDialog(Bundle b) { + callback("onDialog", Bundle.class, b); + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/core/ModuleFactory.java b/AppFrame/src/main/java/com/arialyy/frame/core/ModuleFactory.java new file mode 100644 index 00000000..0f99fec2 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/core/ModuleFactory.java @@ -0,0 +1,68 @@ +package com.arialyy.frame.core; + +import android.content.Context; + +import com.arialyy.frame.module.AbsModule; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Created by lyy on 2015/8/31. + * Module共享工厂 + */ +public class ModuleFactory { + + private final String TAG = "ModuleFactory"; + + private Map mModules = new ConcurrentHashMap<>(); + + private ModuleFactory() { + + } + + /** + * 需要保证每个对象都有独立的享元工厂 + */ + public static ModuleFactory newInstance() { + return new ModuleFactory(); + } + + /** + * 获取Module + */ + public M getModule(Context context, Class clazz) { + M module = (M) mModules.get(clazz.hashCode()); + if (module == null) { + return newInstanceModule(context, clazz); + } + return module; + } + + /** + * 构造一个新的Module + */ + private T newInstanceModule(Context context, Class clazz) { + Class[] paramTypes = { Context.class }; + Object[] params = { context }; + try { + Constructor con = clazz.getConstructor(paramTypes); + con.setAccessible(true); + T module = con.newInstance(params); + mModules.put(clazz.hashCode(), module); + return module; + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (InstantiationException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/core/NotifyHelp.java b/AppFrame/src/main/java/com/arialyy/frame/core/NotifyHelp.java new file mode 100644 index 00000000..6edce3ae --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/core/NotifyHelp.java @@ -0,0 +1,81 @@ +package com.arialyy.frame.core; + +import android.util.SparseArray; + +import com.arialyy.frame.util.ReflectionUtil; + +/** + * Created by lyy on 2016/4/5. + * 更新帮助类 + */ +public class NotifyHelp { + private static volatile NotifyHelp INSTANCE = null; + private static final Object LOCK = new Object(); + private SparseArray> mNotifyObjs = new SparseArray<>(); + + public interface OnNotifyCallback { + public void onNotify(int action, Object obj); + } + + public static NotifyHelp getInstance() { + if (INSTANCE == null) { + synchronized (LOCK) { + INSTANCE = new NotifyHelp(); + } + } + return INSTANCE; + } + + private NotifyHelp() { + + } + + public void addObj(int flag, OnNotifyCallback callback) { + SparseArray array = mNotifyObjs.get(flag); + if (array == null) { + array = new SparseArray<>(); + } + array.put(callback.hashCode(), callback); + mNotifyObjs.put(flag, array); + } + + public void removeObj(int flag) { + mNotifyObjs.delete(flag); + } + + public void clear() { + mNotifyObjs.clear(); + } + + public void update(int flag) { + update(flag, -1, null); + } + + public void update(int flag, int action) { + update(flag, action, null); + } + + public void update(int flag, int action, Object obj) { + if (mNotifyObjs == null || mNotifyObjs.size() == 0) { + return; + } + SparseArray array = mNotifyObjs.get(flag); + if (array == null || array.size() == 0) { + return; + } + try { + int[] keys = (int[]) ReflectionUtil.getField(SparseArray.class, "mKeys").get(array); + for (int key : keys) { + if (key == 0) { + continue; + } + OnNotifyCallback callback = array.get(key); + if (callback != null) { + callback.onNotify(action, obj); + } + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/core/PermissionHelp.java b/AppFrame/src/main/java/com/arialyy/frame/core/PermissionHelp.java new file mode 100644 index 00000000..c487333e --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/core/PermissionHelp.java @@ -0,0 +1,70 @@ +package com.arialyy.frame.core; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.provider.Settings; +import android.support.annotation.NonNull; + +import com.arialyy.frame.permission.OnPermissionCallback; +import com.arialyy.frame.permission.PermissionManager; +import com.arialyy.frame.util.AndroidVersionUtil; + +/** + * Created by lyy on 2016/4/12. + * 权限回调帮助类 + */ +class PermissionHelp { + public static final Object LOCK = new Object(); + public volatile static PermissionHelp INSTANCE = null; + public PermissionManager mPm; + + public static PermissionHelp getInstance() { + if (INSTANCE == null) { + synchronized (LOCK) { + INSTANCE = new PermissionHelp(); + } + } + return INSTANCE; + } + + private PermissionHelp() { + mPm = PermissionManager.getInstance(); + } + + /** + * 处理权限申请回调 + */ + public void handlePermissionCallback(int requestCode, @NonNull String[] permissions, + @NonNull int[] grantResults) { + for (int state : grantResults) { + if (state == PackageManager.PERMISSION_GRANTED) { + mPm.onSuccess(permissions); + } else { + mPm.onFail(permissions); + } + } + } + + /** + * 处理特殊权限申请,如悬浮框,系统设置修改等特殊权限 + */ + public void handleSpecialPermissionCallback(Context context, int requestCode, int resultCode, + Intent data) { + if (AndroidVersionUtil.hasM()) { + if (requestCode == OnPermissionCallback.PERMISSION_ALERT_WINDOW) { + if (Settings.canDrawOverlays(context)) { //在这判断是否请求权限成功 + mPm.onSuccess(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); + } else { + mPm.onFail(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); + } + } else if (requestCode == OnPermissionCallback.PERMISSION_WRITE_SETTING) { + if (Settings.System.canWrite(context)) { + mPm.onSuccess(Settings.ACTION_MANAGE_WRITE_SETTINGS); + } else { + mPm.onFail(Settings.ACTION_MANAGE_WRITE_SETTINGS); + } + } + } + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/http/HttpUtil.java b/AppFrame/src/main/java/com/arialyy/frame/http/HttpUtil.java new file mode 100644 index 00000000..f6cf56a4 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/http/HttpUtil.java @@ -0,0 +1,339 @@ +package com.arialyy.frame.http; + +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.support.annotation.NonNull; +import android.text.TextUtils; + +import com.arialyy.frame.cache.CacheUtil; +import com.arialyy.frame.http.inf.IResponse; +import com.arialyy.frame.util.show.L; + +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.FormBody; +import okhttp3.Headers; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +/** + * Created by “AriaLyy@outlook.com” on 2015/11/5. + * 网络连接工具 + */ +public class HttpUtil { + private static final String TAG = "HttpUtil"; + private Context mContext = null; + private static volatile HttpUtil mUtil = null; + private static final Object LOCK = new Object(); + private CacheUtil mCacheUtil; + private Handler mHandler; + private static final int TIME_OUT = 5000; + public static final String CONTENT_TYPE_IMG = "image/*"; + public static final String CONTENT_TYPE_TEXT = "text/*"; + public static final String CONTENT_TYPE_FILE = "application/octet-stream"; + + private HttpUtil() { + } + + private HttpUtil(Context context) { + mContext = context; + mCacheUtil = new CacheUtil.Builder(context).openDiskCache().build(); + mHandler = new Handler(Looper.getMainLooper()); + } + + public static HttpUtil getInstance(Context context) { + if (mUtil == null) { + synchronized (LOCK) { + if (mUtil == null) { + mUtil = new HttpUtil(context); + } + } + } + return mUtil; + } + + public void get(final @NonNull String url, @NonNull final IResponse absResponse) { + get(url, null, absResponse, false); + } + + public void get(final @NonNull String url, @NonNull final IResponse absResponse, + boolean useCache) { + get(url, null, absResponse, useCache); + } + + public void get(final @NonNull String url, final Map params, + @NonNull final IResponse absResponse) { + get(url, params, absResponse, false); + } + + public void post(final @NonNull String url, @NonNull final IResponse absResponse) { + post(url, null, null, absResponse, false); + } + + public void post(final @NonNull String url, final Map params, + @NonNull final IResponse absResponse) { + post(url, params, null, absResponse, false); + } + + public void post(final @NonNull String url, final Map params, + @NonNull final IResponse absResponse, final boolean useCache) { + post(url, params, null, absResponse, useCache); + } + + public void post(final @NonNull String url, final Map params, + final Map header, @NonNull final IResponse absResponse) { + post(url, params, header, absResponse, false); + } + + /** + * 上传文件 + * + * @param key 上传文件键值 + */ + public void uploadFile(@NonNull final String url, @NonNull final String filePath, + @NonNull final String key, final String contentType, final Map header, + @NonNull final IResponse absResponse) { + new Thread(new Runnable() { + @Override public void run() { + File file = new File(filePath); + String BOUNDARY = UUID.randomUUID().toString(); // 边界标识 随机生成 + String PREFIX = "--", LINE_END = "\r\n"; + String CONTENT_TYPE = "multipart/form-data"; // 内容类型 + try { + HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); + conn.setReadTimeout(5000); + conn.setConnectTimeout(5000); + conn.setDoInput(true); + conn.setDoOutput(true); + conn.setUseCaches(false); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Charset", "utf-8"); // 设置编码 + conn.setRequestProperty("connection", "keep-alive"); + conn.setRequestProperty("Content-Type", CONTENT_TYPE + ";boundary=" + BOUNDARY); + + if (header != null && header.size() > 0) { + Set keys = header.keySet(); + for (String key : keys) { + conn.setRequestProperty(key, header.get(key)); + } + } + OutputStream outputSteam = conn.getOutputStream(); + DataOutputStream dos = new DataOutputStream(outputSteam); + StringBuilder sb = new StringBuilder(); + sb.append(PREFIX); + sb.append(BOUNDARY); + sb.append(LINE_END); + sb.append("Content-Disposition: form-data; name=\"") + .append(key) + .append("\"; filename=\"") + .append(file.getName()) + .append("\"") + .append(LINE_END); + sb.append("Content-Type:").append(contentType).append("; charset=utf-8").append(LINE_END); + sb.append(LINE_END); + dos.write(sb.toString().getBytes()); + InputStream is = new FileInputStream(file); + byte[] bytes = new byte[1024]; + int len = 0; + while ((len = is.read(bytes)) != -1) { + dos.write(bytes, 0, len); + } + is.close(); + dos.write(LINE_END.getBytes()); + byte[] end_data = (PREFIX + BOUNDARY + PREFIX + LINE_END).getBytes(); + dos.write(end_data); + dos.flush(); + int res = conn.getResponseCode(); + if (res == 200) { + BufferedInputStream inputStream = new BufferedInputStream(conn.getInputStream()); + byte[] buf = new byte[1024]; + StringBuilder stringBuilder = new StringBuilder(); + while (inputStream.read(buf) > 0) { + stringBuilder.append(new String(buf, 0, buf.length)); + } + String data = stringBuilder.toString(); + L.j(data); + absResponse.onResponse(data); + } else { + absResponse.onError("error"); + } + } catch (MalformedURLException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + }).start(); + } + + /** + * 基本get方法 + */ + public void get(final @NonNull String url, final Map params, + @NonNull final IResponse absResponse, final boolean useCache) { + L.v(TAG, "请求链接 >>>> " + url); + String requestUrl = url; + if (params != null && params.size() > 0) { + Set keys = params.keySet(); + int i = 0; + requestUrl += "?"; + for (String key : keys) { + i++; + requestUrl += key + "=" + params.get(key) + (i < params.size() ? "&" : ""); + } + L.v(TAG, "请求参数为 >>>> "); + L.m(params); + } + + OkHttpClient client = new OkHttpClient.Builder().connectTimeout(TIME_OUT, TimeUnit.MILLISECONDS) + .writeTimeout(TIME_OUT, TimeUnit.MILLISECONDS) + .readTimeout(300000, TimeUnit.MILLISECONDS) + .build(); + final Request request = new Request.Builder().url(requestUrl).build(); + Call call = client.newCall(request); + + //请求加入调度 + call.enqueue(new Callback() { + @Override public void onFailure(Call call, IOException e) { + L.e(TAG, "请求链接【" + url + "】失败"); + String data = null; + if (useCache) { + data = mCacheUtil.getStringCache(url + L.m2s(params)); + L.d(TAG, "数据获取成功,获取到的数据为 >>>> "); + L.j(data); + } + if (TextUtils.isEmpty(data)) { + setOnError(request, absResponse); + } else { + setOnResponse(data, absResponse); + } + } + + @Override public void onResponse(Call call, Response response) throws IOException { + String data = response.body().string(); + L.d(TAG, "数据获取成功,获取到的数据为 >>>> "); + L.j(data); + if (useCache) { + L.v(TAG, "缓存链接【" + url + "】的数据"); + mCacheUtil.putStringCache(url + L.m2s(params), data); + } + setOnResponse(data, absResponse); + } + }); + } + + /** + * 基本的Post方法 + */ + public void post(final @NonNull String url, final Map params, + final Map header, @NonNull final IResponse absResponse, + final boolean useCache) { + L.v(TAG, "请求链接 >>>> " + url); + OkHttpClient client = new OkHttpClient.Builder().connectTimeout(TIME_OUT, TimeUnit.MILLISECONDS) + .writeTimeout(TIME_OUT, TimeUnit.MILLISECONDS) + .readTimeout(300000, TimeUnit.MILLISECONDS) + .build(); + FormBody.Builder formB = new FormBody.Builder(); + //头数据 + Headers.Builder hb = new Headers.Builder(); + if (header != null && header.size() > 0) { + Set keys = header.keySet(); + for (String key : keys) { + hb.add(key, header.get(key)); + } + L.v(TAG, "请求的头数据为 >>>> "); + L.m(header); + } + //请求参数 + if (params != null && params.size() > 0) { + Set keys = params.keySet(); + for (String key : keys) { + formB.add(key, params.get(key)); + } + L.v(TAG, "请求参数为 >>>> "); + L.m(params); + } else { + formB.add("null", "null"); + } + + Request request = + new Request.Builder().url(url).post(formB.build()).headers(hb.build()).build(); + Call call = client.newCall(request); + call.enqueue(new Callback() { + @Override public void onFailure(Call call, IOException e) { + L.e(TAG, "请求链接【" + url + "】失败"); + String data = null; + if (useCache) { + data = mCacheUtil.getStringCache(url + L.m2s(params)); + L.d(TAG, "从缓存读取的数据为 >>>> "); + L.j(data); + } + if (TextUtils.isEmpty(data)) { + setOnError(call, absResponse); + } else { + setOnResponse(data, absResponse); + } + } + + @Override public void onResponse(Call call, Response response) throws IOException { + String data = response.body().string(); + L.d(TAG, "数据获取成功,获取到的数据为 >>>>"); + L.j(data); + if (useCache) { + L.v(TAG, "缓存链接【" + url + "】的数据"); + mCacheUtil.putStringCache(url + L.m2s(params), data); + } + setOnResponse(data, absResponse); + } + }); + } + + private void setOnError(final Object error, final IResponse response) { + mHandler.post(new Runnable() { + @Override public void run() { + response.onError(error); + } + }); + } + + private void setOnResponse(final String data, final IResponse response) { + mHandler.post(new Runnable() { + @Override public void run() { + response.onResponse(data); + } + }); + } + + /** + * 返回String类型的响应 + */ + public static class AbsResponse implements IResponse { + + @Override public void onResponse(String data) { + + } + + @Override public void onError(Object error) { + + } + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/http/WebApiModule.java b/AppFrame/src/main/java/com/arialyy/frame/http/WebApiModule.java new file mode 100644 index 00000000..3b4b6deb --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/http/WebApiModule.java @@ -0,0 +1,41 @@ +//package com.arialyy.frame.http; +// +//import java.util.concurrent.TimeUnit; +// +//import javax.inject.Singleton; +// +//import okhttp3.OkHttpClient; +//import dagger.Module; +//import dagger.Provides; +//import okhttp3.logging.HttpLoggingInterceptor; +//import retrofit2.Retrofit; +//import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; +//import retrofit2.converter.gson.GsonConverterFactory; +// +//@Module +//public class WebApiModule { +// @Provides +// @Singleton +// public OkHttpClient provideOkHttpClient() { +// final OkHttpClient.Builder builder = new OkHttpClient.Builder(); +//// if (BuildConfig.DEBUG) { +// HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); +// logging.setLevel(HttpLoggingInterceptor.Level.BODY); +// builder.addInterceptor(logging); +//// } +// builder.connectTimeout(60 * 1000, TimeUnit.MILLISECONDS) +// .readTimeout(60 * 1000, TimeUnit.MILLISECONDS); +// return builder.build(); +// } +// +// @Provides +// @Singleton +// public Retrofit provideRetrofit(OkHttpClient okHttpClient) { +// Retrofit.Builder builder = new Retrofit.Builder(); +// builder.client(okHttpClient) +//// .baseUrl(GameConstant.PACKAGE_TEST_BASE_URL) +// .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) +// .addConverterFactory(GsonConverterFactory.create()); +// return builder.build(); +// } +//} \ No newline at end of file diff --git a/AppFrame/src/main/java/com/arialyy/frame/http/inf/IRequest.java b/AppFrame/src/main/java/com/arialyy/frame/http/inf/IRequest.java new file mode 100644 index 00000000..7dace3c5 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/http/inf/IRequest.java @@ -0,0 +1,39 @@ +package com.arialyy.frame.http.inf; + +import android.support.annotation.NonNull; + +/** + * 网络请求类 + */ +public interface IRequest { + /** + * post请求 + * + * @param url 请求连接 + * @param param 请求参数 + * @param headers 请求头信息 + * @param usCache 是否使用缓存 + * @param body 请求主体 + * @param response 回调接口 + */ + public void post(@NonNull String url, T param, T headers, boolean usCache, byte[] body, + @NonNull IResponse response); + + /** + * get请求 + * + * @param url 请求连接 + * @param param 请求参数 + * @param headers 请求头信息 + * @param usCache 是否使用缓存 + * @param body 请求主体 + * @param response 回调接口 + */ + public void get(@NonNull String url, T param, T headers, boolean usCache, byte[] body, + @NonNull IResponse response); + + /** + * 默认缓存文件夹 + */ + public static final String NET_CACHE_DIR = "HttpCache"; +} \ No newline at end of file diff --git a/AppFrame/src/main/java/com/arialyy/frame/http/inf/IResponse.java b/AppFrame/src/main/java/com/arialyy/frame/http/inf/IResponse.java new file mode 100644 index 00000000..4779b71b --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/http/inf/IResponse.java @@ -0,0 +1,16 @@ +package com.arialyy.frame.http.inf; + +/** + * 数据响应接口 + */ +public interface IResponse { + /** + * 响应的数据回调 + */ + public void onResponse(String data); + + /** + * 错误返回回掉 + */ + public void onError(Object error); +} \ No newline at end of file diff --git a/AppFrame/src/main/java/com/arialyy/frame/module/AbsModule.java b/AppFrame/src/main/java/com/arialyy/frame/module/AbsModule.java new file mode 100644 index 00000000..754d90dc --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/module/AbsModule.java @@ -0,0 +1,143 @@ +package com.arialyy.frame.module; + +import android.content.Context; + +import android.databinding.ViewDataBinding; +import com.arialyy.frame.core.AbsActivity; +import com.arialyy.frame.core.BindingFactory; +import com.arialyy.frame.module.inf.ModuleListener; +import com.arialyy.frame.util.StringUtil; +import com.arialyy.frame.util.show.L; + +/** + * Created by AriaLyy on 2015/2/3. + * 抽象的module + */ +public class AbsModule { + public String TAG = ""; + private Context mContext; + private ModuleListener mModuleListener; + private OnCallback mCallback; + private BindingFactory mBindingFactory; + private Object mHost; + + public interface OnCallback { + public void onSuccess(int result, Object success); + + public void onError(int result, Object error); + } + + public AbsModule(Context context) { + mContext = context; + init(); + } + + /** + * 初始化一些东西 + */ + private void init() { + TAG = StringUtil.getClassName(this); + mBindingFactory = BindingFactory.newInstance(); + } + + /** + * 设置Module监听 + * + * @param moduleListener Module监听 + */ + public void setModuleListener(ModuleListener moduleListener) { + if (moduleListener == null) throw new NullPointerException("ModuleListener不能为空"); + this.mModuleListener = moduleListener; + } + + /** + * 为Binding设置寄主 + */ + public void setHost(Object host) { + mHost = host; + } + + /** + * 成功的回调 + */ + private void successCallback(int key, Object obj) { + if (mCallback == null) { + L.e(TAG, "OnCallback 为 null"); + return; + } + mCallback.onSuccess(key, obj); + } + + /** + * 失败的回调 + */ + public void errorCallback(int key, Object obj) { + if (mCallback == null) { + L.e(TAG, "OnCallback 为 null"); + return; + } + mCallback.onError(key, obj); + } + + /** + * 获取Context + * + * @return Context + */ + public Context getContext() { + return mContext; + } + + /** + * 设置Module回调 + * + * @param callback Module 回调 + */ + public void setCallback(OnCallback callback) { + mCallback = callback; + } + + /** + * 获取ViewDataBinding + * + * @param clazz ViewDataBinding实例 + */ + protected VB getBinding(Class clazz) { + return mBindingFactory.getBinding(mHost, clazz); + } + + /** + * 统一的回调,如果已经设置了OnCallback,则使用OnCallback; + * 否则将使用dataCallback,{@link AbsActivity#dataCallback(int, Object)} + * + * @param result 返回码 + * @param data 回调数据 + */ + protected void callback(final int result, final Object data) { + if (mCallback != null) { + successCallback(result, data); + return; + } + mModuleListener.callback(result, data); + } + + /** + * module回调 + * + * @param method 回调的方法名 + */ + @Deprecated protected void callback(String method) { + mModuleListener.callback(method); + } + + /** + * 带参数的module回调 + * + * @param method 回调的方法名 + * @param dataClassType 回调数据类型 + * @param data 回调数据 + */ + @Deprecated protected void callback(String method, Class dataClassType, Object data) { + mModuleListener.callback(method, dataClassType, data); + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/module/IOCProxy.java b/AppFrame/src/main/java/com/arialyy/frame/module/IOCProxy.java new file mode 100644 index 00000000..d5f3a8e7 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/module/IOCProxy.java @@ -0,0 +1,112 @@ +package com.arialyy.frame.module; + +import com.arialyy.frame.module.inf.ModuleListener; +import com.arialyy.frame.util.ReflectionUtil; +import com.arialyy.frame.util.show.FL; +import com.arialyy.frame.util.show.L; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * Created by AriaLyy on 2015/2/3. + * 代理对象 + */ +public class IOCProxy implements ModuleListener { + private static final String TAG = "IOCProxy"; + private static final String mMethod = "dataCallback"; + + private Object mObj; + + /** + * 初始化静态代理 + */ + public static IOCProxy newInstance(Object obj, AbsModule module) { + return new IOCProxy(obj, module); + } + + public static IOCProxy newInstance(Object obj) { + return new IOCProxy(obj, null); + } + + /** + * 被代理对象 + */ + private IOCProxy(Object obj, AbsModule module) { + this.mObj = obj; + + if (module != null) { + module.setModuleListener(this); + } + } + + public void changeModule(AbsModule module) { + module.setModuleListener(this); + } + + /** + * 统一的数据回调 + * + * @param result 返回码 + * @param data 回调数据 + */ + @Override public void callback(int result, Object data) { + synchronized (this) { + try { + Method m = ReflectionUtil.getMethod(mObj.getClass(), mMethod, int.class, Object.class); + m.setAccessible(true); + m.invoke(mObj, result, data); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + } + + /** + * 数据回调 + * + * @param method 方法名 + */ + @Override @Deprecated public void callback(String method) { + synchronized (this) { + try { + Method m = mObj.getClass().getDeclaredMethod(method); + m.setAccessible(true); + m.invoke(mObj); + } catch (NoSuchMethodException e) { + L.e(TAG, "无法找到" + method + "方法"); + FL.e(TAG, FL.getExceptionString(e)); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + } + + /** + * 带参数的回调 + * + * @param method 方法名 + * @param dataClassType 参数类型,如 int.class + * @param data 数据 + */ + @Override @Deprecated public void callback(String method, Class dataClassType, Object data) { + synchronized (this) { + try { + Method m = mObj.getClass().getDeclaredMethod(method, dataClassType); + m.setAccessible(true); + m.invoke(mObj, data); + } catch (NoSuchMethodException e) { + L.e(TAG, "无法找到" + method + "方法"); + FL.e(TAG, FL.getExceptionString(e)); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/module/ModuleFactory.java b/AppFrame/src/main/java/com/arialyy/frame/module/ModuleFactory.java new file mode 100644 index 00000000..ad4ac3d3 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/module/ModuleFactory.java @@ -0,0 +1,61 @@ +package com.arialyy.frame.module; + +import android.util.SparseIntArray; + +import com.arialyy.frame.util.ObjUtil; +import com.arialyy.frame.util.show.L; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Created by lyy on 2016/7/7. + */ +public class ModuleFactory { + private Map, AbsModule.OnCallback> mPool = new HashMap<>(); + private SparseIntArray mKeyIndex = new SparseIntArray(); + private static final String TAG = "ModuleFactory"; + + public ModuleFactory() { + + } + + public void addCallback(int key, AbsModule.OnCallback callback) { + if (checkKey(key, callback)) { + L.e(TAG, "key 已经和 callback对应"); + return; + } + if (mPool.containsValue(callback)) { + Set oldKeys = ObjUtil.getKeyByValue(mPool, callback); + if (oldKeys != null) { + if (!oldKeys.contains(key)) { + oldKeys.add(key); + mKeyIndex.put(key, callback.hashCode()); + } + } else { + oldKeys = new HashSet<>(); + oldKeys.add(key); + mPool.put(oldKeys, callback); + mKeyIndex.put(key, callback.hashCode()); + } + } else { + Set newKeys = new HashSet<>(); + newKeys.add(key); + mPool.put(newKeys, callback); + mKeyIndex.put(key, callback.hashCode()); + } + } + + /** + * 检查key和callback的对应关系 + * + * @return true : key已经和value对应,false : key没有和value对应 + */ + private boolean checkKey(int key, AbsModule.OnCallback callback) { + return mKeyIndex.indexOfKey(key) != -1 + || mKeyIndex.indexOfValue(callback.hashCode()) != -1 + && mKeyIndex.valueAt(callback.hashCode()) == key; + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/module/inf/ModuleListener.java b/AppFrame/src/main/java/com/arialyy/frame/module/inf/ModuleListener.java new file mode 100644 index 00000000..6806438f --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/module/inf/ModuleListener.java @@ -0,0 +1,31 @@ +package com.arialyy.frame.module.inf; + +/** + * Created by AriaLyy on 2015/2/3. + * Module监听 + */ +public interface ModuleListener { + /** + * 无参的回调 + * + * @param method 方法名 + */ + public void callback(String method); + + /** + * 带参数的回调 + * + * @param method 方法名 + * @param dataClassType 参数类型 + * @param data 数据 + */ + public void callback(String method, Class dataClassType, Object data); + + /** + * 统一接口的回调,回调接口为dataCallback + * + * @param result 返回码 + * @param data 回调数据 + */ + public void callback(int result, Object data); +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/permission/OnPermissionCallback.java b/AppFrame/src/main/java/com/arialyy/frame/permission/OnPermissionCallback.java new file mode 100644 index 00000000..442f4e97 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/permission/OnPermissionCallback.java @@ -0,0 +1,14 @@ +package com.arialyy.frame.permission; + +/** + * Created by lyy on 2016/4/11. + * 权限回调 + */ +public interface OnPermissionCallback { + public static final int PERMISSION_ALERT_WINDOW = 0xad1; + public static final int PERMISSION_WRITE_SETTING = 0xad2; + + public void onSuccess(String... permissions); + + public void onFail(String... permissions); +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/permission/PermissionManager.java b/AppFrame/src/main/java/com/arialyy/frame/permission/PermissionManager.java new file mode 100644 index 00000000..f20db64b --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/permission/PermissionManager.java @@ -0,0 +1,143 @@ +package com.arialyy.frame.permission; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.os.Build; +import android.provider.Settings; +import android.support.v4.app.Fragment; +import android.util.SparseArray; + +import com.arialyy.frame.util.show.L; + +import java.util.Arrays; +import java.util.List; + +/** + * Created by lyy on 2016/4/11. + * 权限管理工具类 + */ +@TargetApi(Build.VERSION_CODES.M) public class PermissionManager implements OnPermissionCallback { + private static final String TAG = "PermissionManager"; + private PermissionUtil mPu; + private SparseArray mCallbacks = new SparseArray<>(); + private static volatile PermissionManager INSTANCE = null; + private static final Object LOCK = new Object(); + + public static PermissionManager getInstance() { + if (INSTANCE == null) { + synchronized (LOCK) { + INSTANCE = new PermissionManager(); + } + } + return INSTANCE; + } + + private PermissionManager() { + mPu = PermissionUtil.getInstance(); + } + + /** + * 检查权限 + * + * @param permission 需要检查的权限 + * @return true:已经授权 + */ + public boolean checkPermission(Activity activity, String permission) { + return mPu.checkPermission(activity, permission); + } + + /** + * 检查权限 + * + * @param permission 需要检查的权限 + * @return true:已经授权 + */ + public boolean checkPermission(Fragment fragment, String permission) { + Activity activity = fragment.getActivity(); + return checkPermission(activity, permission); + } + + /** + * 申请悬浮框权限 + * + * @param obj obj 只能是Activity、Fragment 的子类及其衍生类 + */ + public void requestAlertWindowPermission(Object obj, OnPermissionCallback callback) { + int hashCode = Arrays.hashCode(new String[] { Settings.ACTION_MANAGE_OVERLAY_PERMISSION }); + registerCallback(callback, hashCode); + mPu.requestAlertWindowPermission(obj); + } + + /** + * 申请修改系统设置权限 + * + * @param obj obj 只能是Activity、Fragment 的子类及其衍生类 + */ + public void requestWriteSettingPermission(Object obj, OnPermissionCallback callback) { + int hashCode = Arrays.hashCode(new String[] { Settings.ACTION_MANAGE_WRITE_SETTINGS }); + registerCallback(callback, hashCode); + mPu.requestWriteSetting(obj); + } + + /** + * 申请权限 + * + * @param obj Activity || Fragment + * @param permission 权限 + */ + public void requestPermission(Object obj, OnPermissionCallback callback, String... permission) { + requestPermission(obj, "", callback, registerCallback(obj, callback, permission)); + } + + /** + * 申请权限 + * + * @param obj Activity || Fragment + * @param hint 如果框对话框包含“不再询问”选择框的时候的提示用语。 + * @param permission 权限 + */ + private void requestPermission(Object obj, String hint, OnPermissionCallback callback, + String... permission) { + mPu.requestPermission(obj, hint, 0, registerCallback(obj, callback, permission)); + } + + private void registerCallback(OnPermissionCallback callback, int hashCode) { + OnPermissionCallback c = mCallbacks.get(hashCode); + if (c == null) { + mCallbacks.append(hashCode, callback); + } + } + + private String[] registerCallback(Object obj, OnPermissionCallback callback, + String... permission) { + List list = mPu.checkPermission(obj, permission); + if (list == null || list.size() == 0) { + return null; + } + String[] denyPermission = mPu.list2Array(list); + int hashCode = Arrays.hashCode(denyPermission); + OnPermissionCallback c = mCallbacks.get(hashCode); + if (c == null) { + mCallbacks.append(hashCode, callback); + } + return denyPermission; + } + + @Override public void onSuccess(String... permissions) { + int hashCode = Arrays.hashCode(permissions); + OnPermissionCallback c = mCallbacks.get(hashCode); + if (c != null) { + c.onSuccess(permissions); + mCallbacks.remove(hashCode); + } + } + + @Override public void onFail(String... permissions) { + int hashCode = Arrays.hashCode(permissions); + OnPermissionCallback c = mCallbacks.get(hashCode); + if (c != null) { + c.onFail(permissions); + mCallbacks.remove(hashCode); + } + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/permission/PermissionUtil.java b/AppFrame/src/main/java/com/arialyy/frame/permission/PermissionUtil.java new file mode 100644 index 00000000..72ccd7d1 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/permission/PermissionUtil.java @@ -0,0 +1,201 @@ +package com.arialyy.frame.permission; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Build; +import android.provider.Settings; +import android.support.v4.app.Fragment; +import android.text.TextUtils; + +import com.arialyy.frame.util.AndroidVersionUtil; +import com.arialyy.frame.util.show.L; +import com.arialyy.frame.util.show.T; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by lyy on 2016/4/11. + * 6.0权限帮助工具 + */ +@TargetApi(Build.VERSION_CODES.M) class PermissionUtil { + public static final Object LOCK = new Object(); + public volatile static PermissionUtil INSTANCE = null; + private static final String TAG = "PermissionUtil"; + + public static PermissionUtil getInstance() { + if (INSTANCE == null) { + synchronized (LOCK) { + INSTANCE = new PermissionUtil(); + } + } + return INSTANCE; + } + + private PermissionUtil() { + } + + /** + * 申请权限 + */ + public void requestPermission(Object obj, int requestCode, String... permission) { + if (!AndroidVersionUtil.hasM()) { + return; + } + requestPermission(obj, "", requestCode, permission); + } + + /** + * 申请权限 + * + * @param hint 如果框对话框包含”不再询问“选择框的时候的提示用语。 + */ + public void requestPermission(Object obj, String hint, int requestCode, String... permission) { + if (!AndroidVersionUtil.hasM() || permission == null || permission.length == 0) { + return; + } + Activity activity = null; + Fragment fragment = null; + if (obj instanceof Activity) { + activity = (Activity) obj; + } else if (obj instanceof Fragment) { + fragment = (Fragment) obj; + activity = fragment.getActivity(); + } else { + L.e(TAG, "obj 只能是 Activity 或者 fragment 及其子类"); + return; + } + if (!TextUtils.isEmpty(hint)) { + for (String str : permission) { + if (fragment != null) { + if (fragment.shouldShowRequestPermissionRationale(str)) { + T.showShort(fragment.getContext(), hint); + break; + } + } else { + if (activity.shouldShowRequestPermissionRationale(str)) { + T.showShort(activity, hint); + break; + } + } + } + } + if (fragment != null) { + fragment.requestPermissions(permission, requestCode); + } else { + activity.requestPermissions(permission, requestCode); + } + } + + protected String[] list2Array(List denyPermission) { + String[] array = new String[denyPermission.size()]; + for (int i = 0, count = denyPermission.size(); i < count; i++) { + array[i] = denyPermission.get(i); + } + return array; + } + + /** + * 检查没有被授权的权限 + */ + public List checkPermission(Object obj, String... permission) { + if (!AndroidVersionUtil.hasM() || permission == null || permission.length == 0) { + return null; + } + Activity activity = null; + if (obj instanceof Activity) { + activity = (Activity) obj; + } else if (obj instanceof Fragment) { + activity = ((Fragment) obj).getActivity(); + } else { + L.e(TAG, "obj 只能是 Activity 或者 fragment 及其子类"); + return null; + } + List denyPermissions = new ArrayList<>(); + for (String p : permission) { + if (activity.checkSelfPermission(p) != PackageManager.PERMISSION_GRANTED) { + denyPermissions.add(p); + } + } + return denyPermissions; + } + + /** + * 检查应用是否有该权限 + * + * @param permission 权限,Manifest.permission.CAMERA + * @return true ==> 已经授权 + */ + public boolean checkPermission(Activity activity, String permission) { + return AndroidVersionUtil.hasM() + && activity.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED; + } + + /* + * 请求悬浮权限 + * 在onActivityResult里面添加以下代码 + * protected void onActivityResult(int requestCode, int resultCode, Intent data) { + * super.onActivityResult(requestCode, resultCode, data); + * if (requestCode == OnPermissionCallback.PERMISSION_ALERT_WINDOW) { + * if (Settings.canDrawOverlays(this)) { //在这判断是否请求权限成功 + * Log.i(LOGTAG, "onActivityResult granted"); + * } + * } + * } + * + * @param obj + */ + public void requestAlertWindowPermission(Object obj) { + if (!AndroidVersionUtil.hasM()) { + return; + } + Activity activity = null; + Fragment fragment = null; + if (obj instanceof Activity) { + activity = (Activity) obj; + } else if (obj instanceof Fragment) { + fragment = (Fragment) obj; + activity = fragment.getActivity(); + } else { + L.e(TAG, "obj 只能是 Activity 或者 fragment 及其衍生类"); + return; + } + Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); + intent.setData(Uri.parse("package:" + activity.getPackageName())); + if (fragment != null) { + fragment.startActivityForResult(intent, OnPermissionCallback.PERMISSION_ALERT_WINDOW); + } else { + activity.startActivityForResult(intent, OnPermissionCallback.PERMISSION_ALERT_WINDOW); + } + } + + /** + * 请求修改系统设置权限 + */ + public void requestWriteSetting(Object obj) { + if (!AndroidVersionUtil.hasM()) { + return; + } + Activity activity = null; + Fragment fragment = null; + if (obj instanceof Activity) { + activity = (Activity) obj; + } else if (obj instanceof Fragment) { + fragment = (Fragment) obj; + activity = fragment.getActivity(); + } else { + L.e(TAG, "obj 只能是 Activity 或者 fragment 及其衍生类"); + return; + } + Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS); + intent.setData(Uri.parse("package:" + activity.getPackageName())); + if (fragment != null) { + fragment.startActivityForResult(intent, OnPermissionCallback.PERMISSION_WRITE_SETTING); + } else { + activity.startActivityForResult(intent, OnPermissionCallback.PERMISSION_WRITE_SETTING); + } + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/temp/AbsTempView.java b/AppFrame/src/main/java/com/arialyy/frame/temp/AbsTempView.java new file mode 100644 index 00000000..108820b2 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/temp/AbsTempView.java @@ -0,0 +1,74 @@ +package com.arialyy.frame.temp; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.LinearLayout; + +import com.arialyy.frame.util.StringUtil; +import com.arialyy.frame.util.show.L; + + +/** + * Created by lyy on 2016/4/27. + * 抽象的填充类 + */ +public abstract class AbsTempView extends LinearLayout implements ITempView { + private OnTempBtClickListener mBtListener; + private static String TAG; + protected int mType = ERROR; + + public AbsTempView(Context context) { + this(context, null); + } + + public AbsTempView(Context context, AttributeSet attrs) { + super(context, attrs); + initView(context); + } + + private void initView(Context context) { + View view = LayoutInflater.from(context).inflate(setLayoutId(), this); + TAG = StringUtil.getClassName(this); + init(); + } + + protected abstract void init(); + + /** + * 如果界面有按钮,则需要对着按钮的点击事件进行监听 + */ + public void setBtListener(@NonNull OnTempBtClickListener listener) { + mBtListener = listener; + } + + protected abstract int setLayoutId(); + + /** + * 将按钮点击事件传递给TempView调用类 + * + * @param type {@link ITempView} + */ + protected void onTempBtClick(View view, int type) { + if (mBtListener != null) { + mBtListener.onBtTempClick(view, type); + } + } + + @Override public void setType(int type) { + mType = type; + if (type == LOADING) { + onLoading(); + return; + } + if (type == ERROR) { + onError(); + } else if (type == DATA_NULL) { + onNull(); + } else { + L.e(TAG, "类型错误"); + } + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/temp/ITempView.java b/AppFrame/src/main/java/com/arialyy/frame/temp/ITempView.java new file mode 100644 index 00000000..fb101f25 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/temp/ITempView.java @@ -0,0 +1,34 @@ +package com.arialyy.frame.temp; + +/** + * Created by lyy on 2016/4/27. + */ +public interface ITempView { + public static final int ERROR = 0xaff1; + public static final int DATA_NULL = 0xaff2; + public static final int LOADING = 0xaff3; + + /** + * 设置填充界面类型 + * + * @param type {@link ITempView#ERROR} + * {@link ITempView#DATA_NULL} + * {@link ITempView#LOADING} + */ + public void setType(int type); + + /** + * 在这处理type = ITempView#ERROR 时的逻辑 + */ + public void onError(); + + /** + * 在这处理type = ITempView#DATA_NULL 时的逻辑 + */ + public void onNull(); + + /** + * 在这处理type = ITempView#LOADING 时的逻辑 + */ + public void onLoading(); +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/temp/OnTempBtClickListener.java b/AppFrame/src/main/java/com/arialyy/frame/temp/OnTempBtClickListener.java new file mode 100644 index 00000000..aa84c568 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/temp/OnTempBtClickListener.java @@ -0,0 +1,10 @@ +package com.arialyy.frame.temp; + +import android.view.View; + +public interface OnTempBtClickListener { + /** + * @param type {@link ITempView#ERROR}, {@link ITempView#DATA_NULL}, {@link ITempView#LOADING} + */ + public void onBtTempClick(View view, int type); +} \ No newline at end of file diff --git a/AppFrame/src/main/java/com/arialyy/frame/temp/TempView.java b/AppFrame/src/main/java/com/arialyy/frame/temp/TempView.java new file mode 100644 index 00000000..83297ece --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/temp/TempView.java @@ -0,0 +1,60 @@ +package com.arialyy.frame.temp; + +import android.content.Context; +import android.view.View; +import android.widget.Button; +import android.widget.FrameLayout; +import android.widget.ProgressBar; +import android.widget.TextView; + +import com.lyy.frame.R; + +/** + * Created by lyy on 2016/1/11. + * 错误填充View + */ +public class TempView extends AbsTempView { + ProgressBar mPb; + TextView mHint; + Button mBt; + FrameLayout mErrorContent; + + public TempView(Context context) { + super(context); + } + + @Override protected void init() { + mPb = (ProgressBar) findViewById(R.id.pb); + mHint = (TextView) findViewById(R.id.hint); + mBt = (Button) findViewById(R.id.bt); + mErrorContent = (FrameLayout) findViewById(R.id.error); + mBt.setOnClickListener(new OnClickListener() { + @Override public void onClick(View v) { + onTempBtClick(v, mType); + } + }); + } + + @Override protected int setLayoutId() { + return R.layout.layout_error_temp; + } + + @Override public void onError() { + mErrorContent.setVisibility(VISIBLE); + mPb.setVisibility(GONE); + mHint.setText("网络错误"); + mBt.setText("点击刷新"); + } + + @Override public void onNull() { + mErrorContent.setVisibility(VISIBLE); + mPb.setVisibility(GONE); + mHint.setText("数据为空"); + mBt.setText("点击刷新"); + } + + @Override public void onLoading() { + mErrorContent.setVisibility(GONE); + mPb.setVisibility(VISIBLE); + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/util/AESEncryption.java b/AppFrame/src/main/java/com/arialyy/frame/util/AESEncryption.java new file mode 100644 index 00000000..4607f81d --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/util/AESEncryption.java @@ -0,0 +1,156 @@ +package com.arialyy.frame.util; + +import android.annotation.SuppressLint; +import android.support.annotation.NonNull; + +import java.io.File; +import java.security.SecureRandom; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +/** + * AES加密 + * + * @author lyy + */ +public class AESEncryption { + /** + * 4.4的要Cipher cipher = Cipher.getInstance("AES/ECB/ZeroBytePadding"); + */ + private static final int JELLY_BEAN_4_2 = 17; + + /** + * 加密文件 + * + * @param filePath 原始文件路径 + * @return 加密后的文件路径 + */ + public static String encryptFile(@NonNull String filePath) { + File file = new File(filePath); + return ""; + } + + /** + * 加密函数 + * + * @param seed 密钥 + * @param cleartext 说要进行加密的密码 + * @return 返回的是16进制的加密类型 + * @throws Exception + */ + public static String encryptString(String seed, String cleartext) throws Exception { + byte[] rawKey = getRawKey(seed.getBytes()); + byte[] result = encryptByte(rawKey, cleartext.getBytes()); + return toHex(result); + } + + /** + * 解密函数 + * + * @param seed 密钥 + * @param encrypted 进行加密后的密码 + * @return 返回原来的密码 + * @throws Exception + */ + public static String decryptString(String seed, String encrypted) throws Exception { + byte[] rawKey = getRawKey(seed.getBytes()); + byte[] enc = toByte(encrypted); + byte[] result = decryptByte(rawKey, enc); + return new String(result); + } + + /** + * 获取key + * + * @throws Exception + */ + @SuppressLint("DeletedProvider") private static byte[] getRawKey(byte[] seed) throws Exception { + KeyGenerator kgen = KeyGenerator.getInstance("AES"); + + SecureRandom sr = null; + if (android.os.Build.VERSION.SDK_INT >= JELLY_BEAN_4_2) { + sr = SecureRandom.getInstance("SHA1PRNG", "Crypto"); + } else { + sr = SecureRandom.getInstance("SHA1PRNG"); + } + // SecureRandom sr = SecureRandom.getInstance("SHA1PRNG"); + sr.setSeed(seed); + kgen.init(128, sr); // 192 and 256 bits may not be available + SecretKey skey = kgen.generateKey(); + return skey.getEncoded(); + } + + /** + * 4.3以上的要用cipher = Cipher.getInstance("AES/ECB/ZeroBytePadding"); + * 加密byte流 + * + * @throws Exception + */ + private static byte[] encryptByte(byte[] raw, byte[] clear) throws Exception { + SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES"); + Cipher cipher = null; + if (android.os.Build.VERSION.SDK_INT > JELLY_BEAN_4_2) { + cipher = Cipher.getInstance("AES/ECB/ZeroBytePadding"); + } else { + cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + } + cipher.init(Cipher.ENCRYPT_MODE, skeySpec); + return cipher.doFinal(clear); + } + + /** + * 4.3以上的要用cipher = Cipher.getInstance("AES/ECB/ZeroBytePadding"); + * 解密byte流 + * + * @throws Exception + */ + private static byte[] decryptByte(byte[] raw, byte[] encrypted) throws Exception { + SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES"); + Cipher cipher = null; + if (android.os.Build.VERSION.SDK_INT > JELLY_BEAN_4_2) { + cipher = Cipher.getInstance("AES/ECB/ZeroBytePadding"); + } else { + cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + } + cipher.init(Cipher.DECRYPT_MODE, skeySpec); + return cipher.doFinal(encrypted); + } + + public static String toHex(String txt) { + return toHex(txt.getBytes()); + } + + public static String fromHex(String hex) { + return new String(toByte(hex)); + } + + public static byte[] toByte(String hexString) { + int len = hexString.length() / 2; + byte[] result = new byte[len]; + for (int i = 0; i < len; i++) + result[i] = Integer.valueOf(hexString.substring(2 * i, 2 * i + 2), 16).byteValue(); + return result; + } + + /** + * 把用户的key转换为16为的key + * AES算法的秘钥要求16位 + */ + public static String toHex(byte[] buf) { + if (buf == null) return ""; + StringBuffer result = new StringBuffer(2 * buf.length); + for (byte aBuf : buf) { + appendHex(result, aBuf); + } + return result.toString(); + } + + private final static String HEX = "0123456789ABCDEF"; + + private static void appendHex(StringBuffer sb, byte b) { + sb.append(HEX.charAt((b >> 4) & 0x0f)).append(HEX.charAt(b & 0x0f)); + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/util/AndroidUtils.java b/AppFrame/src/main/java/com/arialyy/frame/util/AndroidUtils.java new file mode 100644 index 00000000..252a4744 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/util/AndroidUtils.java @@ -0,0 +1,691 @@ +package com.arialyy.frame.util; + +import android.Manifest; +import android.annotation.TargetApi; +import android.app.Activity; +import android.app.ActivityManager; +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.os.StatFs; +import android.provider.Settings; +import android.support.annotation.NonNull; +import android.support.v4.app.ActivityCompat; +import android.telephony.TelephonyManager; +import android.text.format.Formatter; +import android.util.DisplayMetrics; +import android.view.WindowManager; +import com.arialyy.frame.util.show.FL; +import com.arialyy.frame.util.show.L; +import java.io.BufferedReader; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + +/** + * android系统工具类 + */ +public class AndroidUtils { + private static final String TAG = "AndroidUtils"; + + private AndroidUtils() { + + } + + private static final float DENSITY = Resources.getSystem().getDisplayMetrics().density; + + // 获得机身可用内存 + public static String getRomAvailableSize(Context context) { + File path = Environment.getDataDirectory(); + StatFs stat = new StatFs(path.getPath()); + long blockSize = stat.getBlockSize(); + long availableBlocks = stat.getAvailableBlocks(); + return Formatter.formatFileSize(context, blockSize * availableBlocks); + } + + /** + * 重启app + */ + public static void reStartApp(Context context) { + Intent intent = context.getPackageManager() + .getLaunchIntentForPackage(context.getPackageName()); + PendingIntent restartIntent = + PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_ONE_SHOT); + AlarmManager mgr = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 1000, restartIntent); // 1秒钟后重启应用 + System.exit(0); + } + + /** + * 另外一种dp转PX方法 + */ + public static int dp2px(int dp) { + return Math.round(dp * DENSITY); + } + + /** + * 检查权限 + * + * @param permission android.permission.WRITE_EXTERNAL_STORAGE + * @return manifest 已经定义了则返回true + */ + public static boolean checkPermission(@NonNull Context context, @NonNull String permission) { + return context.checkCallingOrSelfPermission(permission) == PackageManager.PERMISSION_GRANTED; + } + + /** + * 申请root权限 + * + * @return 应用程序是/否获取Root权限 + */ + public static boolean getRootPermission(String pkgCodePath) { + Process process = null; + DataOutputStream os = null; + try { + String cmd = "chmod 777 " + pkgCodePath; + process = Runtime.getRuntime().exec("su"); //切换到root帐号 + os = new DataOutputStream(process.getOutputStream()); + os.writeBytes(cmd + "\n"); + os.writeBytes("exit\n"); + os.flush(); + return process.waitFor() == 0; + } catch (Exception e) { + return false; + } finally { + try { + if (os != null) { + os.close(); + } + if (process != null) { + process.destroy(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + /** + * 获取指定包名的包信息 + */ + public static PackageInfo getAppInfo(Context context, String packageName) { + try { + return context.getPackageManager().getPackageInfo(packageName, PackageManager.GET_ACTIVITIES); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + return null; + } + } + + /** + * 获取未安装软件包的包名 + */ + public static String getApkPackageName(Context context, String apkPath) { + PackageManager pm = context.getPackageManager(); + PackageInfo info = pm.getPackageArchiveInfo(apkPath, PackageManager.GET_ACTIVITIES); + if (info != null) { + ApplicationInfo appInfo = info.applicationInfo; + appInfo.sourceDir = apkPath; + appInfo.publicSourceDir = apkPath; + return appInfo.packageName; + } + return ""; + } + + /** + * 判断是否安装 + */ + public static boolean apkIsInstall(Context context, String apkPath) { + PackageManager pm = context.getPackageManager(); + PackageInfo info = pm.getPackageArchiveInfo(apkPath, PackageManager.GET_ACTIVITIES); + if (info != null) { + ApplicationInfo appInfo = info.applicationInfo; + appInfo.sourceDir = apkPath; + appInfo.publicSourceDir = apkPath; + try { + pm.getPackageInfo(appInfo.packageName, PackageManager.GET_UNINSTALLED_PACKAGES); + return true; + } catch (PackageManager.NameNotFoundException localNameNotFoundException) { + return false; + } + } + return false; + } + + /** + * 判断服务是否后台运行 + * + * @param context Context + * @return true 在运行 false 不在运行 + */ + public static boolean isServiceRun(Context context, Class clazz) { + boolean isRun = false; + ActivityManager activityManager = + (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + List serviceList = + activityManager.getRunningServices(Integer.MAX_VALUE); + int size = serviceList.size(); + for (int i = 0; i < size; i++) { + if (serviceList.get(i).service.getClassName().equals(clazz.getName())) { + isRun = true; + break; + } + } + return isRun; + } + + /** + * 启动另外一个App + */ + public static void startOtherApp(Context context, String packageName) { + PackageManager pm = context.getPackageManager(); + Intent launcherIntent = pm.getLaunchIntentForPackage(packageName); + launcherIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + //launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER); + context.startActivity(launcherIntent); + } + + /** + * 判断手机是否拥有Root权限。 + * + * @return 有root权限返回true,否则返回false。 + */ + public static boolean isRoot() { + String binPath = "/system/bin/su"; + String xBinPath = "/system/xbin/su"; + return new File(binPath).exists() && isExecutable(binPath) + || new File(xBinPath).exists() && isExecutable(xBinPath); + } + + private static boolean isExecutable(String filePath) { + Process p = null; + try { + p = Runtime.getRuntime().exec("ls -l " + filePath); + // 获取返回内容 + BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream())); + String str = in.readLine(); + L.i(TAG, str); + if (str != null && str.length() >= 4) { + char flag = str.charAt(3); + if (flag == 's' || flag == 'x') return true; + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (p != null) { + p.destroy(); + } + } + return false; + } + + /** + * 静默卸载 + */ + public static boolean uninstallInBackground(String packageName) { + String command = "pm uninstall -k " + packageName + "\n"; + ShellUtils.CommandResult result = ShellUtils.execCommand(command, true); + String errorMsg = result.errorMsg; + L.d(TAG, "error msg = " + result.errorMsg); + L.d(TAG, "success msg = " + result.successMsg); + int res = result.result; + return res == 0 && !errorMsg.contains("Failure"); + } + + /** + * 静默安装APk + * 执行具体的静默安装逻辑,需要手机ROOT。 + * + * @param apkPath 要安装的apk文件的路径 + * @return 安装成功返回true,安装失败返回false。 + */ + public static boolean installInBackground(String apkPath) { + String command = "pm install -r " + apkPath + "\n"; + ShellUtils.CommandResult result = ShellUtils.execCommand(command, true); + String errorMsg = result.errorMsg; + L.d(TAG, "error msg = " + result.errorMsg); + L.d(TAG, "success msg = " + result.successMsg); + int res = result.result; + return res == 0 && !errorMsg.contains("Failure"); + } + + /** + * 获取状态栏高度 + */ + public static int getStatusBarHeight(Context context) { + int result = 0; + int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); + if (resourceId > 0) { + result = context.getResources().getDimensionPixelSize(resourceId); + } + return result; + } + + /** + * 获取导航栏高度 + */ + public static int getNavigationBarHeight(Context context) { + Resources resources = context.getResources(); + int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android"); + if (resourceId > 0) { + return resources.getDimensionPixelSize(resourceId); + } + return 0; + } + + /** + * 获取当前窗口的高度, 该高度是不包含导航栏和状态栏的 + */ + public static int getWindowHeight(Activity activity) { + return activity.getWindow().getWindowManager().getDefaultDisplay().getHeight(); + } + + /** + * 获取当前窗口的宽度 + */ + public static int getWindowWidth(Activity activity) { + return activity.getWindow().getWindowManager().getDefaultDisplay().getWidth(); + } + + /** + * 获取屏幕参数,这个获取到的高是包含导航栏和状态栏的 + */ + public static int[] getScreenParams(Context context) { + WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + return new int[] { wm.getDefaultDisplay().getWidth(), wm.getDefaultDisplay().getHeight() }; + } + + /** + * 检查手机是否安装了指定的APK + * + * @param packageName 该APK的包名 + */ + public static boolean checkApkExists(Context context, String packageName) { + List packageInfos = getAllApps(context); + for (PackageInfo pi : packageInfos) { + if (pi.packageName.equals(packageName)) { + return true; + } + } + return false; + } + + /** + * 查询手机内所有应用 + */ + public static List getAllApps(Context context) { + List apps = new ArrayList(); + PackageManager pManager = context.getPackageManager(); + //获取手机内所有应用 + List paklist = pManager.getInstalledPackages(0); + for (int i = 0; i < paklist.size(); i++) { + PackageInfo pak = paklist.get(i); + apps.add(pak); + } + return apps; + } + + /** + * 查询手机内非系统应用 + */ + public static List getAllNoSystemApps(Context context) { + List apps = new ArrayList(); + PackageManager pManager = context.getPackageManager(); + //获取手机内所有应用 + List paklist = pManager.getInstalledPackages(0); + for (int i = 0; i < paklist.size(); i++) { + PackageInfo pak = paklist.get(i); + //判断是否为非系统预装的应用程序 + if ((pak.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) <= 0) { + // customs applications + apps.add(pak); + } + } + return apps; + } + + /** + * 获取目录 + */ + public static String getSourcePath(Context context, String packageName) { + ApplicationInfo appInfo = null; + try { + appInfo = context.getPackageManager().getApplicationInfo(packageName, 0); + return appInfo.sourceDir; + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + return null; + } + + /** + * 获取包名 + */ + public static String getPackageName(Context context) { + return getPackageInfo(context).packageName; + } + + /** + * 获取应用名 + */ + public static String getAppName(Context context) { + try { + return context.getString(context.getApplicationInfo().labelRes); + } catch (Resources.NotFoundException e) { + FL.e(TAG, FL.getExceptionString(e)); + return ""; + } + } + + /** + * 获取设备的唯一ID + */ + public static String getAndroidId(Context context) { + return Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID); + } + + /** + * 返回版本名称 + */ + public static String getVersionName(Context context) { + return getPackageInfo(context).versionName; + } + + /** + * 返回版本号 + */ + public static int getVersionCode(Context context) { + return getPackageInfo(context).versionCode; + } + + /** + * 打开一个隐藏了图标的APP + */ + public static void openAppWithAction(Context context, String packageName, String activity) { + ComponentName componentName = new ComponentName(packageName, activity); + try { + Intent intent = new Intent(); + intent.setComponent(componentName); + context.startActivity(intent); + } catch (Exception e) { + FL.e(TAG, "没有找到应用程序:packageName:" + packageName + " activity:" + activity); + } + } + + /** + * 应用是否被安装 + */ + public static boolean isInstall(Context context, String packageName) { + PackageInfo packageInfo = null; + try { + packageInfo = context.getPackageManager().getPackageInfo(packageName, 0); + } catch (PackageManager.NameNotFoundException e) { + // e.printStackTrace(); + } + return packageInfo != null; + } + + /** + * 安装APP + */ + public static void install(Context context, File file) { + L.e(TAG, "install Apk:" + file.getName()); + context.startActivity(getInstallIntent(file)); + } + + /** + * 卸载APk + * + * @param packageName 包名 + */ + public static void uninstall(Context context, String packageName) { + Uri packageURI = Uri.parse("package:" + packageName); + Intent uninstallIntent = new Intent(Intent.ACTION_DELETE, packageURI); + uninstallIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(uninstallIntent); + } + + /** + * 获取安装应用的Intent + */ + public static Intent getInstallIntent(File file) { + Intent intent = new Intent(); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setAction(Intent.ACTION_VIEW); + intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive"); + return intent; + } + + /** + * 拷贝Assets中的文件到指定目录 + */ + public static boolean copyFileFromAssets(Context context, String fileName, String path) { + boolean copyIsFinish = false; + try { + InputStream is = context.getAssets().open(fileName); + File file = FileUtil.createFile(path); + FileOutputStream fos = new FileOutputStream(file); + byte[] temp = new byte[1024]; + int i = 0; + while ((i = is.read(temp)) > 0) { + fos.write(temp, 0, i); + } + fos.close(); + is.close(); + copyIsFinish = true; + } catch (IOException e) { + e.printStackTrace(); + } + return copyIsFinish; + } + + /** + * 获取版本信息 + */ + public static PackageInfo getPackageInfo(Context context) { + PackageInfo pkg = null; + if (context == null) { + return null; + } + try { + pkg = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + return pkg; + } + + /** + * 获取设备的显示属性 + */ + public static DisplayMetrics getDisplayMetrics(Context context) { + return context.getResources().getDisplayMetrics(); + } + + /** + * 获取电话号码 + */ + public static String getLocalPhoneNumber(Context context) { + if (ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_SMS) + != PackageManager.PERMISSION_GRANTED + && ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_NUMBERS) + != PackageManager.PERMISSION_GRANTED + && ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) + != PackageManager.PERMISSION_GRANTED) { + String line1Number = getTelephonyManager(context).getLine1Number(); + return line1Number == null ? "" : line1Number; + } + return null; + } + + /** + * 获取设备型号(Nexus5) + */ + public static String getDeviceModel() { + return Build.MODEL; + } + + /** + * 获取电话通讯管理(可以通过这个对象获取手机号码等) + */ + public static TelephonyManager getTelephonyManager(Context context) { + return (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + } + + /** + * 获取版本号 + */ + public static String getSystemVersion() { + // 获取android版本号 + return Build.VERSION.RELEASE; + } + + /** + * 返回ApplicationInfo(可以通过这个读取meta-data等等) + */ + public static ApplicationInfo getApplicationInfo(Context context) { + if (context == null) { + return null; + } + ApplicationInfo applicationInfo = null; + try { + applicationInfo = context.getPackageManager() + .getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + return applicationInfo; + } + + /** + * 获取MetaData的Bundle + */ + public static Bundle getMetaData(Context context) { + ApplicationInfo applicationInfo = getApplicationInfo(context); + if (applicationInfo == null) { + return new Bundle(); + } + return applicationInfo.metaData; + } + + /** + * 应用是否启动 + */ + public static boolean appIsRunning(Context context) { + boolean isAppRunning = false; + ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + List list = am.getRunningTasks(100); + String packageName = getPackageInfo(context).packageName; + for (ActivityManager.RunningTaskInfo info : list) { + if (info.topActivity.getPackageName().equals(packageName) + && info.baseActivity.getPackageName().equals(packageName)) { + isAppRunning = true; + //find it, break + break; + } + } + return isAppRunning; + } + + /** + * 检查系统是否有这个Intent,在启动Intent的时候需要检查,因为启动一个没有的Intent程序会Crash + */ + public static boolean isIntentSafe(Context context, Intent intent) { + PackageManager packageManager = context.getPackageManager(); + List activities = packageManager.queryIntentActivities(intent, 0); + return activities.size() > 0; + } + + /** + * 获取当前包的系统缓存目录 + * "/Android/data/" + context.getPackageName() + "/cache" + */ + public static String getDiskCacheDir(Context context) { + // Check if media is mounted or storage is built-in, if so, try and use external cache dir + // otherwise use internal cache dir + File cacheDirPath = getExternalCacheDir(context); + if (cacheDirPath == null) { + cacheDirPath = context.getCacheDir(); + } + return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) + || !isExternalStorageRemovable() ? cacheDirPath.getPath() : context.getCacheDir().getPath(); + } + + /** + * 获取当前包的外置路径 + * "/Android/data/" + context.getPackageName() + */ + public static String getDiskPackage(Context context) { + return new File(getDiskCacheDir(context)).getParent(); + } + + /** + * Check if external storage is built-in or removable. + * + * @return True if external storage is removable (like an SD card), false + * otherwise. + */ + @TargetApi(Build.VERSION_CODES.GINGERBREAD) public static boolean isExternalStorageRemovable() { + return !AndroidVersionUtil.hasGingerbread() || Environment.isExternalStorageRemovable(); + } + + /** + * Get the external app cache directory. + * + * @param context The context to use + * @return The external cache dir + */ + @TargetApi(Build.VERSION_CODES.FROYO) public static File getExternalCacheDir(Context context) { + if (AndroidVersionUtil.hasFroyo()) { + File cacheDir; + if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) + || !Environment.isExternalStorageRemovable()) { + // /sdcard/Android/data//cache + if (context == null) { + NullPointerException ex = new NullPointerException("context == null"); + FL.e(TAG, FL.getExceptionString(ex)); + throw ex; + } + cacheDir = context.getExternalCacheDir(); + } else { + // /data/data//cache + cacheDir = context.getCacheDir(); + } + return cacheDir; + } + + // Before Froyo we need to construct the external cache dir ourselves + final String cacheDir = "/Android/data/" + context.getPackageName() + "/cache/"; + return new File(Environment.getExternalStorageDirectory().getPath() + cacheDir); + } + + /** + * Check how much usable space is available at a given path. + * + * @param path The path to check + * @return The space available in bytes + */ + @TargetApi(Build.VERSION_CODES.GINGERBREAD) public static long getUsableSpace(File path) { + if (AndroidVersionUtil.hasGingerbread()) { + return path.getUsableSpace(); + } + final StatFs stats = new StatFs(path.getPath()); + return (long) stats.getBlockSize() * (long) stats.getAvailableBlocks(); + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/util/AndroidVersionUtil.java b/AppFrame/src/main/java/com/arialyy/frame/util/AndroidVersionUtil.java new file mode 100644 index 00000000..f25195cd --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/util/AndroidVersionUtil.java @@ -0,0 +1,90 @@ +package com.arialyy.frame.util; + +import android.os.Build; + +/** + * android版本检测工具 + * + * @author lyy + */ +public class AndroidVersionUtil { + private AndroidVersionUtil() { + } + + /** + * 当前Android系统版本是否在( Donut) Android 1.6或以上 + */ + public static boolean hasDonut() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.DONUT; + } + + /** + * 当前Android系统版本是否在( Eclair) Android 2.0或 以上 + */ + public static boolean hasEclair() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR; + } + + /** + * 当前Android系统版本是否在( Froyo) Android 2.2或 Android 2.2以上 + */ + public static boolean hasFroyo() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO; + } + + /** + * 当前Android系统版本是否在( Gingerbread) Android 2.3x或 Android 2.3x 以上 + */ + public static boolean hasGingerbread() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD; + } + + /** + * 当前Android系统版本是否在( Honeycomb) Android3.1或 Android3.1以上 + */ + public static boolean hasHoneycomb() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB; + } + + /** + * 当前Android系统版本是否在( HoneycombMR1) Android3.1.1或 Android3.1.1以上 + */ + public static boolean hasHoneycombMR1() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1; + } + + /** + * 当前Android系统版本是否在( IceCreamSandwich) Android4.0或 Android4.0以上 + */ + public static boolean hasIcecreamsandwich() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH; + } + + /** + * 当前android系统版本是否在(JellyBean)Android4.1或android4.1以上 + */ + public static boolean hasJellyBean() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN; + } + + /** + * 当前android系统版本是否在(KitKat)Android4.4或android4.4以上 + */ + public static boolean hasKitKat() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; + } + + /** + * 当前是否在5.0以上 + */ + public static boolean hasLollipop() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; + } + + /** + * 当前是否在6.0以上 + */ + public static boolean hasM() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M; + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/util/AppSigning.java b/AppFrame/src/main/java/com/arialyy/frame/util/AppSigning.java new file mode 100644 index 00000000..c0227856 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/util/AppSigning.java @@ -0,0 +1,82 @@ +package com.arialyy.frame.util; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.Signature; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; + +/** + * 应用签名信息 + * Created by YangQiang on 2014/11/27. + */ +public class AppSigning { + public final static String MD5 = "MD5"; + public final static String SHA1 = "SHA1"; + public final static String SHA256 = "SHA256"; + + /** + * 返回一个签名的对应类型的字符串 + */ + public static ArrayList getSingInfo(Context context, String packageName, String type) { + ArrayList result = new ArrayList(); + try { + Signature[] signs = getSignatures(context, packageName); + for (Signature sig : signs) { + String tmp = "error!"; + if (MD5.equals(type)) { + tmp = getSignatureString(sig, MD5); + } else if (SHA1.equals(type)) { + tmp = getSignatureString(sig, SHA1); + } else if (SHA256.equals(type)) { + tmp = getSignatureString(sig, SHA256); + } + result.add(tmp); + } + } catch (Exception e) { + e.printStackTrace(); + } + return result; + } + + /** + * 返回对应包的签名信息 + */ + public static Signature[] getSignatures(Context context, String packageName) { + PackageInfo packageInfo = null; + try { + packageInfo = + context.getPackageManager().getPackageInfo(packageName, PackageManager.GET_SIGNATURES); + return packageInfo.signatures; + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + return null; + } + + /** + * 获取相应的类型的字符串(把签名的byte[]信息转换成16进制) + */ + public static String getSignatureString(Signature sig, String type) { + byte[] hexBytes = sig.toByteArray(); + String fingerprint = "error!"; + try { + MessageDigest digest = MessageDigest.getInstance(type); + if (digest != null) { + byte[] digestBytes = digest.digest(hexBytes); + StringBuilder sb = new StringBuilder(); + for (byte digestByte : digestBytes) { + sb.append((Integer.toHexString((digestByte & 0xFF) | 0x100)).substring(1, 3)); + } + fingerprint = sb.toString(); + } + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + + return fingerprint; + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/util/AppUtils.java b/AppFrame/src/main/java/com/arialyy/frame/util/AppUtils.java new file mode 100644 index 00000000..22b609d1 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/util/AppUtils.java @@ -0,0 +1,135 @@ +package com.arialyy.frame.util; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.Signature; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; + +/** + * 跟App相关的辅助类 + */ +public class AppUtils { + /** + * 应用签名信息 + */ + public final static String MD5 = "MD5"; + public final static String SHA1 = "SHA1"; + public final static String SHA256 = "SHA256"; + + /** + * 返回一个签名的对应类型的字符串 + */ + public static ArrayList getSingInfo(Context context, String packageName, String type) { + ArrayList result = new ArrayList(); + try { + Signature[] signs = getSignatures(context, packageName); + for (Signature sig : signs) { + String tmp = "error!"; + if (MD5.equals(type)) { + tmp = getSignatureString(sig, MD5); + } else if (SHA1.equals(type)) { + tmp = getSignatureString(sig, SHA1); + } else if (SHA256.equals(type)) { + tmp = getSignatureString(sig, SHA256); + } + result.add(tmp); + } + } catch (Exception e) { + e.printStackTrace(); + } + return result; + } + + /** + * 返回对应包的签名信息 + */ + public static Signature[] getSignatures(Context context, String packageName) { + PackageInfo packageInfo = null; + try { + packageInfo = + context.getPackageManager().getPackageInfo(packageName, PackageManager.GET_SIGNATURES); + return packageInfo.signatures; + } catch (NameNotFoundException e) { + e.printStackTrace(); + } + return null; + } + + /** + * 获取相应的类型的字符串(把签名的byte[]信息转换成16进制) + */ + public static String getSignatureString(Signature sig, String type) { + byte[] hexBytes = sig.toByteArray(); + String fingerprint = "error!"; + try { + MessageDigest digest = MessageDigest.getInstance(type); + if (digest != null) { + byte[] digestBytes = digest.digest(hexBytes); + StringBuilder sb = new StringBuilder(); + for (byte digestByte : digestBytes) { + sb.append((Integer.toHexString((digestByte & 0xFF) | 0x100)).substring(1, 3)); + } + fingerprint = sb.toString(); + } + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + + return fingerprint; + } + + private AppUtils() { + throw new UnsupportedOperationException("cannot be instantiated"); + } + + /** + * 获取应用程序名称 + */ + public static String getAppName(Context context) { + try { + PackageManager packageManager = context.getPackageManager(); + PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0); + int labelRes = packageInfo.applicationInfo.labelRes; + return context.getResources().getString(labelRes); + } catch (NameNotFoundException e) { + e.printStackTrace(); + } + return null; + } + + /** + * [获取应用程序版本名称信息] + * + * @return 当前应用的版本名称 + */ + public static String getVersionName(Context context) { + try { + PackageManager packageManager = context.getPackageManager(); + PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0); + return packageInfo.versionName; + } catch (NameNotFoundException e) { + e.printStackTrace(); + } + return null; + } + + /** + * 获取应用版本号 + * + * @return 当前应用版本 + */ + public static int getVersionNumber(Context context) { + try { + PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); + return info.versionCode; + } catch (NameNotFoundException e) { + e.printStackTrace(); + } + return 1; + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/util/CalendarUtils.java b/AppFrame/src/main/java/com/arialyy/frame/util/CalendarUtils.java new file mode 100644 index 00000000..82ca9311 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/util/CalendarUtils.java @@ -0,0 +1,299 @@ +package com.arialyy.frame.util; + +import com.arialyy.frame.util.show.FL; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; + +/** + * 日历工具类 Create by lyy on 13-7-8. + */ +public class CalendarUtils { + private static final String TAG = "CalendarUtils"; + /** + * 完整的日期时间格式 + */ + public final static String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; + /** + * 只有日期的格式 + */ + public final static String DATE_FORMAT = "yyyy-MM-dd"; + /** + * 只有时间的格式 + */ + public final static String TIME_FORMAT = "HH:mm:ss"; + /** + * 带中文的日期格式(2000年01月01日) + */ + public final static String DATE_FORMAT_WITH_CHINESE = "yyyy年MM月dd日"; + /** + * 短时间格式(HH:mm) + */ + public final static String SHORT_TIME_FORMAT = "HH:mm"; + + /** + * 私有构造 + */ + private CalendarUtils() { + } + + /** + * 把String类型的日期转换成Calendar对象 + * + * @param string (日期时间:2000-00-00 00:00:00) + */ + public static Calendar transformStringToCalendar(String string) { + return transformStringToCalendar(string, DATE_TIME_FORMAT); + } + + /** + * 通过SimpleDataFormat格式把string转换成Calendar + * + * @param string 日期字符串 + * @param format 目标日期格式 + */ + public static Calendar transformStringToCalendar(String string, String format) { + SimpleDateFormat sdf = new SimpleDateFormat(format); + Date date = null; + try { + date = sdf.parse(string); + } catch (ParseException e) { + e.printStackTrace(); + } + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + return calendar; + } + + /** + * 把日期字符串转换成TimeMillis + */ + public static long transformStringToMillis(String string) { + return transformStringToCalendar(string).getTimeInMillis(); + } + + /** + * 通过TimeMillis转换成秒钟 + * + * @param millis TimeMillis + */ + public static long getSecondWithTimeMillis(long millis) { + return millis / 1000; + } + + /** + * 返回两个日期相差的秒 + */ + public static long getIntervalInSeconds(Calendar calendar, Calendar targetCalendar) { + return (calendar.getTimeInMillis() - targetCalendar.getTimeInMillis()) / 1000; + } + + /** + * 格式化日期 + * + * @param string 有效的日期字符 + * @param format 格式化的格式 + */ + public static String formatWithString(String string, String format) { + SimpleDateFormat sdf = new SimpleDateFormat(format); + return sdf.format(transformStringToCalendar(string).getTime()); + } + + /** + * 格式化日期 + * + * @param src 源日期字符 + * @param srcFormat 源日期格式 + * @param targetFormat 目标日期格式 + */ + public static String formatWithString(String src, String srcFormat, String targetFormat) { + SimpleDateFormat sdf = new SimpleDateFormat(srcFormat); + try { + Date date = sdf.parse(src); + SimpleDateFormat targetSdf = new SimpleDateFormat(targetFormat); + return targetSdf.format(date); + } catch (Exception e) { + FL.e(TAG, "src=" + src + " " + srcFormat + " " + targetFormat); + e.printStackTrace(); + } + return null; + } + + /** + * 格式化成日期时间 + * + * @param string 将要被格式化的日期字符串 + * @return 格式:(2000-00-00 00:00:00) + */ + public static String formatDateTimeWithString(String string) { + return formatWithString(string, DATE_TIME_FORMAT); + } + + /** + * 格式化日期 + * + * @param srcDate 源日期字符 + * @param srcDateFormat 源日期格式 + * @param targetFormat 目标格式 + */ + public static String formatDateTimeWithString(String srcDate, String srcDateFormat, + String targetFormat) { + SimpleDateFormat sdf = new SimpleDateFormat(srcDateFormat); + try { + Date date = sdf.parse(srcDate); + SimpleDateFormat parseSdf = new SimpleDateFormat(targetFormat); + return parseSdf.format(date); + } catch (ParseException e) { + FL.e(TAG, "srcDate:" + + srcDate + + " srcDateFormat:" + + srcDateFormat + + " targetFormat" + + targetFormat); + e.printStackTrace(); + } + return null; + } + + /** + * 格式化日期 + * + * @param string 将要被格式化的日期字符串 + * @return 格式:(2000-00-00) + */ + public static String formatDateWithString(String string) { + return formatWithString(string, DATE_FORMAT); + } + + /** + * 格式化日期 + * + * @param string 将要被格式化的日期字符串 + * @return 格式:(00:00:00) + */ + public static String formatTimeWithString(String string) { + return formatWithString(string, TIME_FORMAT); + } + + /** + * 格式化日期 + * + * @param string 将要被格式化的日期字符串 + * @return 格式:(2000年01月01日) + */ + public static String formatStringToChinese(String string) { + return formatWithString(string, DATE_FORMAT_WITH_CHINESE); + } + + /** + * Data日期格式化成String + * + * @param date 将要被格式化的data + * @return 格式:(2000-00-00 00:00:00) + */ + public static String formatDateTimeWithDate(Date date) { + return formatStringWithDate(date, DATE_TIME_FORMAT); + } + + /** + * 时间戳格式的数据格式化成需要的格式 + * + * @param string 将要被格式化的时间戳 + * @return 格式:(2000-00-00 00:00:00) + */ + public static String formatDateTimeWithTime(String string) { + return formatStringWithDate(timeToData(string), DATE_TIME_FORMAT); + } + + /** + * 把中文日期(2000年01月01日)格式化成标准日期(2000-01-01) + * + * @param data 将要格式化的日期字符串 + * @return 格式:(2000-00-00); + */ + public static String formatChineseDataToData(String data) { + data = data.replace("年", "-"); + data = data.replace("月", "-"); + data = data.replace("日", ""); + return data; + } + + /** + * 日期格式化成String + * + * @param date 将要格式化的日期字符串 + * @return 格式:(format格式) + */ + public static String formatStringWithDate(Date date, String format) { + SimpleDateFormat sdf = new SimpleDateFormat(format); + return sdf.format(date); + } + + /** + * 把String类型的时间转换成Calendar + * + * @param time 时间格式:00:00:00 + */ + public static Calendar transformStringTimeToCalendar(String time) { + Calendar calendar = Calendar.getInstance(); + String[] split = time.split(":"); + if (split.length > 0) { + calendar.set(Calendar.HOUR_OF_DAY, Integer.parseInt(split[0])); + } + if (split.length > 1) { + calendar.set(Calendar.MINUTE, Integer.parseInt(split[1])); + } + if (split.length > 2) { + calendar.set(Calendar.SECOND, Integer.parseInt(split[2])); + } + return calendar; + } + + /** + * 把时间戳:1234567890123,转换成Date对象 + */ + public static Date timeToData(String time) { + return new Date(Long.parseLong(time)); + } + + /** + * 比较两个字符串日期的大小 + * + * @return 小于0:srcData 小于 tagData; + * 等于0:则srcData = tagData; + * 大于0:则srcData 大余 tagData + */ + public static int compare(String srcDate, String tagDate) { + return srcDate.compareTo(tagDate); + } + + /** + * 返回现在的日期和时间 + * + * @return 格式:2000-00-00 00:00:00 + */ + public static String getNowDataTime() { + Calendar calendar = Calendar.getInstance(); + return formatDateTimeWithDate(calendar.getTime()); + } + + /** + * 返回当前的日期 + * + * @return 格式:2000-00-00 + */ + public static String getData() { + return getNowDataTime().split(" ")[0]; + } + + /** + * 返回当前时间 + * + * @return 格式:00:00:00 + */ + public static String getTime() { + return getNowDataTime().split(" ")[1]; + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/util/DensityUtils.java b/AppFrame/src/main/java/com/arialyy/frame/util/DensityUtils.java new file mode 100644 index 00000000..e52e2b30 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/util/DensityUtils.java @@ -0,0 +1,55 @@ +package com.arialyy.frame.util; + +import android.content.Context; +import android.content.res.Resources; +import android.util.TypedValue; + +/** + * 常用单位转换的辅助类 + */ +public class DensityUtils { + private DensityUtils() { + /* cannot be instantiated */ + throw new UnsupportedOperationException("cannot be instantiated"); + } + + private static final float DENSITY = Resources.getSystem().getDisplayMetrics().density; + + /** + * 另外一种dp转PX方法 + */ + public static int dp2px(int dp) { + return Math.round(dp * DENSITY); + } + + /** + * dp转px + */ + public static int dp2px(Context context, float dpVal) { + return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpVal, + context.getResources().getDisplayMetrics()); + } + + /** + * sp转px + */ + public static int sp2px(Context context, float spVal) { + return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spVal, + context.getResources().getDisplayMetrics()); + } + + /** + * px转dp + */ + public static float px2dp(Context context, float pxVal) { + final float scale = context.getResources().getDisplayMetrics().density; + return (pxVal / scale); + } + + /** + * px转sp + */ + public static float px2sp(Context context, float pxVal) { + return (pxVal / context.getResources().getDisplayMetrics().scaledDensity); + } +} \ No newline at end of file diff --git a/AppFrame/src/main/java/com/arialyy/frame/util/DrawableUtil.java b/AppFrame/src/main/java/com/arialyy/frame/util/DrawableUtil.java new file mode 100644 index 00000000..9d7c531e --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/util/DrawableUtil.java @@ -0,0 +1,88 @@ +package com.arialyy.frame.util; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.PixelFormat; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.view.View; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public class DrawableUtil { + + /** + * View 转换为bitmap + */ + public static Bitmap convertViewToBitmap(View v) { + v.setDrawingCacheEnabled(true); + v.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); + v.layout(0, 0, v.getMeasuredWidth(), v.getMeasuredHeight()); + + v.buildDrawingCache(true); + Bitmap b = Bitmap.createBitmap(v.getDrawingCache()); + v.setDrawingCacheEnabled(false); // clear drawing cache + return b; + } + + /** + * 收缩图片 + */ + public static Drawable zoomDrawable(Drawable drawable, int w, int h) { + int width = drawable.getIntrinsicWidth(); + int height = drawable.getIntrinsicHeight(); + Bitmap oldbmp = drawableToBitmap(drawable); + Matrix matrix = new Matrix(); + float scaleWidth = ((float) w / width); + float scaleHeight = ((float) h / height); + matrix.postScale(scaleWidth, scaleHeight); + Bitmap newbmp = Bitmap.createBitmap(oldbmp, 0, 0, width, height, matrix, true); + return new BitmapDrawable(null, newbmp); + } + + /** + * drawable转bitMap + */ + public static Bitmap drawableToBitmap(Drawable drawable) { + int width = drawable.getIntrinsicWidth(); + int height = drawable.getIntrinsicHeight(); + Bitmap bitmap = Bitmap.createBitmap(width, height, + drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 + : Bitmap.Config.RGB_565); + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, width, height); + drawable.draw(canvas); + return bitmap; + } + + /** + * BitMap2Byte + */ + public static byte[] getBitmapByte(Bitmap bitmap) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out); + try { + out.flush(); + out.close(); + } catch (IOException e) { + e.printStackTrace(); + } + return out.toByteArray(); + } + + /** + * Byte2BitMap + */ + public static Bitmap getBitmapFromByte(byte[] temp) { + if (temp != null) { + Bitmap bitmap = BitmapFactory.decodeByteArray(temp, 0, temp.length); + return bitmap; + } else { + return null; + } + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/util/FileUtil.java b/AppFrame/src/main/java/com/arialyy/frame/util/FileUtil.java new file mode 100644 index 00000000..dbf57705 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/util/FileUtil.java @@ -0,0 +1,520 @@ +package com.arialyy.frame.util; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; +import android.text.TextUtils; + +import com.arialyy.frame.util.show.FL; +import com.arialyy.frame.util.show.L; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Enumeration; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/** + * 文件操作工具 可以创建和删除文件等 + */ +public class FileUtil { + private static final String KB = "KB"; + private static final String MB = "MB"; + private static final String GB = "GB"; + + private FileUtil() { + } + + private static final String TAG = "FileUtil"; + + /** + * 获取文件夹大小 + */ + public static long getDirSize(String filePath) { + long size = 0; + File f = new File(filePath); + if (f.isDirectory()) { + File[] files = f.listFiles(); + for (File file : files) { + if (file.isDirectory()) { + size += getDirSize(file.getPath()); + continue; + } + size += file.length(); + } + } else { + size += f.length(); + } + return size; + } + + /** + * 存储bitmap + */ + public static void saveBitmap(@NonNull String filePath, @NonNull Bitmap bitmap) { + File file = createFile(filePath); + try { + FileOutputStream os = new FileOutputStream(file); + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os); + os.flush(); + os.close(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * 格式化文件大小 + * + * @param size file.length() 获取文件大小 + */ + public static String formatFileSize(double size) { + double kiloByte = size / 1024; + if (kiloByte < 1) { + return size + "B"; + } + + double megaByte = kiloByte / 1024; + if (megaByte < 1) { + BigDecimal result1 = new BigDecimal(Double.toString(kiloByte)); + return result1.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "KB"; + } + + double gigaByte = megaByte / 1024; + if (gigaByte < 1) { + BigDecimal result2 = new BigDecimal(Double.toString(megaByte)); + return result2.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "MB"; + } + + double teraBytes = gigaByte / 1024; + if (teraBytes < 1) { + BigDecimal result3 = new BigDecimal(Double.toString(gigaByte)); + return result3.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "GB"; + } + BigDecimal result4 = new BigDecimal(teraBytes); + return result4.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "TB"; + } + + /** + * 获取文件后缀名 + */ + public static String getFileExtensionName(String fileName) { + if (TextUtils.isEmpty(fileName)) { + return ""; + } + int endP = fileName.lastIndexOf("."); + return endP > -1 ? fileName.substring(endP + 1, fileName.length()) : ""; + } + + /** + * 校验文件MD5码 + */ + public static boolean checkMD5(String md5, File updateFile) { + if (TextUtils.isEmpty(md5) || updateFile == null) { + L.e(TAG, "MD5 string empty or updateFile null"); + return false; + } + + String calculatedDigest = getFileMD5(updateFile); + if (calculatedDigest == null) { + L.e(TAG, "calculatedDigest null"); + return false; + } + return calculatedDigest.equalsIgnoreCase(md5); + } + + /** + * 获取文件MD5码 + */ + public static String getFileMD5(File updateFile) { + MessageDigest digest; + try { + digest = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + L.e(TAG, "Exception while getting digest", e); + return null; + } + + InputStream is; + try { + is = new FileInputStream(updateFile); + } catch (FileNotFoundException e) { + L.e(TAG, "Exception while getting FileInputStream", e); + return null; + } + + byte[] buffer = new byte[8192]; + int read; + try { + while ((read = is.read(buffer)) > 0) { + digest.update(buffer, 0, read); + } + byte[] md5sum = digest.digest(); + BigInteger bigInt = new BigInteger(1, md5sum); + String output = bigInt.toString(16); + // Fill to 32 chars + output = String.format("%32s", output).replace(' ', '0'); + return output; + } catch (IOException e) { + throw new RuntimeException("Unable to process file for MD5", e); + } finally { + try { + is.close(); + } catch (IOException e) { + L.e(TAG, "Exception on closing MD5 input stream", e); + } + } + } + + /** + * 解压缩功能. + * 将ZIP_FILENAME文件解压到ZIP_DIR目录下. + * + * @param zipFile 压缩文件 + * @param folderPath 解压目录 + */ + public static int unZipFile(File zipFile, String folderPath) { + ZipFile zfile = null; + try { + zfile = new ZipFile(zipFile); + Enumeration zList = zfile.entries(); + ZipEntry ze = null; + byte[] buf = new byte[1024]; + while (zList.hasMoreElements()) { + ze = (ZipEntry) zList.nextElement(); + if (ze.isDirectory()) { + // L.d(TAG, "ze.getName() = " + ze.getName()); + String dirstr = folderPath + ze.getName(); + //dirstr.trim(); + dirstr = new String(dirstr.getBytes("8859_1"), "GB2312"); + // L.d(TAG, "str = " + dirstr); + File f = new File(dirstr); + f.mkdir(); + continue; + } + // L.d(TAG, "ze.getName() = " + ze.getName()); + OutputStream os = new BufferedOutputStream( + new FileOutputStream(getRealFileName(folderPath, ze.getName()))); + InputStream is = new BufferedInputStream(zfile.getInputStream(ze)); + int readLen = 0; + while ((readLen = is.read(buf)) != -1) { + os.write(buf, 0, readLen); + } + is.close(); + os.close(); + } + zfile.close(); + } catch (IOException e) { + e.printStackTrace(); + } + + return 0; + } + + /** + * 给定根目录,返回一个相对路径所对应的实际文件名. + * + * @param baseDir 指定根目录 + * @param absFileName 相对路径名,来自于ZipEntry中的name + * @return java.io.File 实际的文件 + */ + private static File getRealFileName(String baseDir, String absFileName) { + String[] dirs = absFileName.split("/"); + File ret = new File(baseDir); + String substr = null; + if (dirs.length > 1) { + for (int i = 0; i < dirs.length - 1; i++) { + substr = dirs[i]; + try { + //substr.trim(); + substr = new String(substr.getBytes("8859_1"), "GB2312"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + ret = new File(ret, substr); + } + // L.d("upZipFile", "1ret = " + ret); + if (!ret.exists()) ret.mkdirs(); + substr = dirs[dirs.length - 1]; + try { + //substr.trim(); + substr = new String(substr.getBytes("8859_1"), "GB2312"); + // L.d("upZipFile", "substr = " + substr); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + ret = new File(ret, substr); + // L.d("upZipFile", "2ret = " + ret); + return ret; + } + + return ret; + } + + /** + * 通过流创建文件 + */ + public static void createFileFormInputStream(InputStream is, String path) { + try { + FileOutputStream fos = new FileOutputStream(path); + byte[] buf = new byte[1376]; + while (is.read(buf) > 0) { + fos.write(buf, 0, buf.length); + } + is.close(); + fos.flush(); + fos.close(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * 从文件读取对象 + */ + public static Object readObj(String path) { + FileInputStream fis = null; + ObjectInputStream ois = null; + try { + fis = new FileInputStream(path); + ois = new ObjectInputStream(fis); + return ois.readObject(); + } catch (FileNotFoundException e) { + FL.e(TAG, FL.getExceptionString(e)); + } catch (IOException e) { + FL.e(TAG, FL.getExceptionString(e)); + } catch (ClassNotFoundException e) { + FL.e(TAG, FL.getExceptionString(e)); + } finally { + try { + if (fis != null) { + fis.close(); + } + if (ois != null) { + ois.close(); + } + } catch (IOException e) { + FL.e(TAG, FL.getExceptionString(e)); + } + } + return null; + } + + /** + * 存储对象到文件,只有实现了Serailiable接口的对象才能被存储 + */ + public static void writeObj(String path, Object object) { + FileOutputStream fos = null; + ObjectOutputStream oos = null; + File file = new File(path); + if (!file.getParentFile().exists()) { + file.mkdirs(); + } + try { + fos = new FileOutputStream(path); + oos = new ObjectOutputStream(fos); + oos.writeObject(object); + } catch (FileNotFoundException e) { + FL.e(TAG, FL.getExceptionString(e)); + } catch (IOException e) { + FL.e(TAG, FL.getExceptionString(e)); + } finally { + try { + if (fos != null) { + fos.close(); + } + if (oos != null) { + oos.close(); + } + } catch (IOException e) { + FL.e(TAG, FL.getExceptionString(e)); + } + } + } + + /** + * 创建文件 当文件不存在的时候就创建一个文件,否则直接返回文件 + */ + public static File createFile(String path) { + File file = new File(path); + if (!file.getParentFile().exists()) { + FL.d(TAG, "目标文件所在路径不存在,准备创建……"); + if (!createDir(file.getParent())) { + FL.d(TAG, "创建目录文件所在的目录失败!文件路径【" + path + "】"); + } + } + // 创建目标文件 + try { + if (!file.exists()) { + if (file.createNewFile()) { + FL.d(TAG, "创建文件成功:" + file.getAbsolutePath()); + } + return file; + } else { + return file; + } + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + /** + * 创建目录 当目录不存在的时候创建文件,否则返回false + */ + public static boolean createDir(String path) { + File file = new File(path); + if (!file.exists()) { + if (!file.mkdirs()) { + FL.d(TAG, "创建失败,请检查路径和是否配置文件权限!"); + return false; + } + return true; + } + return false; + } + + /** + * 拷贝文件 + */ + public static boolean copy(String fromPath, String toPath) { + File file = new File(fromPath); + if (!file.exists()) { + return false; + } + createFile(toPath); + return copyFile(fromPath, toPath); + } + + /** + * 拷贝文件 + */ + private static boolean copyFile(String fromFile, String toFile) { + InputStream fosfrom = null; + OutputStream fosto = null; + try { + fosfrom = new FileInputStream(fromFile); + fosto = new FileOutputStream(toFile); + byte bt[] = new byte[1024]; + int c; + while ((c = fosfrom.read(bt)) > 0) { + fosto.write(bt, 0, c); + } + return true; + } catch (Exception ex) { + ex.printStackTrace(); + return false; + } finally { + try { + if (fosfrom != null) { + fosfrom.close(); + } + if (fosto != null) { + fosto.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + /** + * 删除文件 如果文件存在删除文件,否则返回false + */ + public static boolean deleteFile(String path) { + File file = new File(path); + if (file.exists()) { + file.delete(); + return true; + } + return false; + } + + /** + * 递归删除目录下的所有文件及子目录下所有文件 + * + * @param dir 将要删除的文件目录 + * @return 删除成功返回true,否则返回false,如果文件是空,那么永远返回true + */ + public static boolean deleteDir(File dir) { + if (dir == null) { + return true; + } + if (dir.isDirectory()) { + String[] children = dir.list(); + // 递归删除目录中的子目录下 + for (int i = 0; i < children.length; i++) { + boolean success = deleteDir(new File(dir, children[i])); + if (!success) { + return false; + } + } + } + // 目录此时为空,可以删除 + return dir.delete(); + } + + /** + * 递归返回文件或者目录的大小(单位:KB) + * 不建议使用这个方法,有点坑 + * 可以使用下面的方法:http://blog.csdn.net/loongggdroid/article/details/12304695 + */ + private static float getSize(String path, Float size) { + File file = new File(path); + if (file.exists()) { + if (file.isDirectory()) { + String[] children = file.list(); + for (int fileIndex = 0; fileIndex < children.length; ++fileIndex) { + float tmpSize = + getSize(file.getPath() + File.separator + children[fileIndex], size) / 1000; + size += tmpSize; + } + } else if (file.isFile()) { + size += file.length(); + } + } + return size; + } + + /** + * 获取apk文件的icon + * + * @param path apk文件路径 + */ + public static Drawable getApkIcon(Context context, String path) { + PackageManager pm = context.getPackageManager(); + PackageInfo info = pm.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES); + if (info != null) { + ApplicationInfo appInfo = info.applicationInfo; + //android有bug,需要下面这两句话来修复才能获取apk图片 + appInfo.sourceDir = path; + appInfo.publicSourceDir = path; + // String packageName = appInfo.packageName; //得到安装包名称 + // String version=info.versionName; //得到版本信息 + return pm.getApplicationIcon(appInfo); + } + return null; + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/util/KeyBoardUtils.java b/AppFrame/src/main/java/com/arialyy/frame/util/KeyBoardUtils.java new file mode 100644 index 00000000..c4a77dfd --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/util/KeyBoardUtils.java @@ -0,0 +1,37 @@ +package com.arialyy.frame.util; + +import android.content.Context; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; + +/** + * 打开或关闭软键盘 + * + * @author zhy + */ +public class KeyBoardUtils { + /** + * 打卡软键盘 + * + * @param mEditText 输入框 + * @param mContext 上下文 + */ + public static void openKeybord(EditText mEditText, Context mContext) { + InputMethodManager imm = + (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(mEditText, InputMethodManager.RESULT_SHOWN); + imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY); + } + + /** + * 关闭软键盘 + * + * @param mEditText 输入框 + * @param mContext 上下文 + */ + public static void closeKeybord(EditText mEditText, Context mContext) { + InputMethodManager imm = + (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0); + } +} \ No newline at end of file diff --git a/AppFrame/src/main/java/com/arialyy/frame/util/MathUtil.java b/AppFrame/src/main/java/com/arialyy/frame/util/MathUtil.java new file mode 100644 index 00000000..1df2d6fb --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/util/MathUtil.java @@ -0,0 +1,47 @@ +package com.arialyy.frame.util; + +import java.math.BigDecimal; + +/** + * Created by AriaLyy on 2015/1/4. + * 精度转换 + */ +public class MathUtil { + + /** + * http://spiritfrog.iteye.com/blog/602144 + */ + public class MBigDecimal { + public static final int ROUND_UP = 0; + public static final int ROUND_DOWN = 1; + public static final int ROUND_CEILING = 2; + public static final int ROUND_FLOOR = 3; + public static final int ROUND_HALF_UP = 4; + public static final int ROUND_HALF_DOWN = 5; + public static final int ROUND_HALF_EVEN = 6; + public static final int ROUND_UNNECESSARY = 7; + } + + /** + * 设置精度 + * float/double的精度取值方式分为以下几种:
+ * java.math.BigDecimal.ROUND_UP
+ * java.math.BigDecimal.ROUND_DOWN
+ * java.math.BigDecimal.ROUND_CEILING
+ * java.math.BigDecimal.ROUND_FLOOR
+ * java.math.BigDecimal.ROUND_HALF_UP
+ * java.math.BigDecimal.ROUND_HALF_DOWN
+ * java.math.BigDecimal.ROUND_HALF_EVEN
+ * + * @param scale 精度位数(保留的小数位数) + * @param roundingMode 精度取值方式 + * @return 精度计算后的数据 + */ + public static double round(double value, int scale, int roundingMode) { + BigDecimal bd = new BigDecimal(value); + bd = bd.setScale(scale, roundingMode); + double d = bd.doubleValue(); + bd = null; + return d; + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/util/NetUtils.java b/AppFrame/src/main/java/com/arialyy/frame/util/NetUtils.java new file mode 100644 index 00000000..36c05eb8 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/util/NetUtils.java @@ -0,0 +1,146 @@ +package com.arialyy.frame.util; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.provider.Settings; +import android.telephony.TelephonyManager; +import android.text.TextUtils; + +/** + * 跟网络相关的工具类 + */ +public class NetUtils { + + /** + * 没有网络 + */ + public static final int NETWORK_TYPE_INVALID = 0; + /** + * wap网络 + */ + public static final int NETWORK_TYPE_WAP = 1; + /** + * 2G网络 + */ + public static final int NETWORK_TYPE_2G = 2; + /** + * 3G和3G以上网络,或统称为快速网络 + */ + public static final int NETWORK_TYPE_3G = 3; + /** + * wifi网络 + */ + public static final int NETWORK_TYPE_WIFI = 4; + + private NetUtils() { + /* cannot be instantiated */ + throw new UnsupportedOperationException("cannot be instantiated"); + } + + /** + * 判断网络是否连接 + */ + public static boolean isConnected(Context context) { + ConnectivityManager cm = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo ni = cm.getActiveNetworkInfo(); + return ni != null && ni.isConnectedOrConnecting(); + } + + /** + * 判断是否是wifi连接 + */ + public static boolean isWifi(Context context) { + return getNetWorkType(context) == NETWORK_TYPE_WIFI; + } + + /** + * 获取网络状态,wifi,wap,2g,3g. + * + * @param context 上下文 + * @return int 网络状态 {@link #NETWORK_TYPE_2G},{@link #NETWORK_TYPE_3G}, + * {@link #NETWORK_TYPE_INVALID},{@link #NETWORK_TYPE_WAP},{@link #NETWORK_TYPE_WIFI} + */ + public static int getNetWorkType(Context context) { + int netWorkType = -1; + ConnectivityManager manager = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo networkInfo = manager.getActiveNetworkInfo(); + if (networkInfo != null && networkInfo.isConnected()) { + String type = networkInfo.getTypeName(); + if (type.equalsIgnoreCase("WIFI")) { + netWorkType = NETWORK_TYPE_WIFI; + } else if (type.equalsIgnoreCase("MOBILE")) { + String proxyHost = android.net.Proxy.getDefaultHost(); + netWorkType = TextUtils.isEmpty(proxyHost) ? (isFastMobileNetwork(context) ? NETWORK_TYPE_3G + : NETWORK_TYPE_2G) : NETWORK_TYPE_WAP; + } + } else { + netWorkType = NETWORK_TYPE_INVALID; + } + return netWorkType; + } + + private static boolean isFastMobileNetwork(Context context) { + TelephonyManager telephonyManager = + (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + switch (telephonyManager.getNetworkType()) { + case TelephonyManager.NETWORK_TYPE_1xRTT: + return false; // ~ 50-100 kbps + case TelephonyManager.NETWORK_TYPE_CDMA: + return false; // ~ 14-64 kbps + case TelephonyManager.NETWORK_TYPE_EDGE: + return false; // ~ 50-100 kbps + case TelephonyManager.NETWORK_TYPE_EVDO_0: + return true; // ~ 400-1000 kbps + case TelephonyManager.NETWORK_TYPE_EVDO_A: + return true; // ~ 600-1400 kbps + case TelephonyManager.NETWORK_TYPE_GPRS: + return false; // ~ 100 kbps + case TelephonyManager.NETWORK_TYPE_HSDPA: + return true; // ~ 2-14 Mbps + case TelephonyManager.NETWORK_TYPE_HSPA: + return true; // ~ 700-1700 kbps + case TelephonyManager.NETWORK_TYPE_HSUPA: + return true; // ~ 1-23 Mbps + case TelephonyManager.NETWORK_TYPE_UMTS: + return true; // ~ 400-7000 kbps + case TelephonyManager.NETWORK_TYPE_EHRPD: + return true; // ~ 1-2 Mbps + case TelephonyManager.NETWORK_TYPE_EVDO_B: + return true; // ~ 5 Mbps + case TelephonyManager.NETWORK_TYPE_HSPAP: + return true; // ~ 10-20 Mbps + case TelephonyManager.NETWORK_TYPE_IDEN: + return false; // ~25 kbps + case TelephonyManager.NETWORK_TYPE_LTE: + return true; // ~ 10+ Mbps + case TelephonyManager.NETWORK_TYPE_UNKNOWN: + return false; + default: + return false; + } + } + + /** + * 打开网络设置界面 + */ + public static void openSetting(Context context) { + if (AndroidVersionUtil.hasHoneycomb()) { + Intent intent = new Intent(Settings.ACTION_WIFI_SETTINGS); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } else { + Intent intent = new Intent("/"); + ComponentName cm = + new ComponentName("com.android.settings", "com.android.settings.WirelessSettings"); + intent.setComponent(cm); + intent.setAction("android.intent.action.VIEW"); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } + } +} \ No newline at end of file diff --git a/AppFrame/src/main/java/com/arialyy/frame/util/NinePatchBitmapFactory.java b/AppFrame/src/main/java/com/arialyy/frame/util/NinePatchBitmapFactory.java new file mode 100644 index 00000000..aca9b3ec --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/util/NinePatchBitmapFactory.java @@ -0,0 +1,224 @@ +package com.arialyy.frame.util; + +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Color; +import android.graphics.Rect; +import android.graphics.drawable.NinePatchDrawable; +import android.util.DisplayMetrics; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.List; + +/** + * 加载。9图片 + * Time: 2:37 PM + */ +public class NinePatchBitmapFactory { + + private static final int NO_COLOR = 0x00000001; + + private static final int TRANSPARENT_COLOR = 0x00000000; + + public static NinePatchDrawable createNinePatchDrawable(Resources res, Bitmap bitmap) { + RangeLists rangeLists = checkBitmap(bitmap); + Bitmap trimedBitmap = trimBitmap(bitmap); + return createNinePatchWithCapInsets(res, trimedBitmap, rangeLists.rangeListX, + rangeLists.rangeListY, null); + } + + public static NinePatchDrawable createNinePatchWithCapInsets(Resources res, Bitmap bitmap, + List rangeListX, List rangeListY, String srcName) { + ByteBuffer buffer = getByteBuffer(rangeListX, rangeListY); + return new NinePatchDrawable(res, bitmap, buffer.array(), new Rect(), srcName); + } + + private static ByteBuffer getByteBuffer(List rangeListX, List rangeListY) { + ByteBuffer buffer = ByteBuffer.allocate( + 4 + 4 * 7 + 4 * 2 * rangeListX.size() + 4 * 2 * rangeListY.size() + 4 * 9) + .order(ByteOrder.nativeOrder()); + buffer.put((byte) 0x01); // was serialised + buffer.put((byte) (rangeListX.size() * 2)); // x div + buffer.put((byte) (rangeListY.size() * 2)); // y div + buffer.put((byte) 0x09); // color + + // skip + buffer.putInt(0); + buffer.putInt(0); + + // padding + buffer.putInt(0); + buffer.putInt(0); + buffer.putInt(0); + buffer.putInt(0); + + // skip 4 bytes + buffer.putInt(0); + + for (Range range : rangeListX) { + buffer.putInt(range.start); + buffer.putInt(range.end); + } + for (Range range : rangeListY) { + buffer.putInt(range.start); + buffer.putInt(range.end); + } + buffer.putInt(NO_COLOR); + buffer.putInt(NO_COLOR); + buffer.putInt(NO_COLOR); + buffer.putInt(NO_COLOR); + buffer.putInt(NO_COLOR); + buffer.putInt(NO_COLOR); + buffer.putInt(NO_COLOR); + buffer.putInt(NO_COLOR); + buffer.putInt(NO_COLOR); + + return buffer; + } + + public static class RangeLists { + public List rangeListX; + public List rangeListY; + } + + public static class Range { + public int start; + public int end; + } + + public static RangeLists checkBitmap(Bitmap bitmap) { + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + + List rangeListX = new ArrayList(); + + int pos = -1; + for (int i = 1; i < width - 1; i++) { + int color = bitmap.getPixel(i, 0); + int alpha = Color.alpha(color); + int red = Color.red(color); + int green = Color.green(color); + int blue = Color.blue(color); + // System.out.println( String.valueOf(alpha) + "," + String.valueOf(red) + "," + String.valueOf(green) + "," + String.valueOf(blue) ); + if (alpha == 255 && red == 0 && green == 0 && blue == 0) { + if (pos == -1) { + pos = i - 1; + } + } else { + if (pos != -1) { + Range range = new Range(); + range.start = pos; + range.end = i - 1; + rangeListX.add(range); + pos = -1; + } + } + } + if (pos != -1) { + Range range = new Range(); + range.start = pos; + range.end = width - 2; + rangeListX.add(range); + } + for (Range range : rangeListX) { + System.out.println("(" + range.start + "," + range.end + ")"); + } + + List rangeListY = new ArrayList(); + + pos = -1; + for (int i = 1; i < height - 1; i++) { + int color = bitmap.getPixel(0, i); + int alpha = Color.alpha(color); + int red = Color.red(color); + int green = Color.green(color); + int blue = Color.blue(color); + if (alpha == 255 && red == 0 && green == 0 && blue == 0) { + if (pos == -1) { + pos = i - 1; + } + } else { + if (pos != -1) { + Range range = new Range(); + range.start = pos; + range.end = i - 1; + rangeListY.add(range); + pos = -1; + } + } + } + if (pos != -1) { + Range range = new Range(); + range.start = pos; + range.end = height - 2; + rangeListY.add(range); + } + for (Range range : rangeListY) { + System.out.println("(" + range.start + "," + range.end + ")"); + } + + RangeLists rangeLists = new RangeLists(); + rangeLists.rangeListX = rangeListX; + rangeLists.rangeListY = rangeListY; + + return rangeLists; + } + + public static Bitmap trimBitmap(Bitmap bitmap) { + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + + Bitmap result = Bitmap.createBitmap(bitmap, 1, 1, width - 2, height - 2); + + return result; + } + + public static Bitmap loadBitmap(File file) { + BufferedInputStream bis = null; + try { + bis = new BufferedInputStream(new FileInputStream(file)); + return BitmapFactory.decodeStream(bis); + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + bis.close(); + } catch (Exception e) { + + } + } + return null; + } + + public static String getDensityPostfix(Resources res) { + String result = null; + switch (res.getDisplayMetrics().densityDpi) { + case DisplayMetrics.DENSITY_LOW: + result = "ldpi"; + break; + case DisplayMetrics.DENSITY_MEDIUM: + result = "mdpi"; + break; + case DisplayMetrics.DENSITY_HIGH: + result = "hdpi"; + break; + case DisplayMetrics.DENSITY_XHIGH: + result = "xhdpi"; + break; + case DisplayMetrics.DENSITY_XXHIGH: + result = "xxhdpi"; + break; + case DisplayMetrics.DENSITY_XXXHIGH: + result = "xxxhdpi"; + break; + } + return result; + } +} \ No newline at end of file diff --git a/AppFrame/src/main/java/com/arialyy/frame/util/ObjUtil.java b/AppFrame/src/main/java/com/arialyy/frame/util/ObjUtil.java new file mode 100644 index 00000000..8d3cbb81 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/util/ObjUtil.java @@ -0,0 +1,49 @@ +package com.arialyy.frame.util; + +import java.util.Map; +import java.util.Set; + +/** + * Created by lyy on 2016/7/6. + */ +public class ObjUtil { + + /** + * 打印Map + */ + public static String getMapString(Map map) { + Set set = map.keySet(); + if (set.size() < 1) { + return "[]"; + } + StringBuilder strBuilder = new StringBuilder(); + Object[] array = set.toArray(); + strBuilder.append("[").append(array[0]).append("=").append(map.get(array[0])); + for (int i = 1; i < array.length; ++i) { + strBuilder.append(", "); + strBuilder.append(array[i]).append("="); + strBuilder.append(map.get(array[i])); + } + strBuilder.append("]"); + return strBuilder.toString(); + } + + /** + * 通过Value 获取key + */ + public static T getKeyByValue(Map map, E value) { + for (Map.Entry entry : map.entrySet()) { + if (equals(value, entry.getValue())) { + return entry.getKey(); + } + } + return null; + } + + /** + * 比较两个对象是否相等 + */ + public static boolean equals(Object a, Object b) { + return (a == null) ? (b == null) : a.equals(b); + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/util/ReflectionUtil.java b/AppFrame/src/main/java/com/arialyy/frame/util/ReflectionUtil.java new file mode 100644 index 00000000..c645c272 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/util/ReflectionUtil.java @@ -0,0 +1,150 @@ +package com.arialyy.frame.util; + +import android.content.Context; +import android.content.res.AssetManager; +import android.content.res.XmlResourceParser; +import android.text.TextUtils; + +import com.arialyy.frame.util.show.FL; +import com.arialyy.frame.util.show.L; + +import org.xmlpull.v1.XmlPullParser; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import dalvik.system.PathClassLoader; + +/** + * Created by lyy on 2015/7/30. + * 反射工具类 + */ +public class ReflectionUtil { + private static final String TAG = "ReflectionUtil"; + private static final String ID = "$id"; + private static final String LAYOUT = "$layout"; + private static final String STYLE = "$style"; + private static final String STRING = "$string"; + private static final String DRAWABLE = "$drawable"; + private static final String ARRAY = "$array"; + private static final String COLOR = "color"; + private static final String ANIM = "anim"; + + /** + * 从SDcard读取layout + */ + public static XmlPullParser getLayoutXmlPullParser(Context context, String filePath, + String fileName) { + XmlResourceParser paser = null; + AssetManager asset = context.getResources().getAssets(); + try { + Method method = asset.getClass().getMethod("addAssetPath", String.class); + int cookie = (Integer) method.invoke(asset, filePath); + if (cookie == 0) { + FL.e(TAG, "加载路径失败"); + } + paser = asset.openXmlResourceParser(cookie, fileName + ".xml"); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + return paser; + } + + /** + * 获取类里面的所在字段 + */ + public static Field[] getFields(Class clazz) { + Field[] fields = null; + fields = clazz.getDeclaredFields(); + if (fields == null || fields.length == 0) { + Class superClazz = clazz.getSuperclass(); + if (superClazz != null) { + fields = getFields(superClazz); + } + } + return fields; + } + + /** + * 获取类里面的指定对象,如果该类没有则从父类查询 + */ + public static Field getField(Class clazz, String name) { + Field field = null; + try { + field = clazz.getDeclaredField(name); + } catch (NoSuchFieldException e) { + try { + field = clazz.getField(name); + } catch (NoSuchFieldException e1) { + if (clazz.getSuperclass() == null) { + return field; + } else { + field = getField(clazz.getSuperclass(), name); + } + } + } + if (field != null) { + field.setAccessible(true); + } + return field; + } + + /** + * 利用递归找一个类的指定方法,如果找不到,去父亲里面找直到最上层Object对象为止。 + * + * @param clazz 目标类 + * @param methodName 方法名 + * @param params 方法参数类型数组 + * @return 方法对象 + */ + public static Method getMethod(Class clazz, String methodName, final Class... params) { + Method method = null; + try { + method = clazz.getDeclaredMethod(methodName, params); + } catch (NoSuchMethodException e) { + try { + method = clazz.getMethod(methodName, params); + } catch (NoSuchMethodException ex) { + if (clazz.getSuperclass() == null) { + L.e(TAG, "无法找到" + methodName + "方法"); + FL.e(TAG, FL.getExceptionString(e)); + return method; + } else { + method = getMethod(clazz.getSuperclass(), methodName, params); + } + } + } + if (method != null) { + method.setAccessible(true); + } + return method; + } + + /** + * 加载指定的反射类 + */ + public static Class loadClass(Context context, String ClassName) { + String packageName = AndroidUtils.getPackageName(context); + String sourcePath = AndroidUtils.getSourcePath(context, packageName); + if (!TextUtils.isEmpty(sourcePath)) { + PathClassLoader cl = + new PathClassLoader(sourcePath, "/data/app/", ClassLoader.getSystemClassLoader()); + try { + return cl.loadClass(ClassName); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + } else { + FL.e(TAG, "没有【" + sourcePath + "】目录"); + } + return null; + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/util/RegularExpression.java b/AppFrame/src/main/java/com/arialyy/frame/util/RegularExpression.java new file mode 100644 index 00000000..fe0a9f57 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/util/RegularExpression.java @@ -0,0 +1,95 @@ +package com.arialyy.frame.util; + +/** + * Created by AriaLyy on 2015/1/10. + * 正则表达式规则 + */ +public class RegularExpression { + + /** + * APK包 + */ + public static String APK = "^(.*)\\.(apk)$"; + /** + * 视频 + */ + public static String VIDEO = "^(.*)\\.(mpeg-4|h.264|h.265|rmvb|xvid|vp6|h.263|mpeg-1|mpeg-2|avi|" + + "mov|mkv|flv|3gp|3g2|asf|wmv|mp4|m4v|tp|ts|mtp|m2t)$"; + /** + * 音频 + */ + public static String MUSIC = "^(.*)\\.(aac|vorbis|flac|mp3|mp2|wma)$"; + + /** + * 文本 + */ + public static String TEXT = "^(.*)\\.(txt|xml|html)$"; + /** + * 压缩包 + */ + public static String ZIP = "^(.*)\\.(zip|rar|7z)$"; + /** + * DOC + */ + public static String DOC = "^(.*)\\.(doc|docx)"; + /** + * PPT + */ + public static String PPT = "^(.*)\\.(ppt|pptx)"; + /** + * xls + */ + public static String XLS = "^(.*)\\.(xls|xlsx)"; + /** + * vcf + */ + public static String VCF = "^(.*)\\.(vcf)"; + /** + * pdf + */ + public static String PDF = "^(.*)\\.(pdf)"; + /** + * SQL + */ + public static String SQL = "^(.*)\\.(sql|db)"; + + /** + * 图片 + */ + public static String IMG = "^(.*)\\.(jpg|bmp|png|gif|jpeg|psd)"; + + /** + * 中文 + */ + public static String CHINESE = "[\\u4e00-\\u9fa5]"; + + /** + * 首尾空白字符 + */ + public static String START_OR_END_NONE = "^\\s*|\\s*"; + + /** + * 空白行 + */ + public static String NOTE_ITEM = "\\n\\s*\\r"; + + /** + * 邮箱 + */ + public static String EMAIL = "\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*"; + + /** + * url + */ + public static String URL = "[a-zA-z]+://[^\\s]*"; + + /** + * QQ + */ + public static String QQ = "[1-9][0-9]{4,}"; + + /** + * IP + */ + public static String IP = "\\d+\\.\\d+\\.\\d+\\.\\d+"; +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/util/ScreenUtil.java b/AppFrame/src/main/java/com/arialyy/frame/util/ScreenUtil.java new file mode 100644 index 00000000..4a448667 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/util/ScreenUtil.java @@ -0,0 +1,208 @@ +package com.arialyy.frame.util; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.ContentResolver; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.ColorMatrix; +import android.graphics.ColorMatrixColorFilter; +import android.graphics.Paint; +import android.graphics.Rect; +import android.net.Uri; +import android.os.Build; +import android.provider.Settings; +import android.util.DisplayMetrics; +import android.view.View; +import android.view.WindowManager; + +/** + * Created by AriaLyy on 2015/1/22. + * 屏幕工具 + */ +public class ScreenUtil { + + private volatile static ScreenUtil mUtil = null; + private static final Object LOCK = new Object(); + + private ScreenUtil() { + } + + public static ScreenUtil getInstance() { + if (mUtil == null) { + synchronized (LOCK) { + if (mUtil == null) { + mUtil = new ScreenUtil(); + } + } + } + return mUtil; + } + + /** + * 设置灰度 + * + * @param greyScale true:灰度 + */ + @TargetApi(Build.VERSION_CODES.HONEYCOMB) public void setGreyScale(View v, boolean greyScale) { + if (greyScale) { + // Create a paint object with 0 saturation (black and white) + ColorMatrix cm = new ColorMatrix(); + cm.setSaturation(0); + Paint greyScalePaint = new Paint(); + greyScalePaint.setColorFilter(new ColorMatrixColorFilter(cm)); + // Create a hardware layer with the greyScale paint + v.setLayerType(View.LAYER_TYPE_HARDWARE, greyScalePaint); + } else { + // Remove the hardware layer + v.setLayerType(View.LAYER_TYPE_NONE, null); + } + } + + /** + * 获得屏幕高度 + */ + public int getScreenWidth(Context context) { + WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + DisplayMetrics outMetrics = new DisplayMetrics(); + wm.getDefaultDisplay().getMetrics(outMetrics); + return outMetrics.widthPixels; + } + + /** + * 获得屏幕宽度 + */ + public int getScreenHeight(Context context) { + WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + DisplayMetrics outMetrics = new DisplayMetrics(); + wm.getDefaultDisplay().getMetrics(outMetrics); + return outMetrics.heightPixels; + } + + /** + * 获得状态栏的高度 + */ + public int getStatusHeight(Context context) { + + int statusHeight = -1; + try { + Class clazz = Class.forName("com.android.internal.R$dimen"); + Object object = clazz.newInstance(); + int height = Integer.parseInt(clazz.getField("status_bar_height").get(object).toString()); + statusHeight = context.getResources().getDimensionPixelSize(height); + } catch (Exception e) { + e.printStackTrace(); + } + return statusHeight; + } + + /** + * 获取当前屏幕截图,包含状态栏 + * + * @return error return null + */ + public Bitmap snapShotWithStatusBar(Activity activity) { + final View view = activity.getWindow().getDecorView(); + view.setDrawingCacheEnabled(true); + view.buildDrawingCache(); + int width = getScreenWidth(activity); + int height = getScreenHeight(activity); + // view.layout(0, 0, width, height); + + Bitmap bmp = view.getDrawingCache(); + if (bmp == null) { + return null; + } + Bitmap bp = null; + bp = Bitmap.createBitmap(bmp, 0, 0, width, height); + view.destroyDrawingCache(); + return bp; + } + + /** + * 获取当前屏幕截图,不包含状态栏 + */ + public Bitmap snapShotWithoutStatusBar(Activity activity) { + View view = activity.getWindow().getDecorView(); + view.setDrawingCacheEnabled(true); + view.buildDrawingCache(); + Bitmap bmp = view.getDrawingCache(); + Rect frame = new Rect(); + activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame); + int statusBarHeight = frame.top; + + int width = getScreenWidth(activity); + int height = getScreenHeight(activity); + Bitmap bp = null; + bp = Bitmap.createBitmap(bmp, 0, statusBarHeight, width, height - statusBarHeight); + view.destroyDrawingCache(); + return bp; + } + + /** + * 判断是否开启了自动亮度调节 + */ + public boolean isAutoBrightness(ContentResolver aContentResolver) { + boolean automicBrightness = false; + try { + automicBrightness = + Settings.System.getInt(aContentResolver, Settings.System.SCREEN_BRIGHTNESS_MODE) + == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC; + } catch (Settings.SettingNotFoundException e) { + e.printStackTrace(); + } + return automicBrightness; + } + + /** + * 获取屏幕的亮度 + */ + public float getScreenBrightness(Activity activity) { + int nowBrightnessValue = 0; + ContentResolver resolver = activity.getContentResolver(); + try { + nowBrightnessValue = Settings.System.getInt(resolver, Settings.System.SCREEN_BRIGHTNESS); + } catch (Exception e) { + e.printStackTrace(); + } + return nowBrightnessValue; + } + + /** + * 设置亮度(手动设置亮度,需要关闭自动设置亮度的开关) + * + * @param brightness 0-1 + */ + public void setBrightness(Activity activity, float brightness) { + stopAutoBrightness(activity); + WindowManager.LayoutParams lp = activity.getWindow().getAttributes(); + lp.screenBrightness = brightness * (1f / 255f); + activity.getWindow().setAttributes(lp); + } + + /** + * 停止自动亮度调节 + */ + public void stopAutoBrightness(Activity activity) { + Settings.System.putInt(activity.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE, + Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL); + } + + /** + * 开启亮度自动调节 + */ + public void startAutoBrightness(Activity activity) { + Settings.System.putInt(activity.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE, + Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC); + } + + /** + * 保存亮度设置状态 + */ + public void saveBrightness(ContentResolver resolver, int brightness) { + Uri uri = Settings.System.getUriFor("screen_brightness"); + Settings.System.putInt(resolver, "screen_brightness", brightness); + // resolver.registerContentObserver(uri, true, myContentObserver); + resolver.notifyChange(uri, null); + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/util/SharePreUtil.java b/AppFrame/src/main/java/com/arialyy/frame/util/SharePreUtil.java new file mode 100644 index 00000000..795749f8 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/util/SharePreUtil.java @@ -0,0 +1,145 @@ +package com.arialyy.frame.util; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.text.TextUtils; + +import com.google.gson.Gson; + +/** + * 配置文件工具类 + * + * @author Administrator + */ +public class SharePreUtil { + + /** + * 删除键值对 + */ + public static void removeKey(String preName, Context context, String key) { + SharedPreferences pre = context.getSharedPreferences(preName, Context.MODE_PRIVATE); + Editor editor = pre.edit(); + editor.remove(key); + editor.commit(); + } + + /** + * 从配置文件读取字符串 + * + * @param preName 配置文件名 + * @param key 字符串键值 + * @return 键值对应的字符串, 默认返回"" + */ + public static String getString(String preName, Context context, String key) { + SharedPreferences pre = context.getSharedPreferences(preName, Context.MODE_PRIVATE); + return pre.getString(key, ""); + } + + /** + * 从配置文件读取int数据 + * + * @param preName 配置文件名 + * @param key int的键值 + * @return 键值对应的int, 默认返回-1 + */ + public static int getInt(String preName, Context context, String key) { + SharedPreferences pre = context.getSharedPreferences(preName, Context.MODE_PRIVATE); + return pre.getInt(key, -1); + } + + /** + * 从配置文件读取Boolean值 + * + * @return 如果没有,默认返回false + */ + public static Boolean getBoolean(String preName, Context context, String key) { + SharedPreferences pre = context.getSharedPreferences(preName, Context.MODE_PRIVATE); + return pre.getBoolean(key, false); + } + + /** + * 从配置文件获取float数据 + * + * @return 默认返回0.0f + */ + public static float getFloat(String preName, Context context, String key) { + SharedPreferences pre = context.getSharedPreferences(preName, Context.MODE_PRIVATE); + return pre.getFloat(key, 0.0f); + } + + /** + * 从配置文件获取对象 + */ + public static T getObject(String preName, Context context, String key, Class clazz) { + SharedPreferences pre = context.getSharedPreferences(preName, Context.MODE_PRIVATE); + String str = pre.getString(key, ""); + return TextUtils.isEmpty(str) ? null : new Gson().fromJson(str, clazz); + } + + /** + * 存储字符串到配置文件 + * + * @param preName 配置文件名 + * @param key 存储的键值 + * @param value 需要存储的字符串 + * @return 成功标志 + */ + public static Boolean putString(String preName, Context context, String key, String value) { + SharedPreferences pre = context.getSharedPreferences(preName, Context.MODE_PRIVATE); + Editor editor = pre.edit(); + editor.putString(key, value); + return editor.commit(); + } + + /** + * 保存Float数据到配置文件 + */ + public static Boolean putFloat(String preName, Context context, String key, float value) { + SharedPreferences pre = context.getSharedPreferences(preName, Context.MODE_PRIVATE); + Editor editor = pre.edit(); + editor.putFloat(key, value); + return editor.commit(); + } + + /** + * 存储数字到配置文件 + * + * @param preName 配置文件名 + * @param key 存储的键值 + * @param value 需要存储的数字 + * @return 成功标志 + */ + public static Boolean putInt(String preName, Context context, String key, int value) { + SharedPreferences pre = context.getSharedPreferences(preName, Context.MODE_PRIVATE); + Editor editor = pre.edit(); + editor.putInt(key, value); + return editor.commit(); + } + + /** + * 存储Boolean值到配置文件 + * + * @param preName 配置文件名 + * @param key 键值 + * @param value 需要存储的boolean值 + */ + public static Boolean putBoolean(String preName, Context context, String key, Boolean value) { + SharedPreferences pre = context.getSharedPreferences(preName, Context.MODE_PRIVATE); + Editor editor = pre.edit(); + editor.putBoolean(key, value); + return editor.commit(); + } + + /** + * 存放对象 + */ + public static Boolean putObject(String preName, Context context, String key, Class clazz, + Object obj) { + String str = new Gson().toJson(obj, clazz); + SharedPreferences pre = context.getSharedPreferences(preName, Context.MODE_PRIVATE); + Editor editor = pre.edit(); + editor.putString(key, str); + return editor.commit(); + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/util/ShellUtils.java b/AppFrame/src/main/java/com/arialyy/frame/util/ShellUtils.java new file mode 100644 index 00000000..ec150e0b --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/util/ShellUtils.java @@ -0,0 +1,212 @@ +package com.arialyy.frame.util; + +import java.io.BufferedReader; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.List; + +/** + * ShellUtils + *

    + *
  • {@link ShellUtils#checkRootPermission()}
  • + *
+ *
    + *
  • {@link ShellUtils#execCommand(String, boolean)}
  • + *
  • {@link ShellUtils#execCommand(String, boolean, boolean)}
  • + *
  • {@link ShellUtils#execCommand(List, boolean)}
  • + *
  • {@link ShellUtils#execCommand(List, boolean, boolean)}
  • + *
  • {@link ShellUtils#execCommand(String[], boolean)}
  • + *
  • {@link ShellUtils#execCommand(String[], boolean, boolean)}
  • + *
+ * + * @author Trinea 2013-5-16 + */ +public class ShellUtils { + public static final String COMMAND_SU = "su"; + public static final String COMMAND_SH = "sh"; + public static final String COMMAND_EXIT = "exit\n"; + public static final String COMMAND_LINE_END = "\n"; + + /** + * check whether has root permission + */ + public static boolean checkRootPermission() { + return execCommand("echo root", true, false).result == 0; + } + + /** + * execute shell command, default return result msg + * + * @param command command + * @param isRoot whether need to run with root + * @see ShellUtils#execCommand(String[], boolean, boolean) + */ + public static CommandResult execCommand(String command, boolean isRoot) { + return execCommand(new String[] { command }, isRoot, true); + } + + /** + * execute shell commands, default return result msg + * + * @param commands command list + * @param isRoot whether need to run with root + * @see ShellUtils#execCommand(String[], boolean, boolean) + */ + public static CommandResult execCommand(List commands, boolean isRoot) { + return execCommand(commands == null ? null : commands.toArray(new String[] {}), isRoot, true); + } + + /** + * execute shell commands, default return result msg + * + * @param commands command array + * @param isRoot whether need to run with root + * @see ShellUtils#execCommand(String[], boolean, boolean) + */ + public static CommandResult execCommand(String[] commands, boolean isRoot) { + return execCommand(commands, isRoot, true); + } + + /** + * execute shell command + * + * @param command command + * @param isRoot whether need to run with root + * @param isNeedResultMsg whether need result msg + * @see ShellUtils#execCommand(String[], boolean, boolean) + */ + public static CommandResult execCommand(String command, boolean isRoot, boolean isNeedResultMsg) { + return execCommand(new String[] { command }, isRoot, isNeedResultMsg); + } + + /** + * execute shell commands + * + * @param commands command list + * @param isRoot whether need to run with root + * @param isNeedResultMsg whether need result msg + * @see ShellUtils#execCommand(String[], boolean, boolean) + */ + public static CommandResult execCommand(List commands, boolean isRoot, + boolean isNeedResultMsg) { + return execCommand(commands == null ? null : commands.toArray(new String[] {}), isRoot, + isNeedResultMsg); + } + + /** + * execute shell commands + * + * @param commands command array + * @param isRoot whether need to run with root + * @param isNeedResultMsg whether need result msg + * @return
    + *
  • if isNeedResultMsg is false, {@link CommandResult#successMsg} is null and + * {@link CommandResult#errorMsg} is null.
  • + *
  • if {@link CommandResult#result} is -1, there maybe some excepiton.
  • + *
+ */ + public static CommandResult execCommand(String[] commands, boolean isRoot, + boolean isNeedResultMsg) { + int result = -1; + if (commands == null || commands.length == 0) { + return new CommandResult(result, null, null); + } + Process process = null; + BufferedReader successResult = null; + BufferedReader errorResult = null; + StringBuilder successMsg = null; + StringBuilder errorMsg = null; + DataOutputStream os = null; + try { + process = Runtime.getRuntime().exec(isRoot ? COMMAND_SU : COMMAND_SH); + os = new DataOutputStream(process.getOutputStream()); + for (String command : commands) { + if (command == null) { + continue; + } + // donnot use os.writeBytes(commmand), avoid chinese charset error + os.write(command.getBytes()); + os.writeBytes(COMMAND_LINE_END); + os.flush(); + } + os.writeBytes(COMMAND_EXIT); + os.flush(); + result = process.waitFor(); + // get command result + if (isNeedResultMsg) { + successMsg = new StringBuilder(); + errorMsg = new StringBuilder(); + successResult = new BufferedReader(new InputStreamReader(process.getInputStream())); + errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream())); + String s; + while ((s = successResult.readLine()) != null) { + successMsg.append(s); + } + while ((s = errorResult.readLine()) != null) { + errorMsg.append(s); + } + } + } catch (IOException e) { + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + if (os != null) { + os.close(); + } + if (successResult != null) { + successResult.close(); + } + if (errorResult != null) { + errorResult.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + if (process != null) { + process.destroy(); + } + } + return new CommandResult(result, successMsg == null ? null : successMsg.toString(), + errorMsg == null ? null : errorMsg.toString()); + } + + /** + * result of command + *
    + *
  • {@link CommandResult#result} means result of command, 0 means normal, else means error, + * same to excute in + * linux shell
  • + *
  • {@link CommandResult#successMsg} means success message of command result
  • + *
  • {@link CommandResult#errorMsg} means error message of command result
  • + *
+ * + * @author Trinea 2013-5-16 + */ + public static class CommandResult { + /** + * result of command + **/ + public int result; + /** + * success message of command result + **/ + public String successMsg; + /** + * error message of command result + **/ + public String errorMsg; + + public CommandResult(int result) { + this.result = result; + } + + public CommandResult(int result, String successMsg, String errorMsg) { + this.result = result; + this.successMsg = successMsg; + this.errorMsg = errorMsg; + } + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/util/StreamUtil.java b/AppFrame/src/main/java/com/arialyy/frame/util/StreamUtil.java new file mode 100644 index 00000000..19ef0059 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/util/StreamUtil.java @@ -0,0 +1,23 @@ +package com.arialyy.frame.util; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; + +/** + * Created by AriaLyy on 2015/4/10. + */ +public class StreamUtil { + /** + * 得到图片字节流 数组大小 + */ + public static byte[] readStream(InputStream inStream) throws Exception { + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int len = 0; + while ((len = inStream.read(buffer)) != -1) { + outStream.write(buffer, 0, len); + } + outStream.close(); + return outStream.toByteArray(); + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/util/StringUtil.java b/AppFrame/src/main/java/com/arialyy/frame/util/StringUtil.java new file mode 100644 index 00000000..0a31f6ec --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/util/StringUtil.java @@ -0,0 +1,328 @@ +package com.arialyy.frame.util; + +import android.app.Activity; +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageManager; +import android.text.Layout; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.TextPaint; +import android.text.style.ForegroundColorSpan; +import android.text.style.URLSpan; +import android.text.style.UnderlineSpan; +import android.widget.TextView; + +import java.math.BigDecimal; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 字符串工具类 + * + * @author lyy + */ +public class StringUtil { + public static final int APPLICATION = 0; + public static final int BROADCAST = 1; + public static final int SERVICE = 2; + public static final int ACTIVITY = 3; + + /** + * 获取字体长度 + */ + public static int getTextLen(TextView textView) { + TextPaint paint = textView.getPaint(); + return (int) Layout.getDesiredWidth(textView.getText().toString(), 0, + textView.getText().length(), paint); + } + + /** + * 给某段支付设置下划线 + */ + public static SpannableString underLineHight(String str, String underLineStr) { + if (!str.contains(underLineStr)) { + return null; + } + // 创建一个 SpannableString对象 + SpannableString sp = new SpannableString(str); + int index = str.indexOf(underLineStr); + //设置背景颜色, StrikethroughSpan()是设置中划线 + sp.setSpan(new UnderlineSpan(), index, index + underLineStr.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + return sp; + } + + /** + * 高亮所有关键字 + * + * @param str 这个字符串 + * @param key 关键字 + */ + public static SpannableString highlightKeyword(String str, String key, int highlightColor) { + if (!str.contains(key)) { + return null; + } + SpannableString sp = new SpannableString(str); + key = Pattern.quote(key); + Pattern p = Pattern.compile(key); + Matcher m = p.matcher(str); + + while (m.find()) { //通过正则查找,逐个高亮 + int start = m.start(); + int end = m.end(); + sp.setSpan(new ForegroundColorSpan(highlightColor), start, end, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + return sp; + } + + /** + * 创建一个含有超链接的字符串 + * + * @param text 整段字符串 + * @param clickText 含有超链接的字符 + * @param url 超链接 + */ + public static SpannableString createLinkText(String text, String clickText, String url) { + if (!text.contains(clickText)) { + return null; + } + SpannableString sp = new SpannableString(text); + int index = text.indexOf(clickText); + // 设置超链接 + sp.setSpan(new URLSpan(url), index, index + clickText.length(), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + return sp; + } + + /** + * 将缓存的key转换为hash码 + * + * @param key 缓存的key + * @return 转换后的key的值, 系统便是通过该key来读写缓存 + */ + public static String keyToHashKey(String key) { + String cacheKey; + try { + final MessageDigest mDigest = MessageDigest.getInstance("MD5"); + mDigest.update(key.getBytes()); + cacheKey = bytesToHexString(mDigest.digest()); + } catch (NoSuchAlgorithmException e) { + cacheKey = String.valueOf(key.hashCode()); + } + return cacheKey; + } + + /** + * 读取Activity节点的meta-data + */ + public static String getActivityMetaData(Activity activity, String key) { + try { + return activity.getPackageManager() + .getActivityInfo(activity.getComponentName(), + PackageManager.GET_META_DATA).metaData.getString(key); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + return null; + } + + /** + * 读取Service节点的meta-data + * + * @param serviceClazz 服务的class + */ + public static String getServiceMetaData(Context context, Class serviceClazz, + String key) { + try { + return context.getPackageManager() + .getServiceInfo(new ComponentName(context, serviceClazz), + PackageManager.GET_META_DATA).metaData.getString(key); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + return null; + } + + /** + * 读取BroadCast节点meta-data数据 + * + * @param receiverClazz 广播接收器的class + */ + public static String getBroadCasetMetaData(Context context, + Class receiverClazz, String key) { + try { + return context.getPackageManager() + .getReceiverInfo(new ComponentName(context, receiverClazz), + PackageManager.GET_META_DATA).metaData.getString(key); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + return null; + } + + /** + * 读取Application节点的meta-data数据 + */ + public static String getApplicationMetaData(Context context, String key) { + try { + return context.getPackageManager() + .getApplicationInfo(context.getPackageName(), + PackageManager.GET_META_DATA).metaData.getString(key); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + return null; + } + + /** + * 用AES算法解密加密的密码 + * + * @param seed 密钥 + * @param password 加密的密码 + * @return 解密后的密码, 默认返回"" + */ + public static String decryptPassword(String seed, String password) { + try { + return AESEncryption.decryptString(seed, password); + } catch (Exception e) { + e.printStackTrace(); + } + return ""; + } + + /** + * 从XML读取字符串 + * + * @param id 字符串id + */ + public static String getStringFromXML(Context context, int id) { + return context.getResources().getString(id); + } + + /** + * 从xml读取字符串数组 + */ + public static String[] getStringArrayFromXML(Context context, int id) { + return context.getResources().getStringArray(id); + } + + /** + * 将字符串数组转换为list + */ + public static List stringArray2List(String[] strArray) { + List list = new ArrayList(); + Collections.addAll(list, strArray); + return list; + } + + /** + * 高亮整段字符串 + */ + public static SpannableStringBuilder highLightStr(String str, int color) { + SpannableStringBuilder style = new SpannableStringBuilder(str); + style.setSpan(new ForegroundColorSpan(color), 0, str.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + return style; + } + + /** + * 高亮代码片段 + * + * @param str 整段字符串 + * @param highLightStr 要高亮的代码段 + * @param color 高亮颜色 + */ + public static SpannableStringBuilder highLightStr(String str, String highLightStr, int color) { + int start = str.indexOf(highLightStr); + if (start == -1) { + return null; + } + SpannableStringBuilder style = new SpannableStringBuilder(str); + // new BackgroundColorSpan(Color.RED)背景高亮 + // ForegroundColorSpan(Color.RED) 字体高亮 + style.setSpan(new ForegroundColorSpan(color), start, start + highLightStr.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + return style; + } + + /** + * 字符串转hashcode + */ + public static int keyToHashCode(String str) { + int total = 0; + for (int i = 0; i < str.length(); i++) { + char ch = str.charAt(i); + if (ch == '-') ch = (char) 28; // does not contain the same last 5 bits as any letter + if (ch == '\'') ch = (char) 29; // nor this + total = (total * 33) + (ch & 0x1F); + } + return total; + } + + /** + * 字符串转dbouble + */ + public static double strToDouble(String str) { + // double d = Double.parseDouble(str); + + /* 以下代码处理精度问题 */ + BigDecimal bDeci = new BigDecimal(str); + // BigDecimal chushu =new BigDecimal(100000000); + // BigDecimal result =bDeci.divide(chushu,new + // MathContext(4));//MathConText(4)表示结果精确4位! + // return result.doubleValue() * 100000000; + return bDeci.doubleValue(); + } + + /** + * 将普通字符串转换为16位进制字符串 + */ + public static String bytesToHexString(byte[] src) { + StringBuilder stringBuilder = new StringBuilder("0x"); + if (src == null || src.length <= 0) { + return null; + } + char[] buffer = new char[2]; + for (byte aSrc : src) { + buffer[0] = Character.forDigit((aSrc >>> 4) & 0x0F, 16); + buffer[1] = Character.forDigit(aSrc & 0x0F, 16); + stringBuilder.append(buffer); + } + return stringBuilder.toString(); + } + + /** + * 把字符串长度加满16位 + * + * @return 16位长度的字符串 + */ + public static String addStrLenTo16(String str) { + //由于汉字的特殊性,长度要用byte来判断 + for (int i = str.getBytes().length; i < 16; i++) { + str += '\0'; + } + return str; + } + + /** + * 获取对象名 + * + * @param obj 对象 + * @return 对象名 + */ + public static String getClassName(Object obj) { + String arrays[] = obj.getClass().getName().split("\\."); + return arrays[arrays.length - 1]; + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/util/TextUtil.java b/AppFrame/src/main/java/com/arialyy/frame/util/TextUtil.java new file mode 100644 index 00000000..d5d1d781 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/util/TextUtil.java @@ -0,0 +1,245 @@ +package com.arialyy.frame.util; + +import android.widget.TextView; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 文本处理工具 + */ +public class TextUtil { + private TextUtil() { + + } + + /** + * 格式化字符 + */ + public static String decimalFormat(Number scr, String format) { + return new DecimalFormat(format).format(scr); + } + + /** + * 替换字符号(不带空格) + */ + public static String replaceSymbol(String str) { + String dest = ""; + if (str != null) { + // Pattern p = Pattern.compile("\\s*|\t|\r|\n"); + Pattern p = Pattern.compile("\t|\r|\n"); + Matcher m = p.matcher(str); + dest = m.replaceAll(""); + } + return dest; + } + + /** + * 首字母大写 + */ + public static String firstUpperCase(String str) { + if (android.text.TextUtils.isEmpty(str)) { + return null; + } + return str.replaceFirst(str.substring(0, 1), str.substring(0, 1).toUpperCase()); + } + + /** + * 从文本中读取数据,返回成List对象 + */ + public static List getTextToList(File file) { + FileInputStream fileInputStream = null; + InputStreamReader inputStreamReader = null; + BufferedReader bufferedReader = null; + List list = new ArrayList(); + try { + fileInputStream = new FileInputStream(file); + inputStreamReader = new InputStreamReader(fileInputStream); + bufferedReader = new BufferedReader(inputStreamReader); + String text; + while ((text = bufferedReader.readLine()) != null) { + list.add(text); + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + if (fileInputStream != null) { + fileInputStream.close(); + } + if (inputStreamReader != null) { + inputStreamReader.close(); + } + if (bufferedReader != null) { + bufferedReader.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + return list; + } + + /** + * 从文本中读取数据,返回成List对象 + */ + public static List getTextToList(InputStream inputStream) { + InputStreamReader inputStreamReader = null; + BufferedReader bufferedReader = null; + List list = new ArrayList(); + try { + inputStreamReader = new InputStreamReader(inputStream); + bufferedReader = new BufferedReader(inputStreamReader); + String text; + while ((text = bufferedReader.readLine()) != null) { + list.add(text); + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + if (inputStreamReader != null) { + inputStreamReader.close(); + } + if (bufferedReader != null) { + bufferedReader.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + return list; + } + + /** + * 判断一个数是否是整数 + */ + public static boolean isInteger(String numStr) { + try { + double parseDouble = Double.parseDouble(numStr); + return parseDouble % 1 == 0; + } catch (Exception exception) { + return false; + } + } + + /** + * 判断一个数是否是大于0的数 + */ + public static boolean isPositiveInteger(String numStr) { + if (isInteger(numStr)) { + double parseDouble = Double.parseDouble(numStr); + if (parseDouble > 0) { + return true; + } + } + return false; + } + + /** + * 格式化数据 + * + * @param value 需要转换的值 + * @param pattern 小数位数 + */ + public static String decimalFormat(double value, String pattern) { + DecimalFormat df = new DecimalFormat(pattern); + return df.format(value); + } + + /** + * 格式化数据 + * + * @param value 需要转换的值 + * @param pattern 小数位数 + */ + public static String decimalFormat(String value, String pattern) { + DecimalFormat df = new DecimalFormat(pattern); + return df.format(Double.parseDouble(value)); + } + + /** + * 格式化数据 + */ + public static String decimalFormat(double value, int scale) { + return decimalFormat(value, getScalePattern(scale)); + } + + /** + * 格式化数据 + */ + public static String decimalFormat(String value, int scale) { + return decimalFormat(value, getScalePattern(scale)); + } + + /** + * 返回小数位数的匹配 + */ + private static String getScalePattern(int scale) { + StringBuffer sb = new StringBuffer("#0."); + if (scale <= 0) { + sb = new StringBuffer("#"); + } + for (int i = 0; i < scale; ++i) { + sb.append("0"); + } + return sb.toString(); + } + + /** + * 返回TextView的值,没有或者null返回0 + */ + public static String getViewText(TextView view) { + if (view == null) { + return "0"; + } + boolean empty = android.text.TextUtils.isEmpty(view.getText().toString()); + return empty ? "0" : view.getText().toString(); + } + + /** + * 替换字符串 + */ + public static String replace(String source, int index, String before, String after) { + Matcher matcher = Pattern.compile(before).matcher(source); + for (int counter = 0; matcher.find(); counter++) { + if (counter == index) { + return source.substring(0, matcher.start()) + after + source.substring(matcher.end(), + source.length()); + } + } + return source; + } + + public static String JsonToString(String src) { + if ("{}".equals(src) || "[]".equals(src)) { + return ""; + } + return src; + } + + /** + * 去掉空格和特殊字符 + */ + public static String trimString(String str) { + if (android.text.TextUtils.isEmpty(str)) { + return ""; + } else { + return str.trim(); + } + } +} \ No newline at end of file diff --git a/AppFrame/src/main/java/com/arialyy/frame/util/show/FL.java b/AppFrame/src/main/java/com/arialyy/frame/util/show/FL.java new file mode 100644 index 00000000..1665cd57 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/util/show/FL.java @@ -0,0 +1,200 @@ +package com.arialyy.frame.util.show; + +import android.util.Log; +import com.arialyy.frame.util.CalendarUtils; +import java.io.File; +import java.io.FileWriter; +import java.io.PrintWriter; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Created by “AriaLyy@outlook.com” on 2015/4/1. + * 写入文件的log,由于使用到反射和文件流的操作,建议在需要的地方才去使用 + */ +public class FL { + public static boolean isDebug = true;// 是否需要打印bug,可以在application的onCreate函数里面初始化 + public static String PATH = "/GameBar2/log/AriaFrameLog__"; //log路径 + static String LINE_SEPARATOR = System.getProperty("line.separator"); //等价于"\n\r",唯一的作用是能装逼 + static int JSON_INDENT = 4; + + private static String printLine(String tag, boolean isTop) { + String top = + "╔══════════════════════════════════════════ JSON ═══════════════════════════════════════"; + String bottom = + "╚═══════════════════════════════════════════════════════════════════════════════════════"; + if (isTop) { + Log.d(tag, top); + return top; + } else { + Log.d(tag, bottom); + return bottom; + } + } + + /** + * 打印JSON + */ + public static void j(String tag, String jsonStr) { + if (isDebug) { + String message; + try { + if (jsonStr.startsWith("{")) { + JSONObject jsonObject = new JSONObject(jsonStr); + message = jsonObject.toString(JSON_INDENT); //这个是核心方法 + } else if (jsonStr.startsWith("[")) { + JSONArray jsonArray = new JSONArray(jsonStr); + message = jsonArray.toString(JSON_INDENT); + } else { + message = jsonStr; + } + } catch (JSONException e) { + message = jsonStr; + } + + message = LINE_SEPARATOR + message; + String temp = "\n" + printLine(tag, true) + "\n"; + String[] lines = message.split(LINE_SEPARATOR); + for (String line : lines) { + temp += "║ " + line + "\n"; + Log.d(tag, "║ " + line); + } + temp += printLine(tag, false); + writeLogToFile(tag, temp); + } + } + + // 下面四个是默认tag的函数 + public static void i(Object obj, String msg) { + String TAG = getTag(obj); + if (isDebug) { + Log.i(TAG, msg); + writeLogToFile(TAG, msg); + } + } + + public static void d(Object obj, String msg) { + String TAG = getTag(obj); + if (isDebug) { + Log.d(TAG, msg); + writeLogToFile(TAG, msg); + } + } + + public static void e(Object obj, String msg) { + String TAG = getTag(obj); + if (isDebug) { + Log.e(TAG, msg); + writeLogToFile(TAG, msg); + } + } + + public static void v(Object obj, String msg) { + String TAG = getTag(obj); + if (isDebug) { + Log.v(TAG, msg); + writeLogToFile(TAG, msg); + } + } + + public static void i(String TAG, String msg) { + if (isDebug) { + Log.i(TAG, msg); + writeLogToFile(TAG, msg); + } + } + + public static void d(String TAG, String msg) { + if (isDebug) { + Log.d(TAG, msg); + writeLogToFile(TAG, msg); + } + } + + public static void e(String TAG, String msg) { + if (isDebug) { + Log.e(TAG, msg); + writeLogToFile(TAG, msg); + } + } + + public static void v(String TAG, String msg) { + if (isDebug) { + Log.v(TAG, msg); + writeLogToFile(TAG, msg); + } + } + + /** + * 获取类名 + */ + private static String getTag(Object object) { + Class cls = object.getClass(); + String tag = cls.getName(); + String arrays[] = tag.split("\\."); + tag = arrays[arrays.length - 1]; + return tag; + } + + /** + * 返回日志路径 + */ + public static String getLogPath() { + String path = PATH + CalendarUtils.getData() + ".txt"; + return android.os.Environment.getExternalStorageDirectory().getPath() + File.separator + path; + } + + /** + * 把日志记录到文件 + */ + private static int writeLogToFile(String tag, String message) { + StringBuffer stringBuffer = new StringBuffer(); + stringBuffer.append(CalendarUtils.getNowDataTime()); + stringBuffer.append(" "); + stringBuffer.append(tag); + stringBuffer.append(" "); + stringBuffer.append(message); + stringBuffer.append("\n"); + PrintWriter writer = null; + try { + writer = new PrintWriter(new FileWriter(getLogPath(), true)); + writer.append(stringBuffer); + writer.flush(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (writer != null) { + writer.close(); + } + } + return 0; + } + + /** + * 将异常信息转换为字符串 + */ + public static String getExceptionString(Throwable ex) { + StringBuilder err = new StringBuilder(); + err.append("ExceptionDetailed:\n"); + err.append("====================Exception Info====================\n"); + err.append(ex.toString()); + err.append("\n"); + StackTraceElement[] stack = ex.getStackTrace(); + for (StackTraceElement stackTraceElement : stack) { + err.append(stackTraceElement.toString()).append("\n"); + } + Throwable cause = ex.getCause(); + if (cause != null) { + err.append("【Caused by】: "); + err.append(cause.toString()); + err.append("\n"); + StackTraceElement[] stackTrace = cause.getStackTrace(); + for (StackTraceElement stackTraceElement : stackTrace) { + err.append(stackTraceElement.toString()).append("\n"); + } + } + err.append("==================================================="); + return err.toString(); + } +} diff --git a/AppFrame/src/main/java/com/arialyy/frame/util/show/L.java b/AppFrame/src/main/java/com/arialyy/frame/util/show/L.java new file mode 100644 index 00000000..c7caf117 --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/util/show/L.java @@ -0,0 +1,265 @@ +package com.arialyy.frame.util.show; + +import android.util.Log; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.Map; +import java.util.Set; + +/** + * Created by Lyy on 2015/4/1. + * 普通的Log管理类 + */ +public class L { + /** + * Drawing toolbox + */ + private static final char TOP_LEFT_CORNER = '╔'; + private static final char BOTTOM_LEFT_CORNER = '╚'; + private static final char MIDDLE_CORNER = '╟'; + private static final char HORIZONTAL_DOUBLE_LINE = '║'; + private static final String DOUBLE_DIVIDER = "════════════════════════════════════════════"; + private static final String SINGLE_DIVIDER = "────────────────────────────────────────────"; + private static final String TOP_BORDER = TOP_LEFT_CORNER + DOUBLE_DIVIDER + DOUBLE_DIVIDER; + private static final String BOTTOM_BORDER = BOTTOM_LEFT_CORNER + DOUBLE_DIVIDER + DOUBLE_DIVIDER; + private static final String MIDDLE_BORDER = MIDDLE_CORNER + SINGLE_DIVIDER + SINGLE_DIVIDER; + private static final char I = 'I', W = 'W', D = 'D', E = 'E', V = 'V', A = 'A', M = 'M'; + + static String LINE_SEPARATOR = System.getProperty("line.separator"); //等价于"\n\r",唯一的作用是能装逼 + static int JSON_INDENT = 4; + + private L() { + /* cannot be instantiated */ + throw new UnsupportedOperationException("cannot be instantiated"); + } + + public static boolean isDebug = true;// 是否需要打印bug,可以在application的onCreate函数里面初始化 + private static final String TAG = "lyy"; + + /** + * map to str + */ + public static String m2s(Map map) { + if (isDebug) { + if (map == null) { + return ""; + } + StringBuilder sb = new StringBuilder(); + Set set = map.entrySet(); + for (Object aSet : set) { + Map.Entry entry = (Map.Entry) aSet; + sb.append(entry.getValue()); + } + return sb.toString(); + } + return ""; + } + + /** + * 打印MAp + */ + public static void m(Map map) { + if (isDebug) { + Set set = map.entrySet(); + if (set.size() < 1) { + printLog(D, "[]"); + return; + } + + int i = 0; + String[] s = new String[set.size()]; + for (Object aSet : set) { + Map.Entry entry = (Map.Entry) aSet; + s[i] = entry.getKey() + " = " + entry.getValue() + ",\n"; + i++; + } + printLog(V, s); + } + } + + /** + * 打印JSON + */ + public static void j(String jsonStr) { + if (isDebug) { + String message; + try { + if (jsonStr.startsWith("{")) { + JSONObject jsonObject = new JSONObject(jsonStr); + message = jsonObject.toString(JSON_INDENT); //这个是核心方法 + } else if (jsonStr.startsWith("[")) { + JSONArray jsonArray = new JSONArray(jsonStr); + message = jsonArray.toString(JSON_INDENT); + } else { + message = jsonStr; + } + } catch (JSONException e) { + message = jsonStr; + } + + message = LINE_SEPARATOR + message; + String[] lines = message.split(LINE_SEPARATOR); + printLog(D, lines); + } + } + + // 下面四个是默认tag的函数 + public static void i(String... msg) { + if (isDebug) printLog(I, msg); + } + + public static void d(String... msg) { + if (isDebug) printLog(D, msg); + } + + public static void w(String... msg) { + if (isDebug) printLog(W, msg); + } + + public static void e(String... msg) { + if (isDebug) printLog(E, msg); + } + + public static void e(Throwable tr) { + if (isDebug) printLog(E, FL.getExceptionString(tr)); + } + + public static void v(String... msg) { + if (isDebug) printLog(V, msg); + } + + // 下面是传入自定义tag的函数 + public static void i(String tag, String msg) { + if (isDebug) Log.i(tag, msg); + } + + public static void d(String tag, String msg) { + if (isDebug) Log.d(tag, msg); + } + + public static void w(String tag, String msg) { + if (isDebug) Log.w(tag, msg); + } + + public static void e(String tag, String msg) { + if (isDebug) Log.e(tag, msg); + } + + public static void v(String tag, String msg) { + if (isDebug) Log.v(tag, msg); + } + + //带异常的 + public static void i(String tag, String msg, Throwable tr) { + if (isDebug) Log.i(tag, msg, tr); + } + + public static void d(String tag, String msg, Throwable tr) { + if (isDebug) Log.d(tag, msg, tr); + } + + public static void w(String tag, String msg, Throwable tr) { + if (isDebug) Log.w(tag, msg, tr); + } + + public static void e(String tag, String msg, Throwable tr) { + if (isDebug) Log.e(tag, msg, tr); + } + + public static void v(String tag, String msg, Throwable tr) { + if (isDebug) Log.v(tag, msg, tr); + } + + /** + * 统一打印 + */ + private static void printHunk(char type, String str) { + switch (type) { + case I: + Log.i(TAG, str); + break; + case D: + Log.d(TAG, str); + break; + case E: + Log.e(TAG, str); + break; + case V: + Log.v(TAG, str); + break; + case A: + Log.wtf(TAG, str); + break; + case W: + Log.w(TAG, str); + break; + } + } + + /** + * 打印头部信息 + */ + private static void printHead(char type) { + printHunk(type, TOP_BORDER); + printHunk(type, HORIZONTAL_DOUBLE_LINE + " Thread:"); + printHunk(type, HORIZONTAL_DOUBLE_LINE + " " + Thread.currentThread().getName()); + printHunk(type, MIDDLE_BORDER); + } + + /** + * 打印Log被调用的位置 + */ + private static void printLocation(char type, String... msg) { + StackTraceElement[] stack = Thread.currentThread().getStackTrace(); + int i = 0; + for (StackTraceElement e : stack) { + String name = e.getClassName(); + if (!name.equals(L.class.getName())) { + i++; + } else { + break; + } + } + i += 3; + String className = stack[i].getFileName(); + String methodName = stack[i].getMethodName(); + int lineNumber = stack[i].getLineNumber(); + StringBuilder sb = new StringBuilder(); + printHunk(type, HORIZONTAL_DOUBLE_LINE + " Location:"); + sb.append(HORIZONTAL_DOUBLE_LINE) + .append(" (") + .append(className) + .append(":") + .append(lineNumber) + .append(")# ") + .append(methodName); + printHunk(type, sb.toString()); + printHunk(type, msg == null || msg.length == 0 ? BOTTOM_BORDER : MIDDLE_BORDER); + } + + /** + * 打印消息 + */ + private static void printMsg(char type, String... msg) { + printHunk(type, HORIZONTAL_DOUBLE_LINE + " msg:"); + for (String str : msg) { + printHunk(type, HORIZONTAL_DOUBLE_LINE + " " + str); + } + printHunk(type, BOTTOM_BORDER); + } + + /** + * 打印log + */ + private static void printLog(char type, String... msg) { + printHead(type); + printLocation(type, msg); + if (msg == null || msg.length == 0) { + return; + } + printMsg(type, msg); + } +} \ No newline at end of file diff --git a/AppFrame/src/main/java/com/arialyy/frame/util/show/T.java b/AppFrame/src/main/java/com/arialyy/frame/util/show/T.java new file mode 100644 index 00000000..60a1b86c --- /dev/null +++ b/AppFrame/src/main/java/com/arialyy/frame/util/show/T.java @@ -0,0 +1,62 @@ +package com.arialyy.frame.util.show; + +import android.content.Context; +import android.widget.Toast; + +/** + * Created by Lyy on 2015/4/1. + * Toast统一管理类 + */ +public class T { + + private T() { + throw new UnsupportedOperationException("cannot be instantiated"); + } + + /** + * 是否显示Tost + */ + public static boolean isShow = true; + + /** + * 短时间显示Toast + */ + public static void showShort(Context context, CharSequence message) { + if (isShow) Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); + } + + /** + * 短时间显示Toast + */ + public static void showShort(Context context, int message) { + if (isShow) Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); + } + + /** + * 长时间显示Toast + */ + public static void showLong(Context context, CharSequence message) { + if (isShow) Toast.makeText(context, message, Toast.LENGTH_LONG).show(); + } + + /** + * 长时间显示Toast + */ + public static void showLong(Context context, int message) { + if (isShow) Toast.makeText(context, message, Toast.LENGTH_LONG).show(); + } + + /** + * 自定义显示Toast时间 + */ + public static void show(Context context, CharSequence message, int duration) { + if (isShow) Toast.makeText(context, message, duration).show(); + } + + /** + * 自定义显示Toast时间 + */ + public static void show(Context context, int message, int duration) { + if (isShow) Toast.makeText(context, message, duration).show(); + } +} \ No newline at end of file diff --git a/AppFrame/src/main/res/drawable/selector_green_bt.xml b/AppFrame/src/main/res/drawable/selector_green_bt.xml new file mode 100644 index 00000000..72bd5b97 --- /dev/null +++ b/AppFrame/src/main/res/drawable/selector_green_bt.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AppFrame/src/main/res/layout/layout_error_temp.xml b/AppFrame/src/main/res/layout/layout_error_temp.xml new file mode 100644 index 00000000..e0c2831b --- /dev/null +++ b/AppFrame/src/main/res/layout/layout_error_temp.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + +