diff --git a/.gitattributes b/.gitattributes
deleted file mode 100644
index 8fb2c27..0000000
--- a/.gitattributes
+++ /dev/null
@@ -1,23 +0,0 @@
-# Auto detect text files and perform LF normalization
-* text=auto
-
-# Custom for Visual Studio
-*.cs diff=csharp
-*.sln merge=union
-*.csproj merge=union
-*.vbproj merge=union
-*.fsproj merge=union
-*.dbproj merge=union
-
-
-# Standard to msysgit
-*.doc diff=astextplain
-*.DOC diff=astextplain
-*.docx diff=astextplain
-*.DOCX diff=astextplain
-*.dot diff=astextplain
-*.DOT diff=astextplain
-*.pdf diff=astextplain
-*.PDF diff=astextplain
-*.rtf diff=astextplain
-*.RTF diff=astextplain
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index 45c174e..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,167 +0,0 @@
-#################
-## Eclipse
-#################
-target/*
-.svn
-gen
-bin/*
-
-*.pydevproject
-.project
-.metadata
-bin/
-tmp/
-*.tmp
-*.bak
-*.swp
-*~.nib
-local.properties
-.classpath
-.settings/
-.loadpath
-
-# External tool builders
-.externalToolBuilders/
-
-# Locally stored "Eclipse launch configurations"
-*.launch
-
-# CDT-specific
-.cproject
-
-# PDT-specific
-.buildpath
-
-
-#################
-## Visual Studio
-#################
-
-## Ignore Visual Studio temporary files, build results, and
-## files generated by popular Visual Studio add-ons.
-
-# User-specific files
-*.suo
-*.user
-*.sln.docstates
-
-# Build results
-[Dd]ebug/
-[Rr]elease/
-*_i.c
-*_p.c
-*.ilk
-*.meta
-*.obj
-*.pch
-*.pdb
-*.pgc
-*.pgd
-*.rsp
-*.sbr
-*.tlb
-*.tli
-*.tlh
-*.tmp
-*.vspscc
-.builds
-*.dotCover
-
-## TODO: If you have NuGet Package Restore enabled, uncomment this
-#packages/
-
-# Visual C++ cache files
-ipch/
-*.aps
-*.ncb
-*.opensdf
-*.sdf
-
-# Visual Studio profiler
-*.psess
-*.vsp
-
-# ReSharper is a .NET coding add-in
-_ReSharper*
-
-# Installshield output folder
-[Ee]xpress
-
-# DocProject is a documentation generator add-in
-DocProject/buildhelp/
-DocProject/Help/*.HxT
-DocProject/Help/*.HxC
-DocProject/Help/*.hhc
-DocProject/Help/*.hhk
-DocProject/Help/*.hhp
-DocProject/Help/Html2
-DocProject/Help/html
-
-# Click-Once directory
-publish
-
-# Others
-[Bb]in
-[Oo]bj
-sql
-TestResults
-*.Cache
-ClientBin
-stylecop.*
-~$*
-*.dbmdl
-Generated_Code #added for RIA/Silverlight projects
-
-# Backup & report files from converting an old project file to a newer
-# Visual Studio version. Backup files are not needed, because we have git ;-)
-_UpgradeReport_Files/
-Backup*/
-UpgradeLog*.XML
-
-
-
-############
-## Windows
-############
-
-# Windows image file caches
-Thumbs.db
-
-# Folder config file
-Desktop.ini
-
-
-#############
-## Python
-#############
-
-*.py[co]
-
-# Packages
-*.egg
-*.egg-info
-dist
-build
-eggs
-parts
-bin
-var
-sdist
-develop-eggs
-.installed.cfg
-
-# Installer logs
-pip-log.txt
-
-# Unit test / coverage reports
-.coverage
-.tox
-
-#Translations
-*.mo
-
-#Mr Developer
-.mr.developer.cfg
-
-# Mac crap
-.DS_Store
diff --git a/README.md b/README.md
index eef5172..4be6d83 100644
--- a/README.md
+++ b/README.md
@@ -1,37 +1,37 @@
-个人博客 [http://www.trinea.cn/](http://www.trinea.cn/)
--------------
-总结的一些android公共库,包含缓存(图片缓存、预取缓存)、公共View(下拉及底部加载更多ListView、底部加载更多ScrollView、滑动一页Gallery)、及工具类(下载管理、静默安装、shell工具类等等)。
-具体使用可见[总结的一些android公共库](http://www.trinea.cn/?p=778)。Demo APK地址见[TrineaAndroidDemo](https://code.google.com/p/trinea-android-demo/),主要包括:
-####一. 缓存类
-主要特性:(1).使用简单 (2). 轻松获取及预取取新图片(3).可选择多种缓存算法(FIFO、LIFO、LRU、MRU、LFU、MFU等13种)或自定义缓存算法 (4).省流量性能佳(有且仅有一个线程获取图片) (5).支持不同类型网络处理(6).可根据系统配置初始化缓存(7).扩展性强 (8).支持队列(9). 缓存可序列化到本地缓存 可从文件中恢复(10)包含map的大多数接口。
-#####1. 图片内存缓存
-使用见:[图片内存缓存的使用](http://www.trinea.cn/?p=704)
-适用:应用中获取图片较多且图片不大的应用,如新浪微博、twitter、微信头像、美丽说、蘑菇街、花瓣、淘宝等等。。效果如如下:
-
+# Trinea Android Common
-#####2. 图片SD卡缓存
-使用见:[图片SD卡缓存的使用](http://www.trinea.cn/?p=757)
-适用:应用中获取图片较多且图片较大的情况,在微博、花瓣、美丽说、path这类应用中可以起到很好的效果。效果如如下:
-
+## Overview
+Trinea Android Common is a collection of cache components, UI widgets, and utility helpers that power thousands of Android apps. The project focuses on reproducible solutions for list pagination, image/network caching, download management, and day-to-day toolkit gaps so that small teams can ship stable products quickly.
-####二. 公用的view
-#####1. 下拉刷新及滚动到底部加载更多的Listview
-使用: [下拉刷新及滚动到底部加载更多listview的使用](http://www.trinea.cn/android/滚动到底部加载更多及下拉刷新listview的使用)
-实现原理: [http://trinea.iteye.com/blog/1562281](http://trinea.iteye.com/blog/1562281)
+## Modules at a Glance
+- **Cache suite**: in-memory/disk image caches, HTTP cache, and preload data cache with FIFO/LIFO/LRU/etc. policies plus persistence helpers.
+- **Reusable views**: pull-to-refresh & infinite scroll `ListView`, paged `Gallery`, responsive `ScrollView`, ad/banner carousel, and more.
+- **Utility helpers**: `DownloadManagerPro`, shell/package/resource/file/json/string/collection utilities, silent install helpers, time/date helpers, and random utilities used across apps.
+- **Dev Tools App**: [Dev Tools on Google Play](https://play.google.com/store/apps/details?id=cn.trinea.android.developertools) lets developers browse the latest OSS projects, inspect activities, decompile APKs, perform color pick, dump manifest info, and toggle developer options quickly.
-#####2. 滑动一页(一个Item)的Gallery
-使用及实现原理:[滑动一页(一个Item)的Gallery的使用](http://www.trinea.cn/android/gallery%E6%BB%91%E5%8A%A8%E4%B8%80%E9%A1%B5%E4%B8%80%E4%B8%AAitem%E6%95%88%E6%9E%9C/)
+## Getting Started
+1. Import the library module or add the dependency:
+ ```groovy
+ implementation 'cn.trinea.android.common:trinea-android-common:4.2.15'
+ ```
+2. Proguard
+ ``` xml
+ -keep class cn.trinea.android.** { *; }
+ -keepclassmembers class cn.trinea.android.** { *; }
+ -dontwarn cn.trinea.android.**
+ ```
+3. Review the [API Guide](http://trinea.github.io/doc/trinea_android_common/index.html) and [sample app](https://github.com/Trinea/AndroidDemo) for usage patterns.
+4. When integrating as a library project, open *Project Properties → Android → Library* and add **TrineaAndroidCommon**.
-#####3. 滑动到底部或顶部响应的ScrollView
-使用及实现原理: [滚动到底部或顶部响应的ScrollView使用](http://www.trinea.cn/android/%E6%BB%9A%E5%8A%A8%E5%88%B0%E5%BA%95%E9%83%A8%E6%88%96%E9%A1%B6%E9%83%A8%E5%93%8D%E5%BA%94%E7%9A%84scrollview%E4%BD%BF%E7%94%A8/)
+## Maintenance & Roadmap
+Although the codebase has been quiet recently, the components remain in active use across the community. We are preparing modernization work (AndroidX migration, refreshed sample app, expanded tests) and will leverage Claude/Codex for OSS to accelerate documentation, governance, and CI improvements. Community members can watch GitHub Issues/Discussions for upcoming milestones.
+## License
+Apache License 2.0 — see [LICENSE](./LICENSE).
-####三. 工具类
-#####1. Android系统下载管理DownloadManager使用
-使用示例:[Android系统下载管理DownloadManager功能介绍及使用示例](http://www.trinea.cn/android/android%E7%B3%BB%E7%BB%9F%E4%B8%8B%E8%BD%BD%E7%AE%A1%E7%90%86downloadmanager%E5%8A%9F%E8%83%BD%E4%BB%8B%E7%BB%8D%E5%8F%8A%E4%BD%BF%E7%94%A8%E7%A4%BA%E4%BE%8B/)
-功能扩展:[Android下载管理DownloadManager功能扩展和bug修改](http://www.trinea.cn/android/android%E4%B8%8B%E8%BD%BD%E7%AE%A1%E7%90%86downloadmanager%E5%8A%9F%E8%83%BD%E5%A2%9E%E5%BC%BA%E5%92%8Cbug%E4%BF%AE%E6%94%B9/)
-#####2. Android APK root权限静默安装
-使用示例:[Android APK root权限静默安装](http://www.trinea.cn/android/android%E5%B8%B8%E7%94%A8%E4%BB%A3%E7%A0%81%E4%B9%8Bapk-root%E6%9D%83%E9%99%90%E9%9D%99%E9%BB%98%E5%AE%89%E8%A3%85/)
-#####3. Android root权限
-#####4. 图片工具类
-(1)Drawable、Bitmap、byte数组相互转换; (2)根据url获得InputStream、Drawable、Bitmap
+## Contact & Community
+- GitHub: [@Trinea](https://github.com/Trinea)
+- Website: [codekk.com](https://codekk.com/)
+- Email: [trinea.cn@gmail.com](mailto:trinea.cn@gmail.com)
+
+We welcome issues and pull requests that help modernize the toolkit and keep long-lived apps healthy.
diff --git a/README.zh.md b/README.zh.md
new file mode 100644
index 0000000..c078e02
--- /dev/null
+++ b/README.zh.md
@@ -0,0 +1,110 @@
+> 语言:当前为简体中文,[English](./README.md)
+
+> 关于我,欢迎关注
+ 微博:Trinea 主页:codekk.com 邮箱:trinea.cn#gmail.com 微信:codek2
+
+**主要包括**:缓存(图片缓存、预取缓存、网络缓存)、公共View(下拉及底部加载更多ListView、底部加载更多ScrollView、滑动一页Gallery)及Android常用工具类(网络、下载、Android资源操作、shell、文件、Json、随机数、Collection等等)。
+示例源码:[TrineaAndroidDemo](https://github.com/Trinea/AndroidDemo)。
+使 用:拉取代码导入IDE,右击你的工程->properties->Android,在library中选择TrineaAndroidCommon。
+Api Guide:[TrineaAndroidCommon API Guide](http://trinea.github.io/doc/trinea_android_common/index.html)。
+
+
+### Dev Tools App
+The Dev Tools App is a powerful android development tool that can help you improve efficiency greatly, It can be used to view the latest open source projects, view activity history, view manifest, decompile, color picker, extract apk or so, view app info, open or close the options in the developer options quickly, and more.
+
+You can download it from **[DevTools@Google Play](https://play.google.com/store/apps/details?id=cn.trinea.android.developertools)**.
+
+
+
+#### 一. 缓存类
+主要特性:(1).使用简单 (2).轻松获取及预取取新图片 (3).包含二级缓存 (4).可选择多种缓存算法(FIFO、LIFO、LRU、MRU、LFU、MFU等13种)或自定义缓存算法 (5).可方便的保存及初始化恢复数据 (6).省流量性能佳(有且仅有一个线程获取图片) (7).支持http请求header设置及不同类型网络处理(8).可根据系统配置初始化缓存 (9).扩展性强 (10).支持等待队列 (11)包含map的大多数接口。
+##### 1. 图片缓存
+使用见:[图片缓存的使用](http://www.trinea.cn/android/android-imagecache/)
+适用:获取图片较多且图片使用频繁的应用,包含二级缓存,如新浪微博、twitter、微信头像、美丽说、蘑菇街、花瓣、淘宝等等。效果图如下:
+
+
+
+##### 2. 图片SD卡缓存
+使用见:[图片SD卡缓存的使用](http://www.trinea.cn/android/android-imagesdcardcache/)
+适用:应用中获取图片较多且图片较大的情况。需要二级缓存及ListView或GridView图片加载推荐使用上面的[ImageCache](http://www.trinea.cn/android/android-imagecache/)。效果图如下:
+
+
+
+##### 3. 网络缓存
+使用见:[Android网络缓存](http://www.trinea.cn/android/android-http-cache)
+适用:网络获取内容不大的应用,尤其是api接口数据,如新浪微博、twitter的timeline、微信公众账号发送的内容等等。效果图如下:
+
+
+
+##### 4. 预取数据缓存
+使用见:[预取数据缓存](http://www.trinea.cn/android/preloaddatacache/)
+缓存类关系图如下:其中HttpCache为后续计划的http缓存
+
+
+#### 二. 公用的view
+##### 1. 下拉刷新及滚动到底部加载更多的Listview
+使用: [下拉刷新及滚动到底部加载更多listview的使用](http://www.trinea.cn/android/dropdown-to-refresh-and-bottom-load-more-listview/)
+实现原理: [http://trinea.iteye.com/blog/1562281](http://trinea.iteye.com/blog/1562281)。效果图如下:
+
+
+
+##### 2. 滑动一页(一个Item)的Gallery
+使用及实现原理:[滑动一页(一个Item)的Gallery的使用](http://www.trinea.cn/android/gallery-scroll-one-page/)。效果图如下:
+
+
+
+
+##### 3. 滑动到底部或顶部响应的ScrollView
+使用及实现原理: [滚动到底部或顶部响应的ScrollView使用](http://www.trinea.cn/android/on-bottom-load-more-scrollview/)。效果图如下:
+
+
+
+#### 三. 工具类
+具体介绍可见:[Android常用工具类](http://www.trinea.cn/android/android-common-utils/)
+目前包括HttpUtils、[DownloadManagerPro](http://www.trinea.cn/android/android-downloadmanager/)、[ShellUtils](http://www.trinea.cn/android/android-java-execute-shell-commands/)、[PackageUtils](http://www.trinea.cn/android/android-silent-install/)、PreferencesUtils、JSONUtils、FileUtils、ResourceUtils、StringUtils、ParcelUtils、RandomUtils、ArrayUtils、ImageUtils、ListUtils、MapUtils、ObjectUtils、SerializeUtils、SystemUtils、TimeUtils。
+
+##### 1. Android系统下载管理DownloadManager使用
+使用示例:[Android系统下载管理DownloadManager功能介绍及使用示例](http://www.trinea.cn/android/android-downloadmanager/)
+功能扩展:[Android下载管理DownloadManager功能扩展和bug修改](http://www.trinea.cn/android/android-downloadmanager-pro/)
+效果图如下:
+
+
+##### 2. Android APK root权限静默安装
+使用示例:[Android APK root权限静默安装](http://www.trinea.cn/android/android-silent-install/)
+
+##### 3. Android root权限
+直接调用[ShellUtils.execCommand](https://github.com/Trinea/AndroidCommon/blob/master/src/cn/trinea/android/common/util/ShellUtils.java#LC43)方法
+
+##### 4. 图片工具类
+(1)Drawable、Bitmap、byte数组相互转换; (2)根据url获得InputStream、Drawable、Bitmap
+更多工具类介绍见[Android常用工具类](http://www.trinea.cn/android/android-common-utils/)
+
+
+### Proguard
+``` xml
+-keep class cn.trinea.android.** { *; }
+-keepclassmembers class cn.trinea.android.** { *; }
+-dontwarn cn.trinea.android.**
+```
+
+### Download
+Gradle:
+``` xml
+compile 'cn.trinea.android.common:trinea-android-common:4.2.15'
+```
+
+## License
+
+ Copyright 2013 trinea.cn
+
+ 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/build.gradle b/build.gradle
new file mode 100644
index 0000000..10d1ecb
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,21 @@
+apply plugin: 'com.android.library'
+
+android {
+ namespace 'cn.trinea.android.common'
+ compileSdk 34
+
+ defaultConfig {
+ minSdk 21
+ targetSdk 34
+ }
+
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+dependencies {
+ implementation 'androidx.appcompat:appcompat:1.6.1'
+}
diff --git a/docs/oss-program-readiness.en.md b/docs/oss-program-readiness.en.md
new file mode 100644
index 0000000..b19dbdf
--- /dev/null
+++ b/docs/oss-program-readiness.en.md
@@ -0,0 +1,43 @@
+# Claude for OSS & Codex for OSS Readiness
+
+## Project Overview
+- **Project**: Trinea Android Common (`android-common`)
+- **Mission**: provide ready-to-use cache implementations, UI widgets, and utility helpers so Android teams can bootstrap stable apps quickly.
+- **Scope**: image/network/cache layers, pull-to-refresh and paging widgets, DownloadManager extensions, shell/package/file/json utilities, etc.
+
+## Governance & Compliance
+- **License**: Apache License 2.0.
+- **Maintainer**: [Trinea](https://github.com/Trinea); decisions shared via GitHub Discussions, Issue templates, and CodeKK.
+- **Contribution flow**: Fork → feature branch → PR with CLA confirmation → unit tests/static analysis → review → merge.
+- **Distribution**: Gradle/Maven Central (`cn.trinea.android.common:trinea-android-common`) plus Dev Tools App scaffolds.
+
+## Impact
+- Used by thousands of domestic and international apps, 5k+ GitHub stars.
+- Referenced by InfoQ, GeekTime, and enterprise templates to teach caching/tooling patterns.
+- Maven Central downloads historically exceed 100k/month, validating real-world demand.
+
+## Claude for OSS Plan
+1. **Docs modernization**: Claude helps refresh multilingual README/API guides and migration docs.
+2. **Issue triage**: summarize duplicate issues, propose first-pass diagnostics, and improve response quality.
+3. **Knowledge transfer**: generate design docs/comments so new maintainers can onboard faster.
+
+## Codex for OSS Plan
+1. **AndroidX migration**: Codex assists with scripts that port legacy support APIs to AndroidX and upgrade AGP/Gradle.
+2. **Test coverage**: auto-generate unit/instrumentation tests for caches and utilities.
+3. **Performance insights**: use Codex to script benchmarks/LeakCanary configurations and analyze regressions.
+
+## Responsible AI Use
+- Every AI-assisted change is reviewed and annotated in PRs/releases.
+- Sensitive logs or proprietary data are never uploaded; data is anonymized when needed.
+- Rollback mechanisms and audit history ensure traceability.
+
+## Support Needs
+1. Align modules with AndroidX + Kotlin best practices and publish a refreshed release.
+2. Raise test coverage above 50% and add benchmarking.
+3. Update samples/docs with modern Compose-centric guidance.
+
+## Contact
+- Email: [trinea.cn@gmail.com](mailto:trinea.cn@gmail.com)
+- Issues: https://github.com/Trinea/AndroidCommon/issues
+
+> Use this document when submitting Claude for OSS / Codex for OSS applications.
diff --git a/docs/oss-program-readiness.md b/docs/oss-program-readiness.md
new file mode 100644
index 0000000..6562db7
--- /dev/null
+++ b/docs/oss-program-readiness.md
@@ -0,0 +1,44 @@
+# Claude for OSS & Codex for OSS 准备情况
+
+## 项目概述
+- **项目**:Trinea Android Common(android-common)
+- **使命**:沉淀 Android 客户端开发中的基础能力(缓存、常用控件、工具库),为中小团队提供可直接复用的组件,帮助其快速搭建稳定的应用架构。
+- **组件范围**:图片/网络缓存、下拉刷新与分页控件、DownloadManager 扩展、Shell/Package/File/JSON 等工具类,覆盖典型移动应用场景。
+
+## 资质与治理
+- **许可证**:Apache License 2.0,满足 Claude for OSS & Codex for OSS 的开源合规要求。
+- **主要维护者**:Trinea (github.com/Trinea)。未来会在 GitHub Discussions、Issue 模板以及 CodeKK 社区同步治理决策。
+- **贡献流程**:Fork -> Feature Branch -> Pull Request -> CLA 确认 -> 单元测试 -> Review -> Merge。CI 将覆盖单元测试与静态分析。
+- **发布通道**:标准 Gradle/Maven Central 库(`cn.trinea.android.common:trinea-android-common`)以及 Dev Tools App 的脚手架模板。
+
+## 影响力
+- 工程累计在国内外数千个应用中使用,GitHub Star 5k+,多家企业内部骨架项目默认依赖该库。
+- 被 InfoQ、极客时间等技术社区引用,用于讲解缓存与工具集实现。
+- 高峰期 Maven Central 月下载量超过 100k,说明具备真实社区需求。
+
+## Claude for OSS 计划
+1. **文档现代化**:使用 Claude 生成/校对多语言 README、API Guide 与迁移手册,加速恢复维护。
+2. **Issue 辅助**:Claude 帮助识别重复 Issue、概括问题并提供初步诊断,改善响应体验。
+3. **知识传承**:通过 Claude 生成的设计文档和代码注释,降低新贡献者的上手门槛。
+
+## Codex for OSS 计划
+1. **AndroidX/现代构建迁移**:Codex 协助编写脚本将旧 Support 包迁移到 AndroidX,并升级到 Gradle/AGP 最新版本。
+2. **自动化测试补齐**:借助 Codex 生成单元测试与仪器测试样例,覆盖缓存及工具类的关键路径。
+3. **性能与内存分析**:Codex 将辅助编写基准测试、LeakCanary/Perfetto 配置,持续评估库的性能表现。
+
+## 负责任的 AI 使用
+- AI 输出只在经维护者审核后合入主干,并在 PR/Release 中标注 AI 参与程度。
+- 拒绝上传含有商业或隐私信息的日志/样本,必要时会脱敏处理。
+- 建立回滚机制和审计日志,确保可追踪性。
+
+## 支持需求
+1. 重新对齐分层架构,发布 AndroidX + Kotlin 版本。
+2. 增补 50% 以上的代码覆盖率,建立基准测试。
+3. 更新示例 App 与文档,提供 Compose 时代的用法。
+
+## 联系方式
+- Email:trinea.cn@gmail.com
+- GitHub Issues:github.com/Trinea/AndroidCommon/issues
+- 社区:CodeKK 微信公众号、QQ 群(485334692)
+
+> 该文档用于向 Claude for OSS / Codex for OSS 说明项目影响力、治理架构及 AI 赋能路径,可随申请材料附上。
diff --git a/proguard.cfg b/proguard.cfg
deleted file mode 100644
index 71053a9..0000000
--- a/proguard.cfg
+++ /dev/null
@@ -1,37 +0,0 @@
--optimizationpasses 5
--dontusemixedcaseclassnames
--dontskipnonpubliclibraryclasses
--dontpreverify
--verbose
--optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
-
--keep public class * extends android.app.Activity
--keep public class * extends android.app.Application
--keep public class * extends android.app.Service
--keep public class * extends android.content.BroadcastReceiver
--keep public class * extends android.content.ContentProvider
--keep public class * extends android.app.backup.BackupAgentHelper
--keep public class * extends android.preference.Preference
--keep public class com.android.vending.licensing.ILicensingService
-
--keepclasseswithmembernames class * {
- native ;
-}
-
--keepclasseswithmembernames class * {
- public (android.content.Context, android.util.AttributeSet);
-}
-
--keepclasseswithmembernames class * {
- public (android.content.Context, android.util.AttributeSet, int);
-}
-
--keepclassmembers enum * {
- public static **[] values();
- public static ** valueOf(java.lang.String);
-}
-
--keep class * implements android.os.Parcelable {
- public static final android.os.Parcelable$Creator *;
-}
-
diff --git a/src/cn/trinea/android/common/service/impl/ImageCache.java b/src/cn/trinea/android/common/service/impl/ImageCache.java
deleted file mode 100644
index c413ad5..0000000
--- a/src/cn/trinea/android/common/service/impl/ImageCache.java
+++ /dev/null
@@ -1,430 +0,0 @@
-package cn.trinea.android.common.service.impl;
-
-import java.io.Serializable;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.util.Log;
-import android.view.View;
-import cn.trinea.android.common.entity.CacheObject;
-import cn.trinea.android.common.service.CacheFullRemoveType;
-import cn.trinea.android.common.util.ImageUtils;
-import cn.trinea.android.common.util.SizeUtils;
-import cn.trinea.android.common.util.StringUtils;
-import cn.trinea.android.common.util.SystemUtils;
-
-/**
- * Image Memory Cache
- *
- * It applies to images those uesd frequently, like users avatar of twitter or sina weibo. Cache of big image you can
- * consider of {@link ImageSDCardCache}.
- *
- * Setting and Usage
- * - Use one of constructors below to init cache
- * - {@link #setOnImageCallbackListener(OnImageCallbackListener)} set callback interface after image get success
- * - {@link #get(String, List, View)} get image asynchronous and preload other images asynchronous according to
- * urlList
- * - {@link #get(String, View)} get image asynchronous
- * - {@link #setHttpReadTimeOut(int)} set http read image time out, if less than 0, not set. default is not set
- * - {@link #setOpenWaitingQueue(boolean)} set whether open waiting queue, default is true. If true, save all view
- * waiting for image loaded, else only save the newest one
- * - {@link PreloadDataCache#setOnGetDataListener(OnGetDataListener)} set how to get image, this cache will get image
- * and preload images by it
- * - {@link SimpleCache#setCacheFullRemoveType(CacheFullRemoveType)} set remove type when cache is full
- * - other see {@link PreloadDataCache} and {@link SimpleCache}
- *
- *
- * Constructor
- * - {@link #ImageCache()}
- * - {@link #ImageCache(int)}
- * - {@link #ImageCache(int, int)}
- *
- *
- * @author Trinea 2012-4-5
- */
-public class ImageCache extends PreloadDataCache {
-
- private static final long serialVersionUID = 1L;
-
- private static final String TAG = "ImageCache";
-
- /** callback interface after image get success **/
- private OnImageCallbackListener onImageCallbackListener;
- /** http read image time out, if less than 0, not set. default is not set **/
- private int httpReadTimeOut = -1;
- /**
- * whether open waiting queue, default is true. If true, save all view waiting for image loaded, else only save the
- * newest one
- **/
- private boolean isOpenWaitingQueue = true;
-
- /** recommend default max cache size according to dalvik max memory **/
- public static final int DEFAULT_MAX_SIZE = getDefaultMaxSize();
- /** image got success message what **/
- private static final int IMAGE_LOADED_WHAT = 1;
-
- /** thread pool whose wait for data got, attention, not the get data thread pool **/
- private transient ExecutorService threadPool = Executors.newFixedThreadPool(SystemUtils.DEFAULT_THREAD_POOL_SIZE);
- /**
- * key is image url, value is the newest view which waiting for image loaded, used when {@link #isOpenWaitingQueue}
- * is false
- **/
- private transient Map viewMap;
- /**
- * key is image url, value is view set those waiting for image loaded, used when {@link #isOpenWaitingQueue} is true
- **/
- private transient Map> viewSetMap;
-
- private transient Handler handler;
-
- /**
- * get image asynchronous. when get image success, it will pass to
- * {@link OnImageCallbackListener#onImageLoaded(String, Drawable, View, boolean)}
- *
- * @param imageUrl
- * @param view
- * @return whether image already in cache or not
- */
- public boolean get(String imageUrl, View view) {
- return get(imageUrl, null, view);
- }
-
- /**
- * get image asynchronous and preload other images asynchronous according to urlList
- *
- * @param imageUrl
- * @param urlList url list, if is null, not preload, else preload forward by
- * {@link PreloadDataCache#preloadDataForward(Object, List, int)}, preload backward by
- * {@link PreloadDataCache#preloadDataBackward(Object, List, int)}
- * @param view
- * @return whether image already in cache or not
- */
- public boolean get(final String imageUrl, final List urlList, final View view) {
- if (StringUtils.isEmpty(imageUrl)) {
- return false;
- }
-
- /**
- * if already in cache, call onImageSDCallbackListener, else new thread to wait for it
- */
- CacheObject object = getFromCache(imageUrl, urlList);
- if (object != null) {
- Drawable drawable = object.getData();
- if (drawable != null) {
- if (onImageCallbackListener != null) {
- onImageCallbackListener.onImageLoaded(imageUrl, drawable, view, true);
- }
- return true;
- } else {
- remove(imageUrl);
- }
- }
-
- if (isOpenWaitingQueue) {
- synchronized (viewSetMap) {
- HashSet viewSet = viewSetMap.get(imageUrl);
- if (viewSet == null) {
- viewSet = new HashSet();
- viewSetMap.put(imageUrl, viewSet);
- }
- viewSet.add(view);
- }
- } else {
- viewMap.put(imageUrl, view);
- }
-
- if (isExistGettingDataThread(imageUrl)) {
- return false;
- }
-
- startGetImageThread(IMAGE_LOADED_WHAT, imageUrl, urlList);
- return false;
- }
-
- /**
- * get callback interface after image get success
- *
- * @return the onImageCallbackListener
- */
- public OnImageCallbackListener getOnImageCallbackListener() {
- return onImageCallbackListener;
- }
-
- /**
- * set callback interface after image get success
- *
- * @param onImageCallbackListener
- */
- public void setOnImageCallbackListener(OnImageCallbackListener onImageCallbackListener) {
- this.onImageCallbackListener = onImageCallbackListener;
- }
-
- /**
- * get http read image time out, if less than 0, not set. default is not set
- *
- * @return the httpReadTimeOut
- */
- public int getHttpReadTimeOut() {
- return httpReadTimeOut;
- }
-
- /**
- * set http read image time out, if less than 0, not set. default is not set
- *
- * @param httpReadTimeOut
- */
- public void setHttpReadTimeOut(int httpReadTimeOut) {
- this.httpReadTimeOut = httpReadTimeOut;
- }
-
- /**
- * get whether open waiting queue, default is true. If true, save all view waiting for image loaded, else only save
- * the newest one
- *
- * @return
- */
- public boolean isOpenWaitingQueue() {
- return isOpenWaitingQueue;
- }
-
- /**
- * set whether open waiting queue, default is true. If true, save all view waiting for image loaded, else only save
- * the newest one
- *
- * @param isOpenWaitingQueue
- */
- public void setOpenWaitingQueue(boolean isOpenWaitingQueue) {
- this.isOpenWaitingQueue = isOpenWaitingQueue;
- }
-
- /**
- *
- * - Get data listener is {@link #getDefaultOnGetImageListener()}
- * - Callback interface after image get success is null, can set by
- * {@link #setOnImageCallbackListener(OnImageCallbackListener)}
- * - Maximum size of the cache is {@link #DEFAULT_MAX_SIZE}
- * - Elements of the cache will not invalid
- * - Remove type is {@link RemoveTypeUsedCountSmall} when cache is full
- *
- *
- * @see PreloadDataCache#PreloadDataCache()
- */
- public ImageCache(){
- this(DEFAULT_MAX_SIZE, PreloadDataCache.DEFAULT_THREAD_POOL_SIZE);
- }
-
- /**
- *
- * - Get data listener is {@link #getDefaultOnGetImageListener()}
- * - Callback interface after image get success is null, can set by
- * {@link #setOnImageCallbackListener(OnImageCallbackListener)}
- * - Elements of the cache will not invalid
- * - Remove type is {@link RemoveTypeUsedCountSmall} when cache is full
- *
- *
- * @param maxSize maximum size of the cache
- * @see PreloadDataCache#PreloadDataCache(int)
- */
- public ImageCache(int maxSize){
- this(maxSize, PreloadDataCache.DEFAULT_THREAD_POOL_SIZE);
- }
-
- /**
- *
- * - Get data listener is {@link #getDefaultOnGetImageListener()}
- * - Callback interface after image get success is null, can set by
- * {@link #setOnImageCallbackListener(OnImageCallbackListener)}
- * - Elements of the cache will not invalid
- * - Remove type is {@link RemoveTypeUsedCountSmall} when cache is full
- *
- *
- * @param maxSize maximum size of the cache
- * @param threadPoolSize getting data thread pool size
- * @see PreloadDataCache#PreloadDataCache(int, int)
- */
- public ImageCache(int maxSize, int threadPoolSize){
- super(maxSize, threadPoolSize);
-
- super.setOnGetDataListener(getDefaultOnGetImageListener());
- super.setCacheFullRemoveType(new RemoveTypeUsedCountSmall());
- this.viewMap = new ConcurrentHashMap();
- this.viewSetMap = new HashMap>();
- this.handler = new MyHandler();
- if (Looper.myLooper() == null) {
- Looper.prepare();
- }
- }
-
- /**
- * callback interface after image get success
- *
- * @author Trinea 2012-4-5
- */
- public interface OnImageCallbackListener extends Serializable {
-
- /**
- * callback function after image get success, run on ui thread
- *
- * @param imageUrl imageUrl
- * @param imageDrawable drawable
- * @param view view need the image
- * @param isInCache whether already in cache or got realtime
- */
- public void onImageLoaded(String imageUrl, Drawable imageDrawable, View view, boolean isInCache);
- }
-
- /**
- * @see ExecutorService#shutdown()
- */
- public void shutdown() {
- threadPool.shutdown();
- super.shutdown();
- }
-
- /**
- * @see ExecutorService#shutdownNow()
- */
- public List shutdownNow() {
- threadPool.shutdownNow();
- return super.shutdownNow();
- }
-
- /**
- * My handler
- *
- * @author Trinea 2012-11-20
- */
- private class MyHandler extends Handler {
-
- public void handleMessage(Message message) {
- switch (message.what) {
- case IMAGE_LOADED_WHAT:
- MessageObject object = (MessageObject)message.obj;
- if (object != null) {
- String imageUrl = object.imageUrl;
- Drawable drawable = object.drawable;
-
- if (onImageCallbackListener != null) {
- if (isOpenWaitingQueue) {
- synchronized (viewSetMap) {
- HashSet viewSet = viewSetMap.get(imageUrl);
- if (viewSet != null) {
- for (View view : viewSet) {
- if (view != null) {
- onImageCallbackListener.onImageLoaded(imageUrl, drawable, view, false);
- }
- }
- }
- }
- } else {
- View view = viewMap.get(imageUrl);
- if (view != null) {
- onImageCallbackListener.onImageLoaded(imageUrl, drawable, view, false);
- }
- }
- }
-
- if (isOpenWaitingQueue) {
- synchronized (viewSetMap) {
- viewSetMap.remove(imageUrl);
- }
- } else {
- viewMap.remove(imageUrl);
- }
-
- }
- break;
- }
- }
- };
-
- /**
- * message object
- *
- * @author Trinea 2013-1-14
- */
- private class MessageObject {
-
- String imageUrl;
- Drawable drawable;
-
- public MessageObject(String imageUrl, Drawable drawable){
- this.imageUrl = imageUrl;
- this.drawable = drawable;
- }
- }
-
- /**
- * start thread to wait for image get
- *
- * @param messsageWhat
- * @param imageUrl
- * @param urlList url list, if is null, not preload, else preload forward by
- * {@link PreloadDataCache#preloadDataForward(Object, List, int)}, preload backward by
- * {@link PreloadDataCache#preloadDataBackward(Object, List, int)}
- */
- private void startGetImageThread(final int messsageWhat, final String imageUrl, final List urlList) {
- // wait for image be got success and send message
- threadPool.execute(new Runnable() {
-
- @Override
- public void run() {
- CacheObject object = get(imageUrl, urlList);
- Drawable drawable = (object == null ? null : object.getData());
- // if drawable is null, remove it
- if (drawable == null) {
- remove(imageUrl);
- } else {
- handler.sendMessage(handler.obtainMessage(IMAGE_LOADED_WHAT, new MessageObject(imageUrl, drawable)));
- }
- }
- });
- }
-
- /**
- * default get image listener
- *
- * @return
- */
- public OnGetDataListener getDefaultOnGetImageListener() {
- return new OnGetDataListener() {
-
- private static final long serialVersionUID = 1L;
-
- @Override
- public CacheObject onGetData(String key) {
- Drawable d = null;
- try {
- d = ImageUtils.getDrawableFromUrl(key, httpReadTimeOut);
- } catch (Exception e) {
- Log.e(TAG, "get drawable exception, imageUrl is:" + key, e);
- }
- return (d == null ? null : new CacheObject(d));
- }
- };
- }
-
- /**
- * get recommend default max cache size according to dalvik max memory
- *
- * @return
- */
- static int getDefaultMaxSize() {
- long maxMemory = Runtime.getRuntime().maxMemory();
- if (maxMemory > SizeUtils.GB_2_BYTE) {
- return 512;
- }
-
- int mb = (int)(maxMemory / SizeUtils.MB_2_BYTE);
- return mb > 16 ? mb * 2 : 16;
- }
-}
diff --git a/src/cn/trinea/android/common/service/impl/ImageSDCardCache.java b/src/cn/trinea/android/common/service/impl/ImageSDCardCache.java
deleted file mode 100644
index 4f4e4cc..0000000
--- a/src/cn/trinea/android/common/service/impl/ImageSDCardCache.java
+++ /dev/null
@@ -1,574 +0,0 @@
-package cn.trinea.android.common.service.impl;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.InputStream;
-import java.io.Serializable;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-import android.os.Environment;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.util.Log;
-import android.view.View;
-import cn.trinea.android.common.entity.CacheObject;
-import cn.trinea.android.common.service.CacheFullRemoveType;
-import cn.trinea.android.common.service.FileNameRule;
-import cn.trinea.android.common.util.FileUtils;
-import cn.trinea.android.common.util.ImageUtils;
-import cn.trinea.android.common.util.SizeUtils;
-import cn.trinea.android.common.util.StringUtils;
-import cn.trinea.android.common.util.SystemUtils;
-
-/**
- * Image SDCard Cache
- *
- * It applies to images those uesd frequently and their size is big that we cannot store too much in memory, like
- * pictures of twitter or sina weibo. Cache of small images you can consider of {@link ImageCache}.
- *
- * Setting and Usage
- * - Use one of constructors below to init cache
- * - {@link #setOnImageSDCallbackListener(OnImageSDCallbackListener)} set callback interface after image get success
- * - {@link #get(String, List, View)} get image asynchronous and preload other images asynchronous according to
- * urlList
- * - {@link #get(String, View)} get image asynchronous
- * - {@link #setFileNameRule(FileNameRule)} set file name rule which be used when saving images, default is
- * {@link FileNameRuleImageUrl}
- * - {@link #setCacheFolder(String)} set cache folder path which be used when saving images, default is
- * {@link #DEFAULT_CACHE_FOLDER}
- * - {@link #setHttpReadTimeOut(int)} set http read image time out, if less than 0, not set. default is not set
- * - {@link #setOpenWaitingQueue(boolean)} set whether open waiting queue, default is true. If true, save all view
- * waiting for image loaded, else only save the newest one
- * - {@link PreloadDataCache#setOnGetDataListener(OnGetDataListener)} set how to get image, this cache will get image
- * and preload images by it
- * - {@link SimpleCache#setCacheFullRemoveType(CacheFullRemoveType)} set remove type when cache is full
- * - other see {@link PreloadDataCache} and {@link SimpleCache}
- *
- *
- * Constructor
- * - {@link #ImageSDCardCache()}
- * - {@link #ImageSDCardCache(int)}
- * - {@link #ImageSDCardCache(int, int)}
- *
- *
- * @author Trinea 2012-4-5
- */
-public class ImageSDCardCache extends PreloadDataCache {
-
- private static final long serialVersionUID = 1L;
-
- private static final String TAG = "ImageSDCardCache";
-
- /** callback interface after image get success **/
- private OnImageSDCallbackListener onImageSDCallbackListener;
- /** cache folder path which be used when saving images, default is {@link #DEFAULT_CACHE_FOLDER} **/
- private String cacheFolder = DEFAULT_CACHE_FOLDER;
- /** file name rule which be used when saving images, default is {@link FileNameRuleImageUrl} **/
- private FileNameRule fileNameRule = new FileNameRuleImageUrl();
- /** http read image time out, if less than 0, not set. default is not set **/
- private int httpReadTimeOut = -1;
- /**
- * whether open waiting queue, default is true. If true, save all view waiting for image loaded, else only save the
- * newest one
- **/
- private boolean isOpenWaitingQueue = true;
-
- /** recommend default max cache size according to dalvik max memory **/
- public static final int DEFAULT_MAX_SIZE = getDefaultMaxSize();
- /** cache folder path which be used when saving images **/
- public static final String DEFAULT_CACHE_FOLDER = Environment.getExternalStorageDirectory()
- .getAbsolutePath()
- + File.separator
- + "Trinea"
- + File.separator
- + "AndroidCommon"
- + File.separator + "ImageCache";
-
- /** image got success message what **/
- private static final int IMAGE_LOADED_WHAT = 1;
- /** image reloaded success message what **/
- private static final int IMAGE_RELOADED_WHAT = 2;
-
- /** thread pool whose wait for data got, attention, not the get data thread pool **/
- private transient ExecutorService threadPool = Executors.newFixedThreadPool(SystemUtils.DEFAULT_THREAD_POOL_SIZE);
- /**
- * key is image url, value is the newest view which waiting for image loaded, used when {@link #isOpenWaitingQueue}
- * is false
- **/
- private transient Map viewMap;
- /**
- * key is image url, value is view set those waiting for image loaded, used when {@link #isOpenWaitingQueue} is true
- **/
- private transient Map> viewSetMap;
- private transient Handler handler;
-
- /**
- * get image asynchronous. when get image success, it will pass to
- * {@link OnImageSDCallbackListener#onImageLoaded(String, String, View, boolean)}
- *
- * @param imageUrl
- * @param view
- * @return whether image already in cache or not
- */
- public boolean get(String imageUrl, View view) {
- return get(imageUrl, null, view);
- }
-
- /**
- * get image asynchronous and preload other images asynchronous according to urlList
- *
- * @param imageUrl
- * @param urlList url list, if is null, not preload, else preload forward by
- * {@link PreloadDataCache#preloadDataForward(Object, List, int)}, preload backward by
- * {@link PreloadDataCache#preloadDataBackward(Object, List, int)}
- * @param view
- * @return whether image already in cache or not
- */
- public boolean get(final String imageUrl, final List urlList, final View view) {
- if (StringUtils.isEmpty(imageUrl)) {
- return false;
- }
-
- /**
- * if already in cache, call onImageSDCallbackListener, else new thread to wait for it
- */
- CacheObject object = getFromCache(imageUrl, urlList);
- if (object != null) {
- String imagePath = object.getData();
- if (!StringUtils.isEmpty(imagePath) && FileUtils.isFileExist(imagePath)) {
- if (onImageSDCallbackListener != null) {
- onImageSDCallbackListener.onImageLoaded(imageUrl, imagePath, view, true);
- }
- return true;
- } else {
- remove(imageUrl);
- }
- }
-
- if (isOpenWaitingQueue) {
- synchronized (viewSetMap) {
- HashSet viewSet = viewSetMap.get(imageUrl);
- if (viewSet == null) {
- viewSet = new HashSet();
- viewSetMap.put(imageUrl, viewSet);
- }
- viewSet.add(view);
- }
- } else {
- viewMap.put(imageUrl, view);
- }
-
- if (isExistGettingDataThread(imageUrl)) {
- return false;
- }
-
- startGetImageThread(IMAGE_LOADED_WHAT, imageUrl, urlList);
- return false;
- }
-
- /**
- * get cache folder path which be used when saving images, default is {@link #DEFAULT_CACHE_FOLDER}
- *
- * @return the cacheFolder
- */
- public String getCacheFolder() {
- return cacheFolder;
- }
-
- /**
- * set cache folder path which be used when saving images, default is {@link #DEFAULT_CACHE_FOLDER}
- *
- * @param cacheFolder
- */
- public void setCacheFolder(String cacheFolder) {
- if (StringUtils.isEmpty(cacheFolder)) {
- throw new IllegalArgumentException("The cacheFolder of cache can not be null.");
- }
-
- this.cacheFolder = cacheFolder;
- }
-
- /**
- * get file name rule which be used when saving images, default is {@link FileNameRuleImageUrl}
- *
- * @return the fileNameRule
- */
- public FileNameRule getFileNameRule() {
- return fileNameRule;
- }
-
- /**
- * set file name rule which be used when saving images, default is {@link FileNameRuleImageUrl}
- *
- * @param fileNameRule
- */
- public void setFileNameRule(FileNameRule fileNameRule) {
- if (fileNameRule == null) {
- throw new IllegalArgumentException("The fileNameRule of cache can not be null.");
- }
- this.fileNameRule = fileNameRule;
- }
-
- /**
- * get callback interface after image get success
- *
- * @return the onImageSDCallbackListener
- */
- public OnImageSDCallbackListener getOnImageSDCallbackListener() {
- return onImageSDCallbackListener;
- }
-
- /**
- * set callback interface after image get success
- *
- * @param onImageSDCallbackListener the onImageSDCallbackListener to set
- */
- public void setOnImageSDCallbackListener(OnImageSDCallbackListener onImageSDCallbackListener) {
- this.onImageSDCallbackListener = onImageSDCallbackListener;
- }
-
- /**
- * get http read image time out, if less than 0, not set. default is not set
- *
- * @return the httpReadTimeOut
- */
- public int getHttpReadTimeOut() {
- return httpReadTimeOut;
- }
-
- /**
- * set http read image time out, if less than 0, not set. default is not set
- *
- * @param httpReadTimeOut
- */
- public void setHttpReadTimeOut(int httpReadTimeOut) {
- this.httpReadTimeOut = httpReadTimeOut;
- }
-
- /**
- * get whether open waiting queue, default is true. If true, save all view waiting for image loaded, else only save
- * the newest one
- *
- * @return
- */
- public boolean isOpenWaitingQueue() {
- return isOpenWaitingQueue;
- }
-
- /**
- * set whether open waiting queue, default is true. If true, save all view waiting for image loaded, else only save
- * the newest one
- *
- * @param isOpenWaitingQueue
- */
- public void setOpenWaitingQueue(boolean isOpenWaitingQueue) {
- this.isOpenWaitingQueue = isOpenWaitingQueue;
- }
-
- /**
- *
- * - Get data listener is {@link #getDefaultOnGetImageListener()}
- * - Callback interface after image get success is null, can set by
- * {@link #setOnImageSDCallbackListener(OnImageSDCallbackListener)}
- * - Maximum size of the cache is {@link #DEFAULT_MAX_SIZE}
- * - Elements of the cache will not invalid
- * - Remove type is {@link RemoveTypeUsedCountSmall} when cache is full
- *
- *
- * @see PreloadDataCache#PreloadDataCache()
- */
- public ImageSDCardCache(){
- this(DEFAULT_MAX_SIZE, PreloadDataCache.DEFAULT_THREAD_POOL_SIZE);
- }
-
- /**
- *
- * - Get data listener is {@link #getDefaultOnGetImageListener()}
- * - Callback interface after image get success is null, can set by
- * {@link #setOnImageSDCallbackListener(OnImageSDCallbackListener)}
- * - Elements of the cache will not invalid
- * - Remove type is {@link RemoveTypeUsedCountSmall} when cache is full
- *
- *
- * @param maxSize maximum size of the cache
- * @see PreloadDataCache#PreloadDataCache(int)
- */
- public ImageSDCardCache(int maxSize){
- this(maxSize, PreloadDataCache.DEFAULT_THREAD_POOL_SIZE);
- }
-
- /**
- *
- * - Get data listener is {@link #getDefaultOnGetImageListener()}
- * - Callback interface after image get success is null, can set by
- * {@link #setOnImageSDCallbackListener(OnImageSDCallbackListener)}
- * - Elements of the cache will not invalid
- * - Remove type is {@link RemoveTypeUsedCountSmall} when cache is full
- *
- *
- * @param maxSize maximum size of the cache
- * @param threadPoolSize getting data thread pool size
- * @see PreloadDataCache#PreloadDataCache(int, int)
- */
- public ImageSDCardCache(int maxSize, int threadPoolSize){
- super(maxSize, threadPoolSize);
-
- super.setOnGetDataListener(getDefaultOnGetImageListener());
- super.setCacheFullRemoveType(new RemoveTypeUsedCountSmall());
- this.viewMap = new ConcurrentHashMap();
- this.viewSetMap = new HashMap>();
- this.handler = new MyHandler();
- if (Looper.myLooper() == null) {
- Looper.prepare();
- }
- }
-
- /**
- * callback interface after image get success
- *
- * @author Trinea 2012-4-5
- */
- public interface OnImageSDCallbackListener extends Serializable {
-
- /**
- * callback function after image get success, run on ui thread
- *
- * @param imageUrl imageUrl
- * @param imagePath image path
- * @param view view need the image
- * @param isInCache whether already in cache or got realtime
- */
- public void onImageLoaded(String imageUrl, String imagePath, View view, boolean isInCache);
- }
-
- /**
- * @see ExecutorService#shutdown()
- */
- public void shutdown() {
- threadPool.shutdown();
- super.shutdown();
- }
-
- /**
- * @see ExecutorService#shutdownNow()
- */
- public List shutdownNow() {
- threadPool.shutdownNow();
- return super.shutdownNow();
- }
-
- /**
- * My handler
- *
- * @author Trinea 2012-11-20
- */
- private class MyHandler extends Handler {
-
- public void handleMessage(Message message) {
- switch (message.what) {
- case IMAGE_LOADED_WHAT:
- case IMAGE_RELOADED_WHAT:
- MessageObject object = (MessageObject)message.obj;
- if (object != null) {
- String imageUrl = object.imageUrl;
- String imagePath = object.imagePath;
-
- if (onImageSDCallbackListener != null) {
- if (isOpenWaitingQueue) {
- synchronized (viewSetMap) {
- HashSet viewSet = viewSetMap.get(imageUrl);
- if (viewSet != null) {
- for (View view : viewSet) {
- if (view != null) {
- onImageSDCallbackListener.onImageLoaded(imageUrl, imagePath, view,
- false);
- }
- }
- }
- }
- } else {
- View view = viewMap.get(imageUrl);
- if (view != null) {
- onImageSDCallbackListener.onImageLoaded(imageUrl, imagePath, view, false);
- }
- }
- }
-
- if (isOpenWaitingQueue) {
- synchronized (viewSetMap) {
- viewSetMap.remove(imageUrl);
- }
- } else {
- viewMap.remove(imageUrl);
- }
- }
- break;
- }
- }
- }
-
- /**
- * message object
- *
- * @author Trinea 2013-1-14
- */
- private class MessageObject {
-
- String imageUrl;
- String imagePath;
-
- public MessageObject(String imageUrl, String imagePath, List urlList){
- this.imageUrl = imageUrl;
- this.imagePath = imagePath;
- }
- }
-
- /**
- * start thread to wait for image get
- *
- * @param messsageWhat
- * @param imageUrl
- * @param urlList url list, if is null, not preload, else preload forward by
- * {@link PreloadDataCache#preloadDataForward(Object, List, int)}, preload backward by
- * {@link PreloadDataCache#preloadDataBackward(Object, List, int)}
- */
- private void startGetImageThread(final int messsageWhat, final String imageUrl, final List urlList) {
- // wait for image be got success and send message
- threadPool.execute(new Runnable() {
-
- @Override
- public void run() {
- CacheObject object = get(imageUrl, urlList);
- String imagePath = (object == null ? null : object.getData());
- // if image file not exist, remove it from cache and reload it
- if (StringUtils.isEmpty(imagePath) || !FileUtils.isFileExist(imagePath)) {
- remove(imageUrl);
- if (messsageWhat == IMAGE_LOADED_WHAT) {
- startGetImageThread(IMAGE_RELOADED_WHAT, imageUrl, urlList);
- }
- } else {
- handler.sendMessage(handler.obtainMessage(messsageWhat, new MessageObject(imageUrl, imagePath,
- urlList)));
- }
- }
- });
- }
-
- /**
- * delete file when full remove one
- */
- @Override
- protected CacheObject fullRemoveOne() {
- CacheObject o = super.fullRemoveOne();
- if (o != null) {
- deleteFile(o.getData());
- }
- return o;
- }
-
- /**
- * delete file when remove
- */
- @Override
- public CacheObject remove(String key) {
- CacheObject o = super.remove(key);
- if (o != null) {
- deleteFile(o.getData());
- }
- return o;
- }
-
- /**
- * delete file when clear cache
- */
- @Override
- public void clear() {
- for (CacheObject value : values()) {
- if (value != null) {
- deleteFile(value.getData());
- }
- }
- super.clear();
- }
-
- /**
- * delete file
- *
- * @param path
- * @return
- */
- private boolean deleteFile(String path) {
- if (!StringUtils.isEmpty(path)) {
- if (!FileUtils.deleteFile(path)) {
- Log.e(TAG, new StringBuilder().append("delete file fail, path is ").append(path).toString());
- return false;
- }
- }
- return true;
- }
-
- /**
- * default get image listener
- *
- * @return
- */
- public OnGetDataListener getDefaultOnGetImageListener() {
- return new OnGetDataListener() {
-
- private static final long serialVersionUID = 1L;
-
- @Override
- public CacheObject onGetData(String key) {
-
- String savePath = null;
- try {
- InputStream stream = ImageUtils.getInputStreamFromUrl(key, httpReadTimeOut);
- if (stream != null) {
- savePath = cacheFolder + File.separator + fileNameRule.getFileName(key);
- try {
- FileUtils.writeFile(savePath, stream);
- } catch (Exception e) {
- if (e.getCause() instanceof FileNotFoundException) {
- FileUtils.makeFolders(savePath);
- FileUtils.writeFile(savePath, stream);
- } else {
- Log.e(TAG,
- new StringBuilder().append("get drawable exception while write to file, imageUrl is: ")
- .append(key).append(", savePath is ").append(savePath)
- .toString(), e);
- savePath = null;
- }
- }
- }
- } catch (Exception e) {
- Log.e(TAG, new StringBuilder().append("get drawable exception, imageUrl is:").append(key)
- .toString(), e);
- }
-
- return (StringUtils.isEmpty(savePath) ? null : new CacheObject(savePath));
- }
- };
- }
-
- /**
- * get recommend default max cache size according to dalvik max memory
- *
- * @return
- */
- static int getDefaultMaxSize() {
- long maxMemory = Runtime.getRuntime().maxMemory();
- if (maxMemory > SizeUtils.GB_2_BYTE) {
- return 256;
- }
-
- int mb = (int)(maxMemory / SizeUtils.MB_2_BYTE);
- return mb > 8 ? mb : 8;
- }
-}
diff --git a/src/cn/trinea/android/common/util/JSONUtils.java b/src/cn/trinea/android/common/util/JSONUtils.java
deleted file mode 100644
index 0dcd1ba..0000000
--- a/src/cn/trinea/android/common/util/JSONUtils.java
+++ /dev/null
@@ -1,598 +0,0 @@
-package cn.trinea.android.common.util;
-
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-/**
- * Json Utils
- *
- * @author Trinea 2012-5-12
- */
-public class JSONUtils {
-
- /**
- * get Long from jsonObject
- *
- * @param jsonObject
- * @param key
- * @param defaultValue
- * @return
- * - if jsonObject is null, return defaultValue
- * - if key is null or empty, return defaultValue
- * - if {@link JSONObject#getLong(String)} exception, return defaultValue
- * - return {@link JSONObject#getLong(String)}
- *
- */
- public static Long getLong(JSONObject jsonObject, String key, Long defaultValue) {
- if (jsonObject == null || StringUtils.isEmpty(key)) {
- return defaultValue;
- }
-
- try {
- return jsonObject.getLong(key);
- } catch (JSONException e) {
- e.printStackTrace();
- return defaultValue;
- }
- }
-
- /**
- * get Long from jsonData
- *
- * @param jsonData
- * @param key
- * @param defaultValue
- * @return
- * - if jsonObject is null, return defaultValue
- * - if jsonData {@link JSONObject#JSONObject(String)} exception, return defaultValue
- * - return {@link JSONUtils#getLong(JSONObject, String, JSONObject)}
- *
- */
- public static Long getLong(String jsonData, String key, Long defaultValue) {
- if (StringUtils.isEmpty(jsonData)) {
- return defaultValue;
- }
-
- try {
- JSONObject jsonObject = new JSONObject(jsonData);
- return getLong(jsonObject, key, defaultValue);
- } catch (JSONException e) {
- e.printStackTrace();
- return defaultValue;
- }
- }
-
- /**
- * @param jsonObject
- * @param key
- * @param defaultValue
- * @return
- * @see {@link JSONUtils#getLong(JSONObject, String, Long)}
- */
- public static long getLong(JSONObject jsonObject, String key, long defaultValue) {
- return getLong(jsonObject, key, (Long)defaultValue);
- }
-
- /**
- * @param jsonData
- * @param key
- * @param defaultValue
- * @return
- * @see {@link JSONUtils#getLong(String, String, Long)}
- */
- public static long getLong(String jsonData, String key, long defaultValue) {
- return getLong(jsonData, key, (Long)defaultValue);
- }
-
- /**
- * get Int from jsonObject
- *
- * @param jsonObject
- * @param key
- * @param defaultValue
- * @return
- * - if jsonObject is null, return defaultValue
- * - if key is null or empty, return defaultValue
- * - if {@link JSONObject#getInt(String)} exception, return defaultValue
- * - return {@link JSONObject#getInt(String)}
- *
- */
- public static Integer getInt(JSONObject jsonObject, String key, Integer defaultValue) {
- if (jsonObject == null || StringUtils.isEmpty(key)) {
- return defaultValue;
- }
-
- try {
- return jsonObject.getInt(key);
- } catch (JSONException e) {
- e.printStackTrace();
- return defaultValue;
- }
- }
-
- /**
- * get Int from jsonData
- *
- * @param jsonData
- * @param key
- * @param defaultValue
- * @return
- * - if jsonObject is null, return defaultValue
- * - if jsonData {@link JSONObject#JSONObject(String)} exception, return defaultValue
- * - return {@link JSONUtils#getInt(JSONObject, String, JSONObject)}
- *
- */
- public static Integer getInt(String jsonData, String key, Integer defaultValue) {
- if (StringUtils.isEmpty(jsonData)) {
- return defaultValue;
- }
-
- try {
- JSONObject jsonObject = new JSONObject(jsonData);
- return getInt(jsonObject, key, defaultValue);
- } catch (JSONException e) {
- e.printStackTrace();
- return defaultValue;
- }
- }
-
- /**
- * @param jsonObject
- * @param key
- * @param defaultValue
- * @return
- * @see {@link JSONUtils#getInt(JSONObject, String, Integer)}
- */
- public static int getInt(JSONObject jsonObject, String key, int defaultValue) {
- return getInt(jsonObject, key, (Integer)defaultValue);
- }
-
- /**
- * @param jsonObject
- * @param key
- * @param defaultValue
- * @return
- * @see {@link JSONUtils#getInt(String, String, Integer)}
- */
- public static int getInt(String jsonData, String key, int defaultValue) {
- return getInt(jsonData, key, (Integer)defaultValue);
- }
-
- /**
- * get Double from jsonObject
- *
- * @param jsonObject
- * @param key
- * @param defaultValue
- * @return
- * - if jsonObject is null, return defaultValue
- * - if key is null or empty, return defaultValue
- * - if {@link JSONObject#getDouble(String)} exception, return defaultValue
- * - return {@link JSONObject#getDouble(String)}
- *
- */
- public static Double getDouble(JSONObject jsonObject, String key, Double defaultValue) {
- if (jsonObject == null || StringUtils.isEmpty(key)) {
- return defaultValue;
- }
-
- try {
- return jsonObject.getDouble(key);
- } catch (JSONException e) {
- e.printStackTrace();
- return defaultValue;
- }
- }
-
- /**
- * get Double from jsonData
- *
- * @param jsonData
- * @param key
- * @param defaultValue
- * @return
- * - if jsonObject is null, return defaultValue
- * - if jsonData {@link JSONObject#JSONObject(String)} exception, return defaultValue
- * - return {@link JSONUtils#getDouble(JSONObject, String, JSONObject)}
- *
- */
- public static Double getDouble(String jsonData, String key, Double defaultValue) {
- if (StringUtils.isEmpty(jsonData)) {
- return defaultValue;
- }
-
- try {
- JSONObject jsonObject = new JSONObject(jsonData);
- return getDouble(jsonObject, key, defaultValue);
- } catch (JSONException e) {
- e.printStackTrace();
- return defaultValue;
- }
- }
-
- /**
- * @param jsonObject
- * @param key
- * @param defaultValue
- * @return
- * @see {@link JSONUtils#getDouble(JSONObject, String, Double)}
- */
- public static double getDouble(JSONObject jsonObject, String key, double defaultValue) {
- return getDouble(jsonObject, key, (Double)defaultValue);
- }
-
- /**
- * @param jsonObject
- * @param key
- * @param defaultValue
- * @return
- * @see {@link JSONUtils#getDouble(String, String, Double)}
- */
- public static double getDouble(String jsonData, String key, double defaultValue) {
- return getDouble(jsonData, key, (Double)defaultValue);
- }
-
- /**
- * get String from jsonObject
- *
- * @param jsonObject
- * @param key
- * @param defaultValue
- * @return
- * - if jsonObject is null, return defaultValue
- * - if key is null or empty, return defaultValue
- * - if {@link JSONObject#getString(String)} exception, return defaultValue
- * - return {@link JSONObject#getString(String)}
- *
- */
- public static String getString(JSONObject jsonObject, String key, String defaultValue) {
- if (jsonObject == null || StringUtils.isEmpty(key)) {
- return defaultValue;
- }
-
- try {
- return jsonObject.getString(key);
- } catch (JSONException e) {
- e.printStackTrace();
- return defaultValue;
- }
- }
-
- /**
- * get String from jsonData
- *
- * @param jsonData
- * @param key
- * @param defaultValue
- * @return
- * - if jsonObject is null, return defaultValue
- * - if jsonData {@link JSONObject#JSONObject(String)} exception, return defaultValue
- * - return {@link JSONUtils#getString(JSONObject, String, JSONObject)}
- *
- */
- public static String getString(String jsonData, String key, String defaultValue) {
- if (StringUtils.isEmpty(jsonData)) {
- return defaultValue;
- }
-
- try {
- JSONObject jsonObject = new JSONObject(jsonData);
- return getString(jsonObject, key, defaultValue);
- } catch (JSONException e) {
- e.printStackTrace();
- return defaultValue;
- }
- }
-
- /**
- * get String array from jsonObject
- *
- * @param jsonObject
- * @param key
- * @param defaultValue
- * @return
- * - if jsonObject is null, return defaultValue
- * - if key is null or empty, return defaultValue
- * - if {@link JSONObject#getJSONArray(String)} exception, return defaultValue
- * - if {@link JSONArray#getString(int)} exception, return defaultValue
- * - return string array
- *
- */
- public static String[] getStringArray(JSONObject jsonObject, String key, String[] defaultValue) {
- if (jsonObject == null || StringUtils.isEmpty(key)) {
- return defaultValue;
- }
-
- try {
- JSONArray statusArray = jsonObject.getJSONArray(key);
- if (statusArray != null) {
- String[] value = new String[statusArray.length()];
- for (int i = 0; i < statusArray.length(); i++) {
- value[i] = statusArray.getString(i);
- }
- return value;
- }
- } catch (JSONException e) {
- e.printStackTrace();
- return defaultValue;
- }
- return defaultValue;
- }
-
- /**
- * get String array from jsonData
- *
- * @param jsonData
- * @param key
- * @param defaultValue
- * @return
- * - if jsonObject is null, return defaultValue
- * - if jsonData {@link JSONObject#JSONObject(String)} exception, return defaultValue
- * - return {@link JSONUtils#getStringArray(JSONObject, String, JSONObject)}
- *
- */
- public static String[] getStringArray(String jsonData, String key, String[] defaultValue) {
- if (StringUtils.isEmpty(jsonData)) {
- return defaultValue;
- }
-
- try {
- JSONObject jsonObject = new JSONObject(jsonData);
- return getStringArray(jsonObject, key, defaultValue);
- } catch (JSONException e) {
- e.printStackTrace();
- return defaultValue;
- }
- }
-
- /**
- * get JSONObject from jsonObject
- *
- * @param jsonObject
- * @param key
- * @param defaultValue
- * @return
- * - if jsonObject is null, return defaultValue
- * - if key is null or empty, return defaultValue
- * - if {@link JSONObject#getJSONObject(String)} exception, return defaultValue
- * - return {@link JSONObject#getJSONObject(String)}
- *
- */
- public static JSONObject getJSONObject(JSONObject jsonObject, String key, JSONObject defaultValue) {
- if (jsonObject == null || StringUtils.isEmpty(key)) {
- return defaultValue;
- }
-
- try {
- return jsonObject.getJSONObject(key);
- } catch (JSONException e) {
- e.printStackTrace();
- return defaultValue;
- }
- }
-
- /**
- * get JSONObject from jsonData
- *
- * @param jsonData
- * @param key
- * @param defaultValue
- * @return
- * - if jsonObject is null, return defaultValue
- * - if jsonData {@link JSONObject#JSONObject(String)} exception, return defaultValue
- * - return {@link JSONUtils#getJSONObject(JSONObject, String, JSONObject)}
- *
- */
- public static JSONObject getJSONObject(String jsonData, String key, JSONObject defaultValue) {
- if (StringUtils.isEmpty(jsonData)) {
- return defaultValue;
- }
-
- try {
- JSONObject jsonObject = new JSONObject(jsonData);
- return getJSONObject(jsonObject, key, defaultValue);
- } catch (JSONException e) {
- e.printStackTrace();
- return defaultValue;
- }
- }
-
- /**
- * get JSONArray from jsonObject
- *
- * @param jsonObject
- * @param key
- * @param defaultValue
- * @return
- * - if jsonObject is null, return defaultValue
- * - if key is null or empty, return defaultValue
- * - if {@link JSONObject#getJSONArray(String)} exception, return defaultValue
- * - return {@link JSONObject#getJSONArray(String)}
- *
- */
- public static JSONArray getJSONArray(JSONObject jsonObject, String key, JSONArray defaultValue) {
- if (jsonObject == null || StringUtils.isEmpty(key)) {
- return defaultValue;
- }
-
- try {
- return jsonObject.getJSONArray(key);
- } catch (JSONException e) {
- e.printStackTrace();
- return defaultValue;
- }
- }
-
- /**
- * get JSONArray from jsonData
- *
- * @param jsonData
- * @param key
- * @param defaultValue
- * @return
- * - if jsonObject is null, return defaultValue
- * - if jsonData {@link JSONObject#JSONObject(String)} exception, return defaultValue
- * - return {@link JSONUtils#getJSONArray(JSONObject, String, JSONObject)}
- *
- */
- public static JSONArray getJSONArray(String jsonData, String key, JSONArray defaultValue) {
- if (StringUtils.isEmpty(jsonData)) {
- return defaultValue;
- }
-
- try {
- JSONObject jsonObject = new JSONObject(jsonData);
- return getJSONArray(jsonObject, key, defaultValue);
- } catch (JSONException e) {
- e.printStackTrace();
- return defaultValue;
- }
- }
-
- /**
- * get Boolean from jsonObject
- *
- * @param jsonObject
- * @param key
- * @param defaultValue
- * @return
- * - if jsonObject is null, return defaultValue
- * - if key is null or empty, return defaultValue
- * - return {@link JSONObject#getBoolean(String)}
- *
- */
- public static boolean getBoolean(JSONObject jsonObject, String key, Boolean defaultValue) {
- if (jsonObject == null || StringUtils.isEmpty(key)) {
- return defaultValue;
- }
-
- try {
- return jsonObject.getBoolean(key);
- } catch (JSONException e) {
- e.printStackTrace();
- return defaultValue;
- }
- }
-
- /**
- * get Boolean from jsonData
- *
- * @param jsonData
- * @param key
- * @param defaultValue
- * @return
- * - if jsonObject is null, return defaultValue
- * - if jsonData {@link JSONObject#JSONObject(String)} exception, return defaultValue
- * - return {@link JSONUtils#getBoolean(JSONObject, String, Boolean)}
- *
- */
- public static boolean getBoolean(String jsonData, String key, Boolean defaultValue) {
- if (StringUtils.isEmpty(jsonData)) {
- return defaultValue;
- }
-
- try {
- JSONObject jsonObject = new JSONObject(jsonData);
- return getBoolean(jsonObject, key, defaultValue);
- } catch (JSONException e) {
- e.printStackTrace();
- return defaultValue;
- }
- }
-
- /**
- * get map from jsonObject.
- *
- * @param jsonObject key-value pairs json
- * @param key
- * @return
- * - if jsonObject is null, return null
- * - return {@link JSONUtils#parseKeyAndValueToMap(String)}
- *
- */
- public static Map getMap(JSONObject jsonObject, String key) {
- return JSONUtils.parseKeyAndValueToMap(JSONUtils.getString(jsonObject, key, null));
- }
-
- /**
- * get map from jsonData.
- *
- * @param jsonData key-value pairs string
- * @param key
- * @return
- * - if jsonData is null, return null
- * - if jsonData length is 0, return empty map
- * - if jsonData {@link JSONObject#JSONObject(String)} exception, return null
- * - return {@link JSONUtils#getMap(JSONObject, String)}
- *
- */
- public static Map getMap(String jsonData, String key) {
-
- if (jsonData == null) {
- return null;
- } else if (jsonData.length() == 0) {
- return new HashMap();
- }
-
- try {
- JSONObject jsonObject = new JSONObject(jsonData);
- return getMap(jsonObject, key);
- } catch (JSONException e) {
- e.printStackTrace();
- return null;
- }
- }
-
- /**
- * parse key-value pairs to map. ignore empty key, if getValue exception, put empty value
- *
- * @param sourceObj key-value pairs json
- * @return
- * - if sourceObj is null, return null
- * - else parse entry by {@link MapUtils#putMapNotEmptyKey(Map, String, String)} one by one
- *
- */
- @SuppressWarnings("rawtypes")
- public static Map parseKeyAndValueToMap(JSONObject sourceObj) {
- if (sourceObj == null) {
- return null;
- }
-
- Map keyAndValueMap = new HashMap();
- for (Iterator iter = sourceObj.keys(); iter.hasNext();) {
- String key = (String)iter.next();
- MapUtils.putMapNotEmptyKey(keyAndValueMap, key, getString(sourceObj, key, ""));
-
- }
- return keyAndValueMap;
- }
-
- /**
- * parse key-value pairs to map. ignore empty key, if getValue exception, put empty value
- *
- * @param source key-value pairs json
- * @return
- * - if source is null or source's length is 0, return empty map
- * - if source {@link JSONObject#JSONObject(String)} exception, return null
- * - return {@link JSONUtils#parseKeyAndValueToMap(JSONObject)}
- *
- */
- public static Map parseKeyAndValueToMap(String source) {
- if (StringUtils.isEmpty(source)) {
- return null;
- }
-
- try {
- JSONObject jsonObject = new JSONObject(source);
- return parseKeyAndValueToMap(jsonObject);
- } catch (JSONException e) {
- e.printStackTrace();
- return null;
- }
- }
-}
diff --git a/src/cn/trinea/android/common/util/PackageUtils.java b/src/cn/trinea/android/common/util/PackageUtils.java
deleted file mode 100644
index 2098517..0000000
--- a/src/cn/trinea/android/common/util/PackageUtils.java
+++ /dev/null
@@ -1,383 +0,0 @@
-package cn.trinea.android.common.util;
-
-import java.io.File;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.net.Uri;
-import android.util.Log;
-
-import cn.trinea.android.common.util.ShellUtils.CommandResult;
-
-/**
- * PackageUtils
- *
- * Install package
- * - {@link PackageUtils#installNormal(Context, String)}
- * - {@link PackageUtils#installSilent(Context, String)}
- * - {@link PackageUtils#install(Context, String)}
- *
- *
- * Is system application
- * - {@link PackageUtils#isSystemApplication(Context)}
- * - {@link PackageUtils#isSystemApplication(Context, String)}
- * - {@link PackageUtils#isSystemApplication(PackageManager, String)}
- *
- *
- * @author Trinea 2013-5-15
- */
-public class PackageUtils {
-
- public static final String TAG = "PackageUtils";
-
- /**
- * install according conditions
- *
- * - if system application or rooted, see {@link #installSilent(Context, String)}
- * - else see {@link #installNormal(Context, String)}
- *
- *
- * @param context
- * @param filePath
- * @return
- */
- public static final int install(Context context, String filePath) {
- if (!PackageUtils.isSystemApplication(context)) {
- boolean isRoot = ShellUtils.checkRootPermission();
- if (!isRoot) {
- return installNormal(context, filePath) ? INSTALL_SUCCEEDED : INSTALL_FAILED_INVALID_URI;
- }
- }
-
- return installSilent(context, filePath);
- }
-
- /**
- * install package normal by system intent
- *
- * @param context
- * @param filePath file path of package
- * @return whether apk exist
- */
- public static boolean installNormal(Context context, String filePath) {
- Intent i = new Intent(Intent.ACTION_VIEW);
- File file = new File(filePath);
- if (file != null && file.length() > 0 && file.exists() && file.isFile()) {
- i.setDataAndType(Uri.parse("file://" + filePath), "application/vnd.android.package-archive");
- i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- context.startActivity(i);
- return true;
- }
- return false;
- }
-
- /**
- * install package silent by root
- *
- * Attentions:
- * - Don't call this on the ui thread, it costs some times.
- * - You should add android.permission.INSTALL_PACKAGES in manifest, so no need to request root
- * permission, if you are system app.
- *
- *
- * @param context file path of package
- * @param filePath file path of package
- * @return {@link PackageUtils#INSTALL_SUCCEEDED} means install success, other means failed. details see
- * {@link PackageUtils#INSTALL_FAILED_*}
- */
- public static int installSilent(Context context, String filePath) {
- if (filePath == null || filePath.length() == 0) {
- return INSTALL_FAILED_INVALID_URI;
- }
-
- File file = new File(filePath);
- if (file == null || file.length() <= 0 || !file.exists() || !file.isFile()) {
- return INSTALL_FAILED_INVALID_URI;
- }
-
- /**
- * if context is system app, don,t need root permission, but should add in mainfest
- **/
- StringBuilder command = new StringBuilder().append("pm install -r ").append(filePath.replace(" ", "\\ "));
- CommandResult commandResult = ShellUtils.execCommand(command.toString(), !isSystemApplication(context), true);
- if (commandResult.successMsg != null
- && (commandResult.successMsg.contains("Success") || commandResult.successMsg.contains("success"))) {
- return INSTALL_SUCCEEDED;
- }
-
- Log.e(TAG, new StringBuilder().append("successMsg:").append(commandResult.successMsg).append(", ErrorMsg:")
- .append(commandResult.errorMsg).toString());
- if (commandResult.errorMsg != null) {
- if (commandResult.errorMsg.contains("INSTALL_FAILED_ALREADY_EXISTS")) {
- return INSTALL_FAILED_ALREADY_EXISTS;
- } else if (commandResult.errorMsg.contains("INSTALL_FAILED_INVALID_APK")) {
- return INSTALL_FAILED_INVALID_APK;
- } else if (commandResult.errorMsg.contains("INSTALL_FAILED_INVALID_URI")) {
- return INSTALL_FAILED_INVALID_URI;
- } else if (commandResult.errorMsg.contains("INSTALL_FAILED_INSUFFICIENT_STORAGE")) {
- return INSTALL_FAILED_INSUFFICIENT_STORAGE;
- } else if (commandResult.errorMsg.contains("INSTALL_FAILED_DUPLICATE_PACKAGE")) {
- return INSTALL_FAILED_DUPLICATE_PACKAGE;
- } else if (commandResult.errorMsg.contains("INSTALL_FAILED_NO_SHARED_USER")) {
- return INSTALL_FAILED_NO_SHARED_USER;
- } else if (commandResult.errorMsg.contains("INSTALL_FAILED_UPDATE_INCOMPATIBLE")) {
- return INSTALL_FAILED_UPDATE_INCOMPATIBLE;
- } else if (commandResult.errorMsg.contains("INSTALL_FAILED_SHARED_USER_INCOMPATIBLE")) {
- return INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
- } else if (commandResult.errorMsg.contains("INSTALL_FAILED_MISSING_SHARED_LIBRARY")) {
- return INSTALL_FAILED_MISSING_SHARED_LIBRARY;
- } else if (commandResult.errorMsg.contains("INSTALL_FAILED_REPLACE_COULDNT_DELETE")) {
- return INSTALL_FAILED_REPLACE_COULDNT_DELETE;
- } else if (commandResult.errorMsg.contains("INSTALL_FAILED_DEXOPT")) {
- return INSTALL_FAILED_DEXOPT;
- } else if (commandResult.errorMsg.contains("INSTALL_FAILED_OLDER_SDK")) {
- return INSTALL_FAILED_OLDER_SDK;
- } else if (commandResult.errorMsg.contains("INSTALL_FAILED_CONFLICTING_PROVIDER")) {
- return INSTALL_FAILED_CONFLICTING_PROVIDER;
- } else if (commandResult.errorMsg.contains("INSTALL_FAILED_NEWER_SDK")) {
- return INSTALL_FAILED_NEWER_SDK;
- } else if (commandResult.errorMsg.contains("INSTALL_FAILED_TEST_ONLY")) {
- return INSTALL_FAILED_TEST_ONLY;
- } else if (commandResult.errorMsg.contains("INSTALL_FAILED_CPU_ABI_INCOMPATIBLE")) {
- return INSTALL_FAILED_CPU_ABI_INCOMPATIBLE;
- } else if (commandResult.errorMsg.contains("INSTALL_FAILED_MISSING_FEATURE")) {
- return INSTALL_FAILED_MISSING_FEATURE;
- } else if (commandResult.errorMsg.contains("INSTALL_FAILED_CONTAINER_ERROR")) {
- return INSTALL_FAILED_CONTAINER_ERROR;
- } else if (commandResult.errorMsg.contains("INSTALL_FAILED_INVALID_INSTALL_LOCATION")) {
- return INSTALL_FAILED_INVALID_INSTALL_LOCATION;
- } else if (commandResult.errorMsg.contains("INSTALL_FAILED_MEDIA_UNAVAILABLE")) {
- return INSTALL_FAILED_MEDIA_UNAVAILABLE;
- } else if (commandResult.errorMsg.contains("INSTALL_FAILED_VERIFICATION_TIMEOUT")) {
- return INSTALL_FAILED_VERIFICATION_TIMEOUT;
- } else if (commandResult.errorMsg.contains("INSTALL_FAILED_VERIFICATION_FAILURE")) {
- return INSTALL_FAILED_VERIFICATION_FAILURE;
- } else if (commandResult.errorMsg.contains("INSTALL_FAILED_PACKAGE_CHANGED")) {
- return INSTALL_FAILED_PACKAGE_CHANGED;
- } else if (commandResult.errorMsg.contains("INSTALL_FAILED_UID_CHANGED")) {
- return INSTALL_FAILED_UID_CHANGED;
- }
- }
- return INSTALL_FAILED_OTHER;
- }
-
- /**
- * whether context is system application
- *
- * @param context
- * @return
- */
- public static boolean isSystemApplication(Context context) {
- if (context == null) {
- return false;
- }
-
- return isSystemApplication(context, context.getPackageName());
- }
-
- /**
- * whether packageName is system application
- *
- * @param context
- * @param packageName
- * @return
- */
- public static boolean isSystemApplication(Context context, String packageName) {
- if (context == null) {
- return false;
- }
-
- return isSystemApplication(context.getPackageManager(), packageName);
- }
-
- /**
- * whether packageName is system application
- *
- * @param packageManager
- * @param packageName
- * @return
- * - if packageManager is null, return false
- * - if package name is null or is empty, return false
- * - if package name not exit, return false
- * - if package name exit, but not system app, return false
- * - else return true
- *
- */
- public static boolean isSystemApplication(PackageManager packageManager, String packageName) {
- if (packageManager == null || packageName == null || packageName.length() == 0) {
- return false;
- }
-
- try {
- ApplicationInfo app = packageManager.getApplicationInfo(packageName, 0);
- return (app != null && (app.flags & ApplicationInfo.FLAG_SYSTEM) > 0);
- } catch (NameNotFoundException e) {
- e.printStackTrace();
- }
- return false;
- }
-
- /**
- * Installation return code
- * install success.
- */
- public static final int INSTALL_SUCCEEDED = 1;
- /**
- * Installation return code
- * the package is already installed.
- */
- public static final int INSTALL_FAILED_ALREADY_EXISTS = -1;
-
- /**
- * Installation return code
- * the package archive file is invalid.
- */
- public static final int INSTALL_FAILED_INVALID_APK = -2;
-
- /**
- * Installation return code
- * the URI passed in is invalid.
- */
- public static final int INSTALL_FAILED_INVALID_URI = -3;
-
- /**
- * Installation return code
- * the package manager service found that the device didn't have enough storage space to install the app.
- */
- public static final int INSTALL_FAILED_INSUFFICIENT_STORAGE = -4;
-
- /**
- * Installation return code
- * a package is already installed with the same name.
- */
- public static final int INSTALL_FAILED_DUPLICATE_PACKAGE = -5;
-
- /**
- * Installation return code
- * the requested shared user does not exist.
- */
- public static final int INSTALL_FAILED_NO_SHARED_USER = -6;
-
- /**
- * Installation return code
- * a previously installed package of the same name has a different signature than the new package (and the old
- * package's data was not removed).
- */
- public static final int INSTALL_FAILED_UPDATE_INCOMPATIBLE = -7;
-
- /**
- * Installation return code
- * the new package is requested a shared user which is already installed on the device and does not have matching
- * signature.
- */
- public static final int INSTALL_FAILED_SHARED_USER_INCOMPATIBLE = -8;
-
- /**
- * Installation return code
- * the new package uses a shared library that is not available.
- */
- public static final int INSTALL_FAILED_MISSING_SHARED_LIBRARY = -9;
-
- /**
- * Installation return code
- * the new package uses a shared library that is not available.
- */
- public static final int INSTALL_FAILED_REPLACE_COULDNT_DELETE = -10;
-
- /**
- * Installation return code
- * the new package failed while optimizing and validating its dex files, either because there was not enough storage
- * or the validation failed.
- */
- public static final int INSTALL_FAILED_DEXOPT = -11;
-
- /**
- * Installation return code
- * the new package failed because the current SDK version is older than that required by the package.
- */
- public static final int INSTALL_FAILED_OLDER_SDK = -12;
-
- /**
- * Installation return code
- * the new package failed because it contains a content provider with the same authority as a provider already
- * installed in the system.
- */
- public static final int INSTALL_FAILED_CONFLICTING_PROVIDER = -13;
-
- /**
- * Installation return code
- * the new package failed because the current SDK version is newer than that required by the package.
- */
- public static final int INSTALL_FAILED_NEWER_SDK = -14;
-
- /**
- * Installation return code
- * the new package failed because it has specified that it is a test-only package and the caller has not supplied
- * the {@link #INSTALL_ALLOW_TEST} flag.
- */
- public static final int INSTALL_FAILED_TEST_ONLY = -15;
-
- /**
- * Installation return code
- * the package being installed contains native code, but none that is compatible with the the device's CPU_ABI.
- */
- public static final int INSTALL_FAILED_CPU_ABI_INCOMPATIBLE = -16;
-
- /**
- * Installation return code
- * the new package uses a feature that is not available.
- */
- public static final int INSTALL_FAILED_MISSING_FEATURE = -17;
-
- /**
- * Installation return code
- * a secure container mount point couldn't be accessed on external media.
- */
- public static final int INSTALL_FAILED_CONTAINER_ERROR = -18;
-
- /**
- * Installation return code
- * the new package couldn't be installed in the specified install location.
- */
- public static final int INSTALL_FAILED_INVALID_INSTALL_LOCATION = -19;
-
- /**
- * Installation return code
- * the new package couldn't be installed in the specified install location because the media is not available.
- */
- public static final int INSTALL_FAILED_MEDIA_UNAVAILABLE = -20;
-
- /**
- * Installation return code
- * the new package couldn't be installed because the verification timed out.
- */
- public static final int INSTALL_FAILED_VERIFICATION_TIMEOUT = -21;
-
- /**
- * Installation return code
- * the new package couldn't be installed because the verification did not succeed.
- */
- public static final int INSTALL_FAILED_VERIFICATION_FAILURE = -22;
-
- /**
- * Installation return code
- * the package changed from what the calling program expected.
- */
- public static final int INSTALL_FAILED_PACKAGE_CHANGED = -23;
-
- /**
- * Installation return code
- * the new package is assigned a different UID than it previously held.
- */
- public static final int INSTALL_FAILED_UID_CHANGED = -24;
-
- /**
- * Installation return code
- * other reason
- */
- public static final int INSTALL_FAILED_OTHER = -1000000;
-}
diff --git a/src/cn/trinea/android/common/util/RandomUtils.java b/src/cn/trinea/android/common/util/RandomUtils.java
deleted file mode 100644
index a705399..0000000
--- a/src/cn/trinea/android/common/util/RandomUtils.java
+++ /dev/null
@@ -1,109 +0,0 @@
-package cn.trinea.android.common.util;
-
-import java.util.Random;
-
-/**
- * Random Utils
- *
- * @author Trinea 2012-5-12
- */
-public class RandomUtils {
-
- public static final String NUMBERS_AND_LETTERS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
- public static final String NUMBERS = "0123456789";
- public static final String LETTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
- public static final String CAPITAL_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
- public static final String LOWER_CASE_LETTERS = "abcdefghijklmnopqrstuvwxyz";
-
- /**
- * get a fixed-length random string, its a mixture of uppercase, lowercase letters and numbers
- *
- * @param length
- * @return
- * @see {@link RandomUtils#getRandom(String source, int length)}
- */
- public static String getRandomNumbersAndLetters(int length) {
- return getRandom(NUMBERS_AND_LETTERS, length);
- }
-
- /**
- * get a fixed-length random string, its a mixture of numbers
- *
- * @param length
- * @return
- * @see {@link RandomUtils#getRandom(String source, int length)}
- */
- public static String getRandomNumbers(int length) {
- return getRandom(NUMBERS, length);
- }
-
- /**
- * get a fixed-length random string, its a mixture of uppercase and lowercase letters
- *
- * @param length
- * @return
- * @see {@link RandomUtils#getRandom(String source, int length)}
- */
- public static String getRandomLetters(int length) {
- return getRandom(LETTERS, length);
- }
-
- /**
- * get a fixed-length random string, its a mixture of uppercase letters
- *
- * @param length
- * @return
- * @see {@link RandomUtils#getRandom(String source, int length)}
- */
- public static String getRandomCapitalLetters(int length) {
- return getRandom(CAPITAL_LETTERS, length);
- }
-
- /**
- * get a fixed-length random string, its a mixture of lowercase letters
- *
- * @param length
- * @return
- * @see {@link RandomUtils#getRandom(String source, int length)}
- */
- public static String getRandomLowerCaseLetters(int length) {
- return getRandom(LOWER_CASE_LETTERS, length);
- }
-
- /**
- * get a fixed-length random string, its a mixture of chars in source
- *
- * @param source
- * @param length
- * @return
- * - if source is null or empty, return null
- * - else see {@link RandomUtils#getRandom(char[] sourceChar, int length)}
- *
- */
- public static String getRandom(String source, int length) {
- return StringUtils.isEmpty(source) ? null : getRandom(source.toCharArray(), length);
- }
-
- /**
- * get a fixed-length random string, its a mixture of chars in sourceChar
- *
- * @param sourceChar
- * @param length
- * @return
- * - if sourceChar is null or empty, return null
- * - if length less than 0, return null
- *
- */
- public static String getRandom(char[] sourceChar, int length) {
- if (sourceChar == null || sourceChar.length == 0 || length < 0) {
- return null;
- }
-
- StringBuilder str = new StringBuilder(length);
- Random random = new Random();
- for (int i = 0; i < length; i++) {
- str.append(sourceChar[random.nextInt(sourceChar.length)]);
- }
- return str.toString();
- }
-}
diff --git a/src/cn/trinea/android/common/util/ResourceUtils.java b/src/cn/trinea/android/common/util/ResourceUtils.java
deleted file mode 100644
index 9ab6040..0000000
--- a/src/cn/trinea/android/common/util/ResourceUtils.java
+++ /dev/null
@@ -1,74 +0,0 @@
-package cn.trinea.android.common.util;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-
-import cn.trinea.android.common.util.StringUtils;
-
-import android.content.Context;
-
-/**
- * ResourceUtils
- *
- * @author Trinea 2012-5-26
- */
-public class ResourceUtils {
-
- /**
- * get an asset using ACCESS_STREAMING mode. This provides access to files that have been bundled with an
- * application as assets -- that is, files placed in to the "assets" directory.
- *
- * @param context
- * @param fileName The name of the asset to open. This name can be hierarchical.
- * @return
- */
- public static String geFileFromAssets(Context context, String fileName) {
- if (context == null || StringUtils.isEmpty(fileName)) {
- return null;
- }
-
- StringBuilder s = new StringBuilder("");
- try {
- InputStreamReader in = new InputStreamReader(context.getResources().getAssets().open(fileName));
- BufferedReader br = new BufferedReader(in);
- String line;
- while ((line = br.readLine()) != null) {
- s.append(line);
- }
- return s.toString();
- } catch (IOException e) {
- e.printStackTrace();
- return null;
- }
- }
-
- /**
- * get a data stream for reading a raw resource. This can only be used with resources whose value is the name of an
- * asset files -- that is, it can be used to open drawable, sound, and raw resources; it will fail on string and
- * color resources.
- *
- * @param context
- * @param resId The resource identifier to open, as generated by the appt tool.
- * @return
- */
- public static String geFileFromRaw(Context context, int resId) {
- if (context == null) {
- return null;
- }
-
- StringBuilder s = new StringBuilder();
- try {
- InputStreamReader in = new InputStreamReader(context.getResources().openRawResource(resId));
- BufferedReader br = new BufferedReader(in);
- String line;
- while ((line = br.readLine()) != null) {
- s.append(line);
- }
- return s.toString();
- } catch (IOException e) {
- e.printStackTrace();
- return null;
- }
- }
-}
diff --git a/src/cn/trinea/android/common/util/ShellUtils.java b/src/cn/trinea/android/common/util/ShellUtils.java
deleted file mode 100644
index 212d01a..0000000
--- a/src/cn/trinea/android/common/util/ShellUtils.java
+++ /dev/null
@@ -1,218 +0,0 @@
-package cn.trinea.android.common.util;
-
-import java.io.BufferedReader;
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.util.List;
-
-/**
- * ShellUtils
- *
- * Check root
- * - {@link ShellUtils#checkRootPermission()}
- *
- *
- * Execte command
- * - {@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
- *
- * @return
- */
- 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
- * @return
- * @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
- * @return
- * @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
- * @return
- * @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
- * @return
- * @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
- * @return
- * @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/AndroidManifest.xml b/src/main/AndroidManifest.xml
similarity index 65%
rename from AndroidManifest.xml
rename to src/main/AndroidManifest.xml
index c6d9fa9..fbadcfd 100644
--- a/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -1,10 +1,9 @@
+ android:versionCode="30"
+ android:versionName="4.2.14" >
-
diff --git a/src/main/java/cn/trinea/android/common/annotation/NotProguard.java b/src/main/java/cn/trinea/android/common/annotation/NotProguard.java
new file mode 100644
index 0000000..40222fd
--- /dev/null
+++ b/src/main/java/cn/trinea/android/common/annotation/NotProguard.java
@@ -0,0 +1,17 @@
+package cn.trinea.android.common.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * NotProguard, Means not proguard something, like class, method, field
+ *
+ * @author Trinea 2015-08-07
+ */
+@Retention(RetentionPolicy.CLASS)
+@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD})
+public @interface NotProguard {
+
+}
\ No newline at end of file
diff --git a/src/main/java/cn/trinea/android/common/constant/DbConstants.java b/src/main/java/cn/trinea/android/common/constant/DbConstants.java
new file mode 100644
index 0000000..8adde5d
--- /dev/null
+++ b/src/main/java/cn/trinea/android/common/constant/DbConstants.java
@@ -0,0 +1,110 @@
+package cn.trinea.android.common.constant;
+
+/**
+ * Some constants about db
+ *
+ * @author Trinea 2013-10-21
+ */
+public class DbConstants {
+
+ public static final String DB_NAME = "trinea_android_common.db";
+ public static final int DB_VERSION = 1;
+
+ private static final String TERMINATOR = ";";
+
+ /** image sdcard cache table **/
+ public static final StringBuffer CREATE_IMAGE_SDCARD_CACHE_TABLE_SQL = new StringBuffer();
+ public static final StringBuffer CREATE_IMAGE_SDCARD_CACHE_TABLE_INDEX_SQL = new StringBuffer();
+ public static final String IMAGE_SDCARD_CACHE_TABLE_TABLE_NAME = "image_sdcard_cache";
+ public static final String IMAGE_SDCARD_CACHE_TABLE_ID = android.provider.BaseColumns._ID;
+ public static final String IMAGE_SDCARD_CACHE_TABLE_TAG = "tag";
+ public static final String IMAGE_SDCARD_CACHE_TABLE_URL = "url";
+ public static final String IMAGE_SDCARD_CACHE_TABLE_PATH = "path";
+ public static final String IMAGE_SDCARD_CACHE_TABLE_ENTER_TIME = "enter_time";
+ public static final String IMAGE_SDCARD_CACHE_TABLE_LAST_USED_TIME = "last_used_time";
+ public static final String IMAGE_SDCARD_CACHE_TABLE_USED_COUNT = "used_count";
+ public static final String IMAGE_SDCARD_CACHE_TABLE_PRIORITY = "priority";
+ public static final String IMAGE_SDCARD_CACHE_TABLE_IS_EXPIRED = "is_expired";
+ public static final String IMAGE_SDCARD_CACHE_TABLE_IS_FOREVER = "is_forever";
+
+ public static final String IMAGE_SDCARD_CACHE_TABLE_INDEX_TAG = "image_sdcard_cache_table_index_tag";
+ public static final String IMAGE_SDCARD_CACHE_TABLE_INDEX_URL = "image_sdcard_cache_table_index_url";
+
+ public static final int IMAGE_SDCARD_CACHE_TABLE_ID_INDEX = 0;
+ public static final int IMAGE_SDCARD_CACHE_TABLE_TAG_INDEX = 1;
+ public static final int IMAGE_SDCARD_CACHE_TABLE_URL_INDEX = 2;
+ public static final int IMAGE_SDCARD_CACHE_TABLE_PATH_INDEX = 3;
+ public static final int IMAGE_SDCARD_CACHE_TABLE_ENTER_TIME_INDEX = 4;
+ public static final int IMAGE_SDCARD_CACHE_TABLE_LAST_USED_TIME_INDEX = 5;
+ public static final int IMAGE_SDCARD_CACHE_TABLE_USED_COUNT_INDEX = 6;
+ public static final int IMAGE_SDCARD_CACHE_TABLE_PRIORITY_INDEX = 7;
+ public static final int IMAGE_SDCARD_CACHE_TABLE_IS_EXPIRED_INDEX = 8;
+ public static final int IMAGE_SDCARD_CACHE_TABLE_IS_FOREVER_INDEX = 9;
+
+ /** http response cache table **/
+ public static final StringBuffer CREATE_HTTP_CACHE_TABLE_SQL = new StringBuffer();
+ public static final StringBuffer CREATE_HTTP_CACHE_TABLE_INDEX_SQL = new StringBuffer();
+ public static final StringBuffer CREATE_HTTP_CACHE_TABLE_UNIQUE_INDEX = new StringBuffer();
+ public static final String HTTP_CACHE_TABLE_TABLE_NAME = "http_cache";
+ public static final String HTTP_CACHE_TABLE_ID = android.provider.BaseColumns._ID;
+ public static final String HTTP_CACHE_TABLE_URL = "url";
+ public static final String HTTP_CACHE_TABLE_RESPONSE = "response";
+ public static final String HTTP_CACHE_TABLE_EXPIRES = "expires";
+ public static final String HTTP_CACHE_TABLE_CREATE_TIME = "gmt_create";
+ public static final String HTTP_CACHE_TABLE_TYPE = "type";
+
+ public static final String HTTP_CACHE_TABLE_UNIQUE_INDEX_URL = "http_cache_table_unique_index_url";
+ public static final String HTTP_CACHE_TABLE_INDEX_TYPE = "http_cache_table_index_type";
+
+ public static final int HTTP_CACHE_TABLE_ID_INDEX = 0;
+ public static final int HTTP_CACHE_TABLE_URL_INDEX = 1;
+ public static final int HTTP_CACHE_TABLE_RESPONSE_INDEX = 2;
+ public static final int HTTP_CACHE_TABLE_EXPIRES_INDEX = 3;
+ public static final int HTTP_CACHE_TABLE_CREATE_TIME_INDEX = 4;
+ public static final int HTTP_CACHE_TABLE_TYPE_INDEX = 5;
+
+ static {
+ /**
+ * sql to image sdcard cache table
+ **/
+ CREATE_IMAGE_SDCARD_CACHE_TABLE_SQL.append("CREATE TABLE ").append(IMAGE_SDCARD_CACHE_TABLE_TABLE_NAME);
+ CREATE_IMAGE_SDCARD_CACHE_TABLE_SQL.append(" (").append(IMAGE_SDCARD_CACHE_TABLE_ID)
+ .append(" integer primary key autoincrement,");
+ CREATE_IMAGE_SDCARD_CACHE_TABLE_SQL.append(IMAGE_SDCARD_CACHE_TABLE_TAG).append(" text,");
+ CREATE_IMAGE_SDCARD_CACHE_TABLE_SQL.append(IMAGE_SDCARD_CACHE_TABLE_URL).append(" text,");
+ CREATE_IMAGE_SDCARD_CACHE_TABLE_SQL.append(IMAGE_SDCARD_CACHE_TABLE_PATH).append(" text,");
+ CREATE_IMAGE_SDCARD_CACHE_TABLE_SQL.append(IMAGE_SDCARD_CACHE_TABLE_ENTER_TIME).append(" integer,");
+ CREATE_IMAGE_SDCARD_CACHE_TABLE_SQL.append(IMAGE_SDCARD_CACHE_TABLE_LAST_USED_TIME).append(" integer,");
+ CREATE_IMAGE_SDCARD_CACHE_TABLE_SQL.append(IMAGE_SDCARD_CACHE_TABLE_USED_COUNT).append(" integer,");
+ CREATE_IMAGE_SDCARD_CACHE_TABLE_SQL.append(IMAGE_SDCARD_CACHE_TABLE_PRIORITY).append(" integer,");
+ CREATE_IMAGE_SDCARD_CACHE_TABLE_SQL.append(IMAGE_SDCARD_CACHE_TABLE_IS_EXPIRED).append(" integer,");
+ CREATE_IMAGE_SDCARD_CACHE_TABLE_SQL.append(IMAGE_SDCARD_CACHE_TABLE_IS_FOREVER).append(" integer)");
+ CREATE_IMAGE_SDCARD_CACHE_TABLE_SQL.append(TERMINATOR);
+
+ CREATE_IMAGE_SDCARD_CACHE_TABLE_INDEX_SQL.append("CREATE INDEX ").append(IMAGE_SDCARD_CACHE_TABLE_INDEX_TAG)
+ .append(" ON ").append(IMAGE_SDCARD_CACHE_TABLE_TABLE_NAME).append("(")
+ .append(IMAGE_SDCARD_CACHE_TABLE_TAG).append(")").append(TERMINATOR).append("CREATE INDEX ")
+ .append(IMAGE_SDCARD_CACHE_TABLE_INDEX_URL).append(" ON ").append(IMAGE_SDCARD_CACHE_TABLE_TABLE_NAME)
+ .append("(").append(IMAGE_SDCARD_CACHE_TABLE_URL).append(")").append(TERMINATOR);
+
+ /**
+ * sql to http response table
+ **/
+ CREATE_HTTP_CACHE_TABLE_SQL.append("CREATE TABLE ").append(HTTP_CACHE_TABLE_TABLE_NAME);
+ CREATE_HTTP_CACHE_TABLE_SQL.append(" (").append(HTTP_CACHE_TABLE_ID)
+ .append(" integer primary key autoincrement,");
+ CREATE_HTTP_CACHE_TABLE_SQL.append(HTTP_CACHE_TABLE_URL).append(" text,");
+ CREATE_HTTP_CACHE_TABLE_SQL.append(HTTP_CACHE_TABLE_RESPONSE).append(" text,");
+ CREATE_HTTP_CACHE_TABLE_SQL.append(HTTP_CACHE_TABLE_EXPIRES).append(" integer,");
+ CREATE_HTTP_CACHE_TABLE_SQL.append(HTTP_CACHE_TABLE_CREATE_TIME).append(" integer,");
+ CREATE_HTTP_CACHE_TABLE_SQL.append(HTTP_CACHE_TABLE_TYPE).append(" integer)").append(TERMINATOR);
+
+ CREATE_HTTP_CACHE_TABLE_UNIQUE_INDEX.append("CREATE UNIQUE INDEX ").append(HTTP_CACHE_TABLE_UNIQUE_INDEX_URL)
+ .append(" ON ").append(HTTP_CACHE_TABLE_TABLE_NAME).append("(").append(HTTP_CACHE_TABLE_URL)
+ .append(")").append(TERMINATOR);
+ CREATE_HTTP_CACHE_TABLE_INDEX_SQL.append("CREATE INDEX ").append(HTTP_CACHE_TABLE_INDEX_TYPE).append(" ON ")
+ .append(HTTP_CACHE_TABLE_TABLE_NAME).append("(").append(HTTP_CACHE_TABLE_TYPE).append(")")
+ .append(TERMINATOR);
+
+ }
+}
diff --git a/src/main/java/cn/trinea/android/common/constant/HttpConstants.java b/src/main/java/cn/trinea/android/common/constant/HttpConstants.java
new file mode 100644
index 0000000..e242c5c
--- /dev/null
+++ b/src/main/java/cn/trinea/android/common/constant/HttpConstants.java
@@ -0,0 +1,13 @@
+package cn.trinea.android.common.constant;
+
+/**
+ * HttpConstants
+ * All in lower case to put and get easy
+ *
+ * @author Trinea 2013-5-12
+ */
+public class HttpConstants {
+
+ public static final String EXPIRES = "expires";
+ public static final String CACHE_CONTROL = "cache-control";
+}
diff --git a/src/main/java/cn/trinea/android/common/dao/HttpCacheDao.java b/src/main/java/cn/trinea/android/common/dao/HttpCacheDao.java
new file mode 100644
index 0000000..d022349
--- /dev/null
+++ b/src/main/java/cn/trinea/android/common/dao/HttpCacheDao.java
@@ -0,0 +1,44 @@
+package cn.trinea.android.common.dao;
+
+import java.util.Map;
+
+import cn.trinea.android.common.entity.HttpResponse;
+
+/**
+ * HttpCacheDao
+ *
+ * @author Trinea 2013-11-04
+ */
+public interface HttpCacheDao {
+
+ /**
+ * insert HttpResponse
+ *
+ * @param httpResponse
+ * @return the row ID of the newly inserted row, or -1 if an error occurred
+ */
+ public long insertHttpResponse(HttpResponse httpResponse);
+
+ /**
+ * get HttpResponse by url
+ *
+ * @param url
+ * @return
+ */
+ public HttpResponse getHttpResponse(String url);
+
+ /**
+ * get HttpResponses by type
+ *
+ * @param type
+ * @return
+ */
+ public Map getHttpResponsesByType(int type);
+
+ /**
+ * delete all http response
+ *
+ * @return
+ */
+ public int deleteAllHttpResponse();
+}
diff --git a/src/main/java/cn/trinea/android/common/dao/ImageSDCardCacheDao.java b/src/main/java/cn/trinea/android/common/dao/ImageSDCardCacheDao.java
new file mode 100644
index 0000000..094eb54
--- /dev/null
+++ b/src/main/java/cn/trinea/android/common/dao/ImageSDCardCacheDao.java
@@ -0,0 +1,40 @@
+package cn.trinea.android.common.dao;
+
+import cn.trinea.android.common.service.impl.ImageSDCardCache;
+
+/**
+ * ImageSDCardCacheDao
+ *
+ * @author Trinea 2013-10-21
+ */
+public interface ImageSDCardCacheDao {
+
+ /**
+ * put all rows in db whose tag is same to tag to imageSDCardCache
+ *
+ * Attentions:
+ * - If imageSDCardCache is null, do nothing
+ * - If tag is null or empty, do nothing
+ *
+ *
+ * @param imageSDCardCache
+ * @param tag tag used to mark this cache when save to and load from db, should be unique and cannot be null or
+ * empty
+ * @return
+ */
+ public boolean putIntoImageSDCardCache(ImageSDCardCache imageSDCardCache, String tag);
+
+ /**
+ * delete all rows in db whose tag is same to tag at first, and insert all data in imageSDCardCache to db
+ *
+ * Attentions:
+ * - If imageSDCardCache is null, do nothing
+ * - If tag is null or empty, do nothing
+ * - Will delete all rows in db whose tag is same to tag at first
+ *
+ *
+ * @param imageSDCardCache
+ * @return
+ */
+ public boolean deleteAndInsertImageSDCardCache(ImageSDCardCache imageSDCardCache, String tag);
+}
diff --git a/src/main/java/cn/trinea/android/common/dao/impl/HttpCacheDaoImpl.java b/src/main/java/cn/trinea/android/common/dao/impl/HttpCacheDaoImpl.java
new file mode 100644
index 0000000..c000946
--- /dev/null
+++ b/src/main/java/cn/trinea/android/common/dao/impl/HttpCacheDaoImpl.java
@@ -0,0 +1,150 @@
+package cn.trinea.android.common.dao.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import cn.trinea.android.common.constant.DbConstants;
+import cn.trinea.android.common.dao.HttpCacheDao;
+import cn.trinea.android.common.entity.HttpResponse;
+import cn.trinea.android.common.util.SqliteUtils;
+import cn.trinea.android.common.util.StringUtils;
+import cn.trinea.android.common.util.TimeUtils;
+
+/**
+ * HttpCacheDaoImpl
+ *
+ * @author Trinea 2013-11-5
+ */
+public class HttpCacheDaoImpl implements HttpCacheDao {
+
+ private SqliteUtils sqliteUtils;
+
+ public HttpCacheDaoImpl(SqliteUtils sqliteUtils) {
+ this.sqliteUtils = sqliteUtils;
+ }
+
+ @Override
+ public long insertHttpResponse(HttpResponse httpResponse) {
+ ContentValues contentValues = httpResponseToCV(httpResponse);
+ if (contentValues == null) {
+ return -1;
+ }
+ synchronized (HttpCacheDaoImpl.class) {
+ return sqliteUtils.getDb().replace(DbConstants.HTTP_CACHE_TABLE_TABLE_NAME, null, contentValues);
+ }
+ }
+
+ @Override
+ public HttpResponse getHttpResponse(String url) {
+ if (StringUtils.isEmpty(url)) {
+ return null;
+ }
+
+ StringBuilder appWhere = new StringBuilder();
+ appWhere.append(DbConstants.HTTP_CACHE_TABLE_URL).append("=?");
+ String[] appWhereArgs = {url};
+ synchronized (HttpCacheDaoImpl.class) {
+ Cursor cursor = sqliteUtils.getDb().query(DbConstants.HTTP_CACHE_TABLE_TABLE_NAME, null,
+ appWhere.toString(), appWhereArgs, null, null, null);
+ if (cursor == null) {
+ return null;
+ }
+
+ HttpResponse httpResponse = null;
+ if (cursor.moveToFirst()) {
+ httpResponse = cursorToHttpResponse(cursor, url);
+ }
+ if (!cursor.isClosed()) {
+ cursor.close();
+ }
+ return httpResponse;
+ }
+ }
+
+ @Override
+ public Map getHttpResponsesByType(int type) {
+ StringBuilder whereClause = new StringBuilder();
+ whereClause.append(DbConstants.HTTP_CACHE_TABLE_TYPE).append("=?");
+ String[] whereClauseArgs = {Integer.toString(type)};
+
+ synchronized (HttpCacheDaoImpl.class) {
+ Cursor cursor = sqliteUtils.getDb().query(DbConstants.HTTP_CACHE_TABLE_TABLE_NAME, null,
+ whereClause.toString(), whereClauseArgs, null, null, null);
+
+ if (cursor == null) {
+ return null;
+ }
+
+ Map httpResponseMap = new HashMap();
+ if (cursor.getCount() > 0) {
+ for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
+ String url = cursor.getString(DbConstants.HTTP_CACHE_TABLE_URL_INDEX);
+ if (StringUtils.isEmpty(url)) {
+ continue;
+ }
+
+ HttpResponse httpResponse = cursorToHttpResponse(cursor, url);
+ if (httpResponse != null) {
+ httpResponseMap.put(url, httpResponse);
+ }
+ }
+ }
+ if (!cursor.isClosed()) {
+ cursor.close();
+ }
+ return httpResponseMap;
+ }
+ }
+
+ @Override
+ public int deleteAllHttpResponse() {
+ return sqliteUtils.getDb().delete(DbConstants.HTTP_CACHE_TABLE_TYPE, null, null);
+ }
+
+ /**
+ * convert cursor to HttpResponse
+ *
+ * @param cursor
+ * @param url
+ * @return
+ */
+ private HttpResponse cursorToHttpResponse(Cursor cursor, String url) {
+ if (cursor == null) {
+ return null;
+ }
+ if (url == null) {
+ url = cursor.getString(DbConstants.HTTP_CACHE_TABLE_URL_INDEX);
+ }
+ if (StringUtils.isEmpty(url)) {
+ return null;
+ }
+
+ HttpResponse httpResponse = new HttpResponse(url);
+ httpResponse.setResponseBody(cursor.getString(DbConstants.HTTP_CACHE_TABLE_RESPONSE_INDEX));
+ httpResponse.setExpiredTime(cursor.getLong(DbConstants.HTTP_CACHE_TABLE_EXPIRES_INDEX));
+ httpResponse.setType(cursor.getInt(DbConstants.HTTP_CACHE_TABLE_TYPE_INDEX));
+ return httpResponse;
+ }
+
+ /**
+ * convert HttpResponse to ContentValues
+ *
+ * @param httpResponse
+ * @return
+ */
+ private static ContentValues httpResponseToCV(HttpResponse httpResponse) {
+ if (httpResponse == null || StringUtils.isEmpty(httpResponse.getUrl())) {
+ return null;
+ }
+
+ ContentValues values = new ContentValues();
+ values.put(DbConstants.HTTP_CACHE_TABLE_URL, httpResponse.getUrl());
+ values.put(DbConstants.HTTP_CACHE_TABLE_RESPONSE, httpResponse.getResponseBody());
+ values.put(DbConstants.HTTP_CACHE_TABLE_EXPIRES, httpResponse.getExpiredTime());
+ values.put(DbConstants.HTTP_CACHE_TABLE_CREATE_TIME, TimeUtils.getCurrentTimeInString());
+ values.put(DbConstants.HTTP_CACHE_TABLE_TYPE, httpResponse.getType());
+ return values;
+ }
+}
diff --git a/src/main/java/cn/trinea/android/common/dao/impl/ImageSDCardCacheDaoImpl.java b/src/main/java/cn/trinea/android/common/dao/impl/ImageSDCardCacheDaoImpl.java
new file mode 100644
index 0000000..c60b3a5
--- /dev/null
+++ b/src/main/java/cn/trinea/android/common/dao/impl/ImageSDCardCacheDaoImpl.java
@@ -0,0 +1,111 @@
+package cn.trinea.android.common.dao.impl;
+
+import java.util.Map.Entry;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import cn.trinea.android.common.constant.DbConstants;
+import cn.trinea.android.common.dao.ImageSDCardCacheDao;
+import cn.trinea.android.common.entity.CacheObject;
+import cn.trinea.android.common.service.impl.ImageSDCardCache;
+import cn.trinea.android.common.util.SqliteUtils;
+import cn.trinea.android.common.util.StringUtils;
+
+/**
+ * ImageSDCardCacheDao
+ *
+ * @author Trinea 2013-10-21
+ */
+public class ImageSDCardCacheDaoImpl implements ImageSDCardCacheDao {
+
+ private SqliteUtils sqliteUtils;
+
+ public ImageSDCardCacheDaoImpl(SqliteUtils sqliteUtils) {
+ this.sqliteUtils = sqliteUtils;
+ }
+
+ @Override
+ public boolean putIntoImageSDCardCache(ImageSDCardCache imageSDCardCache, String tag) {
+ if (imageSDCardCache == null || StringUtils.isEmpty(tag)) {
+ return false;
+ }
+
+ StringBuilder selection = new StringBuilder();
+ selection.append(DbConstants.IMAGE_SDCARD_CACHE_TABLE_TAG).append("=?");
+ String[] selectionArgs = {tag};
+ Cursor cursor = sqliteUtils.getDb().query(DbConstants.IMAGE_SDCARD_CACHE_TABLE_TABLE_NAME, null,
+ selection.toString(), selectionArgs, null, null, null);
+ if (cursor == null) {
+ return true;
+ }
+
+ if (cursor != null && cursor.getCount() > 0) {
+ for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
+ CacheObject value = new CacheObject();
+ String imageUrl = cursor.getString(DbConstants.IMAGE_SDCARD_CACHE_TABLE_URL_INDEX);
+ value.setData(cursor.getString(DbConstants.IMAGE_SDCARD_CACHE_TABLE_PATH_INDEX));
+ value.setUsedCount(cursor.getInt(DbConstants.IMAGE_SDCARD_CACHE_TABLE_USED_COUNT_INDEX));
+ value.setPriority(cursor.getInt(DbConstants.IMAGE_SDCARD_CACHE_TABLE_PRIORITY_INDEX));
+ value.setExpired(cursor.getInt(DbConstants.IMAGE_SDCARD_CACHE_TABLE_IS_EXPIRED_INDEX) == 1);
+ value.setForever(cursor.getInt(DbConstants.IMAGE_SDCARD_CACHE_TABLE_IS_FOREVER_INDEX) == 1);
+ imageSDCardCache.put(imageUrl, value);
+ }
+ }
+ if (cursor != null && !cursor.isClosed()) {
+ cursor.close();
+ }
+ return true;
+ }
+
+ @Override
+ public boolean deleteAndInsertImageSDCardCache(ImageSDCardCache imageSDCardCache, String tag) {
+ if (imageSDCardCache == null || StringUtils.isEmpty(tag)) {
+ return false;
+ }
+
+ SQLiteDatabase db = sqliteUtils.getDb();
+ db.beginTransaction();
+ try {
+ StringBuilder whereClause = new StringBuilder();
+ whereClause.append(DbConstants.IMAGE_SDCARD_CACHE_TABLE_TAG).append("=?");
+ String[] whereArgs = {tag};
+ db.delete(DbConstants.IMAGE_SDCARD_CACHE_TABLE_TABLE_NAME, whereClause.toString(), whereArgs);
+
+ String key;
+ CacheObject value;
+ for (Entry> entry : imageSDCardCache.entrySet()) {
+ if (entry != null && (key = entry.getKey()) != null && (value = entry.getValue()) != null) {
+ db.insert(DbConstants.IMAGE_SDCARD_CACHE_TABLE_TABLE_NAME, null, cacheObjectToCV(tag, key, value));
+ }
+ }
+
+ db.setTransactionSuccessful();
+ return true;
+ } catch (Exception e) {
+ return false;
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ /**
+ * @param tag
+ * @param url
+ * @param value
+ * @return
+ */
+ private static ContentValues cacheObjectToCV(String tag, String url, CacheObject value) {
+ ContentValues values = new ContentValues();
+ values.put(DbConstants.IMAGE_SDCARD_CACHE_TABLE_TAG, tag);
+ values.put(DbConstants.IMAGE_SDCARD_CACHE_TABLE_URL, url);
+ values.put(DbConstants.IMAGE_SDCARD_CACHE_TABLE_PATH, value.getData());
+ values.put(DbConstants.IMAGE_SDCARD_CACHE_TABLE_ENTER_TIME, value.getEnterTime());
+ values.put(DbConstants.IMAGE_SDCARD_CACHE_TABLE_LAST_USED_TIME, value.getLastUsedTime());
+ values.put(DbConstants.IMAGE_SDCARD_CACHE_TABLE_USED_COUNT, value.getUsedCount());
+ values.put(DbConstants.IMAGE_SDCARD_CACHE_TABLE_PRIORITY, value.getPriority());
+ values.put(DbConstants.IMAGE_SDCARD_CACHE_TABLE_IS_EXPIRED, value.isExpired() ? 1 : 0);
+ values.put(DbConstants.IMAGE_SDCARD_CACHE_TABLE_IS_FOREVER, value.isForever() ? 1 : 0);
+ return values;
+ }
+}
diff --git a/src/cn/trinea/android/common/entity/CacheObject.java b/src/main/java/cn/trinea/android/common/entity/CacheObject.java
similarity index 93%
rename from src/cn/trinea/android/common/entity/CacheObject.java
rename to src/main/java/cn/trinea/android/common/entity/CacheObject.java
index e5903dc..4015cea 100644
--- a/src/cn/trinea/android/common/entity/CacheObject.java
+++ b/src/main/java/cn/trinea/android/common/entity/CacheObject.java
@@ -7,7 +7,7 @@
/**
* Object in cache
*
- * @author Trinea 2011-12-23
+ * @author Trinea 2011-12-23
*/
public class CacheObject implements Serializable, Comparable> {
@@ -30,7 +30,7 @@ public class CacheObject implements Serializable, Comparable>
/** data **/
protected V data;
- public CacheObject(){
+ public CacheObject() {
this.enterTime = System.currentTimeMillis();
this.lastUsedTime = System.currentTimeMillis();
this.usedCount = 0;
@@ -39,7 +39,7 @@ public CacheObject(){
this.isForever = false;
}
- public CacheObject(V data){
+ public CacheObject(V data) {
this();
this.data = data;
}
@@ -92,7 +92,7 @@ public long getUsedCount() {
/**
* Set used(got) count
*
- * @return
+ * @param usedCount
*/
public void setUsedCount(long usedCount) {
this.usedCount = usedCount;
@@ -194,6 +194,7 @@ public int compareTo(CacheObject o) {
* if data, enterTime, priority, isExpired, isForever all equals
*/
@SuppressWarnings("unchecked")
+ @Override
public boolean equals(Object o) {
if (o == null) {
return false;
@@ -203,4 +204,9 @@ public boolean equals(Object o) {
return (ObjectUtils.isEquals(this.data, obj.data) && this.enterTime == obj.enterTime
&& this.priority == obj.priority && this.isExpired == obj.isExpired && this.isForever == obj.isForever);
}
+
+ @Override
+ public int hashCode() {
+ return data == null ? 0 : data.hashCode();
+ }
}
diff --git a/src/main/java/cn/trinea/android/common/entity/FailedReason.java b/src/main/java/cn/trinea/android/common/entity/FailedReason.java
new file mode 100644
index 0000000..73f0d09
--- /dev/null
+++ b/src/main/java/cn/trinea/android/common/entity/FailedReason.java
@@ -0,0 +1,49 @@
+package cn.trinea.android.common.entity;
+
+/**
+ * get data failed reason
+ *
+ * @author Trinea 2013-11-25
+ */
+public class FailedReason {
+
+ private FailedType failedType;
+ private Throwable cause;
+
+ public FailedReason(FailedType failedType, String cause) {
+ this.failedType = failedType;
+ this.cause = new Throwable(cause);
+ }
+
+ public FailedReason(FailedType failedType, Throwable cause) {
+ this.failedType = failedType;
+ this.cause = cause;
+ }
+
+ /**
+ * get failedType
+ *
+ * @return the failedType
+ */
+ public FailedType getFailedType() {
+ return failedType;
+ }
+
+ /**
+ * get cause
+ *
+ * @return the cause
+ */
+ public Throwable getCause() {
+ return cause;
+ }
+
+ public static enum FailedType {
+ /** get image from network or save image to sdcard error **/
+ ERROR_IO,
+ /** get image with out of memory error **/
+ ERROR_OUT_OF_MEMORY,
+ /** reserved field, it's no use now, waiting to be perfect^_^ **/
+ ERROR_UNKNOWN,
+ }
+}
diff --git a/src/main/java/cn/trinea/android/common/entity/HttpRequest.java b/src/main/java/cn/trinea/android/common/entity/HttpRequest.java
new file mode 100644
index 0000000..f45d138
--- /dev/null
+++ b/src/main/java/cn/trinea/android/common/entity/HttpRequest.java
@@ -0,0 +1,158 @@
+package cn.trinea.android.common.entity;
+
+import java.net.URLConnection;
+import java.util.HashMap;
+import java.util.Map;
+
+import cn.trinea.android.common.util.HttpUtils;
+
+/**
+ * HttpRequest
+ *
+ * Constructor
+ * - {@link HttpRequest#HttpRequest(String)}
+ * - {@link HttpRequest#HttpRequest(String, Map)}
+ *
+ *
+ * Setting
+ * - {@link #setConnectTimeout(int)}
+ * - {@link #setReadTimeout(int)}
+ * - {@link #setParasMap(Map)}
+ * - {@link #setUserAgent(String)}
+ * - {@link #setRequestProperty(String, String)}
+ * - {@link #setRequestProperties(Map)}
+ *
+ *
+ * @author Trinea 2013-5-12
+ */
+public class HttpRequest {
+
+ private String url;
+ private int connectTimeout;
+ private int readTimeout;
+ private Map parasMap;
+ private Map requestProperties;
+
+ public HttpRequest(String url) {
+ this.url = url;
+ this.connectTimeout = -1;
+ this.readTimeout = -1;
+ requestProperties = new HashMap();
+ }
+
+ public HttpRequest(String url, Map parasMap) {
+ this.url = url;
+ this.parasMap = parasMap;
+ this.connectTimeout = -1;
+ this.readTimeout = -1;
+ requestProperties = new HashMap();
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ /**
+ * @return
+ * @see URLConnection#getConnectTimeout()
+ */
+ public int getConnectTimeout() {
+ return connectTimeout;
+ }
+
+ /**
+ * @param timeoutMillis
+ * @see URLConnection#setConnectTimeout(int)
+ */
+ public void setConnectTimeout(int timeoutMillis) {
+ if (timeoutMillis < 0) {
+ throw new IllegalArgumentException("timeout can not be negative");
+ }
+ connectTimeout = timeoutMillis;
+ }
+
+ /**
+ * @return
+ * @see URLConnection#getReadTimeout()
+ */
+ public int getReadTimeout() {
+ return readTimeout;
+ }
+
+ /**
+ * @param timeoutMillis
+ * @see URLConnection#setReadTimeout(int)
+ */
+ public void setReadTimeout(int timeoutMillis) {
+ if (timeoutMillis < 0) {
+ throw new IllegalArgumentException("timeout can not be negative");
+ }
+ readTimeout = timeoutMillis;
+ }
+
+ /**
+ * get paras map
+ *
+ * @return
+ */
+ public Map getParasMap() {
+ return parasMap;
+ }
+
+ /**
+ * set paras map
+ *
+ * @param parasMap
+ */
+ public void setParasMap(Map parasMap) {
+ this.parasMap = parasMap;
+ }
+
+ /**
+ * @return paras as string
+ */
+ public String getParas() {
+ return HttpUtils.joinParasWithEncodedValue(parasMap);
+ }
+
+ /**
+ * @param field
+ * @param newValue
+ * @see URLConnection#setRequestProperty(String, String)
+ */
+ public void setRequestProperty(String field, String newValue) {
+ requestProperties.put(field, newValue);
+ }
+
+ /**
+ * @param field
+ * @see URLConnection#getRequestProperty(String)
+ */
+ public String getRequestProperty(String field) {
+ return requestProperties.get(field);
+ }
+
+ /**
+ * same to {@link #setRequestProperty(String, String)} filed is User-Agent
+ *
+ * @param value
+ * @see URLConnection#setRequestProperty(String, String)
+ */
+ public void setUserAgent(String value) {
+ requestProperties.put("User-Agent", value);
+ }
+
+ /**
+ * @return
+ */
+ public Map getRequestProperties() {
+ return requestProperties;
+ }
+
+ /**
+ * @param requestProperties
+ */
+ public void setRequestProperties(Map requestProperties) {
+ this.requestProperties = requestProperties;
+ }
+}
diff --git a/src/main/java/cn/trinea/android/common/entity/HttpResponse.java b/src/main/java/cn/trinea/android/common/entity/HttpResponse.java
new file mode 100644
index 0000000..65659e6
--- /dev/null
+++ b/src/main/java/cn/trinea/android/common/entity/HttpResponse.java
@@ -0,0 +1,297 @@
+package cn.trinea.android.common.entity;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import cn.trinea.android.common.constant.HttpConstants;
+import cn.trinea.android.common.service.HttpCache;
+import cn.trinea.android.common.util.HttpUtils;
+import cn.trinea.android.common.util.StringUtils;
+import cn.trinea.android.common.util.TimeUtils;
+
+/**
+ * HttpResponse
+ *
+ * Constructor
+ * - {@link HttpResponse#HttpResponse()}
+ * - {@link HttpResponse#HttpResponse(String)}
+ *
+ *
+ * Get
+ * - {@link #getResponseBody()}
+ * - {@link #getUrl()}
+ * - {@link #getExpiredTime()} expires time
+ * - {@link #getExpiresHeader()}
+ *
+ *
+ * Setting
+ * - {@link #setUrl(String)}
+ * - {@link #setResponseBody(String)}
+ * - {@link #setResponseHeader(String, String)}
+ * - {@link #setResponseHeaders(Map)}
+ *
+ *
+ * @author Trinea 2013-5-12
+ */
+public class HttpResponse {
+
+ private String url;
+ /** http response content **/
+ private String responseBody;
+ private Map responseHeaders;
+ /** type to mark this response **/
+ private int type;
+ /** expired time in milliseconds **/
+ private long expiredTime;
+ /** this is a client mark, whether this response is in client cache **/
+ private boolean isInCache;
+
+ private boolean isInitExpiredTime;
+ /**
+ * An int representing the three digit HTTP Status-Code.
+ *
+ * - 1xx: Informational
+ *
- 2xx: Success
+ *
- 3xx: Redirection
+ *
- 4xx: Client Error
+ *
- 5xx: Server Error
+ *
+ */
+ private int responseCode = -1;
+
+ public HttpResponse(String url) {
+ this.url = url;
+ type = 0;
+ isInCache = false;
+ isInitExpiredTime = false;
+ responseHeaders = new HashMap();
+ }
+
+ public HttpResponse() {
+ responseHeaders = new HashMap();
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ public String getResponseBody() {
+ return responseBody;
+ }
+
+ public void setResponseBody(String responseBody) {
+ this.responseBody = responseBody;
+ }
+
+ /**
+ * get reponse code
+ *
+ * @return An int representing the three digit HTTP Status-Code.
+ *
+ * - 1xx: Informational
+ *
- 2xx: Success
+ *
- 3xx: Redirection
+ *
- 4xx: Client Error
+ *
- 5xx: Server Error
+ *
- -1: http error
+ *
+ */
+ public int getResponseCode() {
+ return responseCode;
+ }
+
+ public void setResponseCode(int responseCode) {
+ this.responseCode = responseCode;
+ }
+
+ /**
+ * not avaliable now
+ *
+ * @return
+ */
+ private Map getResponseHeaders() {
+ return responseHeaders;
+ }
+
+ public void setResponseHeaders(Map responseHeaders) {
+ this.responseHeaders = responseHeaders;
+ }
+
+ /**
+ * get type
+ *
+ * - type to mark this response, default is 0
+ * - it will be used in {@link HttpCache#HttpCache(android.content.Context, int)}
+ *
+ *
+ * @return the type
+ */
+ public int getType() {
+ return type;
+ }
+
+ /**
+ * set type
+ *
+ * - type to mark this response, default is 0, cannot be smaller than 0.
+ * - it will be used in {@link HttpCache#HttpCache(android.content.Context, int)}
+ *
+ *
+ * @param type the type to set
+ */
+ public void setType(int type) {
+ if (type < 0) {
+ throw new IllegalArgumentException("The type of HttpResponse cannot be smaller than 0.");
+ }
+ this.type = type;
+ }
+
+ /**
+ * set expired time in millis
+ *
+ * @param expiredTime
+ */
+ public void setExpiredTime(long expiredTime) {
+ isInitExpiredTime = true;
+ this.expiredTime = expiredTime;
+ }
+
+ /**
+ * get expired time in millis
+ *
+ * - if current time is bigger than expired time, it means this response is dirty
+ *
+ *
+ * @return
+ * - if max-age in cache-control is exists, return current time plus it
+ * - else return expires
+ * - if something error, return -1
+ *
+ */
+ public long getExpiredTime() {
+ if (isInitExpiredTime) {
+ return expiredTime;
+ } else {
+ isInitExpiredTime = true;
+ return expiredTime = getExpiresInMillis();
+ }
+ }
+
+ /**
+ * whether this response has expired
+ *
+ * @return
+ */
+ public boolean isExpired() {
+ return TimeUtils.getCurrentTimeInLong() > expiredTime;
+ }
+
+ /**
+ * get isInCache, this is a client mark, whethero is in client cache
+ *
+ * @return the isInCache
+ */
+ public boolean isInCache() {
+ return isInCache;
+ }
+
+ /**
+ * set isInCache, this is a client mark, whethero is in client cache
+ *
+ * @param isInCache the isInCache to set
+ * @return
+ */
+ public HttpResponse setInCache(boolean isInCache) {
+ this.isInCache = isInCache;
+ return this;
+ }
+
+ /**
+ * http expires in reponse header
+ *
+ * @return null represents http error or no expires in response headers
+ */
+ public String getExpiresHeader() {
+ try {
+ return responseHeaders == null ? null : (String)responseHeaders.get(HttpConstants.EXPIRES);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * http cache-control in reponse header
+ *
+ * @return -1 represents http error or no cache-control in response headers, or max-age in seconds
+ */
+ private long getCacheControlMaxAge() {
+ try {
+ String cacheControl = (String)responseHeaders.get(HttpConstants.CACHE_CONTROL);
+ if (!StringUtils.isEmpty(cacheControl)) {
+ int start = cacheControl.indexOf("max-age=");
+ if (start != -1) {
+ int end = cacheControl.indexOf(",", start);
+ String maxAge;
+ if (end != -1) {
+ maxAge = cacheControl.substring(start + "max-age=".length(), end);
+ } else {
+ maxAge = cacheControl.substring(start + "max-age=".length());
+ }
+ return Long.parseLong(maxAge);
+ }
+ }
+ return -1;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return -1;
+ }
+ }
+
+ /**
+ * get expires
+ *
+ * @return
+ * - if max-age in cache-control is exists, return current time plus it
+ * - else return expires
+ * - if something error, return -1
+ *
+ */
+ private long getExpiresInMillis() {
+ long maxAge = getCacheControlMaxAge();
+ if (maxAge != -1) {
+ return System.currentTimeMillis() + maxAge * 1000;
+ } else {
+ String expire = getExpiresHeader();
+ if (!StringUtils.isEmpty(expire)) {
+ return HttpUtils.parseGmtTime(getExpiresHeader());
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * set response header
+ *
+ * @param field
+ * @param newValue
+ */
+ public void setResponseHeader(String field, String newValue) {
+ if (responseHeaders != null) {
+ responseHeaders.put(field, newValue);
+ }
+ }
+
+ /**
+ * get response header, not avaliable now
+ *
+ * @param field
+ */
+ private Object getResponseHeader(String field) {
+ return responseHeaders == null ? null : responseHeaders.get(field);
+ }
+}
diff --git a/src/main/java/cn/trinea/android/common/entity/PatchResult.java b/src/main/java/cn/trinea/android/common/entity/PatchResult.java
new file mode 100644
index 0000000..5631380
--- /dev/null
+++ b/src/main/java/cn/trinea/android/common/entity/PatchResult.java
@@ -0,0 +1,53 @@
+package cn.trinea.android.common.entity;
+
+/**
+ * PatchResult
+ *
+ * @author Trinea 2013-12-10
+ */
+public class PatchResult {
+
+ private int status;
+ private String message;
+
+ public PatchResult(int status, String message) {
+ this.status = status;
+ this.message = message;
+ }
+
+ /**
+ * get status
+ *
+ * @return the status
+ */
+ public int getStatus() {
+ return status;
+ }
+
+ /**
+ * set status
+ *
+ * @param status the status to set
+ */
+ public void setStatus(int status) {
+ this.status = status;
+ }
+
+ /**
+ * get message
+ *
+ * @return the message
+ */
+ public String getMessage() {
+ return message;
+ }
+
+ /**
+ * set message
+ *
+ * @param message the message to set
+ */
+ public void setMessage(String message) {
+ this.message = message;
+ }
+}
diff --git a/src/cn/trinea/android/common/service/Cache.java b/src/main/java/cn/trinea/android/common/service/Cache.java
similarity index 95%
rename from src/cn/trinea/android/common/service/Cache.java
rename to src/main/java/cn/trinea/android/common/service/Cache.java
index 97b9539..6e7bb2f 100644
--- a/src/cn/trinea/android/common/service/Cache.java
+++ b/src/main/java/cn/trinea/android/common/service/Cache.java
@@ -9,7 +9,7 @@
/**
* Cache interface
*
- * @author Trinea 2011-12-23
+ * @author Trinea 2011-12-23
*/
public interface Cache {
diff --git a/src/cn/trinea/android/common/service/CacheFullRemoveType.java b/src/main/java/cn/trinea/android/common/service/CacheFullRemoveType.java
similarity index 90%
rename from src/cn/trinea/android/common/service/CacheFullRemoveType.java
rename to src/main/java/cn/trinea/android/common/service/CacheFullRemoveType.java
index e38ed34..d084b25 100644
--- a/src/cn/trinea/android/common/service/CacheFullRemoveType.java
+++ b/src/main/java/cn/trinea/android/common/service/CacheFullRemoveType.java
@@ -9,7 +9,7 @@
* when cache is full, compare object is cache with this class, delete the smallest one.
* you can implements this interface.
*
- * @author Trinea 2011-12-26
+ * @author Trinea 2011-12-26
*/
public interface CacheFullRemoveType extends Serializable {
diff --git a/src/cn/trinea/android/common/service/FileNameRule.java b/src/main/java/cn/trinea/android/common/service/FileNameRule.java
similarity index 85%
rename from src/cn/trinea/android/common/service/FileNameRule.java
rename to src/main/java/cn/trinea/android/common/service/FileNameRule.java
index 039eaa3..4f1b1eb 100644
--- a/src/cn/trinea/android/common/service/FileNameRule.java
+++ b/src/main/java/cn/trinea/android/common/service/FileNameRule.java
@@ -7,7 +7,7 @@
/**
* File name rule, used when saving images in {@link ImageSDCardCache}
*
- * @author Trinea 2012-7-6
+ * @author Trinea 2012-7-6
*/
public interface FileNameRule extends Serializable {
diff --git a/src/main/java/cn/trinea/android/common/service/HttpCache.java b/src/main/java/cn/trinea/android/common/service/HttpCache.java
new file mode 100644
index 0000000..3c02e22
--- /dev/null
+++ b/src/main/java/cn/trinea/android/common/service/HttpCache.java
@@ -0,0 +1,406 @@
+package cn.trinea.android.common.service;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+import android.content.Context;
+import android.os.AsyncTask;
+import android.os.Build;
+import cn.trinea.android.common.constant.HttpConstants;
+import cn.trinea.android.common.dao.HttpCacheDao;
+import cn.trinea.android.common.dao.impl.HttpCacheDaoImpl;
+import cn.trinea.android.common.entity.HttpRequest;
+import cn.trinea.android.common.entity.HttpResponse;
+import cn.trinea.android.common.service.impl.ImageCache;
+import cn.trinea.android.common.util.ArrayUtils;
+import cn.trinea.android.common.util.HttpUtils;
+import cn.trinea.android.common.util.SqliteUtils;
+import cn.trinea.android.common.util.StringUtils;
+import cn.trinea.android.common.util.SystemUtils;
+
+/**
+ * Http Cache
+ *
+ * It applies to get and cache api data from server, like json or xml and so on. It applies to apps like weixin, weibo,
+ * twitter, taobao and so on. If want to cache image, please use {@link ImageCache}
+ *
+ * Constructor
+ * - {@link #HttpCache(Context)} to init cache
+ *
+ *
+ * Get data asynchronous
+ * - {@link #httpGet(HttpRequest, HttpCacheListener)}
+ * - {@link #httpGet(String, HttpCacheListener)}
+ *
+ *
+ * Get data synchronous
+ * - {@link #httpGet(HttpRequest)}
+ * - {@link #httpGet(String)}
+ * - {@link #httpGetString(HttpRequest)}
+ * - {@link #httpGetString(String)}
+ *
+ *
+ * @author Trinea 2013-11-1
+ */
+public class HttpCache {
+
+ private Context context;
+
+ /** http memory cache **/
+ private Map cache;
+ /** dao to get data from http db cache **/
+ private HttpCacheDao httpCacheDao;
+ private int type = -1;
+
+ /** Default {@link Executor} that be used to execute tasks in parallel. **/
+ public static final Executor THREAD_POOL_EXECUTOR = Executors
+ .newFixedThreadPool(SystemUtils.DEFAULT_THREAD_POOL_SIZE);
+
+ public HttpCache(Context context) {
+ if (context == null) {
+ throw new IllegalArgumentException("The context can not be null.");
+ }
+ this.context = context;
+ cache = new ConcurrentHashMap();
+ httpCacheDao = new HttpCacheDaoImpl(SqliteUtils.getInstance(context));
+ }
+
+ /**
+ * waiting to be perfect^_^
+ *
+ * @param context
+ * @param type get httpResponse whose type is type into memory as primary cache to improve performance
+ */
+ private HttpCache(Context context, int type) {
+ this(context);
+ this.type = type;
+ initData(type);
+ }
+
+ /**
+ * get httpResponse whose type is type into memory as primary cache to improve performance
+ *
+ * @param type
+ */
+ private void initData(int type) {
+ this.cache = httpCacheDao.getHttpResponsesByType(type);
+ if (cache == null) {
+ cache = new HashMap();
+ }
+ }
+
+ /**
+ * http get
+ *
+ * Attentions:
+ * - Don't call this on the ui thread, it may costs some times. Becaust if not in cache, it get from network
+ * synchronous.
+ * - If you want get data asynchronous, use {@link HttpCache#httpGet(HttpRequest, HttpCacheListener)}
+ *
+ *
+ * @param httpRequest
+ * @return the response of the url, if null represents http error
+ */
+ public HttpResponse httpGet(HttpRequest request) {
+ String url;
+ if (request == null || StringUtils.isEmpty(url = request.getUrl())) {
+ return null;
+ }
+
+ HttpResponse cacheResponse = null;
+ boolean isNoCache = false, isNoStore = false;
+ String requestCacheControl = request.getRequestProperty(HttpConstants.CACHE_CONTROL);
+ if (!StringUtils.isEmpty(requestCacheControl)) {
+ String[] requestCacheControls = requestCacheControl.split(",");
+ if (!ArrayUtils.isEmpty(requestCacheControls)) {
+ List requestCacheControlList = new ArrayList();
+ for (String s : requestCacheControls) {
+ if (s == null) {
+ continue;
+ }
+ requestCacheControlList.add(s.trim());
+ }
+ if (requestCacheControlList.contains("no-cache")) {
+ isNoCache = true;
+ }
+ if (requestCacheControlList.contains("no-store")) {
+ isNoStore = true;
+ }
+ }
+ }
+ if (!isNoCache) {
+ cacheResponse = getFromCache(url);
+ }
+ return cacheResponse == null ? (isNoStore ? HttpUtils.httpGet(url) : putIntoCache(HttpUtils.httpGet(url)))
+ : cacheResponse;
+ }
+
+ /**
+ * http get
+ *
+ * - It gets data from cache or network asynchronous.
+ * - If you want get data synchronous, use {@link HttpCache#httpGet(HttpRequest)} or
+ * {@link HttpCache#httpGetString(HttpRequest)}
+ *
+ *
+ * @param url
+ * @param listener listener which can do something before or after HttpGet. this can be null if you not want to do
+ * something
+ */
+ public void httpGet(String url, HttpCacheListener listener) {
+ // if bigger than android 4.0 use executeOnExecutor, else use execute
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ new HttpCacheStringAsyncTask(listener).executeOnExecutor(THREAD_POOL_EXECUTOR, url);
+ } else {
+ new HttpCacheStringAsyncTask(listener).execute(url);
+ }
+ }
+
+ /**
+ * http get
+ *
+ * - It gets data from cache or network asynchronous.
+ * - If you want get data synchronous, use {@link HttpCache#httpGet(HttpRequest)} or
+ * {@link HttpCache#httpGetString(HttpRequest)}
+ *
+ *
+ * @param request
+ * @param listener listener which can do something before or after HttpGet. this can be null if you not want to do
+ * something
+ */
+ public void httpGet(HttpRequest request, HttpCacheListener listener) {
+ // if bigger than android 4.0 use executeOnExecutor, else use execute
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ new HttpCacheRequestAsyncTask(listener).executeOnExecutor(THREAD_POOL_EXECUTOR, request);
+ } else {
+ new HttpCacheRequestAsyncTask(listener).execute(request);
+ }
+ }
+
+ /**
+ * http get
+ *
+ * Attentions:
+ * - Don't call this on the ui thread, it may costs some times. Becaust if not in cache, it get from network
+ * synchronous.
+ * - If you want get data asynchronous, use {@link HttpCache#httpGet(HttpRequest, HttpCacheListener)}
+ *
+ *
+ * @param url
+ * @return the response of the url, if null represents http error
+ */
+ public HttpResponse httpGet(String url) {
+ return httpGet(new HttpRequest(url));
+ }
+
+ /**
+ * http get
+ *
+ * Attentions:
+ * - Don't call this on the ui thread, it may costs some times. Becaust if not in cache, it get from network
+ * synchronous.
+ * - If you want get data asynchronous, use {@link HttpCache#httpGet(String, HttpCacheListener)}
+ *
+ *
+ * @param url
+ * @return the response body of the url, if null represents http error
+ */
+ public String httpGetString(String url) {
+ HttpResponse cacheResponse = httpGet(new HttpRequest(url));
+ return cacheResponse == null ? null : cacheResponse.getResponseBody();
+ }
+
+ /**
+ * http get
+ *
+ * Attentions:
+ * - Don't call this on the ui thread, it may costs some times. Becaust if not in cache, it get from network
+ * synchronous.
+ * - If you want get data asynchronous, use {@link HttpCache#httpGet(HttpRequest, HttpCacheListener)}
+ *
+ *
+ * @param httpRequest
+ * @return the response body of the url, if null represents http error
+ */
+ public HttpResponse httpGetString(HttpRequest httpRequest) {
+ return httpGet(httpRequest);
+ }
+
+ /**
+ * whether this cache contains the specified url.
+ *
+ * @param url
+ * @return true if this cache contains the specified url and the element is valid, false otherwise.
+ */
+ public boolean containsKey(String url) {
+ return getFromCache(url) != null;
+ }
+
+ /**
+ * whether the element of the specified url has invalided
+ *
+ * @param url
+ * @return true if the element of the specified url has invalided, false otherwise.
+ */
+ protected boolean isExpired(String url) {
+ return getFromCache(url) == null;
+ }
+
+ /**
+ * Removes all elements from this cache, leaving it empty.
+ */
+ public void clear() {
+ cache.clear();
+ httpCacheDao.deleteAllHttpResponse();
+ }
+
+ /**
+ * HttpCacheListener, can do something before or after HttpGet
+ *
+ * @author Trinea 2013-11-15
+ */
+ public static abstract class HttpCacheListener {
+
+ /**
+ * Runs on the UI thread before httpGet.
+ *
+ * - this can be null if you not want to do something
+ *
+ */
+ protected void onPreGet() {}
+
+ /**
+ * Runs on the UI thread after httpGet. The httpResponse is returned by httpGet.
+ *
+ * - this can be null if you not want to do something
+ *
+ *
+ * @param httpResponse get by the url
+ * @param isInCache the data responsed to the url whether is in cache
+ */
+ protected void onPostGet(HttpResponse httpResponse, boolean isInCache) {}
+ }
+
+ /**
+ * get type, waiting to be perfect^_^
+ *
+ * @return the type
+ */
+ private int getType() {
+ return type;
+ }
+
+ /**
+ * put response into cache
+ *
+ * - put response to db, if {@link HttpResponse#getType()} == {@link HttpCache#getType()}, also put into memory
+ * cache
+ *
+ *
+ * @param httpResponse
+ * @return if insert into db error, return null, otherwise return HttpResponse
+ */
+ private HttpResponse putIntoCache(HttpResponse httpResponse) {
+ String url;
+ if (httpResponse == null || (url = httpResponse.getUrl()) == null) {
+ return null;
+ }
+
+ if (type != -1 && type == httpResponse.getType()) {
+ cache.put(url, httpResponse);
+ }
+ return (httpCacheDao.insertHttpResponse(httpResponse) == -1) ? null : httpResponse;
+ }
+
+ /**
+ * get from memory cache first, if not exist in memory cache, get from db
+ *
+ * @param url
+ * @return
+ * - if neither exit in memory cache nor db, return null
+ * - if is expired, return null, otherwise return cache response
+ *
+ */
+ public HttpResponse getFromCache(String url) {
+ if (StringUtils.isEmpty(url)) {
+ return null;
+ }
+
+ HttpResponse cacheResponse = cache.get(url);
+ if (cacheResponse == null) {
+ cacheResponse = httpCacheDao.getHttpResponse(url);
+ }
+ return (cacheResponse == null || cacheResponse.isExpired()) ? null : cacheResponse.setInCache(true);
+ }
+
+ /**
+ * AsyncTask to get data by String url
+ *
+ * @author Trinea 2013-11-15
+ */
+ private class HttpCacheStringAsyncTask extends AsyncTask {
+
+ private HttpCacheListener listener;
+
+ public HttpCacheStringAsyncTask(HttpCacheListener listener) {
+ this.listener = listener;
+ }
+
+ protected HttpResponse doInBackground(String... url) {
+ if (ArrayUtils.isEmpty(url)) {
+ return null;
+ }
+ return httpGet(url[0]);
+ }
+
+ protected void onPreExecute() {
+ if (listener != null) {
+ listener.onPreGet();
+ }
+ }
+
+ protected void onPostExecute(HttpResponse httpResponse) {
+ if (listener != null) {
+ listener.onPostGet(httpResponse, httpResponse == null ? false : httpResponse.isInCache());
+ }
+ }
+ }
+
+ /**
+ * AsyncTask to get data by HttpRequest
+ *
+ * @author Trinea 2013-11-15
+ */
+ private class HttpCacheRequestAsyncTask extends AsyncTask {
+
+ private HttpCacheListener listener;
+
+ public HttpCacheRequestAsyncTask(HttpCacheListener listener) {
+ this.listener = listener;
+ }
+
+ protected HttpResponse doInBackground(HttpRequest... httpRequest) {
+ if (ArrayUtils.isEmpty(httpRequest)) {
+ return null;
+ }
+ return httpGet(httpRequest[0]);
+ }
+
+ protected void onPreExecute() {
+ if (listener != null) {
+ listener.onPreGet();
+ }
+ }
+
+ protected void onPostExecute(HttpResponse httpResponse) {
+ if (listener != null) {
+ listener.onPostGet(httpResponse, httpResponse == null ? false : httpResponse.isInCache());
+ }
+ }
+ }
+}
diff --git a/src/cn/trinea/android/common/service/impl/FileNameRuleCurrentTime.java b/src/main/java/cn/trinea/android/common/service/impl/FileNameRuleCurrentTime.java
similarity index 70%
rename from src/cn/trinea/android/common/service/impl/FileNameRuleCurrentTime.java
rename to src/main/java/cn/trinea/android/common/service/impl/FileNameRuleCurrentTime.java
index 9b9099f..895e344 100644
--- a/src/cn/trinea/android/common/service/impl/FileNameRuleCurrentTime.java
+++ b/src/main/java/cn/trinea/android/common/service/impl/FileNameRuleCurrentTime.java
@@ -13,7 +13,7 @@
* use file suffix in url as target file suffix
*
*
- * @author Trinea 2012-7-6
+ * @author Trinea 2012-7-6
*/
public class FileNameRuleCurrentTime implements FileNameRule {
@@ -22,10 +22,9 @@ public class FileNameRuleCurrentTime implements FileNameRule {
private TimeRule timeRule;
/**
- * @param timeRule, see {@link TimeRule}
- * @return
+ * @param timeRule see {@link TimeRule}
*/
- public FileNameRuleCurrentTime(TimeRule timeRule){
+ public FileNameRuleCurrentTime(TimeRule timeRule) {
super();
this.timeRule = timeRule;
}
@@ -38,10 +37,6 @@ public String getFileName(String imageUrl) {
case TO_MILLIS:
time = now.getTimeInMillis();
break;
- case HOUR_OF_DAY_TO_MILLIS:
- time = ((now.get(Calendar.HOUR_OF_DAY) * 60 + now.get(Calendar.MINUTE)) * 60 + now.get(Calendar.SECOND))
- * 1000 + now.get(Calendar.MILLISECOND);
- break;
case YEAR:
time = now.get(Calendar.YEAR);
break;
@@ -51,17 +46,24 @@ public String getFileName(String imageUrl) {
case MILLISECOND:
time = now.get(Calendar.MILLISECOND);
break;
- case HOUR_OF_DAY_TO_MINUTE:
+ case HOUR_OF_DAY_TO_MILLIS:
+ time = ((now.get(Calendar.HOUR_OF_DAY) * 60 + now.get(Calendar.MINUTE)) * 60 + now.get(Calendar.SECOND))
+ * 1000 + now.get(Calendar.MILLISECOND);
+ break;
+ case HOUR_OF_DAY_TO_SECONDS:
+ time = (now.get(Calendar.HOUR_OF_DAY) * 60 + now.get(Calendar.MINUTE)) * 60 + now.get(Calendar.SECOND);
+ break;
+ case HOUR_OF_DAY_TO_MINUTES:
time = now.get(Calendar.HOUR_OF_DAY) * 60 + now.get(Calendar.MINUTE);
break;
case HOUR_TO_MILLIS:
time = ((now.get(Calendar.HOUR) * 60 + now.get(Calendar.MINUTE)) * 60 + now.get(Calendar.SECOND))
- * 1000 + now.get(Calendar.MILLISECOND);
+ * 1000 + now.get(Calendar.MILLISECOND);
break;
- case MINUTE_TO_SECOND:
+ case MINUTE_TO_SECONDS:
time = now.get(Calendar.MINUTE) * 60 + now.get(Calendar.SECOND);
break;
- case TO_SECOND:
+ case TO_SECONDS:
time = now.getTimeInMillis() / 1000;
break;
default:
@@ -81,20 +83,22 @@ public String getFileName(String imageUrl) {
* {@link #MILLISECOND} milliseconds of current time, E.g., at 2012-7-6 14:37:58.365 PM result is 365
* {@link #HOUR_OF_DAY_TO_MILLIS} milliseconds of current time from hour(24 hours), E.g., at 2012-7-6
* 14:37:58.365 PM result is 52678365
- * {@link #HOUR_OF_DAY_TO_MINUTE} seconds of current time from hour(24 hours), E.g., at 2012-7-6 14:37:58.365 PM
- * result is 877
+ * {@link #HOUR_OF_DAY_TO_SECONDS} seconds of current time from hour(24 hours), E.g., at 2012-7-6 14:37:58.365
+ * PM result is 52678
+ * {@link #HOUR_OF_DAY_TO_MINUTES} minutes of current time from hour(24 hours), E.g., at 2012-7-6 14:37:58.365
+ * PM result is 877
* {@link #HOUR_TO_MILLIS} milliseconds of current time from hour(12 hours), E.g., at 2012-7-6 14:37:58.365 PM
* result is 9478365
- * {@link #MINUTE_TO_SECOND} seconds of current time from hour(12 hours), E.g., at 2012-7-6 14:37:58.365 PM
+ * {@link #MINUTE_TO_SECONDS} seconds of current time from hour(12 hours), E.g., at 2012-7-6 14:37:58.365 PM
* result is 2278
* {@link #TO_MILLIS} current time in milliseconds, E.g., at 2012-7-6 14:37:58.365 PM result is 1341556678365
- * {@link #TO_SECOND} current time in seconds, E.g., at 2012-7-6 14:37:58.365 PM result is 1341556678
+ * {@link #TO_SECONDS} current time in seconds, E.g., at 2012-7-6 14:37:58.365 PM result is 1341556678
*
*
- * @author Trinea 2012-7-6
+ * @author Trinea 2012-7-6
*/
public enum TimeRule {
- YEAR, DAY_OF_MONTH, MILLISECOND, HOUR_OF_DAY_TO_MILLIS, HOUR_OF_DAY_TO_MINUTE, HOUR_TO_MILLIS,
- MINUTE_TO_SECOND, TO_MILLIS, TO_SECOND
+ YEAR, DAY_OF_MONTH, MILLISECOND, HOUR_OF_DAY_TO_MILLIS, HOUR_OF_DAY_TO_SECONDS, HOUR_OF_DAY_TO_MINUTES,
+ HOUR_TO_MILLIS, MINUTE_TO_SECONDS, TO_MILLIS, TO_SECONDS
}
}
diff --git a/src/cn/trinea/android/common/service/impl/FileNameRuleImageUrl.java b/src/main/java/cn/trinea/android/common/service/impl/FileNameRuleImageUrl.java
similarity index 54%
rename from src/cn/trinea/android/common/service/impl/FileNameRuleImageUrl.java
rename to src/main/java/cn/trinea/android/common/service/impl/FileNameRuleImageUrl.java
index d594d1e..27d2436 100644
--- a/src/cn/trinea/android/common/service/impl/FileNameRuleImageUrl.java
+++ b/src/main/java/cn/trinea/android/common/service/impl/FileNameRuleImageUrl.java
@@ -9,9 +9,10 @@
*
* - use image url as file name, replace char with _ if not letter or number
* - use file suffix in url as target file suffix
+ * - use {@link #setFileExtension(String)} set file extension
*
*
- * @author Trinea 2012-11-21
+ * @author Trinea 2012-11-21
*/
public class FileNameRuleImageUrl implements FileNameRule {
@@ -22,17 +23,23 @@ public class FileNameRuleImageUrl implements FileNameRule {
/** max length of file name, not include suffix **/
public static final int MAX_FILE_NAME_LENGTH = 127;
+ private String fileExtension = null;
+
@Override
public String getFileName(String imageUrl) {
if (StringUtils.isEmpty(imageUrl)) {
return DEFAULT_FILE_NAME;
}
- String ext = FileUtils.getFileExtension(imageUrl);
- String fileName = (imageUrl.length() >= MAX_FILE_NAME_LENGTH
- ? imageUrl.substring(imageUrl.length() - MAX_FILE_NAME_LENGTH, imageUrl.length()) : imageUrl).replaceAll("[\\W]",
- "_");
- return StringUtils.isEmpty(ext) ? fileName
- : (new StringBuilder().append(fileName).append(".").append(ext).toString());
+ String ext = (fileExtension == null ? FileUtils.getFileExtension(imageUrl) : fileExtension);
+ String fileName = (imageUrl.length() > MAX_FILE_NAME_LENGTH ? imageUrl.substring(imageUrl.length()
+ - MAX_FILE_NAME_LENGTH, imageUrl.length()) : imageUrl).replaceAll("[\\W]", "_");
+ return StringUtils.isEmpty(ext) ? fileName : (new StringBuilder().append(fileName).append(".")
+ .append(ext.replaceAll("[\\W]", "_")).toString());
+ }
+
+ public FileNameRuleImageUrl setFileExtension(String fileExtension) {
+ this.fileExtension = fileExtension;
+ return this;
}
}
diff --git a/src/main/java/cn/trinea/android/common/service/impl/ImageCache.java b/src/main/java/cn/trinea/android/common/service/impl/ImageCache.java
new file mode 100644
index 0000000..0bb666d
--- /dev/null
+++ b/src/main/java/cn/trinea/android/common/service/impl/ImageCache.java
@@ -0,0 +1,549 @@
+package cn.trinea.android.common.service.impl;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.Environment;
+import android.view.View;
+import cn.trinea.android.common.entity.CacheObject;
+import cn.trinea.android.common.service.CacheFullRemoveType;
+import cn.trinea.android.common.service.FileNameRule;
+import cn.trinea.android.common.service.impl.ImageMemoryCache.OnImageCallbackListener;
+import cn.trinea.android.common.util.FileUtils;
+
+/**
+ * Image Cache
+ *
+ * It's a cache with primary cache and secondary cache. It's a combination of {@link ImageMemoryCache} and
+ * {@link ImageSDCardCache}. It applies to apps those used much images, like sina weibo, twitter, taobao, huaban, weixin
+ * and so on.
+ *
+ * Setting and Usage
+ * - Use one of constructors in sections II to init cache
+ * - {@link ImageMemoryCache#setOnImageCallbackListener(OnImageCallbackListener)} set callback interface after image
+ * get success
+ * - {@link ImageMemoryCache#get(String, List, View)} get image asynchronous and preload other images asynchronous
+ * according to urlList
+ * - {@link ImageMemoryCache#get(String, View)} get image asynchronous
+ * - {@link #initData(Context, String)} or {@link #loadDataFromDb(Context, String)} to init data when app start,
+ * {@link #saveDataToDb(Context, String)} to save data when app exit
+ * - {@link #setHttpReadTimeOut(int)} set http read image time out, if less than 0, not set. default is not set
+ * - {@link PreloadDataCache#setContext(Context)} and {@link #setAllowedNetworkTypes(int)} restrict the types of
+ * networks over which this data can get.
+ * - {@link ImageMemoryCache#setOpenWaitingQueue(boolean)} set whether open waiting queue, default is true. If true,
+ * save all view waiting for image loaded, else only save the newest one
+ * - {@link PreloadDataCache#setOnGetDataListener(OnGetDataListener)} set how to get image, this cache will get image
+ * and preload images by it
+ * - {@link SimpleCache#setCacheFullRemoveType(CacheFullRemoveType)} set remove type when primary cache is full
+ * - {@link #setCacheFullRemoveTypeOfSecondaryCache(CacheFullRemoveType)} set remove type when secondary cache is full
+ *
+ *
+ *
+ * Constructor
+ * - {@link #ImageCache()}
+ * - {@link #ImageCache(int)}
+ * - {@link #ImageCache(int, int)}
+ * - {@link #ImageCache(int, int, int, int)}
+ *
+ *
+ * Attentions
+ * - You should add android.permission.WRITE_EXTERNAL_STORAGE in manifest, to store image to sdcard.
+ * - You should add android.permission.ACCESS_NETWORK_STATE in manifest if you get image from
+ * network.
+ *
+ *
+ * @author Trinea 2013-10-18
+ */
+public class ImageCache extends ImageMemoryCache {
+
+ private static final long serialVersionUID = 1L;
+ private ImageSDCardCache secondaryCache;
+ /** image compress size **/
+ private int compressSize = 1;
+ /** compress listener, advanced image compress, will override compressSize **/
+ private CompressListener compressListener;
+
+ /** cache folder path which be used when saving images **/
+ public static final String DEFAULT_CACHE_FOLDER = new StringBuilder()
+ .append(Environment.getExternalStorageDirectory()
+ .getAbsolutePath()).append(File.separator)
+ .append("Trinea").append(File.separator)
+ .append("AndroidCommon").append(File.separator)
+ .append("ImageCache").toString();
+
+ /**
+ *
+ * - max size of primary cache is {@link ImageMemoryCache#DEFAULT_MAX_SIZE}, max size of secondary cache is
+ * {@link ImageSDCardCache#DEFAULT_MAX_SIZE}
+ * - thread pool size of primary cache and secondary cache both are
+ * {@link PreloadDataCache#DEFAULT_THREAD_POOL_SIZE}
+ *
+ *
+ * @see {@link #ImageCache(int, int, int, int)}
+ */
+ public ImageCache() {
+ this(ImageMemoryCache.DEFAULT_MAX_SIZE, PreloadDataCache.DEFAULT_THREAD_POOL_SIZE,
+ ImageSDCardCache.DEFAULT_MAX_SIZE, PreloadDataCache.DEFAULT_THREAD_POOL_SIZE);
+ }
+
+ /**
+ *
+ * - max size of secondary cache is {@link ImageSDCardCache#DEFAULT_MAX_SIZE}
+ * - thread pool size of primary cache and secondary cache both are
+ * {@link PreloadDataCache#DEFAULT_THREAD_POOL_SIZE}
+ *
+ *
+ * @param primaryCacheMaxSize
+ * @param secondaryCacheMaxSize
+ * @see {@link #ImageCache(int, int, int, int)}
+ */
+ public ImageCache(int primaryCacheMaxSize) {
+ this(primaryCacheMaxSize, PreloadDataCache.DEFAULT_THREAD_POOL_SIZE, ImageSDCardCache.DEFAULT_MAX_SIZE,
+ PreloadDataCache.DEFAULT_THREAD_POOL_SIZE);
+ }
+
+ /**
+ * thread pool size of primary cache and secondary cache both are {@link PreloadDataCache#DEFAULT_THREAD_POOL_SIZE}
+ *
+ * @param primaryCacheMaxSize
+ * @param secondaryCacheMaxSize
+ * @see {@link #ImageCache(int, int, int, int)}
+ */
+ public ImageCache(int primaryCacheMaxSize, int secondaryCacheMaxSize) {
+ this(primaryCacheMaxSize, PreloadDataCache.DEFAULT_THREAD_POOL_SIZE, secondaryCacheMaxSize,
+ PreloadDataCache.DEFAULT_THREAD_POOL_SIZE);
+ }
+
+ /**
+ *
+ * - Callback interface after image get success is null, can set by
+ * {@link PreloadDataCache#setOnImageCallbackListener(OnImageCallbackListener)}
+ * - Get data listener of primary cache is {@link #getOnGetImageListenerOfPrimaryCache()}, you can set by
+ * {@link #setOnGetImageListenerOfPrimaryCache(OnGetDataListener)}, but not recommended, you may destory secondary
+ * cache.
+ * - Get data listener of secondary cache is {@link #getOnGetImageListenerOfSecondaryCache()}, you can set by
+ * {@link #setOnGetImageListenerOfSecondaryCache(OnGetDataListener)}.
+ * - Elements of the cache will not invalid
+ * - Remove type of primary cache is {@link RemoveTypeUsedCountSmall} when cache is full
+ *
+ *
+ * @param primaryCacheMaxSize maximum size of the primary cache
+ * @param primaryCacheThreadPoolSize getting data thread pool size of the primary cache
+ * @param secondaryCacheMaxSize maximum size of the secondary cache
+ * @param secondaryCacheThreadPoolSize getting data thread pool size of the secondary cache
+ */
+ public ImageCache(int primaryCacheMaxSize, int primaryCacheThreadPoolSize, int secondaryCacheMaxSize,
+ int secondaryCacheThreadPoolSize) {
+ super(primaryCacheMaxSize, primaryCacheThreadPoolSize);
+
+ setOnGetDataListener(new OnGetDataListener() {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public CacheObject onGetData(String key) {
+ try {
+ CacheObject object = secondaryCache.get(key);
+ String imagePath = (object == null ? null : object.getData());
+ if (FileUtils.isFileExist(imagePath)) {
+ if (compressListener != null) {
+ compressSize = compressListener.getCompressSize(imagePath);
+ }
+ Bitmap bm;
+ if (compressSize > 1) {
+ BitmapFactory.Options option = new BitmapFactory.Options();
+ option.inSampleSize = compressSize;
+ bm = BitmapFactory.decodeFile(imagePath, option);
+ } else {
+ bm = BitmapFactory.decodeFile(imagePath);
+ }
+ return (bm == null ? null : new CacheObject(bm));
+ } else {
+ secondaryCache.remove(key);
+ }
+ } catch (OutOfMemoryError e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+ });
+ super.setCheckNetwork(false);
+ setCacheFullRemoveType(new RemoveTypeUsedCountSmall());
+
+ secondaryCache = new ImageSDCardCache(secondaryCacheMaxSize, secondaryCacheThreadPoolSize);
+ secondaryCache.setCacheFolder(DEFAULT_CACHE_FOLDER);
+ secondaryCache.setFileNameRule(new FileNameRuleImageUrl().setFileExtension(""));
+ }
+
+ /**
+ * get compressSize
+ *
+ * @return the compressSize
+ */
+ public int getCompressSize() {
+ return compressSize;
+ }
+
+ /**
+ * set image compress scale
+ *
+ * Attentions:
+ * - if {@link #setCompressListener(CompressListener)} is set, this function is not work
+ *
+ *
+ * @param compressSize the compressSize to set
+ * @see {@link #setCompressSize(String)}
+ */
+ public void setCompressSize(int compressSize) {
+ this.compressSize = compressSize;
+ }
+
+ /**
+ * set compressListener
+ *
+ * Attentions:
+ * - if this function is set, the function {@link #setCompressSize(String)} is not work
+ *
+ *
+ * @param compressListener
+ */
+ public void setCompressListener(CompressListener compressListener) {
+ this.compressListener = compressListener;
+ }
+
+ /**
+ * get compressListener
+ *
+ * @return compressListener
+ */
+ public CompressListener getCompressListener() {
+ return compressListener;
+ }
+
+ /**
+ * set image compress scale
+ */
+ public interface CompressListener {
+
+ /**
+ * get image compress scale
+ *
+ * Attentions:
+ * - if this function is set, the function {@link #setCompressSize(String)} is not work
+ *
+ *
+ * @param imagePath
+ * @return return compressSize, If > 1, requests the decoder to subsample the original image, returning a
+ * smaller image to save memory. The sample size is the number of pixels in either dimension that
+ * correspond to a single pixel in the decoded bitmap. For example, inSampleSize == 4 returns an image
+ * that is 1/4 the width/height of the original, and 1/16 the number of pixels. Any value <= 1 is
+ * treated the same as 1. Note: the decoder will try to fulfill this request, but the resulting bitmap
+ * may have different dimensions that precisely what has been requested. Also, powers of 2 are often
+ * faster/easier for the decoder to honor.
+ */
+ public int getCompressSize(String imagePath);
+ }
+
+ /**
+ * get http read image time out of secondary cache, if less than 0, not set. default is not set
+ *
+ * @return the httpReadTimeOut
+ */
+ @Override
+ public int getHttpReadTimeOut() {
+ return secondaryCache.getHttpReadTimeOut();
+ }
+
+ /**
+ * set http read image time out of secondary cache, if less than 0, not set. default is not set, in mills
+ *
+ * @param readTimeOutMillis
+ */
+ @Override
+ public void setHttpReadTimeOut(int readTimeOutMillis) {
+ secondaryCache.setHttpReadTimeOut(readTimeOutMillis);
+ }
+
+ /**
+ * clear both primary cache and secondary cache
+ */
+ @Override
+ public void clear() {
+ super.clear();
+ secondaryCache.clear();
+ }
+
+ @Override
+ public void setForwardCacheNumber(int forwardCacheNumber) {
+ super.setForwardCacheNumber(forwardCacheNumber);
+ secondaryCache.setForwardCacheNumber(forwardCacheNumber);
+ }
+
+ @Override
+ public void setBackwardCacheNumber(int backwardCacheNumber) {
+ super.setForwardCacheNumber(backwardCacheNumber);
+ secondaryCache.setForwardCacheNumber(backwardCacheNumber);
+ }
+
+ @Override
+ public int getAllowedNetworkTypes() {
+ return secondaryCache.getAllowedNetworkTypes();
+ }
+
+ @Override
+ public void setAllowedNetworkTypes(int allowedNetworkTypes) {
+ secondaryCache.setAllowedNetworkTypes(allowedNetworkTypes);
+ }
+
+ @Override
+ public boolean isCheckNetwork() {
+ return secondaryCache.isCheckNetwork();
+ }
+
+ @Override
+ public void setCheckNetwork(boolean isCheckNetwork) {
+ secondaryCache.setCheckNetwork(isCheckNetwork);
+ }
+
+ @Override
+ public boolean checkIsNetworkTypeAllowed() {
+ return secondaryCache.checkIsNetworkTypeAllowed();
+ }
+
+ @Override
+ public Context getContext() {
+ return secondaryCache.getContext();
+ }
+
+ @Override
+ public void setContext(Context context) {
+ secondaryCache.setContext(context);
+ }
+
+ /**
+ * set http request properties
+ *
+ * - If image is from the different server, setRequestProperty("Connection", "false") is recommended. If image is
+ * from the same server, true is recommended, and this is the default value
+ *
+ *
+ * @param requestProperties
+ */
+ public void setRequestProperties(Map requestProperties) {
+ secondaryCache.setRequestProperties(requestProperties);
+ }
+
+ /**
+ * get http request properties
+ *
+ * @return
+ */
+ public Map getRequestProperties() {
+ return secondaryCache.getRequestProperties();
+ }
+
+ /**
+ * Sets the value of the http request header field
+ *
+ * @param field the request header field to be set
+ * @param newValue the new value of the specified property
+ * @see {@link #setRequestProperties(Map)}
+ */
+ public void setRequestProperty(String field, String newValue) {
+ secondaryCache.setRequestProperty(field, newValue);
+ }
+
+ /**
+ * get cache folder path which be used when saving images, default is {@link #DEFAULT_CACHE_FOLDER}
+ *
+ * @return the cacheFolder
+ * @see ImageSDCardCache#getCacheFolder()
+ */
+ public String getCacheFolder() {
+ return secondaryCache.getCacheFolder();
+ }
+
+ /**
+ * set cache folder path which be used when saving images, default is {@link #DEFAULT_CACHE_FOLDER}
+ *
+ * @param cacheFolder
+ * @see ImageSDCardCache#setCacheFolder(String)
+ */
+ public void setCacheFolder(String cacheFolder) {
+ secondaryCache.setCacheFolder(cacheFolder);
+ }
+
+ /**
+ * get file name rule which be used when saving images, default is {@link FileNameRuleImageUrl}
+ *
+ * @return the fileNameRule
+ * @see ImageSDCardCache#getFileNameRule()
+ */
+ public FileNameRule getFileNameRule() {
+ return secondaryCache.getFileNameRule();
+ }
+
+ /**
+ * set file name rule which be used when saving images, default is {@link FileNameRuleImageUrl}
+ *
+ * @param fileNameRule
+ * @see ImageSDCardCache#setFileNameRule(FileNameRule)
+ */
+ public void setFileNameRule(FileNameRule fileNameRule) {
+ secondaryCache.setFileNameRule(fileNameRule);
+ }
+
+ /**
+ * load all data from db and delete unused file in {@link #getCacheFolder()}
+ *
+ * - It's a combination of {@link #loadDataFromDb(Context, String)} and {@link #deleteUnusedFiles()}
+ * - You should use {@link #saveDataToDb(Context, String)} to save data when app exit
+ *
+ *
+ * @param context
+ * @param tag
+ * @see #loadDataFromDb(Context, String)
+ * @see #deleteUnusedFiles()
+ */
+ public void initData(Context context, String tag) {
+ loadDataFromDb(context, tag);
+ deleteUnusedFiles();
+ }
+
+ /**
+ * delete unused file in {@link #getCacheFolder()}, you can use it after {@link #loadDataFromDb(Context, String)} at
+ * first time
+ *
+ * @see {@link ImageSDCardCache#deleteUnusedFiles()}
+ */
+ public void deleteUnusedFiles() {
+ secondaryCache.deleteUnusedFiles();
+ }
+
+ /**
+ * load all data in db whose tag is same to tag to this cache. just put, do not affect the original data
+ *
+ * Attentions:
+ * - If tag is null or empty, throws exception
+ * - You should use {@link #saveDataToDb(Context, String)} to save data when app exit
+ *
+ *
+ * @param context
+ * @param tag tag used to mark this cache when save to and load from db, should be unique and cannot be null or
+ * empty
+ * @return
+ * @see ImageSDCardCache#loadDataFromDb(Context, ImageSDCardCache, String)
+ */
+ public boolean loadDataFromDb(Context context, String tag) {
+ return ImageSDCardCache.loadDataFromDb(context, secondaryCache, tag);
+ }
+
+ /**
+ * delete all rows in db whose tag is same to tag at first, and insert all data in this cache to db
+ *
+ * Attentions:
+ * - If tag is null or empty, throws exception
+ * - Will delete all rows in db whose tag is same to tag at first
+ * - You can use {@link #initData(Context, String)} or {@link #loadDataFromDb(Context, String)} to init data when
+ * app start
+ *
+ *
+ * @param context
+ * @param tag tag used to mark this cache when save to and load from db, should be unique and cannot be null or
+ * empty
+ * @return
+ * @see ImageSDCardCache#saveDataToDb(Context, ImageSDCardCache, String)
+ */
+ public boolean saveDataToDb(Context context, String tag) {
+ return ImageSDCardCache.saveDataToDb(context, secondaryCache, tag);
+ }
+
+ /**
+ * get image file path
+ *
+ * @param imageUrl
+ * @return if not in cache return null, else return full path.
+ */
+ public String getImagePath(String imageUrl) {
+ return secondaryCache.getImagePath(imageUrl);
+ }
+
+ /**
+ * @see ExecutorService#shutdown()
+ */
+ @Override
+ protected void shutdown() {
+ secondaryCache.shutdown();
+ super.shutdown();
+ }
+
+ /**
+ * @see ExecutorService#shutdownNow()
+ */
+ @Override
+ public List shutdownNow() {
+ secondaryCache.shutdownNow();
+ return super.shutdownNow();
+ }
+
+ /**
+ * get get image listener of primary cache
+ *
+ * @return
+ * @see {@link PreloadDataCache#getOnGetDataListener()}
+ */
+ public OnGetDataListener getOnGetImageListenerOfPrimaryCache() {
+ return getOnGetDataListener();
+ }
+
+ /**
+ * set get data listener of primary cache, primary cache will get data and preload data by it
+ *
+ * @param onGetImageListener
+ * @see {@link PreloadDataCache#setOnGetDataListener(OnGetDataListener)}
+ */
+ public void setOnGetImageListenerOfPrimaryCache(OnGetDataListener onGetImageListener) {
+ this.onGetDataListener = onGetImageListener;
+ }
+
+ /**
+ * get get image listener of secondary cache
+ *
+ * @return
+ */
+ public OnGetDataListener getOnGetImageListenerOfSecondaryCache() {
+ return secondaryCache.getOnGetDataListener();
+ }
+
+ /**
+ * set get data listener of secondary cache, secondary cache will get data and preload data by it
+ *
+ * @param onGetImageListener
+ */
+ public void setOnGetImageListenerOfSecondaryCache(OnGetDataListener onGetImageListener) {
+ secondaryCache.setOnGetDataListener(onGetImageListener);
+ }
+
+ /**
+ * get remove type when secondary cache is full
+ *
+ * @return
+ */
+ public CacheFullRemoveType getCacheFullRemoveTypeOfSecondaryCache() {
+ return secondaryCache.getCacheFullRemoveType();
+ }
+
+ /**
+ * set remove type when secondary cache is full
+ *
+ * @param cacheFullRemoveType the cacheFullRemoveType to set
+ */
+ public void setCacheFullRemoveTypeOfSecondaryCache(CacheFullRemoveType cacheFullRemoveType) {
+ secondaryCache.setCacheFullRemoveType(cacheFullRemoveType);
+ }
+}
diff --git a/src/main/java/cn/trinea/android/common/service/impl/ImageMemoryCache.java b/src/main/java/cn/trinea/android/common/service/impl/ImageMemoryCache.java
new file mode 100644
index 0000000..f4054e0
--- /dev/null
+++ b/src/main/java/cn/trinea/android/common/service/impl/ImageMemoryCache.java
@@ -0,0 +1,562 @@
+package cn.trinea.android.common.service.impl;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.view.View;
+import cn.trinea.android.common.entity.CacheObject;
+import cn.trinea.android.common.entity.FailedReason;
+import cn.trinea.android.common.entity.FailedReason.FailedType;
+import cn.trinea.android.common.service.CacheFullRemoveType;
+import cn.trinea.android.common.util.ImageUtils;
+import cn.trinea.android.common.util.SizeUtils;
+import cn.trinea.android.common.util.StringUtils;
+import cn.trinea.android.common.util.SystemUtils;
+
+/**
+ * Image Memory Cache
+ *
+ * It applies to images those uesd frequently, like users avatar of twitter or sina weibo. Cache of big image you can
+ * consider of {@link ImageSDCardCache}.
+ *
+ * Setting and Usage
+ * - Use one of constructors in sections II to init cache
+ * - {@link #setOnImageCallbackListener(OnImageCallbackListener)} set callback interface when getting image
+ * - {@link #get(String, List, View)} get image asynchronous and preload other images asynchronous according to
+ * urlList
+ * - {@link #get(String, View)} get image asynchronous
+ * - {@link #setHttpReadTimeOut(int)} set http read image time out, if less than 0, not set. default is not set
+ * - {@link PreloadDataCache#setContext(Context)} and {@link PreloadDataCache#setAllowedNetworkTypes(int)} restrict
+ * the types of networks over which this data can get.
+ * - {@link #setOpenWaitingQueue(boolean)} set whether open waiting queue, default is true. If true, save all view
+ * waiting for image loaded, else only save the newest one
+ * - {@link PreloadDataCache#setOnGetDataListener(OnGetDataListener)} set how to get image, this cache will get image
+ * and preload images by it
+ * - {@link SimpleCache#setCacheFullRemoveType(CacheFullRemoveType)} set remove type when cache is full
+ * - other see {@link PreloadDataCache} and {@link SimpleCache}
+ *
+ *
+ * Constructor
+ * - {@link #ImageMemoryCache()}
+ * - {@link #ImageMemoryCache(int)}
+ * - {@link #ImageMemoryCache(int, int)}
+ *
+ *
+ * Attentions
+ * - You should add android.permission.ACCESS_NETWORK_STATE in manifest if you get image from
+ * network.
+ *
+ *
+ * @author Trinea 2012-4-5
+ */
+public class ImageMemoryCache extends PreloadDataCache {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final String TAG = "ImageCache";
+
+ /** callback interface when getting image **/
+ private OnImageCallbackListener onImageCallbackListener;
+ /** http read image time out, if less than 0, not set. default is not set **/
+ private int httpReadTimeOut = -1;
+ /**
+ * whether open waiting queue, default is true. If true, save all view waiting for image loaded, else only save the
+ * newest one
+ **/
+ private boolean isOpenWaitingQueue = true;
+ /** http request properties **/
+ private Map requestProperties = null;
+
+ /** recommend default max cache size according to dalvik max memory **/
+ public static final int DEFAULT_MAX_SIZE = getDefaultMaxSize();
+ /** message what for get image successfully **/
+ private static final int WHAT_GET_IMAGE_SUCCESS = 1;
+ /** message what for get image failed **/
+ private static final int WHAT_GET_IMAGE_FAILED = 2;
+
+ /** thread pool whose wait for data got, attention, not the get data thread pool **/
+ private transient ExecutorService threadPool = Executors
+ .newFixedThreadPool(SystemUtils.DEFAULT_THREAD_POOL_SIZE);
+ /**
+ * key is image url, value is the newest view which waiting for image loaded, used when {@link #isOpenWaitingQueue}
+ * is false
+ **/
+ private transient Map viewMap;
+ /**
+ * key is image url, value is view set those waiting for image loaded, used when {@link #isOpenWaitingQueue} is true
+ **/
+ private transient Map> viewSetMap;
+
+ private transient Handler handler;
+
+ /**
+ * get image asynchronous. when get image success, it will pass to
+ * {@link OnImageCallbackListener#onGetSuccess(String, Bitmap, View, boolean)}
+ *
+ * @param imageUrl
+ * @param view
+ * @return whether image already in cache or not
+ */
+ public boolean get(String imageUrl, View view) {
+ return get(imageUrl, null, view);
+ }
+
+ /**
+ * get image asynchronous and preload other images asynchronous according to urlList
+ *
+ * @param imageUrl
+ * @param urlList url list, if is null, not preload, else preload forward by
+ * {@link PreloadDataCache#preloadDataForward(Object, List, int)}, preload backward by
+ * {@link PreloadDataCache#preloadDataBackward(Object, List, int)}
+ * @param view
+ * @return whether image already in cache or not
+ */
+ public boolean get(final String imageUrl, final List urlList, final View view) {
+ if (onImageCallbackListener != null) {
+ onImageCallbackListener.onPreGet(imageUrl, view);
+ }
+
+ if (StringUtils.isEmpty(imageUrl)) {
+ if (onImageCallbackListener != null) {
+ onImageCallbackListener.onGetNotInCache(imageUrl, view);
+ }
+ return false;
+ }
+
+ /**
+ * if already in cache, call onImageSDCallbackListener, else new thread to wait for it
+ */
+ CacheObject object = getFromCache(imageUrl, urlList);
+ if (object != null) {
+ Bitmap bitmap = object.getData();
+ if (bitmap != null) {
+ onGetSuccess(imageUrl, bitmap, view, true);
+ return true;
+ } else {
+ remove(imageUrl);
+ }
+ }
+
+ if (isOpenWaitingQueue) {
+ synchronized (viewSetMap) {
+ HashSet viewSet = viewSetMap.get(imageUrl);
+ if (viewSet == null) {
+ viewSet = new HashSet();
+ viewSetMap.put(imageUrl, viewSet);
+ }
+ viewSet.add(view);
+ }
+ } else {
+ viewMap.put(imageUrl, view);
+ }
+
+ if (onImageCallbackListener != null) {
+ onImageCallbackListener.onGetNotInCache(imageUrl, view);
+ }
+ if (isExistGettingDataThread(imageUrl)) {
+ return false;
+ }
+
+ startGetImageThread(imageUrl, urlList);
+ return false;
+ }
+
+ /**
+ * get callback interface when getting image
+ *
+ * @return the onImageCallbackListener
+ */
+ public OnImageCallbackListener getOnImageCallbackListener() {
+ return onImageCallbackListener;
+ }
+
+ /**
+ * set callback interface when getting image
+ *
+ * @param onImageCallbackListener
+ */
+ public void setOnImageCallbackListener(OnImageCallbackListener onImageCallbackListener) {
+ this.onImageCallbackListener = onImageCallbackListener;
+ }
+
+ /**
+ * get http read image time out, if less than 0, not set. default is not set
+ *
+ * @return the httpReadTimeOut
+ */
+ public int getHttpReadTimeOut() {
+ return httpReadTimeOut;
+ }
+
+ /**
+ * set http read image time out, if less than 0, not set. default is not set, in mills
+ *
+ * @param readTimeOutMillis
+ */
+ public void setHttpReadTimeOut(int readTimeOutMillis) {
+ this.httpReadTimeOut = readTimeOutMillis;
+ }
+
+ /**
+ * get whether open waiting queue, default is true. If true, save all view waiting for image loaded, else only save
+ * the newest one
+ *
+ * @return
+ */
+ public boolean isOpenWaitingQueue() {
+ return isOpenWaitingQueue;
+ }
+
+ /**
+ * set whether open waiting queue, default is true. If true, save all view waiting for image loaded, else only save
+ * the newest one
+ *
+ * @param isOpenWaitingQueue
+ */
+ public void setOpenWaitingQueue(boolean isOpenWaitingQueue) {
+ this.isOpenWaitingQueue = isOpenWaitingQueue;
+ }
+
+ /**
+ * set http request properties
+ *
+ * - If image is from the different server, setRequestProperty("Connection", "false") is recommended. If image is
+ * from the same server, true is recommended, and this is the default value
+ *
+ *
+ * @param requestProperties
+ */
+ public void setRequestProperties(Map requestProperties) {
+ this.requestProperties = requestProperties;
+ }
+
+ /**
+ * get http request properties
+ *
+ * @return
+ */
+ public Map getRequestProperties() {
+ return requestProperties;
+ }
+
+ /**
+ * Sets the value of the http request header field
+ *
+ * @param field the request header field to be set
+ * @param newValue the new value of the specified property
+ * @see {@link #setRequestProperties(Map)}
+ */
+ public void setRequestProperty(String field, String newValue) {
+ if (StringUtils.isEmpty(field)) {
+ return;
+ }
+
+ if (requestProperties == null) {
+ requestProperties = new HashMap();
+ }
+ requestProperties.put(field, newValue);
+ }
+
+ /**
+ *
+ * - Get data listener is {@link #getDefaultOnGetImageListener()}
+ * - callback interface when getting image is null, can set by
+ * {@link #setOnImageCallbackListener(OnImageCallbackListener)}
+ * - Maximum size of the cache is {@link #DEFAULT_MAX_SIZE}
+ * - Elements of the cache will not invalid
+ * - Remove type is {@link RemoveTypeUsedCountSmall} when cache is full
+ *
+ *
+ * @see PreloadDataCache#PreloadDataCache()
+ */
+ public ImageMemoryCache() {
+ this(DEFAULT_MAX_SIZE, PreloadDataCache.DEFAULT_THREAD_POOL_SIZE);
+ }
+
+ /**
+ *
+ * - Get data listener is {@link #getDefaultOnGetImageListener()}
+ * - callback interface when getting image is null, can set by
+ * {@link #setOnImageCallbackListener(OnImageCallbackListener)}
+ * - Elements of the cache will not invalid
+ * - Remove type is {@link RemoveTypeUsedCountSmall} when cache is full
+ *
+ *
+ * @param maxSize maximum size of the cache
+ * @see PreloadDataCache#PreloadDataCache(int)
+ */
+ public ImageMemoryCache(int maxSize) {
+ this(maxSize, PreloadDataCache.DEFAULT_THREAD_POOL_SIZE);
+ }
+
+ /**
+ *
+ * - Get data listener is {@link #getDefaultOnGetImageListener()}
+ * - callback interface when getting image is null, can set by
+ * {@link #setOnImageCallbackListener(OnImageCallbackListener)}
+ * - Elements of the cache will not invalid
+ * - Remove type is {@link RemoveTypeUsedCountSmall} when cache is full
+ *
+ *
+ * @param maxSize maximum size of the cache
+ * @param threadPoolSize getting data thread pool size
+ * @see PreloadDataCache#PreloadDataCache(int, int)
+ */
+ public ImageMemoryCache(int maxSize, int threadPoolSize) {
+ super(maxSize, threadPoolSize);
+
+ super.setOnGetDataListener(getDefaultOnGetImageListener());
+ super.setCacheFullRemoveType(new RemoveTypeUsedCountSmall());
+ this.viewMap = new ConcurrentHashMap();
+ this.viewSetMap = new HashMap>();
+ this.handler = new MyHandler();
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ }
+
+ /**
+ * callback interface when getting image
+ *
+ * @author Trinea 2012-4-5
+ */
+ public interface OnImageCallbackListener {
+
+ /**
+ * callback function before get image, run on ui thread
+ *
+ * @param imageUrl imageUrl
+ * @param view view need the image
+ */
+ public void onPreGet(String imageUrl, View view);
+
+ /**
+ * callback function when get image but image not in cache, run on ui thread.
+ * Will be called after {@link #onPreGet(String, View)}, before
+ * {@link #onGetSuccess(String, String, View, boolean)} and
+ * {@link #onGetFailed(String, String, View, FailedReason)}
+ *
+ * @param imageUrl imageUrl
+ * @param view view need the image
+ */
+ public void onGetNotInCache(String imageUrl, View view);
+
+ /**
+ * callback function after get image successfully, run on ui thread
+ *
+ * @param imageUrl imageUrl
+ * @param loadedImage loaded image bitmap
+ * @param view view need the image
+ * @param isInCache whether already in cache or got realtime
+ */
+ public void onGetSuccess(String imageUrl, Bitmap loadedImage, View view, boolean isInCache);
+
+ /**
+ * callback function after get image failed, run on ui thread
+ *
+ * @param imageUrl imageUrl
+ * @param loadedImage loaded image bitmap
+ * @param view view need the image
+ * @param failedReason failed reason for get image
+ */
+ public void onGetFailed(String imageUrl, Bitmap loadedImage, View view, FailedReason failedReason);
+ }
+
+ /**
+ * @see ExecutorService#shutdown()
+ */
+ protected void shutdown() {
+ threadPool.shutdown();
+ super.shutdown();
+ }
+
+ /**
+ * @see ExecutorService#shutdownNow()
+ */
+ public List shutdownNow() {
+ threadPool.shutdownNow();
+ return super.shutdownNow();
+ }
+
+ /**
+ * My handler
+ *
+ * @author Trinea 2012-11-20
+ */
+ private class MyHandler extends Handler {
+
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case WHAT_GET_IMAGE_SUCCESS:
+ case WHAT_GET_IMAGE_FAILED:
+ MessageObject object = (MessageObject)message.obj;
+ if (object == null) {
+ break;
+ }
+
+ String imageUrl = object.imageUrl;
+ Bitmap bitmap = object.bitmap;
+ if (onImageCallbackListener != null) {
+ if (isOpenWaitingQueue) {
+ synchronized (viewSetMap) {
+ HashSet viewSet = viewSetMap.get(imageUrl);
+ if (viewSet != null) {
+ for (View view : viewSet) {
+ if (view != null) {
+ if (WHAT_GET_IMAGE_SUCCESS == message.what) {
+ onGetSuccess(imageUrl, bitmap, view, false);
+ } else {
+ onImageCallbackListener.onGetFailed(imageUrl, bitmap, view,
+ object.failedReason);
+ }
+ }
+ }
+ }
+ }
+ } else {
+ View view = viewMap.get(imageUrl);
+ if (view != null) {
+ if (WHAT_GET_IMAGE_SUCCESS == message.what) {
+ onGetSuccess(imageUrl, bitmap, view, false);
+ } else {
+ onImageCallbackListener.onGetFailed(imageUrl, bitmap, view, object.failedReason);
+ }
+ }
+ }
+ }
+
+ if (isOpenWaitingQueue) {
+ synchronized (viewSetMap) {
+ viewSetMap.remove(imageUrl);
+ }
+ } else {
+ viewMap.remove(imageUrl);
+ }
+ break;
+ }
+ }
+ };
+
+ private void onGetSuccess(String imageUrl, Bitmap loadedImage, View view, boolean isInCache) {
+ if (onImageCallbackListener == null) {
+ return;
+ }
+
+ try {
+ onImageCallbackListener.onGetSuccess(imageUrl, loadedImage, view, isInCache);
+ } catch (OutOfMemoryError e) {
+ onImageCallbackListener.onGetFailed(imageUrl, loadedImage, view, new FailedReason(
+ FailedType.ERROR_OUT_OF_MEMORY, e));
+ }
+ }
+
+ /**
+ * message object
+ *
+ * @author Trinea 2013-1-14
+ */
+ private class MessageObject {
+
+ String imageUrl;
+ Bitmap bitmap;
+ FailedReason failedReason;
+
+ public MessageObject(String imageUrl, Bitmap bitmap) {
+ this.imageUrl = imageUrl;
+ this.bitmap = bitmap;
+ }
+
+ public MessageObject(String imageUrl, Bitmap bitmap, FailedReason failedReason) {
+ this.imageUrl = imageUrl;
+ this.bitmap = bitmap;
+ this.failedReason = failedReason;
+ }
+
+ }
+
+ /**
+ * start thread to wait for image get
+ *
+ * @param imageUrl
+ * @param urlList url list, if is null, not preload, else preload forward by
+ * {@link PreloadDataCache#preloadDataForward(Object, List, int)}, preload backward by
+ * {@link PreloadDataCache#preloadDataBackward(Object, List, int)}
+ */
+ private void startGetImageThread(final String imageUrl, final List urlList) {
+ // wait for image be got success and send message
+ threadPool.execute(new Runnable() {
+
+ @Override
+ public void run() {
+ try {
+ CacheObject object = get(imageUrl, urlList);
+ Bitmap bitmap = (object == null ? null : object.getData());
+ if (bitmap == null) {
+ // if bitmap is null, remove it
+ remove(imageUrl);
+ String failedException = "get image from network or save image to sdcard error. please make sure you have added permission android.permission.WRITE_EXTERNAL_STORAGE and android.permission.ACCESS_NETWORK_STATE";
+ FailedReason failedReason = new FailedReason(FailedType.ERROR_IO, failedException);
+ handler.sendMessage(handler.obtainMessage(WHAT_GET_IMAGE_FAILED, new MessageObject(imageUrl,
+ bitmap, failedReason)));
+ } else {
+ handler.sendMessage(handler.obtainMessage(WHAT_GET_IMAGE_SUCCESS, new MessageObject(imageUrl,
+ bitmap)));
+ }
+ } catch (OutOfMemoryError e) {
+ MessageObject msg = new MessageObject(imageUrl, null, new FailedReason(
+ FailedType.ERROR_OUT_OF_MEMORY, e));
+ handler.sendMessage(handler.obtainMessage(WHAT_GET_IMAGE_FAILED, msg));
+ }
+ }
+ });
+ }
+
+ /**
+ * default get image from network listener
+ *
+ * @return
+ */
+ public OnGetDataListener getDefaultOnGetImageListener() {
+ return new OnGetDataListener() {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public CacheObject onGetData(String key) {
+ Bitmap d = null;
+ try {
+ d = ImageUtils.getBitmapFromUrl(key, httpReadTimeOut, requestProperties);
+ } catch (Exception e) {
+ Log.e(TAG, "get image exception, imageUrl is:" + key, e);
+ }
+ return (d == null ? null : new CacheObject(d));
+ }
+ };
+ }
+
+ /**
+ * get recommend default max cache size according to dalvik max memory
+ *
+ * @return
+ */
+ static int getDefaultMaxSize() {
+ long maxMemory = Runtime.getRuntime().maxMemory();
+ if (maxMemory > SizeUtils.GB_2_BYTE) {
+ return 512;
+ }
+
+ int mb = (int)(maxMemory / SizeUtils.MB_2_BYTE);
+ return mb > 16 ? mb * 2 : 16;
+ }
+}
diff --git a/src/main/java/cn/trinea/android/common/service/impl/ImageSDCardCache.java b/src/main/java/cn/trinea/android/common/service/impl/ImageSDCardCache.java
new file mode 100644
index 0000000..04d4c2a
--- /dev/null
+++ b/src/main/java/cn/trinea/android/common/service/impl/ImageSDCardCache.java
@@ -0,0 +1,867 @@
+package cn.trinea.android.common.service.impl;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import android.content.Context;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.view.View;
+import cn.trinea.android.common.dao.impl.ImageSDCardCacheDaoImpl;
+import cn.trinea.android.common.entity.CacheObject;
+import cn.trinea.android.common.entity.FailedReason;
+import cn.trinea.android.common.entity.FailedReason.FailedType;
+import cn.trinea.android.common.service.CacheFullRemoveType;
+import cn.trinea.android.common.service.FileNameRule;
+import cn.trinea.android.common.util.FileUtils;
+import cn.trinea.android.common.util.ImageUtils;
+import cn.trinea.android.common.util.SizeUtils;
+import cn.trinea.android.common.util.SqliteUtils;
+import cn.trinea.android.common.util.StringUtils;
+import cn.trinea.android.common.util.SystemUtils;
+
+/**
+ * Image SDCard Cache
+ *
+ * It applies to images those uesd frequently and their size is big that we cannot store too much in memory, like
+ * pictures of twitter or sina weibo. Cache of small images you can consider of {@link ImageMemoryCache}.
+ *
+ * Setting and Usage
+ * - Use one of constructors in sections II to init cache
+ * - {@link #setOnImageSDCallbackListener(OnImageSDCallbackListener)} set callback interface when getting image
+ * - {@link #get(String, List, View)} get image asynchronous and preload other images asynchronous according to
+ * urlList
+ * - {@link #get(String, View)} get image asynchronous
+ * - {@link #initData(Context, String)} or {@link #loadDataFromDb(Context, String)} to init data when app start,
+ * {@link #saveDataToDb(Context, String)} to save data when app exit
+ * - {@link #setFileNameRule(FileNameRule)} set file name rule which be used when saving images, default is
+ * {@link FileNameRuleImageUrl}
+ * - {@link #setCacheFolder(String)} set cache folder path which be used when saving images, default is
+ * {@link #DEFAULT_CACHE_FOLDER}
+ * - {@link #setHttpReadTimeOut(int)} set http read image time out, if less than 0, not set. default is not set
+ * - {@link #setOpenWaitingQueue(boolean)} set whether open waiting queue, default is true. If true, save all view
+ * waiting for image loaded, else only save the newest one
+ * - {@link PreloadDataCache#setOnGetDataListener(OnGetDataListener)} set how to get image, this cache will get image
+ * and preload images by it
+ * - {@link SimpleCache#setCacheFullRemoveType(CacheFullRemoveType)} set remove type when cache is full
+ * - other see {@link PreloadDataCache} and {@link SimpleCache}
+ *
+ *
+ * Constructor
+ * - {@link #ImageSDCardCache()}
+ * - {@link #ImageSDCardCache(int)}
+ * - {@link #ImageSDCardCache(int, int)}
+ *
+ *
+ * Attentions
+ * - You should add android.permission.WRITE_EXTERNAL_STORAGE in manifest, to store image to sdcard.
+ * - You should add android.permission.ACCESS_NETWORK_STATE in manifest if you get image from
+ * network.
+ *
+ *
+ * @author Trinea 2012-4-5
+ */
+public class ImageSDCardCache extends PreloadDataCache {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final String TAG = "ImageSDCardCache";
+
+ /** callback interface when getting image **/
+ private OnImageSDCallbackListener onImageSDCallbackListener;
+ /** cache folder path which be used when saving images, default is {@link #DEFAULT_CACHE_FOLDER} **/
+ private String cacheFolder = DEFAULT_CACHE_FOLDER;
+ /** file name rule which be used when saving images, default is {@link FileNameRuleImageUrl} **/
+ private FileNameRule fileNameRule = new FileNameRuleImageUrl();
+ /** http read image time out, if less than 0, not set. default is not set **/
+ private int httpReadTimeOut = -1;
+ /**
+ * whether open waiting queue, default is true. If true, save all view waiting for image loaded, else only save the
+ * newest one
+ **/
+ private boolean isOpenWaitingQueue = true;
+ /** http request properties **/
+ private Map requestProperties = null;
+
+ /** recommend default max cache size according to dalvik max memory **/
+ public static final int DEFAULT_MAX_SIZE = getDefaultMaxSize();
+ /** cache folder path which be used when saving images **/
+ public static final String DEFAULT_CACHE_FOLDER = new StringBuilder()
+ .append(Environment
+ .getExternalStorageDirectory()
+ .getAbsolutePath())
+ .append(File.separator)
+ .append("Trinea")
+ .append(File.separator)
+ .append("AndroidCommon")
+ .append(File.separator)
+ .append("ImageSDCardCache").toString();
+
+ /** message what for get image successfully **/
+ private static final int WHAT_GET_IMAGE_SUCCESS = 1;
+ /** message what for get image failed **/
+ private static final int WHAT_GET_IMAGE_FAILED = 2;
+
+ /** thread pool whose wait for data got, attention, not the get data thread pool **/
+ private transient ExecutorService threadPool = Executors
+ .newFixedThreadPool(SystemUtils.DEFAULT_THREAD_POOL_SIZE);
+ /**
+ * key is image url, value is the newest view which waiting for image loaded, used when {@link #isOpenWaitingQueue}
+ * is false
+ **/
+ private transient Map viewMap;
+ /**
+ * key is image url, value is view set those waiting for image loaded, used when {@link #isOpenWaitingQueue} is true
+ **/
+ private transient Map> viewSetMap;
+ private transient Handler handler;
+
+ /**
+ * get image asynchronous. when get image success, it will pass to
+ * {@link OnImageSDCallbackListener#onGetSuccess(String, String, View, boolean)}
+ *
+ * @param imageUrl
+ * @param view
+ * @return whether image already in cache or not
+ */
+ public boolean get(String imageUrl, View view) {
+ return get(imageUrl, null, view);
+ }
+
+ /**
+ * get image asynchronous and preload other images asynchronous according to urlList
+ *
+ * @param imageUrl
+ * @param urlList url list, if is null, not preload, else preload forward by
+ * {@link PreloadDataCache#preloadDataForward(Object, List, int)}, preload backward by
+ * {@link PreloadDataCache#preloadDataBackward(Object, List, int)}
+ * @param view
+ * @return whether image already in cache or not
+ */
+ public boolean get(final String imageUrl, final List urlList, final View view) {
+ if (onImageSDCallbackListener != null) {
+ onImageSDCallbackListener.onPreGet(imageUrl, view);
+ }
+
+ if (StringUtils.isEmpty(imageUrl)) {
+ if (onImageSDCallbackListener != null) {
+ onImageSDCallbackListener.onGetNotInCache(imageUrl, view);
+ }
+ return false;
+ }
+
+ /**
+ * if already in cache, call onImageSDCallbackListener, else new thread to wait for it
+ */
+ CacheObject object = getFromCache(imageUrl, urlList);
+ if (object != null) {
+ String imagePath = object.getData();
+ if (!StringUtils.isEmpty(imagePath) && FileUtils.isFileExist(imagePath)) {
+ onGetSuccess(imageUrl, imagePath, view, true);
+ return true;
+ } else {
+ remove(imageUrl);
+ }
+ }
+
+ if (isOpenWaitingQueue) {
+ synchronized (viewSetMap) {
+ HashSet viewSet = viewSetMap.get(imageUrl);
+ if (viewSet == null) {
+ viewSet = new HashSet();
+ viewSetMap.put(imageUrl, viewSet);
+ }
+ viewSet.add(view);
+ }
+ } else {
+ viewMap.put(imageUrl, view);
+ }
+
+ if (onImageSDCallbackListener != null) {
+ onImageSDCallbackListener.onGetNotInCache(imageUrl, view);
+ }
+ if (isExistGettingDataThread(imageUrl)) {
+ return false;
+ }
+
+ startGetImageThread(imageUrl, urlList);
+ return false;
+ }
+
+ /**
+ * get cache folder path which be used when saving images, default is {@link #DEFAULT_CACHE_FOLDER}
+ *
+ * @return the cacheFolder
+ */
+ public String getCacheFolder() {
+ return cacheFolder;
+ }
+
+ /**
+ * set cache folder path which be used when saving images, default is {@link #DEFAULT_CACHE_FOLDER}
+ *
+ * @param cacheFolder
+ */
+ public void setCacheFolder(String cacheFolder) {
+ if (StringUtils.isEmpty(cacheFolder)) {
+ throw new IllegalArgumentException("The cacheFolder of cache can not be null.");
+ }
+
+ this.cacheFolder = cacheFolder;
+ }
+
+ /**
+ * get file name rule which be used when saving images, default is {@link FileNameRuleImageUrl}
+ *
+ * @return the fileNameRule
+ */
+ public FileNameRule getFileNameRule() {
+ return fileNameRule;
+ }
+
+ /**
+ * set file name rule which be used when saving images, default is {@link FileNameRuleImageUrl}
+ *
+ * @param fileNameRule
+ */
+ public void setFileNameRule(FileNameRule fileNameRule) {
+ if (fileNameRule == null) {
+ throw new IllegalArgumentException("The fileNameRule of cache can not be null.");
+ }
+ this.fileNameRule = fileNameRule;
+ }
+
+ /**
+ * get callback interface when getting image
+ *
+ * @return the onImageSDCallbackListener
+ */
+ public OnImageSDCallbackListener getOnImageSDCallbackListener() {
+ return onImageSDCallbackListener;
+ }
+
+ /**
+ * set callback interface when getting image
+ *
+ * @param onImageSDCallbackListener the onImageSDCallbackListener to set
+ */
+ public void setOnImageSDCallbackListener(OnImageSDCallbackListener onImageSDCallbackListener) {
+ this.onImageSDCallbackListener = onImageSDCallbackListener;
+ }
+
+ /**
+ * get http read image time out, if less than 0, not set. default is not set
+ *
+ * @return the httpReadTimeOut
+ */
+ public int getHttpReadTimeOut() {
+ return httpReadTimeOut;
+ }
+
+ /**
+ * set http read image time out, if less than 0, not set. default is not set, in mills
+ *
+ * @param readTimeOutMillis
+ */
+ public void setHttpReadTimeOut(int readTimeOutMillis) {
+ this.httpReadTimeOut = readTimeOutMillis;
+ }
+
+ /**
+ * get whether open waiting queue, default is true. If true, save all view waiting for image loaded, else only save
+ * the newest one
+ *
+ * @return
+ */
+ public boolean isOpenWaitingQueue() {
+ return isOpenWaitingQueue;
+ }
+
+ /**
+ * set whether open waiting queue, default is true. If true, save all view waiting for image loaded, else only save
+ * the newest one
+ *
+ * @param isOpenWaitingQueue
+ */
+ public void setOpenWaitingQueue(boolean isOpenWaitingQueue) {
+ this.isOpenWaitingQueue = isOpenWaitingQueue;
+ }
+
+ /**
+ * set http request properties
+ *
+ * - If image is from the different server, setRequestProperty("Connection", "false") is recommended. If image is
+ * from the same server, true is recommended, and this is the default value
+ *
+ *
+ * @param requestProperties
+ */
+ public void setRequestProperties(Map requestProperties) {
+ this.requestProperties = requestProperties;
+ }
+
+ /**
+ * get http request properties
+ *
+ * @return
+ */
+ public Map getRequestProperties() {
+ return requestProperties;
+ }
+
+ /**
+ * Sets the value of the http request header field
+ *
+ * @param field the request header field to be set
+ * @param newValue the new value of the specified property
+ * @see {@link #setRequestProperties(Map)}
+ */
+ public void setRequestProperty(String field, String newValue) {
+ if (StringUtils.isEmpty(field)) {
+ return;
+ }
+
+ if (requestProperties == null) {
+ requestProperties = new HashMap();
+ }
+ requestProperties.put(field, newValue);
+ }
+
+ /**
+ *
+ * - Get data listener is {@link #getDefaultOnGetImageListener()}
+ * - callback interface when getting image is null, can set by
+ * {@link #setOnImageSDCallbackListener(OnImageSDCallbackListener)}
+ * - Maximum size of the cache is {@link #DEFAULT_MAX_SIZE}
+ * - Elements of the cache will not invalid
+ * - Remove type is {@link RemoveTypeUsedCountSmall} when cache is full
+ *
+ *
+ * @see PreloadDataCache#PreloadDataCache()
+ */
+ public ImageSDCardCache() {
+ this(DEFAULT_MAX_SIZE, PreloadDataCache.DEFAULT_THREAD_POOL_SIZE);
+ }
+
+ /**
+ *
+ * - Get data listener is {@link #getDefaultOnGetImageListener()}
+ * - callback interface when getting image is null, can set by
+ * {@link #setOnImageSDCallbackListener(OnImageSDCallbackListener)}
+ * - Elements of the cache will not invalid
+ * - Remove type is {@link RemoveTypeUsedCountSmall} when cache is full
+ *
+ *
+ * @param maxSize maximum size of the cache
+ * @see PreloadDataCache#PreloadDataCache(int)
+ */
+ public ImageSDCardCache(int maxSize) {
+ this(maxSize, PreloadDataCache.DEFAULT_THREAD_POOL_SIZE);
+ }
+
+ /**
+ *
+ * - Get data listener is {@link #getDefaultOnGetImageListener()}
+ * - callback interface when getting image is null, can set by
+ * {@link #setOnImageSDCallbackListener(OnImageSDCallbackListener)}
+ * - Elements of the cache will not invalid
+ * - Remove type is {@link RemoveTypeUsedCountSmall} when cache is full
+ *
+ *
+ * @param maxSize maximum size of the cache
+ * @param threadPoolSize getting data thread pool size
+ * @see PreloadDataCache#PreloadDataCache(int, int)
+ */
+ public ImageSDCardCache(int maxSize, int threadPoolSize) {
+ super(maxSize, threadPoolSize);
+
+ super.setOnGetDataListener(getDefaultOnGetImageListener());
+ super.setCacheFullRemoveType(new RemoveTypeUsedCountSmall());
+ this.viewMap = new ConcurrentHashMap();
+ this.viewSetMap = new HashMap>();
+ this.handler = new MyHandler();
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ }
+
+ /**
+ * callback interface when getting image
+ *
+ * @author Trinea 2012-4-5
+ */
+ public interface OnImageSDCallbackListener {
+
+ /**
+ * callback function before get image, run on ui thread
+ *
+ * @param imageUrl imageUrl
+ * @param view view need the image
+ */
+ public void onPreGet(String imageUrl, View view);
+
+ /**
+ * callback function when get image but image not in cache, run on ui thread.
+ * Will be called after {@link #onPreGet(String, View)}, before
+ * {@link #onGetSuccess(String, String, View, boolean)} and
+ * {@link #onGetFailed(String, String, View, FailedReason)}
+ *
+ * @param imageUrl imageUrl
+ * @param view view need the image
+ */
+ public void onGetNotInCache(String imageUrl, View view);
+
+ /**
+ * callback function after get image successfully, run on ui thread
+ *
+ * @param imageUrl imageUrl
+ * @param imagePath image path
+ * @param view view need the image
+ * @param isInCache whether already in cache or got realtime
+ */
+ public void onGetSuccess(String imageUrl, String imagePath, View view, boolean isInCache);
+
+ /**
+ * callback function after get image failed, run on ui thread
+ *
+ * @param imageUrl imageUrl
+ * @param imagePath image path
+ * @param view view need the image
+ * @param failedReason failed reason for get image
+ */
+ public void onGetFailed(String imageUrl, String imagePath, View view, FailedReason failedReason);
+ }
+
+ /**
+ * @see ExecutorService#shutdown()
+ */
+ protected void shutdown() {
+ threadPool.shutdown();
+ super.shutdown();
+ }
+
+ /**
+ * @see ExecutorService#shutdownNow()
+ */
+ public List shutdownNow() {
+ threadPool.shutdownNow();
+ return super.shutdownNow();
+ }
+
+ /**
+ * My handler
+ *
+ * @author Trinea 2012-11-20
+ */
+ private class MyHandler extends Handler {
+
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case WHAT_GET_IMAGE_SUCCESS:
+ case WHAT_GET_IMAGE_FAILED:
+ MessageObject object = (MessageObject)message.obj;
+ if (object == null) {
+ break;
+ }
+
+ String imageUrl = object.imageUrl;
+ String imagePath = object.imagePath;
+ if (onImageSDCallbackListener != null) {
+ if (isOpenWaitingQueue) {
+ synchronized (viewSetMap) {
+ HashSet viewSet = viewSetMap.get(imageUrl);
+ if (viewSet != null) {
+ for (View view : viewSet) {
+ if (view != null) {
+ if (WHAT_GET_IMAGE_SUCCESS == message.what) {
+ onGetSuccess(imageUrl, imagePath, view, false);
+ } else {
+ onImageSDCallbackListener.onGetFailed(imageUrl, imagePath, view,
+ object.failedReason);
+ }
+ }
+ }
+ }
+ }
+ } else {
+ View view = viewMap.get(imageUrl);
+ if (view != null) {
+ if (WHAT_GET_IMAGE_SUCCESS == message.what) {
+ onGetSuccess(imageUrl, imagePath, view, false);
+ } else {
+ onImageSDCallbackListener.onGetFailed(imageUrl, imagePath, view,
+ object.failedReason);
+ }
+ }
+ }
+ }
+
+ if (isOpenWaitingQueue) {
+ synchronized (viewSetMap) {
+ viewSetMap.remove(imageUrl);
+ }
+ } else {
+ viewMap.remove(imageUrl);
+ }
+ break;
+ }
+ }
+ }
+
+ private void onGetSuccess(String imageUrl, String imagePath, View view, boolean isInCache) {
+ if (onImageSDCallbackListener == null) {
+ return;
+ }
+
+ try {
+ onImageSDCallbackListener.onGetSuccess(imageUrl, imagePath, view, isInCache);
+ } catch (OutOfMemoryError e) {
+ onImageSDCallbackListener.onGetFailed(imageUrl, imagePath, view, new FailedReason(
+ FailedType.ERROR_OUT_OF_MEMORY, e));
+ }
+ }
+
+ /**
+ * message object
+ *
+ * @author Trinea 2013-1-14
+ */
+ private class MessageObject {
+
+ String imageUrl;
+ String imagePath;
+ FailedReason failedReason;
+
+ public MessageObject(String imageUrl, String imagePath) {
+ this.imageUrl = imageUrl;
+ this.imagePath = imagePath;
+ }
+
+ public MessageObject(String imageUrl, String imagePath, FailedReason failedReason) {
+ this.imageUrl = imageUrl;
+ this.imagePath = imagePath;
+ this.failedReason = failedReason;
+ }
+ }
+
+ /**
+ * start thread to wait for image get
+ *
+ * @param imageUrl
+ * @param urlList url list, if is null, not preload, else preload forward by
+ * {@link PreloadDataCache#preloadDataForward(Object, List, int)}, preload backward by
+ * {@link PreloadDataCache#preloadDataBackward(Object, List, int)}
+ */
+ private void startGetImageThread(final String imageUrl, final List urlList) {
+ // wait for image be got success and send message
+ threadPool.execute(new Runnable() {
+
+ @Override
+ public void run() {
+ try {
+ CacheObject object = get(imageUrl, urlList);
+ String imagePath = (object == null ? null : object.getData());
+ if (StringUtils.isEmpty(imagePath) || !FileUtils.isFileExist(imagePath)) {
+ // if image get fail, remove it
+ remove(imageUrl);
+ String failedException = "get image from network or save image to sdcard error. please make sure you have added permission android.permission.WRITE_EXTERNAL_STORAGE and android.permission.ACCESS_NETWORK_STATE";
+ FailedReason failedReason = new FailedReason(FailedType.ERROR_IO, failedException);
+ handler.sendMessage(handler.obtainMessage(WHAT_GET_IMAGE_FAILED, new MessageObject(imageUrl,
+ imagePath, failedReason)));
+ } else {
+ handler.sendMessage(handler.obtainMessage(WHAT_GET_IMAGE_SUCCESS, new MessageObject(imageUrl,
+ imagePath)));
+ }
+ } catch (OutOfMemoryError e) {
+ MessageObject msg = new MessageObject(imageUrl, null, new FailedReason(
+ FailedType.ERROR_OUT_OF_MEMORY, e));
+ handler.sendMessage(handler.obtainMessage(WHAT_GET_IMAGE_FAILED, msg));
+ }
+ }
+ });
+ }
+
+ /**
+ * delete file when full remove one
+ */
+ @Override
+ protected CacheObject fullRemoveOne() {
+ CacheObject o = super.fullRemoveOne();
+ if (o != null) {
+ deleteFile(o.getData());
+ }
+ return o;
+ }
+
+ /**
+ * delete file when remove
+ */
+ @Override
+ public CacheObject remove(String key) {
+ CacheObject o = super.remove(key);
+ if (o != null) {
+ deleteFile(o.getData());
+ }
+ return o;
+ }
+
+ /**
+ * delete file when clear cache
+ */
+ @Override
+ public void clear() {
+ for (CacheObject value : values()) {
+ if (value != null) {
+ deleteFile(value.getData());
+ }
+ }
+ super.clear();
+ }
+
+ /**
+ * delete unused file in {@link #getCacheFolder()}, you can use it after {@link #loadDataFromDb(Context, String)} at
+ * first time
+ */
+ public void deleteUnusedFiles() {
+ int size = getSize();
+ final HashSet filePathSet = new HashSet(size > 16 ? size : 16);
+ for (CacheObject value : values()) {
+ if (value != null) {
+ filePathSet.add(value.getData());
+ }
+ }
+
+ threadPool.execute(new Runnable() {
+
+ @Override
+ public void run() {
+
+ try {
+ File file = new File(getCacheFolder());
+ if (file != null && file.exists() && file.isDirectory()) {
+ for (File f : file.listFiles()) {
+ if (f.isFile() && !filePathSet.contains(f.getPath())) {
+ f.delete();
+ }
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ Log.e(TAG, "delete unused files fail.");
+ }
+ }
+ });
+ }
+
+ /**
+ * load all data from db and delete unused file in {@link #getCacheFolder()}
+ *
+ * - It's a combination of {@link #loadDataFromDb(Context, String)} and {@link #deleteUnusedFiles()}
+ * - You should use {@link #saveDataToDb(Context, String)} to save data when app exit
+ *
+ *
+ * @param context
+ * @param tag
+ * @see #loadDataFromDb(Context, String)
+ * @see #deleteUnusedFiles()
+ */
+ public void initData(Context context, String tag) {
+ ImageSDCardCache.loadDataFromDb(context, this, tag);
+ deleteUnusedFiles();
+ }
+
+ /**
+ * load all data in db whose tag is same to tag to imageSDCardCache. just put, do not affect the original data
+ *
+ * Attentions:
+ * - If tag is null or empty, throws exception
+ * - You should use {@link #saveDataToDb(Context, String)} to save data when app exit
+ *
+ *
+ * @param context
+ * @param tag tag used to mark this cache when save to and load from db, should be unique and cannot be null or
+ * empty
+ * @return
+ * @see #loadDataFromDb(Context, ImageSDCardCache, String)
+ */
+ public boolean loadDataFromDb(Context context, String tag) {
+ return ImageSDCardCache.loadDataFromDb(context, this, tag);
+ }
+
+ /**
+ * delete all rows in db whose tag is same to tag at first, and insert all data in imageSDCardCache to db
+ *
+ * Attentions:
+ * - If tag is null or empty, throws exception
+ * - Will delete all rows in db whose tag is same to tag at first
+ * - You can use {@link #initData(Context, String)} or {@link #loadDataFromDb(Context, String)} to init data when
+ * app start
+ *
+ *
+ * @param context
+ * @param tag tag used to mark this cache when save to and load from db, should be unique and cannot be null or
+ * empty
+ * @return
+ * @see #saveDataToDb(Context, ImageSDCardCache, String)
+ */
+ public boolean saveDataToDb(Context context, String tag) {
+ return ImageSDCardCache.saveDataToDb(context, this, tag);
+ }
+
+ /**
+ * load all data in db whose tag is same to tag to imageSDCardCache. just put, do not affect the original data
+ *
+ * Attentions:
+ * - If imageSDCardCache is null, throws exception
+ * - If tag is null or empty, throws exception
+ * - You should use {@link #saveDataToDb(Context, ImageSDCardCache, String)} to save data when app exit
+ *
+ *
+ * @param context
+ * @param imageSDCardCache
+ * @param tag tag used to mark this cache when save to and load from db, should be unique and cannot be null or
+ * empty
+ * @return
+ */
+ public static boolean loadDataFromDb(Context context, ImageSDCardCache imageSDCardCache, String tag) {
+ if (context == null || imageSDCardCache == null) {
+ throw new IllegalArgumentException("The context and cache both can not be null.");
+ }
+ if (StringUtils.isEmpty(tag)) {
+ throw new IllegalArgumentException("The tag can not be null or empty.");
+ }
+ return new ImageSDCardCacheDaoImpl(SqliteUtils.getInstance(context)).putIntoImageSDCardCache(imageSDCardCache,
+ tag);
+ }
+
+ /**
+ * delete all rows in db whose tag is same to tag at first, and insert all data in imageSDCardCache to db
+ *
+ * Attentions:
+ * - If imageSDCardCache is null, throws exception
+ * - If tag is null or empty, throws exception
+ * - Will delete all rows in db whose tag is same to tag at first
+ * - You can use {@link #initData(Context, String)} or {@link #loadDataFromDb(Context, ImageSDCardCache, String)}
+ * to init data when app start
+ *
+ *
+ * @param context
+ * @param imageSDCardCache
+ * @param tag tag used to mark this cache when save to and load from db, should be unique and cannot be null or
+ * empty
+ * @return
+ */
+ public static boolean saveDataToDb(Context context, ImageSDCardCache imageSDCardCache, String tag) {
+ if (context == null || imageSDCardCache == null) {
+ throw new IllegalArgumentException("The context and cache both can not be null.");
+ }
+ if (StringUtils.isEmpty(tag)) {
+ throw new IllegalArgumentException("The tag can not be null or empty.");
+ }
+ return new ImageSDCardCacheDaoImpl(SqliteUtils.getInstance(context)).deleteAndInsertImageSDCardCache(
+ imageSDCardCache, tag);
+ }
+
+ /**
+ * get image file path
+ *
+ * @param imageUrl
+ * @return if not in cache return null, else return full path.
+ */
+ public String getImagePath(String imageUrl) {
+ return (this.containsKey(imageUrl)) ? new StringBuilder(cacheFolder).append(File.separator)
+ .append(fileNameRule.getFileName(imageUrl)).toString() : null;
+ }
+
+ /**
+ * delete file
+ *
+ * @param path
+ * @return
+ */
+ private boolean deleteFile(String path) {
+ if (!StringUtils.isEmpty(path)) {
+ if (!FileUtils.deleteFile(path)) {
+ Log.e(TAG, new StringBuilder().append("delete file fail, path is ").append(path).toString());
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * default get image listener
+ *
+ * @return
+ */
+ public OnGetDataListener getDefaultOnGetImageListener() {
+ return new OnGetDataListener() {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public CacheObject onGetData(String key) {
+
+ String savePath = null;
+ InputStream stream = null;
+ try {
+ stream = ImageUtils.getInputStreamFromUrl(key, httpReadTimeOut, requestProperties);
+ } catch (Exception e) {
+ Log.e(TAG, new StringBuilder().append("get image exception, imageUrl is:").append(key).toString(),
+ e);
+ }
+
+ if (stream != null) {
+ savePath = cacheFolder + File.separator + fileNameRule.getFileName(key);
+ try {
+ FileUtils.writeFile(savePath, stream);
+ } catch (Exception e1) {
+ try {
+ if (e1.getCause() instanceof FileNotFoundException) {
+ FileUtils.makeFolders(savePath);
+ FileUtils.writeFile(savePath, stream);
+ } else {
+ Log.e(TAG,
+ new StringBuilder()
+ .append("get image exception while write to file, imageUrl is: ")
+ .append(key).append(", savePath is ").append(savePath).toString(), e1);
+ }
+ } catch (Exception e2) {
+ Log.e(TAG,
+ new StringBuilder()
+ .append("get image exception while write to file, imageUrl is: ")
+ .append(key).append(", savePath is ").append(savePath).toString(), e2);
+ }
+ }
+ }
+ return (StringUtils.isEmpty(savePath) ? null : new CacheObject(savePath));
+ }
+ };
+ }
+
+ /**
+ * get recommend default max cache size according to dalvik max memory
+ *
+ * @return
+ */
+ static int getDefaultMaxSize() {
+ long maxMemory = Runtime.getRuntime().maxMemory();
+ if (maxMemory > SizeUtils.GB_2_BYTE) {
+ return 256;
+ }
+
+ int mb = (int)(maxMemory / SizeUtils.MB_2_BYTE);
+ return mb > 8 ? mb : 8;
+ }
+}
diff --git a/src/cn/trinea/android/common/service/impl/PreloadDataCache.java b/src/main/java/cn/trinea/android/common/service/impl/PreloadDataCache.java
similarity index 89%
rename from src/cn/trinea/android/common/service/impl/PreloadDataCache.java
rename to src/main/java/cn/trinea/android/common/service/impl/PreloadDataCache.java
index bf250a6..771d1af 100644
--- a/src/cn/trinea/android/common/service/impl/PreloadDataCache.java
+++ b/src/main/java/cn/trinea/android/common/service/impl/PreloadDataCache.java
@@ -48,7 +48,7 @@
* {@link #loadCache(String)} restore cache from file
*
*
- * @author Trinea 2012-3-4
+ * @author Trinea 2012-3-4
*/
public class PreloadDataCache extends SimpleCache {
@@ -59,11 +59,13 @@ public class PreloadDataCache extends SimpleCache {
/** count for preload backward, default is {@link #DEFAULT_BACKWARD_CACHE_NUMBER} **/
private int backwardCacheNumber = DEFAULT_BACKWARD_CACHE_NUMBER;
+ /** whether to check the network at first when get data **/
+ private boolean isCheckNetwork = true;
/** allowed network type, default to all network types allowed **/
private int allowedNetworkTypes = ~0;
/** get data listener **/
- private OnGetDataListener onGetDataListener;
+ protected OnGetDataListener onGetDataListener;
/**
* restore threads those getting data, to avoid multi threads get the data for same key so that to save network
@@ -75,7 +77,7 @@ public class PreloadDataCache extends SimpleCache {
private ExecutorService threadPool;
private Context context;
- private static ConnectivityManager connectivityManager;
+ private transient ConnectivityManager connectivityManager;
/** default count for preload forward **/
public static final int DEFAULT_FORWARD_CACHE_NUMBER = 3;
@@ -99,10 +101,10 @@ public class PreloadDataCache extends SimpleCache {
*
* @param key
* @param keyList key list, if is null, not preload, else preload forward by
- * {@link #preloadDataForward(Object, List, int)}, preload backward by
- * {@link #preloadDataBackward(Object, List, int)}
+ * {@link #preloadDataForward(Object, List, int)}, preload backward by
+ * {@link #preloadDataBackward(Object, List, int)}
* @return element if this cache contains the specified key, else get data realtime and wait for it
- * @see {@link #get(Object)}
+ * @see PreloadDataCache#get(Object)
*/
public CacheObject get(K key, List keyList) {
if (key == null) {
@@ -173,10 +175,10 @@ CacheObject getFromCache(K key) {
*
* @param key
* @param keyList key list, if is null, not preload, else preload forward by
- * {@link #preloadDataForward(Object, List, int)}, preload backward by
- * {@link #preloadDataBackward(Object, List, int)}
+ * {@link #preloadDataForward(Object, List, int)}, preload backward by
+ * {@link #preloadDataBackward(Object, List, int)}
* @return element if this cache contains the specified key, null otherwise.
- * @see {@link #getFromCache(Object)}
+ * @see #getFromCache(Object)
*/
CacheObject getFromCache(K key, List keyList) {
if (key == null) {
@@ -282,7 +284,7 @@ protected int preloadDataBackward(K key, List keyList, int cacheCount) {
* @return
*/
private synchronized GetDataThread gettingData(K key) {
- if (containsKey(key) || !checkIsNetworkTypeAllowed()) {
+ if (containsKey(key) || (isCheckNetwork && !checkIsNetworkTypeAllowed())) {
return null;
}
@@ -315,7 +317,7 @@ public synchronized boolean isExistGettingDataThread(K key) {
* Size of getting data thread pool is {@link #DEFAULT_THREAD_POOL_SIZE}
*
*/
- public PreloadDataCache(){
+ public PreloadDataCache() {
this(DEFAULT_MAX_SIZE, DEFAULT_THREAD_POOL_SIZE);
}
@@ -328,7 +330,7 @@ public PreloadDataCache(){
*
* @param maxSize maximum size of the cache
*/
- public PreloadDataCache(int maxSize){
+ public PreloadDataCache(int maxSize) {
this(maxSize, DEFAULT_THREAD_POOL_SIZE);
}
@@ -341,7 +343,7 @@ public PreloadDataCache(int maxSize){
* @param maxSize maximum size of the cache
* @param threadPoolSize getting data thread pool size
*/
- public PreloadDataCache(int maxSize, int threadPoolSize){
+ public PreloadDataCache(int maxSize, int threadPoolSize) {
super(maxSize);
if (threadPoolSize <= 0) {
@@ -426,6 +428,24 @@ public void setAllowedNetworkTypes(int allowedNetworkTypes) {
this.allowedNetworkTypes = allowedNetworkTypes;
}
+ /**
+ * get whether to check the network at first when get data, used when {@link #checkIsNetworkTypeAllowed()}
+ *
+ * @return
+ */
+ public boolean isCheckNetwork() {
+ return isCheckNetwork;
+ }
+
+ /**
+ * set whether to check the network at first when get data, used when {@link #checkIsNetworkTypeAllowed()}
+ *
+ * @param isCheckNetwork
+ */
+ public void setCheckNetwork(boolean isCheckNetwork) {
+ this.isCheckNetwork = isCheckNetwork;
+ }
+
public Context getContext() {
return context;
}
@@ -444,11 +464,11 @@ public void setContext(Context context) {
*
* @param networkType a constant from ConnectivityManager.TYPE_*.
* @return one of the NETWORK_* constants
- *
- * - if {@link #getContext()} is null, return true
- * - if network is not avaliable, return false
- * - if {@link #getAllowedNetworkTypes()} is not match network, return false
- *
+ *
+ * - if {@link #getContext()} is null, return true
+ * - if network is not avaliable, return false
+ * - if {@link #getAllowedNetworkTypes()} is not match network, return false
+ *
*/
public boolean checkIsNetworkTypeAllowed() {
if (connectivityManager == null && context != null) {
@@ -461,7 +481,7 @@ public boolean checkIsNetworkTypeAllowed() {
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
return networkInfo != null
- && (allowedNetworkTypes == ~0 || (translateNetworkTypeToApiFlag(networkInfo.getType()) & allowedNetworkTypes) != 0);
+ && (allowedNetworkTypes == ~0 || (translateNetworkTypeToApiFlag(networkInfo.getType()) & allowedNetworkTypes) != 0);
}
/**
@@ -494,7 +514,7 @@ public static PreloadDataCache loadCache(String filePath) {
/**
* @see ExecutorService#shutdown()
*/
- public void shutdown() {
+ protected void shutdown() {
threadPool.shutdown();
}
@@ -508,7 +528,7 @@ public List shutdownNow() {
/**
* get data interface, implements this to get data
*
- * @author Trinea 2012-3-4
+ * @author Trinea 2012-3-4
*/
public interface OnGetDataListener extends Serializable {
@@ -524,7 +544,7 @@ public interface OnGetDataListener extends Serializable {
/**
* the thread to get data
*
- * @author Trinea 2012-3-4
+ * @author Trinea 2012-3-4
*/
private class GetDataThread implements Runnable {
@@ -538,7 +558,7 @@ private class GetDataThread implements Runnable {
* @param key
* @param onGetDataListener
*/
- public GetDataThread(K key, OnGetDataListener onGetDataListener){
+ public GetDataThread(K key, OnGetDataListener onGetDataListener) {
this.key = key;
this.onGetDataListener = onGetDataListener;
finishGetDataLock = new CountDownLatch(1);
diff --git a/src/cn/trinea/android/common/service/impl/RemoveTypeDrawableLarge.java b/src/main/java/cn/trinea/android/common/service/impl/RemoveTypeBitmapLarge.java
similarity index 54%
rename from src/cn/trinea/android/common/service/impl/RemoveTypeDrawableLarge.java
rename to src/main/java/cn/trinea/android/common/service/impl/RemoveTypeBitmapLarge.java
index 2d275f4..89f54c7 100644
--- a/src/cn/trinea/android/common/service/impl/RemoveTypeDrawableLarge.java
+++ b/src/main/java/cn/trinea/android/common/service/impl/RemoveTypeBitmapLarge.java
@@ -1,33 +1,32 @@
package cn.trinea.android.common.service.impl;
-import android.graphics.drawable.Drawable;
-
+import android.graphics.Bitmap;
import cn.trinea.android.common.entity.CacheObject;
import cn.trinea.android.common.service.CacheFullRemoveType;
import cn.trinea.android.common.util.ImageUtils;
/**
- * Remove type when cache is full, data type of cache is drawable.
+ * Remove type when cache is full, data type of cache is bitmap.
*
- * - if drawable is bigger, remove it first
- * - if drawable is equal to each other, remove the one which is used less
- * - if drawable is equal to each other and used count is equal, remove the one which is first in
+ * - if bitmap is bigger, remove it first
+ * - if bitmap is equal to each other, remove the one which is used less
+ * - if bitmap is equal to each other and used count is equal, remove the one which is first in
*
*
- * @author Trinea 2011-12-26
+ * @author Trinea 2011-12-26
*/
-public class RemoveTypeDrawableLarge implements CacheFullRemoveType {
+public class RemoveTypeBitmapLarge implements CacheFullRemoveType {
private static final long serialVersionUID = 1L;
@Override
- public int compare(CacheObject obj1, CacheObject obj2) {
+ public int compare(CacheObject obj1, CacheObject obj2) {
long sizeOfFile1 = getSize(obj1);
long sizeOfFile2 = getSize(obj2);
if (sizeOfFile1 == sizeOfFile2) {
if (obj1.getUsedCount() == obj2.getUsedCount()) {
- return (obj1.getEnterTime() > obj2.getEnterTime()) ? 1 : ((obj1.getEnterTime() == obj2.getEnterTime())
- ? 0 : -1);
+ return (obj1.getEnterTime() > obj2.getEnterTime()) ? 1
+ : ((obj1.getEnterTime() == obj2.getEnterTime()) ? 0 : -1);
}
return (obj1.getUsedCount() > obj2.getUsedCount() ? 1 : -1);
}
@@ -35,18 +34,18 @@ public int compare(CacheObject obj1, CacheObject obj2) {
}
/**
- * get size of drawable
+ * get size of bitmap
*
* @param o
* @return
*/
- private long getSize(CacheObject o) {
+ private long getSize(CacheObject o) {
if (o == null) {
return -1;
}
// TODO is there any more efficient way?
- byte[] b = ImageUtils.drawableToByte(o.getData());
+ byte[] b = ImageUtils.bitmapToByte(o.getData());
return (b == null ? -1 : b.length);
}
}
diff --git a/src/cn/trinea/android/common/service/impl/RemoveTypeDrawableSmall.java b/src/main/java/cn/trinea/android/common/service/impl/RemoveTypeBitmapSmall.java
similarity index 54%
rename from src/cn/trinea/android/common/service/impl/RemoveTypeDrawableSmall.java
rename to src/main/java/cn/trinea/android/common/service/impl/RemoveTypeBitmapSmall.java
index 9238aee..31df044 100644
--- a/src/cn/trinea/android/common/service/impl/RemoveTypeDrawableSmall.java
+++ b/src/main/java/cn/trinea/android/common/service/impl/RemoveTypeBitmapSmall.java
@@ -1,33 +1,32 @@
package cn.trinea.android.common.service.impl;
-import android.graphics.drawable.Drawable;
-
+import android.graphics.Bitmap;
import cn.trinea.android.common.entity.CacheObject;
import cn.trinea.android.common.service.CacheFullRemoveType;
import cn.trinea.android.common.util.ImageUtils;
/**
- * Remove type when cache is full, data type of cache is drawable.
+ * Remove type when cache is full, data type of cache is bitmap.
*
- * - if drawable is smaller, remove it first
- * - if drawable is equal to each other, remove the one which is used less
- * - if drawable is equal to each other and used count is equal, remove the one which is first in
+ * - if bitmap is smaller, remove it first
+ * - if bitmap is equal to each other, remove the one which is used less
+ * - if bitmap is equal to each other and used count is equal, remove the one which is first in
*
*
- * @author Trinea 2011-12-26
+ * @author Trinea 2011-12-26
*/
-public class RemoveTypeDrawableSmall implements CacheFullRemoveType {
+public class RemoveTypeBitmapSmall implements CacheFullRemoveType {
private static final long serialVersionUID = 1L;
@Override
- public int compare(CacheObject obj1, CacheObject obj2) {
+ public int compare(CacheObject obj1, CacheObject obj2) {
long sizeOfFile1 = getSize(obj1);
long sizeOfFile2 = getSize(obj2);
if (sizeOfFile1 == sizeOfFile2) {
if (obj1.getUsedCount() == obj2.getUsedCount()) {
- return (obj1.getEnterTime() > obj2.getEnterTime()) ? 1 : ((obj1.getEnterTime() == obj2.getEnterTime())
- ? 0 : -1);
+ return (obj1.getEnterTime() > obj2.getEnterTime()) ? 1
+ : ((obj1.getEnterTime() == obj2.getEnterTime()) ? 0 : -1);
}
return (obj1.getUsedCount() > obj2.getUsedCount() ? 1 : -1);
}
@@ -35,18 +34,18 @@ public int compare(CacheObject obj1, CacheObject obj2) {
}
/**
- * get size of drawable
+ * get size of bitmap
*
* @param o
* @return
*/
- private long getSize(CacheObject o) {
+ private long getSize(CacheObject o) {
if (o == null) {
return -1;
}
// TODO is there any more efficient way?
- byte[] b = ImageUtils.drawableToByte(o.getData());
+ byte[] b = ImageUtils.bitmapToByte(o.getData());
return (b == null ? -1 : b.length);
}
}
diff --git a/src/cn/trinea/android/common/service/impl/RemoveTypeDataBig.java b/src/main/java/cn/trinea/android/common/service/impl/RemoveTypeDataBig.java
similarity index 88%
rename from src/cn/trinea/android/common/service/impl/RemoveTypeDataBig.java
rename to src/main/java/cn/trinea/android/common/service/impl/RemoveTypeDataBig.java
index d225b2c..9d4fbf6 100644
--- a/src/cn/trinea/android/common/service/impl/RemoveTypeDataBig.java
+++ b/src/main/java/cn/trinea/android/common/service/impl/RemoveTypeDataBig.java
@@ -8,7 +8,7 @@
* Remove type when cache is full.
* when cache is full, compare data of object in cache, if data is bigger remove it first.
*
- * @author Trinea 2011-12-26
+ * @author Trinea 2011-12-26
*/
public class RemoveTypeDataBig implements CacheFullRemoveType {
diff --git a/src/cn/trinea/android/common/service/impl/RemoveTypeDataSmall.java b/src/main/java/cn/trinea/android/common/service/impl/RemoveTypeDataSmall.java
similarity index 88%
rename from src/cn/trinea/android/common/service/impl/RemoveTypeDataSmall.java
rename to src/main/java/cn/trinea/android/common/service/impl/RemoveTypeDataSmall.java
index ad1c2a1..e8eb9d6 100644
--- a/src/cn/trinea/android/common/service/impl/RemoveTypeDataSmall.java
+++ b/src/main/java/cn/trinea/android/common/service/impl/RemoveTypeDataSmall.java
@@ -8,7 +8,7 @@
* Remove type when cache is full.
* when cache is full, compare data of object in cache, if data is smaller remove it first.
*
- * @author Trinea 2011-12-26
+ * @author Trinea 2011-12-26
*/
public class RemoveTypeDataSmall implements CacheFullRemoveType {
diff --git a/src/cn/trinea/android/common/service/impl/RemoveTypeEnterTimeFirst.java b/src/main/java/cn/trinea/android/common/service/impl/RemoveTypeEnterTimeFirst.java
similarity index 79%
rename from src/cn/trinea/android/common/service/impl/RemoveTypeEnterTimeFirst.java
rename to src/main/java/cn/trinea/android/common/service/impl/RemoveTypeEnterTimeFirst.java
index 87faf9f..5eae988 100644
--- a/src/cn/trinea/android/common/service/impl/RemoveTypeEnterTimeFirst.java
+++ b/src/main/java/cn/trinea/android/common/service/impl/RemoveTypeEnterTimeFirst.java
@@ -7,7 +7,7 @@
* Remove type when cache is full.
* when cache is full, compare enter time of object in cache, if time is smaller remove it first. also FIFO
*
- * @author Trinea 2011-12-26
+ * @author Trinea 2011-12-26
*/
public class RemoveTypeEnterTimeFirst implements CacheFullRemoveType {
@@ -16,6 +16,6 @@ public class RemoveTypeEnterTimeFirst implements CacheFullRemoveType {
@Override
public int compare(CacheObject obj1, CacheObject obj2) {
return (obj1.getEnterTime() > obj2.getEnterTime()) ? 1
- : ((obj1.getEnterTime() == obj2.getEnterTime()) ? 0 : -1);
+ : ((obj1.getEnterTime() == obj2.getEnterTime()) ? 0 : -1);
}
}
diff --git a/src/cn/trinea/android/common/service/impl/RemoveTypeEnterTimeLast.java b/src/main/java/cn/trinea/android/common/service/impl/RemoveTypeEnterTimeLast.java
similarity index 79%
rename from src/cn/trinea/android/common/service/impl/RemoveTypeEnterTimeLast.java
rename to src/main/java/cn/trinea/android/common/service/impl/RemoveTypeEnterTimeLast.java
index 73fb1b6..7d10c46 100644
--- a/src/cn/trinea/android/common/service/impl/RemoveTypeEnterTimeLast.java
+++ b/src/main/java/cn/trinea/android/common/service/impl/RemoveTypeEnterTimeLast.java
@@ -7,7 +7,7 @@
* Remove type when cache is full.
* when cache is full, compare enter time of object in cache, if time is smaller remove it first. also LIFO
*
- * @author Trinea 2011-12-26
+ * @author Trinea 2011-12-26
*/
public class RemoveTypeEnterTimeLast implements CacheFullRemoveType {
@@ -16,6 +16,6 @@ public class RemoveTypeEnterTimeLast implements CacheFullRemoveType {
@Override
public int compare(CacheObject obj1, CacheObject obj2) {
return (obj2.getEnterTime() > obj1.getEnterTime()) ? 1
- : ((obj2.getEnterTime() == obj1.getEnterTime()) ? 0 : -1);
+ : ((obj2.getEnterTime() == obj1.getEnterTime()) ? 0 : -1);
}
}
diff --git a/src/cn/trinea/android/common/service/impl/RemoveTypeFileLarge.java b/src/main/java/cn/trinea/android/common/service/impl/RemoveTypeFileLarge.java
similarity index 88%
rename from src/cn/trinea/android/common/service/impl/RemoveTypeFileLarge.java
rename to src/main/java/cn/trinea/android/common/service/impl/RemoveTypeFileLarge.java
index 6450909..48305d5 100644
--- a/src/cn/trinea/android/common/service/impl/RemoveTypeFileLarge.java
+++ b/src/main/java/cn/trinea/android/common/service/impl/RemoveTypeFileLarge.java
@@ -12,7 +12,7 @@
* if file is equal to each other and used count is equal, remove the one which is first in
*
*
- * @author Trinea 2011-12-26
+ * @author Trinea 2011-12-26
*/
public class RemoveTypeFileLarge implements CacheFullRemoveType {
@@ -24,8 +24,8 @@ public int compare(CacheObject obj1, CacheObject obj2) {
long sizeOfFile2 = (obj2 == null ? -1 : FileUtils.getFileSize(obj2.getData()));
if (sizeOfFile1 == sizeOfFile2) {
if (obj1.getUsedCount() == obj2.getUsedCount()) {
- return (obj1.getEnterTime() > obj2.getEnterTime()) ? 1 : ((obj1.getEnterTime() == obj2.getEnterTime())
- ? 0 : -1);
+ return (obj1.getEnterTime() > obj2.getEnterTime()) ? 1
+ : ((obj1.getEnterTime() == obj2.getEnterTime()) ? 0 : -1);
}
return (obj1.getUsedCount() > obj2.getUsedCount() ? 1 : -1);
}
diff --git a/src/cn/trinea/android/common/service/impl/RemoveTypeFileSmall.java b/src/main/java/cn/trinea/android/common/service/impl/RemoveTypeFileSmall.java
similarity index 88%
rename from src/cn/trinea/android/common/service/impl/RemoveTypeFileSmall.java
rename to src/main/java/cn/trinea/android/common/service/impl/RemoveTypeFileSmall.java
index 2ed83db..201ebb8 100644
--- a/src/cn/trinea/android/common/service/impl/RemoveTypeFileSmall.java
+++ b/src/main/java/cn/trinea/android/common/service/impl/RemoveTypeFileSmall.java
@@ -12,7 +12,7 @@
* if file is equal to each other and used count is equal, remove the one which is first in
*
*
- * @author Trinea 2011-12-26
+ * @author Trinea 2011-12-26
*/
public class RemoveTypeFileSmall implements CacheFullRemoveType {
@@ -24,8 +24,8 @@ public int compare(CacheObject obj1, CacheObject obj2) {
long sizeOfFile2 = (obj2 == null ? -1 : FileUtils.getFileSize(obj2.getData()));
if (sizeOfFile1 == sizeOfFile2) {
if (obj1.getUsedCount() == obj2.getUsedCount()) {
- return (obj1.getEnterTime() > obj2.getEnterTime()) ? 1 : ((obj1.getEnterTime() == obj2.getEnterTime())
- ? 0 : -1);
+ return (obj1.getEnterTime() > obj2.getEnterTime()) ? 1
+ : ((obj1.getEnterTime() == obj2.getEnterTime()) ? 0 : -1);
}
return (obj1.getUsedCount() > obj2.getUsedCount() ? 1 : -1);
}
diff --git a/src/cn/trinea/android/common/service/impl/RemoveTypeLastUsedTimeFirst.java b/src/main/java/cn/trinea/android/common/service/impl/RemoveTypeLastUsedTimeFirst.java
similarity index 78%
rename from src/cn/trinea/android/common/service/impl/RemoveTypeLastUsedTimeFirst.java
rename to src/main/java/cn/trinea/android/common/service/impl/RemoveTypeLastUsedTimeFirst.java
index 63f1047..9d34eb8 100644
--- a/src/cn/trinea/android/common/service/impl/RemoveTypeLastUsedTimeFirst.java
+++ b/src/main/java/cn/trinea/android/common/service/impl/RemoveTypeLastUsedTimeFirst.java
@@ -7,7 +7,7 @@
* Remove type when cache is full.
* when cache is full, compare last used time of object in cache, if time is smaller remove it first.
*
- * @author Trinea 2011-12-26
+ * @author Trinea 2011-12-26
*/
public class RemoveTypeLastUsedTimeFirst implements CacheFullRemoveType {
@@ -15,7 +15,7 @@ public class RemoveTypeLastUsedTimeFirst implements CacheFullRemoveType {
@Override
public int compare(CacheObject obj1, CacheObject obj2) {
- return (obj1.getLastUsedTime() > obj2.getLastUsedTime()) ? 1
- : ((obj1.getLastUsedTime() == obj2.getLastUsedTime()) ? 0 : -1);
+ return (obj1.getLastUsedTime() > obj2.getLastUsedTime()) ? 1 : ((obj1.getLastUsedTime() == obj2
+ .getLastUsedTime()) ? 0 : -1);
}
}
diff --git a/src/cn/trinea/android/common/service/impl/RemoveTypeLastUsedTimeLast.java b/src/main/java/cn/trinea/android/common/service/impl/RemoveTypeLastUsedTimeLast.java
similarity index 78%
rename from src/cn/trinea/android/common/service/impl/RemoveTypeLastUsedTimeLast.java
rename to src/main/java/cn/trinea/android/common/service/impl/RemoveTypeLastUsedTimeLast.java
index 0c18023..e2f80d8 100644
--- a/src/cn/trinea/android/common/service/impl/RemoveTypeLastUsedTimeLast.java
+++ b/src/main/java/cn/trinea/android/common/service/impl/RemoveTypeLastUsedTimeLast.java
@@ -7,7 +7,7 @@
* Remove type when cache is full.
* when cache is full, compare last used time of object in cache, if time is bigger remove it first.
*
- * @author Trinea 2011-12-26
+ * @author Trinea 2011-12-26
*/
public class RemoveTypeLastUsedTimeLast implements CacheFullRemoveType {
@@ -15,7 +15,7 @@ public class RemoveTypeLastUsedTimeLast implements CacheFullRemoveType {
@Override
public int compare(CacheObject obj1, CacheObject obj2) {
- return (obj2.getLastUsedTime() > obj1.getLastUsedTime()) ? 1
- : ((obj2.getLastUsedTime() == obj1.getLastUsedTime()) ? 0 : -1);
+ return (obj2.getLastUsedTime() > obj1.getLastUsedTime()) ? 1 : ((obj2.getLastUsedTime() == obj1
+ .getLastUsedTime()) ? 0 : -1);
}
}
diff --git a/src/cn/trinea/android/common/service/impl/RemoveTypeNotRemove.java b/src/main/java/cn/trinea/android/common/service/impl/RemoveTypeNotRemove.java
similarity index 86%
rename from src/cn/trinea/android/common/service/impl/RemoveTypeNotRemove.java
rename to src/main/java/cn/trinea/android/common/service/impl/RemoveTypeNotRemove.java
index b53fb52..40a8b23 100644
--- a/src/cn/trinea/android/common/service/impl/RemoveTypeNotRemove.java
+++ b/src/main/java/cn/trinea/android/common/service/impl/RemoveTypeNotRemove.java
@@ -6,7 +6,7 @@
/**
* Remove type when cache is full. not remove any one, it means nothing can be put later
*
- * @author Trinea 2011-12-26
+ * @author Trinea 2011-12-26
*/
public class RemoveTypeNotRemove implements CacheFullRemoveType {
diff --git a/src/cn/trinea/android/common/service/impl/RemoveTypePriorityHigh.java b/src/main/java/cn/trinea/android/common/service/impl/RemoveTypePriorityHigh.java
similarity index 89%
rename from src/cn/trinea/android/common/service/impl/RemoveTypePriorityHigh.java
rename to src/main/java/cn/trinea/android/common/service/impl/RemoveTypePriorityHigh.java
index 53dafb6..0fb3bc7 100644
--- a/src/cn/trinea/android/common/service/impl/RemoveTypePriorityHigh.java
+++ b/src/main/java/cn/trinea/android/common/service/impl/RemoveTypePriorityHigh.java
@@ -7,7 +7,7 @@
* Remove type when cache is full.
* when cache is full, compare priority of object in cache, if priority is higher remove it first.
*
- * @author Trinea 2011-12-26
+ * @author Trinea 2011-12-26
*/
public class RemoveTypePriorityHigh implements CacheFullRemoveType {
diff --git a/src/cn/trinea/android/common/service/impl/RemoveTypePriorityLow.java b/src/main/java/cn/trinea/android/common/service/impl/RemoveTypePriorityLow.java
similarity index 89%
rename from src/cn/trinea/android/common/service/impl/RemoveTypePriorityLow.java
rename to src/main/java/cn/trinea/android/common/service/impl/RemoveTypePriorityLow.java
index 4c46d8b..f2a2d72 100644
--- a/src/cn/trinea/android/common/service/impl/RemoveTypePriorityLow.java
+++ b/src/main/java/cn/trinea/android/common/service/impl/RemoveTypePriorityLow.java
@@ -7,7 +7,7 @@
* Remove type when cache is full.
* when cache is full, compare priority of object in cache, if priority is lower remove it first.
*
- * @author Trinea 2011-12-26
+ * @author Trinea 2011-12-26
*/
public class RemoveTypePriorityLow implements CacheFullRemoveType {
diff --git a/src/cn/trinea/android/common/service/impl/RemoveTypeUsedCountBig.java b/src/main/java/cn/trinea/android/common/service/impl/RemoveTypeUsedCountBig.java
similarity index 79%
rename from src/cn/trinea/android/common/service/impl/RemoveTypeUsedCountBig.java
rename to src/main/java/cn/trinea/android/common/service/impl/RemoveTypeUsedCountBig.java
index ace2090..a00b6e8 100644
--- a/src/cn/trinea/android/common/service/impl/RemoveTypeUsedCountBig.java
+++ b/src/main/java/cn/trinea/android/common/service/impl/RemoveTypeUsedCountBig.java
@@ -7,7 +7,7 @@
* Remove type when cache is full.
* when cache is full, compare used count of object in cache, if is bigger remove it first.
*
- * @author Trinea 2011-12-26
+ * @author Trinea 2011-12-26
*/
public class RemoveTypeUsedCountBig implements CacheFullRemoveType {
@@ -16,6 +16,6 @@ public class RemoveTypeUsedCountBig implements CacheFullRemoveType {
@Override
public int compare(CacheObject obj1, CacheObject obj2) {
return (obj2.getUsedCount() > obj1.getUsedCount()) ? 1
- : ((obj2.getUsedCount() == obj1.getUsedCount()) ? 0 : -1);
+ : ((obj2.getUsedCount() == obj1.getUsedCount()) ? 0 : -1);
}
}
diff --git a/src/cn/trinea/android/common/service/impl/RemoveTypeUsedCountSmall.java b/src/main/java/cn/trinea/android/common/service/impl/RemoveTypeUsedCountSmall.java
similarity index 79%
rename from src/cn/trinea/android/common/service/impl/RemoveTypeUsedCountSmall.java
rename to src/main/java/cn/trinea/android/common/service/impl/RemoveTypeUsedCountSmall.java
index 666b59b..50e58c0 100644
--- a/src/cn/trinea/android/common/service/impl/RemoveTypeUsedCountSmall.java
+++ b/src/main/java/cn/trinea/android/common/service/impl/RemoveTypeUsedCountSmall.java
@@ -7,7 +7,7 @@
* Remove type when cache is full.
* when cache is full, compare used count of object in cache, if is smaller remove it first.
*
- * @author Trinea 2011-12-26
+ * @author Trinea 2011-12-26
*/
public class RemoveTypeUsedCountSmall implements CacheFullRemoveType {
@@ -16,6 +16,6 @@ public class RemoveTypeUsedCountSmall implements CacheFullRemoveType {
@Override
public int compare(CacheObject obj1, CacheObject obj2) {
return (obj1.getUsedCount() > obj2.getUsedCount()) ? 1
- : ((obj1.getUsedCount() == obj2.getUsedCount()) ? 0 : -1);
+ : ((obj1.getUsedCount() == obj2.getUsedCount()) ? 0 : -1);
}
}
diff --git a/src/cn/trinea/android/common/service/impl/SimpleCache.java b/src/main/java/cn/trinea/android/common/service/impl/SimpleCache.java
similarity index 96%
rename from src/cn/trinea/android/common/service/impl/SimpleCache.java
rename to src/main/java/cn/trinea/android/common/service/impl/SimpleCache.java
index 7d8607c..468d460 100644
--- a/src/cn/trinea/android/common/service/impl/SimpleCache.java
+++ b/src/main/java/cn/trinea/android/common/service/impl/SimpleCache.java
@@ -47,7 +47,7 @@
* Other interfaces same to {@link Map}
*
*
- * @author Trinea 2011-12-23
+ * @author Trinea 2011-12-23
*/
public class SimpleCache implements Cache