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 @@ -![Trinea](http://img.hb.aicdn.com/651ca2996898c9298248b25c1a8b4d464d4cc25f123e-hjMrz5_fw580)个人博客 [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、微信头像、美丽说、蘑菇街、花瓣、淘宝等等。。效果如如下: -![ImageCahe](http://img.hb.aicdn.com/5f6ef0ecc5a20ff7a5dd8a94684af7b0f7580d8524b6f-yOCNXi_fw580) +# Trinea Android Common -#####2. 图片SD卡缓存 -使用见:[图片SD卡缓存的使用](http://www.trinea.cn/?p=757) -适用:应用中获取图片较多且图片较大的情况,在微博、花瓣、美丽说、path这类应用中可以起到很好的效果。效果如如下: -![ImageSDCardCache](http://img.hb.aicdn.com/37f1265d1c2132277694a39d6036d462f848b3625e51-8YB0M2_fw580) +## 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)**. +![](https://lh3.googleusercontent.com/ERb20Y50r3u_tZMMlqpH5cnS_MC_n366WoKvEjJyFfHz6d-EwvhaEUf7ZKAgRajboTWR=w720-h440-rw) + + +#### 一. 缓存类 +主要特性:(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、微信头像、美丽说、蘑菇街、花瓣、淘宝等等。效果图如下: +![ImageCahe](http://farm4.staticflickr.com/3710/9312163125_81f1c1997b_o.jpg) + + +##### 2. 图片SD卡缓存 +使用见:[图片SD卡缓存的使用](http://www.trinea.cn/android/android-imagesdcardcache/) +适用:应用中获取图片较多且图片较大的情况。需要二级缓存及ListView或GridView图片加载推荐使用上面的[ImageCache](http://www.trinea.cn/android/android-imagecache/)。效果图如下: +![ImageSDCardCache](http://farm3.staticflickr.com/2834/9314949798_ea69bdb5e8_o.jpg) + + +##### 3. 网络缓存 +使用见:[Android网络缓存](http://www.trinea.cn/android/android-http-cache) +适用:网络获取内容不大的应用,尤其是api接口数据,如新浪微博、twitter的timeline、微信公众账号发送的内容等等。效果图如下: +![HttpCache](http://farm3.staticflickr.com/2843/12566457534_2cfa4297a1_o.jpg) + + +##### 4. 预取数据缓存 +使用见:[预取数据缓存](http://www.trinea.cn/android/preloaddatacache/) +缓存类关系图如下:其中HttpCache为后续计划的http缓存 +![Image Cache](https://farm8.staticflickr.com/7336/13991252450_f1e154012d_o.png) + +#### 二. 公用的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)。效果图如下: +![DropDownListView](http://farm8.staticflickr.com/7376/9312162951_74b597ebaa_o.jpg) + + +##### 2. 滑动一页(一个Item)的Gallery +使用及实现原理:[滑动一页(一个Item)的Gallery的使用](http://www.trinea.cn/android/gallery-scroll-one-page/)。效果图如下: +![ViewPager1](http://farm8.staticflickr.com/7330/9321381014_fb404e2430_o.jpg) +![ViewPager2](http://farm3.staticflickr.com/2827/9321380982_d8619d1601_o.jpg) + + +##### 3. 滑动到底部或顶部响应的ScrollView +使用及实现原理: [滚动到底部或顶部响应的ScrollView使用](http://www.trinea.cn/android/on-bottom-load-more-scrollview/)。效果图如下: +![ScrollView](http://farm4.staticflickr.com/3669/9459686814_1a523ceeb6_o.jpg) + + +#### 三. 工具类 +具体介绍可见:[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/) +效果图如下: +![downloadManagerDemo](http://www.trinea.cn/wp-content/uploads/2013/05/downloadDemo2.gif) + +##### 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, Serializable { @@ -80,7 +80,7 @@ public class SimpleCache implements Cache, Serializable { *
  • Remove type is {@link RemoveTypeEnterTimeFirst} when cache is full
  • * */ - public SimpleCache(){ + public SimpleCache() { this(DEFAULT_MAX_SIZE); } @@ -92,7 +92,7 @@ public SimpleCache(){ * * @param maxSize maximum size of the cache */ - public SimpleCache(int maxSize){ + public SimpleCache(int maxSize) { if (maxSize <= 0) { throw new IllegalArgumentException("The maxSize of cache must be greater than 0."); } @@ -125,7 +125,7 @@ public long getValidTime() { * set valid time of elements in cache, in mills * * @param validTime valid time of elements in cache, in mills. If less than 0, it will be set to -1 and means not - * invalid. Rule of invalid see {@link #isExpired(CacheObject)} + * invalid. Rule of invalid see {@link #isExpired(CacheObject)} */ public void setValidTime(long validTime) { this.validTime = validTime <= 0 ? -1 : validTime; @@ -200,7 +200,7 @@ protected synchronized void setUsedInfo(CacheObject obj) { * @param key key * @param value data of {@link CacheObject} * @return return null if cache is full and cannot remove one, else return the value be putted - * @see {@link #put(Object, CacheObject)} + * @see SimpleCache#put(Object, CacheObject) */ @Override public CacheObject put(K key, V value) { @@ -264,7 +264,7 @@ public boolean containsKey(K key) { * * @param key * @return - * @see {@link #isExpired(CacheObject)} + * @see SimpleCache#isExpired(CacheObject) */ protected boolean isExpired(K key) { return validTime == -1 ? false : isExpired(cache.get(key)); @@ -363,7 +363,8 @@ public void clear() { */ protected boolean isExpired(CacheObject obj) { return validTime != -1 - && (obj == null || (obj.isExpired() && !obj.isForever()) || (obj.getEnterTime() + validTime) < System.currentTimeMillis()); + && (obj == null || (obj.isExpired() && !obj.isForever()) || (obj.getEnterTime() + validTime) < System + .currentTimeMillis()); } /** diff --git a/src/main/java/cn/trinea/android/common/util/AppUtils.java b/src/main/java/cn/trinea/android/common/util/AppUtils.java new file mode 100644 index 0000000..a52f63c --- /dev/null +++ b/src/main/java/cn/trinea/android/common/util/AppUtils.java @@ -0,0 +1,79 @@ +package cn.trinea.android.common.util; + +import java.util.List; + +import android.app.ActivityManager; +import android.app.ActivityManager.RunningAppProcessInfo; +import android.app.ActivityManager.RunningTaskInfo; +import android.content.ComponentName; +import android.content.Context; + +/** + * AppUtils + *
      + *
    • {@link AppUtils#isNamedProcess(Context, String)}
    • + *
    + * + * @author Trinea 2014-5-07 + */ +public class AppUtils { + + private AppUtils() { + throw new AssertionError(); + } + + /** + * whether this process is named with processName + * + * @param context + * @param processName + * @return
      + * return whether this process is named with processName + *
    • if context is null, return false
    • + *
    • if {@link ActivityManager#getRunningAppProcesses()} is null, return false
    • + *
    • if one process of {@link ActivityManager#getRunningAppProcesses()} is equal to processName, return + * true, otherwise return false
    • + *
    + */ + public static boolean isNamedProcess(Context context, String processName) { + if (context == null) { + return false; + } + + int pid = android.os.Process.myPid(); + ActivityManager manager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE); + List processInfoList = manager.getRunningAppProcesses(); + if (ListUtils.isEmpty(processInfoList)) { + return false; + } + + for (RunningAppProcessInfo processInfo : processInfoList) { + if (processInfo != null && processInfo.pid == pid + && ObjectUtils.isEquals(processName, processInfo.processName)) { + return true; + } + } + return false; + } + + /** + * whether application is in background + *
      + *
    • need use permission android.permission.GET_TASKS in Manifest.xml
    • + *
    + * + * @param context + * @return if application is in background return true, otherwise return false + */ + public static boolean isApplicationInBackground(Context context) { + ActivityManager am = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE); + List taskList = am.getRunningTasks(1); + if (taskList != null && !taskList.isEmpty()) { + ComponentName topActivity = taskList.get(0).topActivity; + if (topActivity != null && !topActivity.getPackageName().equals(context.getPackageName())) { + return true; + } + } + return false; + } +} diff --git a/src/cn/trinea/android/common/util/ArrayUtils.java b/src/main/java/cn/trinea/android/common/util/ArrayUtils.java similarity index 97% rename from src/cn/trinea/android/common/util/ArrayUtils.java rename to src/main/java/cn/trinea/android/common/util/ArrayUtils.java index 23a3071..e9ebf89 100644 --- a/src/cn/trinea/android/common/util/ArrayUtils.java +++ b/src/main/java/cn/trinea/android/common/util/ArrayUtils.java @@ -16,10 +16,14 @@ *
  • {@link #getNext(long[], long, long, boolean)}
  • * * - * @author Trinea 2011-10-24 + * @author Trinea 2011-10-24 */ public class ArrayUtils { + private ArrayUtils() { + throw new AssertionError(); + } + /** * is null or its length is 0 * diff --git a/src/main/java/cn/trinea/android/common/util/AssetDatabaseOpenHelper.java b/src/main/java/cn/trinea/android/common/util/AssetDatabaseOpenHelper.java new file mode 100644 index 0000000..7deb5f1 --- /dev/null +++ b/src/main/java/cn/trinea/android/common/util/AssetDatabaseOpenHelper.java @@ -0,0 +1,84 @@ +package cn.trinea.android.common.util; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; + +/** + * AssetDatabaseOpenHelper + *
      + *
    • Auto copy databse form assets to /data/data/package_name/databases
    • + *
    • You can use it like {@link SQLiteDatabase}, use {@link #getWritableDatabase()} to create and/or open a database + * that will be used for reading and writing. use {@link #getReadableDatabase()} to create and/or open a database that + * will be used for reading only.
    • + *
    + * + * @author Trinea 2013-12-5 + */ +public class AssetDatabaseOpenHelper { + + private Context context; + private String databaseName; + + public AssetDatabaseOpenHelper(Context context, String databaseName) { + this.context = context; + this.databaseName = databaseName; + } + + /** + * Create and/or open a database that will be used for reading and writing. + * + * @return + * @throws RuntimeException if cannot copy database from assets + * @throws SQLiteException if the database cannot be opened + */ + public synchronized SQLiteDatabase getWritableDatabase() { + File dbFile = context.getDatabasePath(databaseName); + if (dbFile != null && !dbFile.exists()) { + try { + copyDatabase(dbFile); + } catch (IOException e) { + throw new RuntimeException("Error creating source database", e); + } + } + + return SQLiteDatabase.openDatabase(dbFile.getPath(), null, SQLiteDatabase.OPEN_READWRITE); + } + + /** + * Create and/or open a database that will be used for reading only. + * + * @return + * @throws RuntimeException if cannot copy database from assets + * @throws SQLiteException if the database cannot be opened + */ + public synchronized SQLiteDatabase getReadableDatabase() { + File dbFile = context.getDatabasePath(databaseName); + if (dbFile != null && !dbFile.exists()) { + try { + copyDatabase(dbFile); + } catch (IOException e) { + throw new RuntimeException("Error creating source database", e); + } + } + + return SQLiteDatabase.openDatabase(dbFile.getPath(), null, SQLiteDatabase.OPEN_READONLY); + } + + /** + * @return the database name + */ + public String getDatabaseName() { + return databaseName; + } + + private void copyDatabase(File dbFile) throws IOException { + InputStream stream = context.getAssets().open(databaseName); + FileUtils.writeFile(dbFile, stream); + stream.close(); + } +} diff --git a/src/main/java/cn/trinea/android/common/util/CacheManager.java b/src/main/java/cn/trinea/android/common/util/CacheManager.java new file mode 100644 index 0000000..a28e58d --- /dev/null +++ b/src/main/java/cn/trinea/android/common/util/CacheManager.java @@ -0,0 +1,58 @@ +package cn.trinea.android.common.util; + +import android.app.Activity; +import android.content.Context; +import cn.trinea.android.common.service.HttpCache; +import cn.trinea.android.common.service.impl.ImageCache; +import cn.trinea.android.common.service.impl.ImageSDCardCache; + +/** + * CacheManager + * + * @author Trinea 2014-2-11 + */ +public class CacheManager { + + private static HttpCache httpCache = null; + + private CacheManager() { + throw new AssertionError(); + } + + /** + * get the singleton instance of HttpCache + * + * @param context {@link Activity#getApplicationContext()} + * @return + */ + public static HttpCache getHttpCache(Context context) { + if (httpCache == null) { + synchronized (CacheManager.class) { + if (httpCache == null) { + httpCache = new HttpCache(context); + } + } + } + return httpCache; + } + + /** + * get the singleton instance of ImageCache + * + * @return + * @see ImageCacheManager#getImageCache() + */ + public static ImageCache getImageCache() { + return ImageCacheManager.getImageCache(); + } + + /** + * get the singleton instance of ImageSDCardCache + * + * @return + * @see ImageCacheManager#getImageSDCardCache() + */ + public static ImageSDCardCache getImageSDCardCache() { + return ImageCacheManager.getImageSDCardCache(); + } +} diff --git a/src/main/java/cn/trinea/android/common/util/CollectionUtils.java b/src/main/java/cn/trinea/android/common/util/CollectionUtils.java new file mode 100644 index 0000000..aea485c --- /dev/null +++ b/src/main/java/cn/trinea/android/common/util/CollectionUtils.java @@ -0,0 +1,54 @@ +package cn.trinea.android.common.util; + +import java.util.Collection; + +import android.text.TextUtils; + +/** + * CollectionUtils + * + * @author Trinea 2012-7-22 + */ +public class CollectionUtils { + + /** default join separator **/ + public static final CharSequence DEFAULT_JOIN_SEPARATOR = ","; + + private CollectionUtils() { + throw new AssertionError(); + } + + /** + * is null or its size is 0 + * + *
    +     * isEmpty(null)   =   true;
    +     * isEmpty({})     =   true;
    +     * isEmpty({1})    =   false;
    +     * 
    + * + * @param + * @param c + * @return if collection is null or its size is 0, return true, else return false. + */ + public static boolean isEmpty(Collection c) { + return (c == null || c.size() == 0); + } + + /** + * join collection to string, separator is {@link #DEFAULT_JOIN_SEPARATOR} + * + *
    +     * join(null)      =   "";
    +     * join({})        =   "";
    +     * join({a,b})     =   "a,b";
    +     * 
    + * + * @param collection + * @return join collection to string, separator is {@link #DEFAULT_JOIN_SEPARATOR}. if collection is empty, return + * "" + */ + public static String join(Iterable collection) { + return collection == null ? "" : TextUtils.join(DEFAULT_JOIN_SEPARATOR, collection); + } +} diff --git a/src/main/java/cn/trinea/android/common/util/DbHelper.java b/src/main/java/cn/trinea/android/common/util/DbHelper.java new file mode 100644 index 0000000..b7ec54b --- /dev/null +++ b/src/main/java/cn/trinea/android/common/util/DbHelper.java @@ -0,0 +1,37 @@ +package cn.trinea.android.common.util; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import cn.trinea.android.common.constant.DbConstants; + +/** + * db helper + * + * @author Trinea 2013-10-21 + */ +public class DbHelper extends SQLiteOpenHelper { + + public DbHelper(Context context) { + super(context, DbConstants.DB_NAME, null, DbConstants.DB_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.beginTransaction(); + try { + db.execSQL(DbConstants.CREATE_IMAGE_SDCARD_CACHE_TABLE_SQL.toString()); + db.execSQL(DbConstants.CREATE_IMAGE_SDCARD_CACHE_TABLE_INDEX_SQL.toString()); + + db.execSQL(DbConstants.CREATE_HTTP_CACHE_TABLE_SQL.toString()); + db.execSQL(DbConstants.CREATE_HTTP_CACHE_TABLE_INDEX_SQL.toString()); + db.execSQL(DbConstants.CREATE_HTTP_CACHE_TABLE_UNIQUE_INDEX.toString()); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {} +} diff --git a/src/main/java/cn/trinea/android/common/util/DigestUtils.java b/src/main/java/cn/trinea/android/common/util/DigestUtils.java new file mode 100644 index 0000000..a016c86 --- /dev/null +++ b/src/main/java/cn/trinea/android/common/util/DigestUtils.java @@ -0,0 +1,59 @@ +package cn.trinea.android.common.util; + +import java.security.MessageDigest; + +/** + * DigestUtils + * + * @author Trinea 2014-03-20 + */ +public class DigestUtils { + + /** + * Used to build output as Hex + */ + private static final char[] DIGITS_LOWER = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', + 'e', 'f' }; + + private DigestUtils() { + throw new AssertionError(); + } + + /** + * encode By MD5 + * + * @param str + * @return String + */ + public static String md5(String str) { + if (str == null) { + return null; + } + try { + MessageDigest messageDigest = MessageDigest.getInstance("MD5"); + messageDigest.update(str.getBytes()); + return new String(encodeHex(messageDigest.digest())); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order. + * The returned array will be double the length of the passed array, as it takes two characters to represent any + * given byte. + * + * @param data a byte[] to convert to Hex characters + * @return A char[] containing hexadecimal characters + */ + protected static char[] encodeHex(final byte[] data) { + final int l = data.length; + final char[] out = new char[l << 1]; + // two characters form the hex value. + for (int i = 0, j = 0; i < l; i++) { + out[j++] = DIGITS_LOWER[(0xF0 & data[i]) >>> 4]; + out[j++] = DIGITS_LOWER[0x0F & data[i]]; + } + return out; + } +} diff --git a/src/cn/trinea/android/common/util/DownloadManagerPro.java b/src/main/java/cn/trinea/android/common/util/DownloadManagerPro.java similarity index 72% rename from src/cn/trinea/android/common/util/DownloadManagerPro.java rename to src/main/java/cn/trinea/android/common/util/DownloadManagerPro.java index 85e1726..e39f47b 100644 --- a/src/cn/trinea/android/common/util/DownloadManagerPro.java +++ b/src/main/java/cn/trinea/android/common/util/DownloadManagerPro.java @@ -6,6 +6,7 @@ import android.app.DownloadManager.Request; import android.database.Cursor; import android.net.Uri; +import android.os.Build; /** * DownloadManagerPro @@ -22,6 +23,8 @@ * *
      * Operate download + *
    • {@link #isExistPauseAndResumeMethod()} whether exist pauseDownload and resumeDownload method in + * {@link DownloadManager}
    • *
    • {@link #pauseDownload(long...)} pause download. need pauseDownload(long...) method in {@link DownloadManager}
    • *
    • {@link #resumeDownload(long...)} resume download. need resumeDownload(long...) method in {@link DownloadManager}
    • *
    @@ -31,12 +34,15 @@ *
  • {@link RequestPro#setNotiExtras(String)} set noti extras
  • * * - * @author Trinea 2013-5-4 + * @author Trinea 2013-5-4 */ public class DownloadManagerPro { public static final Uri CONTENT_URI = Uri.parse("content://downloads/my_downloads"); + /** represents downloaded file above api 11 **/ public static final String COLUMN_LOCAL_FILENAME = "local_filename"; + /** represents downloaded file below api 11 **/ + public static final String COLUMN_LOCAL_URI = "local_uri"; public static final String METHOD_NAME_PAUSE_DOWNLOAD = "pauseDownload"; public static final String METHOD_NAME_RESUME_DOWNLOAD = "resumeDownload"; @@ -49,7 +55,7 @@ public class DownloadManagerPro { private DownloadManager downloadManager; - public DownloadManagerPro(DownloadManager downloadManager){ + public DownloadManagerPro(DownloadManager downloadManager) { this.downloadManager = downloadManager; } @@ -68,14 +74,14 @@ public int getStatusById(long downloadId) { * * @param downloadId * @return a int array with two elements - *
      - *
    • result[0] represents downloaded bytes, This will initially be -1.
    • - *
    • result[1] represents total bytes, This will initially be -1.
    • - *
    + *
      + *
    • result[0] represents downloaded bytes, This will initially be -1.
    • + *
    • result[1] represents total bytes, This will initially be -1.
    • + *
    */ public int[] getDownloadBytes(long downloadId) { int[] bytesAndStatus = getBytesAndStatus(downloadId); - return new int[] { bytesAndStatus[0], bytesAndStatus[1] }; + return new int[] {bytesAndStatus[0], bytesAndStatus[1]}; } /** @@ -83,14 +89,14 @@ public int[] getDownloadBytes(long downloadId) { * * @param downloadId * @return a int array with three elements - *
      - *
    • result[0] represents downloaded bytes, This will initially be -1.
    • - *
    • result[1] represents total bytes, This will initially be -1.
    • - *
    • result[2] represents download status, This will initially be 0.
    • - *
    + *
      + *
    • result[0] represents downloaded bytes, This will initially be -1.
    • + *
    • result[1] represents total bytes, This will initially be -1.
    • + *
    • result[2] represents download status, This will initially be 0.
    • + *
    */ public int[] getBytesAndStatus(long downloadId) { - int[] bytesAndStatus = new int[] { -1, -1, 0 }; + int[] bytesAndStatus = new int[] {-1, -1, 0}; DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloadId); Cursor c = null; try { @@ -116,16 +122,18 @@ public int[] getBytesAndStatus(long downloadId) { */ public int pauseDownload(long... ids) { initPauseMethod(); - if (pauseDownload != null) { - try { - return ((Integer)pauseDownload.invoke(downloadManager, ids)).intValue(); - } catch (Exception e) { - /** - * accept all exception, include ClassNotFoundException, NoSuchMethodException, - * InvocationTargetException, NullPointException - */ - e.printStackTrace(); - } + if (pauseDownload == null) { + return -1; + } + + try { + return ((Integer)pauseDownload.invoke(downloadManager, ids)).intValue(); + } catch (Exception e) { + /** + * accept all exception, include ClassNotFoundException, NoSuchMethodException, InvocationTargetException, + * NullPointException + */ + e.printStackTrace(); } return -1; } @@ -138,16 +146,18 @@ public int pauseDownload(long... ids) { */ public int resumeDownload(long... ids) { initResumeMethod(); - if (resumeDownload != null) { - try { - return ((Integer)resumeDownload.invoke(downloadManager, ids)).intValue(); - } catch (Exception e) { - /** - * accept all exception, include ClassNotFoundException, NoSuchMethodException, - * InvocationTargetException, NullPointException - */ - e.printStackTrace(); - } + if (resumeDownload == null) { + return -1; + } + + try { + return ((Integer)resumeDownload.invoke(downloadManager, ids)).intValue(); + } catch (Exception e) { + /** + * accept all exception, include ClassNotFoundException, NoSuchMethodException, InvocationTargetException, + * NullPointException + */ + e.printStackTrace(); } return -1; } @@ -164,26 +174,30 @@ public static boolean isExistPauseAndResumeMethod() { } private static void initPauseMethod() { - if (!isInitPauseDownload) { - isInitPauseDownload = true; - try { - pauseDownload = DownloadManager.class.getMethod(METHOD_NAME_PAUSE_DOWNLOAD, long[].class); - } catch (Exception e) { - // accept all exception - e.printStackTrace(); - } + if (isInitPauseDownload) { + return; + } + + isInitPauseDownload = true; + try { + pauseDownload = DownloadManager.class.getMethod(METHOD_NAME_PAUSE_DOWNLOAD, long[].class); + } catch (Exception e) { + // accept all exception + e.printStackTrace(); } } private static void initResumeMethod() { - if (!isInitResumeDownload) { - isInitResumeDownload = true; - try { - resumeDownload = DownloadManager.class.getMethod(METHOD_NAME_RESUME_DOWNLOAD, long[].class); - } catch (Exception e) { - // accept all exception - e.printStackTrace(); - } + if (isInitResumeDownload) { + return; + } + + isInitResumeDownload = true; + try { + resumeDownload = DownloadManager.class.getMethod(METHOD_NAME_RESUME_DOWNLOAD, long[].class); + } catch (Exception e) { + // accept all exception + e.printStackTrace(); } } @@ -194,7 +208,8 @@ private static void initResumeMethod() { * @return */ public String getFileName(long downloadId) { - return getString(downloadId, COLUMN_LOCAL_FILENAME); + return getString(downloadId, (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB ? COLUMN_LOCAL_URI + : COLUMN_LOCAL_FILENAME)); } /** @@ -212,11 +227,12 @@ public String getUri(long downloadId) { * * @param downloadId * @return
      - *
    • if status of downloadId is {@link DownloadManager#STATUS_PAUSED}, return {@link #getPausedReason(long)}
    • - *
    • if status of downloadId is {@link DownloadManager#STATUS_FAILED}, return {@link #getErrorCode(long)}
    • - *
    • if status of downloadId is neither {@link DownloadManager#STATUS_PAUSED} nor - * {@link DownloadManager#STATUS_FAILED}, return 0
    • - *
    + *
  • if status of downloadId is {@link DownloadManager#STATUS_PAUSED}, return + * {@link #getPausedReason(long)}
  • + *
  • if status of downloadId is {@link DownloadManager#STATUS_FAILED}, return {@link #getErrorCode(long)}
  • + *
  • if status of downloadId is neither {@link DownloadManager#STATUS_PAUSED} nor + * {@link DownloadManager#STATUS_FAILED}, return 0
  • + * */ public int getReason(long downloadId) { return getInt(downloadId, DownloadManager.COLUMN_REASON); @@ -227,13 +243,13 @@ public int getReason(long downloadId) { * * @param downloadId * @return
      - *
    • if status of downloadId is {@link DownloadManager#STATUS_PAUSED}, return one of - * {@link DownloadManager#PAUSED_WAITING_TO_RETRY}
      - * {@link DownloadManager#PAUSED_WAITING_FOR_NETWORK}
      - * {@link DownloadManager#PAUSED_QUEUED_FOR_WIFI}
      - * {@link DownloadManager#PAUSED_UNKNOWN}
    • - *
    • else return {@link DownloadManager#PAUSED_UNKNOWN}
    • - *
    + *
  • if status of downloadId is {@link DownloadManager#STATUS_PAUSED}, return one of + * {@link DownloadManager#PAUSED_WAITING_TO_RETRY}
    + * {@link DownloadManager#PAUSED_WAITING_FOR_NETWORK}
    + * {@link DownloadManager#PAUSED_QUEUED_FOR_WIFI}
    + * {@link DownloadManager#PAUSED_UNKNOWN}
  • + *
  • else return {@link DownloadManager#PAUSED_UNKNOWN}
  • + * */ public int getPausedReason(long downloadId) { return getInt(downloadId, DownloadManager.COLUMN_REASON); @@ -263,7 +279,7 @@ public static class RequestPro extends DownloadManager.Request { /** * @param uri the HTTP URI to download. */ - public RequestPro(Uri uri){ + public RequestPro(Uri uri) { super(uri); } diff --git a/src/cn/trinea/android/common/util/FileUtils.java b/src/main/java/cn/trinea/android/common/util/FileUtils.java similarity index 53% rename from src/cn/trinea/android/common/util/FileUtils.java rename to src/main/java/cn/trinea/android/common/util/FileUtils.java index ee65bb8..522ab1e 100644 --- a/src/cn/trinea/android/common/util/FileUtils.java +++ b/src/main/java/cn/trinea/android/common/util/FileUtils.java @@ -2,27 +2,38 @@ import java.io.BufferedReader; import java.io.File; +import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; -import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; +import android.text.TextUtils; + /** * File Utils *
      * Read or write file - *
    • {@link #readFile(String)} read file
    • - *
    • {@link #readFileToList(String)} read file to string list
    • - *
    • {@link #writeFile(String, String, boolean)} write file
    • + *
    • {@link #readFile(String, String)} read file
    • + *
    • {@link #readFileToList(String, String)} read file to string list
    • + *
    • {@link #writeFile(String, String, boolean)} write file from String
    • + *
    • {@link #writeFile(String, String)} write file from String
    • + *
    • {@link #writeFile(String, List, boolean)} write file from String List
    • + *
    • {@link #writeFile(String, List)} write file from String List
    • *
    • {@link #writeFile(String, InputStream)} write file
    • + *
    • {@link #writeFile(String, InputStream, boolean)} write file
    • + *
    • {@link #writeFile(File, InputStream)} write file
    • + *
    • {@link #writeFile(File, InputStream, boolean)} write file
    • *
    *
      * Operate file + *
    • {@link #moveFile(File, File)} or {@link #moveFile(String, String)}
    • + *
    • {@link #copyFile(String, String)}
    • *
    • {@link #getFileExtension(String)}
    • *
    • {@link #getFileName(String)}
    • *
    • {@link #getFileNameWithoutExtension(String)}
    • @@ -34,48 +45,48 @@ *
    • {@link #makeDirs(String)}
    • *
    * - * @author Trinea 2012-5-12 + * @author Trinea 2012-5-12 */ public class FileUtils { public final static String FILE_EXTENSION_SEPARATOR = "."; + private FileUtils() { + throw new AssertionError(); + } + /** * read file * * @param filePath + * @param charsetName The name of a supported {@link java.nio.charset.Charset charset} * @return if file not exist, return null, else return content of file - * @throws IOException if an error occurs while operator BufferedReader + * @throws RuntimeException if an error occurs while operator BufferedReader */ - public static StringBuilder readFile(String filePath) { + public static StringBuilder readFile(String filePath, String charsetName) { File file = new File(filePath); StringBuilder fileContent = new StringBuilder(""); - if (file != null && file.isFile()) { - BufferedReader reader = null; - try { - reader = new BufferedReader(new FileReader(file)); - String line = null; - while ((line = reader.readLine()) != null) { - if (!fileContent.toString().equals("")) { - fileContent.append("\r\n"); - } - fileContent.append(line); - } - reader.close(); - return fileContent; - } catch (IOException e) { - throw new RuntimeException("IOException occurred. ", e); - } finally { - if (reader != null) { - try { - reader.close(); - } catch (IOException e) { - throw new RuntimeException("IOException occurred. ", e); - } + if (file == null || !file.isFile()) { + return null; + } + + BufferedReader reader = null; + try { + InputStreamReader is = new InputStreamReader(new FileInputStream(file), charsetName); + reader = new BufferedReader(is); + String line = null; + while ((line = reader.readLine()) != null) { + if (!fileContent.toString().equals("")) { + fileContent.append("\r\n"); } + fileContent.append(line); } + return fileContent; + } catch (IOException e) { + throw new RuntimeException("IOException occurred. ", e); + } finally { + IOUtils.close(reader); } - return null; } /** @@ -84,41 +95,133 @@ public static StringBuilder readFile(String filePath) { * @param filePath * @param content * @param append is append, if true, write to the end of file, else clear content of file and write into it - * @return return true - * @throws IOException if an error occurs while operator FileWriter + * @return return false if content is empty, true otherwise + * @throws RuntimeException if an error occurs while operator FileWriter */ public static boolean writeFile(String filePath, String content, boolean append) { + if (StringUtils.isEmpty(content)) { + return false; + } + FileWriter fileWriter = null; try { + makeDirs(filePath); fileWriter = new FileWriter(filePath, append); fileWriter.write(content); - fileWriter.close(); return true; } catch (IOException e) { throw new RuntimeException("IOException occurred. ", e); } finally { - if (fileWriter != null) { - try { - fileWriter.close(); - } catch (IOException e) { - throw new RuntimeException("IOException occurred. ", e); + IOUtils.close(fileWriter); + } + } + + /** + * write file + * + * @param filePath + * @param contentList + * @param append is append, if true, write to the end of file, else clear content of file and write into it + * @return return false if contentList is empty, true otherwise + * @throws RuntimeException if an error occurs while operator FileWriter + */ + public static boolean writeFile(String filePath, List contentList, boolean append) { + if (ListUtils.isEmpty(contentList)) { + return false; + } + + FileWriter fileWriter = null; + try { + makeDirs(filePath); + fileWriter = new FileWriter(filePath, append); + int i = 0; + for (String line : contentList) { + if (i++ > 0) { + fileWriter.write("\r\n"); } + fileWriter.write(line); } + return true; + } catch (IOException e) { + throw new RuntimeException("IOException occurred. ", e); + } finally { + IOUtils.close(fileWriter); } } /** - * write file + * write file, the string will be written to the begin of the file + * + * @param filePath + * @param content + * @return + */ + public static boolean writeFile(String filePath, String content) { + return writeFile(filePath, content, false); + } + + /** + * write file, the string list will be written to the begin of the file + * + * @param filePath + * @param contentList + * @return + */ + public static boolean writeFile(String filePath, List contentList) { + return writeFile(filePath, contentList, false); + } + + /** + * write file, the bytes will be written to the begin of the file * * @param filePath * @param stream - * @return return true - * @throws IOException if an error occurs while operator FileWriter + * @return + * @see {@link #writeFile(String, InputStream, boolean)} */ public static boolean writeFile(String filePath, InputStream stream) { + return writeFile(filePath, stream, false); + } + + /** + * write file + * + * @param file the file to be opened for writing. + * @param stream the input stream + * @param append if true, then bytes will be written to the end of the file rather than the beginning + * @return return true + * @throws RuntimeException if an error occurs while operator FileOutputStream + */ + public static boolean writeFile(String filePath, InputStream stream, boolean append) { + return writeFile(filePath != null ? new File(filePath) : null, stream, append); + } + + /** + * write file, the bytes will be written to the begin of the file + * + * @param file + * @param stream + * @return + * @see {@link #writeFile(File, InputStream, boolean)} + */ + public static boolean writeFile(File file, InputStream stream) { + return writeFile(file, stream, false); + } + + /** + * write file + * + * @param file the file to be opened for writing. + * @param stream the input stream + * @param append if true, then bytes will be written to the end of the file rather than the beginning + * @return return true + * @throws RuntimeException if an error occurs while operator FileOutputStream + */ + public static boolean writeFile(File file, InputStream stream, boolean append) { OutputStream o = null; try { - o = new FileOutputStream(filePath); + makeDirs(file.getAbsolutePath()); + o = new FileOutputStream(file, append); byte data[] = new byte[1024]; int length = -1; while ((length = stream.read(data)) != -1) { @@ -131,50 +234,85 @@ public static boolean writeFile(String filePath, InputStream stream) { } catch (IOException e) { throw new RuntimeException("IOException occurred. ", e); } finally { - if (o != null) { - try { - o.close(); - stream.close(); - } catch (IOException e) { - throw new RuntimeException("IOException occurred. ", e); - } - } + IOUtils.close(o); + IOUtils.close(stream); + } + } + + /** + * move file + * + * @param sourceFilePath + * @param destFilePath + */ + public static void moveFile(String sourceFilePath, String destFilePath) { + if (TextUtils.isEmpty(sourceFilePath) || TextUtils.isEmpty(destFilePath)) { + throw new RuntimeException("Both sourceFilePath and destFilePath cannot be null."); + } + moveFile(new File(sourceFilePath), new File(destFilePath)); + } + + /** + * move file + * + * @param srcFile + * @param destFile + */ + public static void moveFile(File srcFile, File destFile) { + boolean rename = srcFile.renameTo(destFile); + if (!rename) { + copyFile(srcFile.getAbsolutePath(), destFile.getAbsolutePath()); + deleteFile(srcFile.getAbsolutePath()); + } + } + + /** + * copy file + * + * @param sourceFilePath + * @param destFilePath + * @return + * @throws RuntimeException if an error occurs while operator FileOutputStream + */ + public static boolean copyFile(String sourceFilePath, String destFilePath) { + InputStream inputStream = null; + try { + inputStream = new FileInputStream(sourceFilePath); + } catch (FileNotFoundException e) { + throw new RuntimeException("FileNotFoundException occurred. ", e); } + return writeFile(destFilePath, inputStream); } /** * read file to string list, a element of list is a line * * @param filePath + * @param charsetName The name of a supported {@link java.nio.charset.Charset charset} * @return if file not exist, return null, else return content of file - * @throws IOException if an error occurs while operator BufferedReader + * @throws RuntimeException if an error occurs while operator BufferedReader */ - public static List readFileToList(String filePath) { + public static List readFileToList(String filePath, String charsetName) { File file = new File(filePath); List fileContent = new ArrayList(); - if (file != null && file.isFile()) { - BufferedReader reader = null; - try { - reader = new BufferedReader(new FileReader(file)); - String line = null; - while ((line = reader.readLine()) != null) { - fileContent.add(line); - } - reader.close(); - return fileContent; - } catch (IOException e) { - throw new RuntimeException("IOException occurred. ", e); - } finally { - if (reader != null) { - try { - reader.close(); - } catch (IOException e) { - throw new RuntimeException("IOException occurred. ", e); - } - } + if (file == null || !file.isFile()) { + return null; + } + + BufferedReader reader = null; + try { + InputStreamReader is = new InputStreamReader(new FileInputStream(file), charsetName); + reader = new BufferedReader(is); + String line = null; + while ((line = reader.readLine()) != null) { + fileContent.add(line); } + return fileContent; + } catch (IOException e) { + throw new RuntimeException("IOException occurred. ", e); + } finally { + IOUtils.close(reader); } - return null; } /** @@ -208,14 +346,11 @@ public static String getFileNameWithoutExtension(String filePath) { int filePosi = filePath.lastIndexOf(File.separator); if (filePosi == -1) { return (extenPosi == -1 ? filePath : filePath.substring(0, extenPosi)); - } else { - if (extenPosi == -1) { - return filePath.substring(filePosi + 1); - } else { - return (filePosi < extenPosi ? filePath.substring(filePosi + 1, extenPosi) - : filePath.substring(filePosi + 1)); - } } + if (extenPosi == -1) { + return filePath.substring(filePosi + 1); + } + return (filePosi < extenPosi ? filePath.substring(filePosi + 1, extenPosi) : filePath.substring(filePosi + 1)); } /** @@ -245,10 +380,7 @@ public static String getFileName(String filePath) { } int filePosi = filePath.lastIndexOf(File.separator); - if (filePosi == -1) { - return filePath; - } - return filePath.substring(filePosi + 1); + return (filePosi == -1) ? filePath : filePath.substring(filePosi + 1); } /** @@ -280,10 +412,7 @@ public static String getFolderName(String filePath) { } int filePosi = filePath.lastIndexOf(File.separator); - if (filePosi == -1) { - return ""; - } - return filePath.substring(0, filePosi); + return (filePosi == -1) ? "" : filePath.substring(0, filePosi); } /** @@ -317,12 +446,8 @@ public static String getFileExtension(String filePath) { int filePosi = filePath.lastIndexOf(File.separator); if (extenPosi == -1) { return ""; - } else { - if (filePosi >= extenPosi) { - return ""; - } - return filePath.substring(extenPosi + 1); } + return (filePosi >= extenPosi) ? "" : filePath.substring(extenPosi + 1); } /** @@ -330,19 +455,19 @@ public static String getFileExtension(String filePath) { * to create this directory.
    *
    *
      - * Attentions: + * Attentions: *
    • makeDirs("C:\\Users\\Trinea") can only create users folder
    • *
    • makeFolder("C:\\Users\\Trinea\\") can create Trinea folder
    • *
    * * @param filePath * @return true if the necessary directories have been created or the target directory already exists, false one of - * the directories can not be created. - *
      - *
    • if {@link FileUtils#getFolderName(String)} return null, return false
    • - *
    • if target directory already exists, return true
    • - *
    • return {@link java.io.File#makeFolder}
    • - *
    + * the directories can not be created. + *
      + *
    • if {@link FileUtils#getFolderName(String)} return null, return false
    • + *
    • if target directory already exists, return true
    • + *
    • return {@link java.io.File#makeFolder}
    • + *
    */ public static boolean makeDirs(String filePath) { String folderName = getFolderName(filePath); @@ -357,7 +482,7 @@ public static boolean makeDirs(String filePath) { /** * @param filePath * @return - * @see {@link #makeDirs(String)} + * @see #makeDirs(String) */ public static boolean makeFolders(String filePath) { return makeDirs(filePath); @@ -410,22 +535,23 @@ public static boolean deleteFile(String path) { } File file = new File(path); - if (file.exists()) { - if (file.isFile()) { - return file.delete(); - } else if (file.isDirectory()) { - for (File f : file.listFiles()) { - if (f.isFile()) { - f.delete(); - } else if (f.isDirectory()) { - deleteFile(f.getAbsolutePath()); - } - } - return file.delete(); - } + if (!file.exists()) { + return true; + } + if (file.isFile()) { + return file.delete(); + } + if (!file.isDirectory()) { return false; } - return true; + for (File f : file.listFiles()) { + if (f.isFile()) { + f.delete(); + } else if (f.isDirectory()) { + deleteFile(f.getAbsolutePath()); + } + } + return file.delete(); } /** @@ -436,12 +562,13 @@ public static boolean deleteFile(String path) { *
      * * @param path - * @return + * @return returns the length of this file in bytes. returns -1 if the file does not exist. */ public static long getFileSize(String path) { if (StringUtils.isBlank(path)) { return -1; } + File file = new File(path); return (file.exists() && file.isFile() ? file.length() : -1); } diff --git a/src/main/java/cn/trinea/android/common/util/HttpUtils.java b/src/main/java/cn/trinea/android/common/util/HttpUtils.java new file mode 100644 index 0000000..534f212 --- /dev/null +++ b/src/main/java/cn/trinea/android/common/util/HttpUtils.java @@ -0,0 +1,574 @@ +package cn.trinea.android.common.util; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.text.SimpleDateFormat; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; + +import android.os.AsyncTask; +import cn.trinea.android.common.constant.HttpConstants; +import cn.trinea.android.common.entity.HttpRequest; +import cn.trinea.android.common.entity.HttpResponse; +import cn.trinea.android.common.service.HttpCache; + +/** + * HttpUtils + *
        + * Http get, you can also use {@link HttpCache} + *
      • {@link #httpGet(HttpRequest)} http get synchronous
      • + *
      • {@link #httpGet(String)} http get synchronous
      • + *
      • {@link #httpGetString(String)} http get synchronous, response is + * String
      • + *
      • {@link #httpGet(HttpRequest, HttpListener)} http get asynchronous
      • + *
      • {@link #httpGet(String, HttpListener)} http get asynchronous
      • + *
      + *
        + * Http post + *
      • {@link #httpPost(HttpRequest)}
      • + *
      • {@link #httpPost(String)}
      • + *
      • {@link #httpPostString(String)}
      • + *
      • {@link #httpPostString(String, Map)}
      • + *
      + *
        + * Http params + *
      • {@link #getUrlWithParas(String, Map)}
      • + *
      • {@link #getUrlWithValueEncodeParas(String, Map)}
      • + *
      • {@link #joinParas(Map)}
      • + *
      • {@link #joinParasWithEncodedValue(Map)}
      • + *
      • {@link #appendParaToUrl(String, String, String)}
      • + *
      • {@link #parseGmtTime(String)}
      • + *
      + * + * @author Trinea 2013-5-12 + */ +public class HttpUtils { + + /** url and para separator **/ + public static final String URL_AND_PARA_SEPARATOR = "?"; + /** parameters separator **/ + public static final String PARAMETERS_SEPARATOR = "&"; + /** paths separator **/ + public static final String PATHS_SEPARATOR = "/"; + /** equal sign **/ + public static final String EQUAL_SIGN = "="; + + private HttpUtils() { + throw new AssertionError(); + } + + /** + * http get synchronous + *
        + *
      • use gzip compression default
      • + *
      • use bufferedReader to improve the reading speed
      • + *
      + * + * @param request + * @return the response of the url, if null represents http error + */ + public static HttpResponse httpGet(HttpRequest request) { + if (request == null) { + return null; + } + + BufferedReader input = null; + HttpURLConnection con = null; + try { + URL url = new URL(request.getUrl()); + try { + HttpResponse response = new HttpResponse(request.getUrl()); + // default gzip encode + con = (HttpURLConnection) url.openConnection(); + setURLConnection(request, con); + input = new BufferedReader(new InputStreamReader(con.getInputStream())); + StringBuilder sb = new StringBuilder(); + String s; + while ((s = input.readLine()) != null) { + sb.append(s).append("\n"); + } + response.setResponseBody(sb.toString()); + setHttpResponse(con, response); + return response; + } catch (IOException e) { + e.printStackTrace(); + } + } catch (MalformedURLException e1) { + e1.printStackTrace(); + } finally { + // close buffered + IOUtils.closeQuietly(input); + // disconnecting releases the resources held by a connection so they may be + // closed or reused + if (con != null) { + con.disconnect(); + } + } + + return null; + } + + /** + * http get synchronous + * + * @param httpUrl + * @return the response of the url, if null represents http error + * @see HttpUtils#httpGet(HttpRequest) + */ + public static HttpResponse httpGet(String httpUrl) { + return httpGet(new HttpRequest(httpUrl)); + } + + /** + * http get synchronous + * + * @param request + * @return the content of the url, if null represents http error + * @see HttpUtils#httpGet(HttpRequest) + */ + public static String httpGetString(HttpRequest request) { + HttpResponse response = httpGet(request); + return response == null ? null : response.getResponseBody(); + } + + /** + * http get synchronous + * + * @param httpUrl + * @return the content of the url, if null represents http error + * @see HttpUtils#httpGet(HttpRequest) + */ + public static String httpGetString(String httpUrl) { + HttpResponse response = httpGet(new HttpRequest(httpUrl)); + return response == null ? null : response.getResponseBody(); + } + + /** + * http get asynchronous + *
        + *
      • It gets data from network asynchronous.
      • + *
      • If you want get data synchronous, use {@link #httpGet(HttpRequest)} or + * {@link #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 static void httpGet(String url, HttpListener listener) { + new HttpStringAsyncTask(listener).execute(url); + } + + /** + * http get asynchronous + *
        + *
      • It gets data 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 static void httpGet(HttpRequest request, HttpListener listener) { + new HttpRequestAsyncTask(listener).execute(request); + } + + /** + * http post + *
        + *
      • use gzip compression default
      • + *
      • use bufferedReader to improve the reading speed
      • + *
      + * + * @param httpUrl + * @param paras + * @return the response of the url, if null represents http error + */ + public static HttpResponse httpPost(HttpRequest request) { + if (request == null) { + return null; + } + + BufferedReader input = null; + HttpURLConnection con = null; + try { + URL url = new URL(request.getUrl()); + try { + HttpResponse response = new HttpResponse(request.getUrl()); + // default gzip encode + con = (HttpURLConnection) url.openConnection(); + setURLConnection(request, con); + con.setRequestMethod("POST"); + con.setDoOutput(true); + String paras = request.getParas(); + if (!StringUtils.isEmpty(paras)) { + con.getOutputStream().write(paras.getBytes()); + } + input = new BufferedReader(new InputStreamReader(con.getInputStream())); + StringBuilder sb = new StringBuilder(); + String s; + while ((s = input.readLine()) != null) { + sb.append(s).append("\n"); + } + response.setResponseBody(sb.toString()); + setHttpResponse(con, response); + return response; + } catch (IOException e) { + e.printStackTrace(); + } + } catch (MalformedURLException e1) { + e1.printStackTrace(); + } finally { + // close buffered + IOUtils.closeQuietly(input); + // disconnecting releases the resources held by a connection so they may be + // closed or reused + if (con != null) { + con.disconnect(); + } + } + + return null; + } + + /** + * http post + * + * @param httpUrl + * @return the response of the url, if null represents http error + * @see HttpUtils#httpPost(HttpRequest) + */ + public static HttpResponse httpPost(String httpUrl) { + return httpPost(new HttpRequest(httpUrl)); + } + + /** + * http post + * + * @param httpUrl + * @return the content of the url, if null represents http error + * @see HttpUtils#httpPost(HttpRequest) + */ + public static String httpPostString(String httpUrl) { + HttpResponse response = httpPost(new HttpRequest(httpUrl)); + return response == null ? null : response.getResponseBody(); + } + + /** + * http post + * + * @param httpUrl + * @param parasMap paras map, key is para name, value is para value. will be + * transfrom to String by + * {@link HttpUtils#joinParas(Map)} + * @return the content of the url, if null represents http error + * @see HttpUtils#httpPost(HttpRequest) + */ + public static String httpPostString(String httpUrl, Map parasMap) { + HttpResponse response = httpPost(new HttpRequest(httpUrl, parasMap)); + return response == null ? null : response.getResponseBody(); + } + + /** + * join url and paras + * + *
      +     * getUrlWithParas(null, {(a, b)})                        =   "?a=b";
      +     * getUrlWithParas("baidu.com", {})                       =   "baidu.com";
      +     * getUrlWithParas("baidu.com", {(a, b), (i, j)})         =   "baidu.com?a=b&i=j";
      +     * getUrlWithParas("baidu.com", {(a, b), (i, j), (c, d)}) =   "baidu.com?a=b&i=j&c=d";
      +     * 
      + * + * @param url url + * @param parasMap paras map, key is para name, value is para value + * @return if url is null, process it as empty string + */ + public static String getUrlWithParas(String url, Map parasMap) { + StringBuilder urlWithParas = new StringBuilder(StringUtils.isEmpty(url) ? "" : url); + String paras = joinParas(parasMap); + if (!StringUtils.isEmpty(paras)) { + urlWithParas.append(URL_AND_PARA_SEPARATOR).append(paras); + } + return urlWithParas.toString(); + } + + /** + * join url and encoded paras + * + * @param url + * @param parasMap + * @return + * @see #getUrlWithParas(String, Map) + * @see StringUtils#utf8Encode(String) + */ + public static String getUrlWithValueEncodeParas(String url, Map parasMap) { + StringBuilder urlWithParas = new StringBuilder(StringUtils.isEmpty(url) ? "" : url); + String paras = joinParasWithEncodedValue(parasMap); + if (!StringUtils.isEmpty(paras)) { + urlWithParas.append(URL_AND_PARA_SEPARATOR).append(paras); + } + return urlWithParas.toString(); + } + + /** + * join paras + * + * @param parasMap paras map, key is para name, value is para value + * @return join key and value with {@link #EQUAL_SIGN}, join keys with + * {@link #PARAMETERS_SEPARATOR} + */ + public static String joinParas(Map parasMap) { + if (parasMap == null || parasMap.size() == 0) { + return null; + } + + StringBuilder paras = new StringBuilder(); + Iterator> ite = parasMap.entrySet().iterator(); + while (ite.hasNext()) { + Map.Entry entry = (Map.Entry) ite.next(); + paras.append(entry.getKey()).append(EQUAL_SIGN).append(entry.getValue()); + if (ite.hasNext()) { + paras.append(PARAMETERS_SEPARATOR); + } + } + return paras.toString(); + } + + /** + * join paras with encoded value + * + * @param parasMap + * @return + * @see #joinParas(Map) + * @see StringUtils#utf8Encode(String) + */ + public static String joinParasWithEncodedValue(Map parasMap) { + StringBuilder paras = new StringBuilder(""); + if (parasMap != null && parasMap.size() > 0) { + Iterator> ite = parasMap.entrySet().iterator(); + try { + while (ite.hasNext()) { + Map.Entry entry = (Map.Entry) ite.next(); + paras.append(entry.getKey()).append(EQUAL_SIGN).append(StringUtils.utf8Encode(entry.getValue())); + if (ite.hasNext()) { + paras.append(PARAMETERS_SEPARATOR); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return paras.toString(); + } + + /** + * append a key and value pair to url + * + * @param url + * @param paraKey + * @param paraValue + * @return + */ + public static String appendParaToUrl(String url, String paraKey, String paraValue) { + if (StringUtils.isEmpty(url)) { + return url; + } + + StringBuilder sb = new StringBuilder(url); + if (!url.contains(URL_AND_PARA_SEPARATOR)) { + sb.append(URL_AND_PARA_SEPARATOR); + } else { + sb.append(PARAMETERS_SEPARATOR); + } + return sb.append(paraKey).append(EQUAL_SIGN).append(paraValue).toString(); + } + + private static final SimpleDateFormat GMT_FORMAT = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss z", + Locale.ENGLISH); + + /** + * parse gmt time to long + * + * @param gmtTime likes Thu, 11 Apr 2013 10:20:30 GMT + * @return -1 represents exception otherwise time in milliseconds + */ + public static long parseGmtTime(String gmtTime) { + try { + return GMT_FORMAT.parse(gmtTime).getTime(); + } catch (Exception e) { + e.printStackTrace(); + } + return -1; + } + + /** + * set HttpRequest to HttpURLConnection + * + * @param request source request + * @param urlConnection destin url connection + */ + private static void setURLConnection(HttpRequest request, HttpURLConnection urlConnection) { + if (request == null || urlConnection == null) { + return; + } + + setURLConnection(request.getRequestProperties(), urlConnection); + if (request.getConnectTimeout() >= 0) { + urlConnection.setConnectTimeout(request.getConnectTimeout()); + } + if (request.getReadTimeout() >= 0) { + urlConnection.setReadTimeout(request.getReadTimeout()); + } + } + + /** + * set HttpURLConnection property + * + * @param requestProperties + * @param urlConnection + */ + public static void setURLConnection(Map requestProperties, HttpURLConnection urlConnection) { + if (urlConnection == null) { + return; + } + + boolean hasUserAgent = false; + if (!MapUtils.isEmpty(requestProperties)) { + for (Map.Entry entry : requestProperties.entrySet()) { + if (!StringUtils.isEmpty(entry.getKey())) { + urlConnection.setRequestProperty(entry.getKey(), entry.getValue()); + if ("User-Agent".equalsIgnoreCase(entry.getKey())) { + hasUserAgent = true; + } + } + } + } + if (!hasUserAgent) { + urlConnection.setRequestProperty("User-Agent", + "Mozilla/5.0 (Linux; Android 10; Android Demo) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Mobile Safari/537.36"); + } + } + + /** + * set HttpURLConnection to HttpResponse + * + * @param urlConnection source url connection + * @param response destin response + */ + private static void setHttpResponse(HttpURLConnection urlConnection, HttpResponse response) { + if (response == null || urlConnection == null) { + return; + } + try { + response.setResponseCode(urlConnection.getResponseCode()); + } catch (IOException e) { + response.setResponseCode(-1); + } + response.setResponseHeader(HttpConstants.EXPIRES, urlConnection.getHeaderField("Expires")); + response.setResponseHeader(HttpConstants.CACHE_CONTROL, urlConnection.getHeaderField("Cache-Control")); + } + + /** + * AsyncTask to get data by String url + * + * @author Trinea 2013-11-15 + */ + private static class HttpStringAsyncTask extends AsyncTask { + + private HttpListener listener; + + public HttpStringAsyncTask(HttpListener 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); + } + } + } + + /** + * AsyncTask to get data by HttpRequest + * + * @author Trinea 2013-11-15 + */ + private static class HttpRequestAsyncTask extends AsyncTask { + + private HttpListener listener; + + public HttpRequestAsyncTask(HttpListener 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); + } + } + } + + /** + * HttpListener, can do something before or after HttpGet + * + * @author Trinea 2013-11-15 + */ + public static abstract class HttpListener { + + /** + * 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 + */ + protected void onPostGet(HttpResponse httpResponse) { + } + } +} diff --git a/src/main/java/cn/trinea/android/common/util/IOUtils.java b/src/main/java/cn/trinea/android/common/util/IOUtils.java new file mode 100644 index 0000000..54ba9c0 --- /dev/null +++ b/src/main/java/cn/trinea/android/common/util/IOUtils.java @@ -0,0 +1,47 @@ +package cn.trinea.android.common.util; + +import java.io.Closeable; +import java.io.IOException; + +/** + * IO utils + * + * @author Vladislav Bauer + */ + +public class IOUtils { + + private IOUtils() { + throw new AssertionError(); + } + + + /** + * Close closable object and wrap {@link IOException} with {@link RuntimeException} + * @param closeable closeable object + */ + public static void close(Closeable closeable) { + if (closeable != null) { + try { + closeable.close(); + } catch (IOException e) { + throw new RuntimeException("IOException occurred. ", e); + } + } + } + + /** + * Close closable and hide possible {@link IOException} + * @param closeable closeable object + */ + public static void closeQuietly(Closeable closeable) { + if (closeable != null) { + try { + closeable.close(); + } catch (IOException e) { + // Ignored + } + } + } + +} diff --git a/src/main/java/cn/trinea/android/common/util/ImageCacheManager.java b/src/main/java/cn/trinea/android/common/util/ImageCacheManager.java new file mode 100644 index 0000000..f05e0ce --- /dev/null +++ b/src/main/java/cn/trinea/android/common/util/ImageCacheManager.java @@ -0,0 +1,191 @@ +package cn.trinea.android.common.util; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.util.Log; +import android.view.View; +import android.view.animation.AlphaAnimation; +import android.widget.ImageView; +import cn.trinea.android.common.entity.CacheObject; +import cn.trinea.android.common.entity.FailedReason; +import cn.trinea.android.common.service.impl.FileNameRuleImageUrl; +import cn.trinea.android.common.service.impl.ImageCache; +import cn.trinea.android.common.service.impl.ImageMemoryCache.OnImageCallbackListener; +import cn.trinea.android.common.service.impl.ImageSDCardCache; +import cn.trinea.android.common.service.impl.ImageSDCardCache.OnImageSDCallbackListener; +import cn.trinea.android.common.service.impl.PreloadDataCache.OnGetDataListener; +import cn.trinea.android.common.service.impl.RemoveTypeLastUsedTimeFirst; + +/** + * ImageCacheManager + * + * @author maxiaohui hackooo@sina.cn 2014-2-14 + */ +public class ImageCacheManager { + + public static final String TAG = "ImageCacheManager"; + private static ImageCache imageCache = null; + private static ImageSDCardCache imageSDCardCache = null; + + private ImageCacheManager() { + throw new AssertionError(); + } + + /** + * get the singleton instance of {@link ImageCache} + * + * @return + */ + public static ImageCache getImageCache() { + if (imageCache == null) { + synchronized (CacheManager.class) { + if (imageCache == null) { + imageCache = new ImageCache(128, 512); + setImageCache(); + } + } + } + return imageCache; + } + + /** + * get the singleton instance of {@link ImageSDCardCache} + * + * @return + */ + public static ImageSDCardCache getImageSDCardCache() { + if (imageSDCardCache == null) { + synchronized (CacheManager.class) { + if (imageSDCardCache == null) { + imageSDCardCache = new ImageSDCardCache(); + setImageSDCardCache(); + } + } + } + return imageSDCardCache; + } + + /** + * set ImageCache properties + */ + private static void setImageCache() { + if (imageCache == null) { + return; + } + + OnImageCallbackListener imageCallBack = new OnImageCallbackListener() { + + @Override + public void onGetSuccess(String imageUrl, Bitmap loadedImage, View view, boolean isInCache) { + if (view != null && loadedImage != null) { + if (view instanceof ImageView) { + ImageView imageView = (ImageView)view; + imageView.setImageBitmap(loadedImage); + // first time show with animation + if (!isInCache) { + imageView.startAnimation(getInAlphaAnimation(2000)); + } + } else { + Log.e(TAG, + "View is not instance of ImageView, you need to setOnImageCallbackListener() by your self"); + } + } + } + + @Override + public void onPreGet(String imageUrl, View view) {} + + @Override + public void onGetFailed(String imageUrl, Bitmap loadedImage, View view, FailedReason failedReason) {} + + @Override + public void onGetNotInCache(String imageUrl, View view) {} + }; + imageCache.setOnImageCallbackListener(imageCallBack); + imageCache.setCacheFullRemoveType(new RemoveTypeLastUsedTimeFirst()); + + imageCache.setHttpReadTimeOut(10000); + imageCache.setValidTime(-1); + } + + /** + * set ImageSDCardCache properties + */ + private static void setImageSDCardCache() { + if (imageSDCardCache == null) { + return; + } + + OnImageSDCallbackListener imageCallBack = new OnImageSDCallbackListener() { + + private static final long serialVersionUID = 1L; + + @Override + public void onGetSuccess(String imageUrl, String imagePath, View view, boolean isInCache) { + if (view != null && view instanceof ImageView) { + ImageView imageView = (ImageView)view; + + // if oom please use BitmapFactory.decodeFile(imagePath, option) + Bitmap bm = BitmapFactory.decodeFile(imagePath); + if (bm != null) { + imageView.setImageBitmap(bm); + + // first time show with animation + if (!isInCache) { + imageView.startAnimation(getInAlphaAnimation(2000)); + } + } + } else { + Log.e(TAG, + "View is not instance of ImageView, you need to setOnImageSDCallbackListener() by your self"); + } + } + + @Override + public void onPreGet(String imageUrl, View view) {} + + @Override + public void onGetNotInCache(String imageUrl, View view) {} + + @Override + public void onGetFailed(String imageUrl, String imagePath, View view, FailedReason failedReason) {} + }; + imageSDCardCache.setOnImageSDCallbackListener(imageCallBack); + imageSDCardCache.setCacheFullRemoveType(new RemoveTypeLastUsedTimeFirst()); + imageSDCardCache.setFileNameRule(new FileNameRuleImageUrl()); + + imageSDCardCache.setHttpReadTimeOut(10000); + imageSDCardCache.setValidTime(-1); + } + + public static AlphaAnimation getInAlphaAnimation(long durationMillis) { + AlphaAnimation inAlphaAnimation = new AlphaAnimation(0, 1); + inAlphaAnimation.setDuration(durationMillis); + return inAlphaAnimation; + } + + /** + * get image from sdcard listener + * + * @return + */ + public static OnGetDataListener getImageFromSdcardListener() { + return new OnGetDataListener() { + + private static final long serialVersionUID = 1L; + + @Override + public CacheObject onGetData(String key) { + if (FileUtils.isFileExist(key)) { + // if oom please use BitmapFactory.decodeFile(imagePath, option),like this + // BitmapFactory.Options option = new BitmapFactory.Options(); + // option.inSampleSize = 2; + // b = BitmapFactory.decodeFile(key, option); + return new CacheObject(BitmapFactory.decodeFile(key)); + } else + return null; + + } + }; + } +} diff --git a/src/cn/trinea/android/common/util/ImageUtils.java b/src/main/java/cn/trinea/android/common/util/ImageUtils.java similarity index 67% rename from src/cn/trinea/android/common/util/ImageUtils.java rename to src/main/java/cn/trinea/android/common/util/ImageUtils.java index 1631065..a725882 100644 --- a/src/cn/trinea/android/common/util/ImageUtils.java +++ b/src/main/java/cn/trinea/android/common/util/ImageUtils.java @@ -6,6 +6,7 @@ import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; +import java.util.Map; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -36,10 +37,14 @@ *
    • {@link #scaleImage(Bitmap, float, float)}
    • *
    * - * @author Trinea 2012-6-27 + * @author Trinea 2012-6-27 */ public class ImageUtils { + private ImageUtils() { + throw new AssertionError(); + } + /** * convert Bitmap to byte array * @@ -110,25 +115,40 @@ public static Drawable byteToDrawable(byte[] b) { * get input stream from network by imageurl, you need to close inputStream yourself * * @param imageUrl - * @param readTimeOut read time out, if less than 0, not set + * @param readTimeOutMillis + * @return + * @see ImageUtils#getInputStreamFromUrl(String, int, boolean) + */ + public static InputStream getInputStreamFromUrl(String imageUrl, int readTimeOutMillis) { + return getInputStreamFromUrl(imageUrl, readTimeOutMillis, null); + } + + /** + * get input stream from network by imageurl, you need to close inputStream yourself + * + * @param imageUrl + * @param readTimeOutMillis read time out, if less than 0, not set, in mills + * @param requestProperties http request properties * @return * @throws MalformedURLException * @throws IOException */ - public static InputStream getInputStreamFromUrl(String imageUrl, int readTimeOut) { + public static InputStream getInputStreamFromUrl(String imageUrl, int readTimeOutMillis, + Map requestProperties) { InputStream stream = null; try { URL url = new URL(imageUrl); HttpURLConnection con = (HttpURLConnection)url.openConnection(); - if (readTimeOut > 0) { - con.setReadTimeout(readTimeOut); + HttpUtils.setURLConnection(requestProperties, con); + if (readTimeOutMillis > 0) { + con.setReadTimeout(readTimeOutMillis); } stream = con.getInputStream(); } catch (MalformedURLException e) { - closeInputStream(stream); + IOUtils.close(stream); throw new RuntimeException("MalformedURLException occurred. ", e); } catch (IOException e) { - closeInputStream(stream); + IOUtils.close(stream); throw new RuntimeException("IOException occurred. ", e); } return stream; @@ -138,13 +158,27 @@ public static InputStream getInputStreamFromUrl(String imageUrl, int readTimeOut * get drawable by imageUrl * * @param imageUrl - * @param readTimeOut read time out, if less than 0, not set + * @param readTimeOutMillis + * @return + * @see ImageUtils#getDrawableFromUrl(String, int, boolean) + */ + public static Drawable getDrawableFromUrl(String imageUrl, int readTimeOutMillis) { + return getDrawableFromUrl(imageUrl, readTimeOutMillis, null); + } + + /** + * get drawable by imageUrl + * + * @param imageUrl + * @param readTimeOutMillis read time out, if less than 0, not set, in mills + * @param requestProperties http request properties * @return */ - public static Drawable getDrawableFromUrl(String imageUrl, int readTimeOut) { - InputStream stream = getInputStreamFromUrl(imageUrl, readTimeOut); + public static Drawable getDrawableFromUrl(String imageUrl, int readTimeOutMillis, + Map requestProperties) { + InputStream stream = getInputStreamFromUrl(imageUrl, readTimeOutMillis, requestProperties); Drawable d = Drawable.createFromStream(stream, "src"); - closeInputStream(stream); + IOUtils.close(stream); return d; } @@ -152,12 +186,25 @@ public static Drawable getDrawableFromUrl(String imageUrl, int readTimeOut) { * get Bitmap by imageUrl * * @param imageUrl + * @param readTimeOut * @return + * @see ImageUtils#getBitmapFromUrl(String, int, boolean) */ public static Bitmap getBitmapFromUrl(String imageUrl, int readTimeOut) { - InputStream stream = getInputStreamFromUrl(imageUrl, readTimeOut); + return getBitmapFromUrl(imageUrl, readTimeOut, null); + } + + /** + * get Bitmap by imageUrl + * + * @param imageUrl + * @param requestProperties http request properties + * @return + */ + public static Bitmap getBitmapFromUrl(String imageUrl, int readTimeOut, Map requestProperties) { + InputStream stream = getInputStreamFromUrl(imageUrl, readTimeOut, requestProperties); Bitmap b = BitmapFactory.decodeStream(stream); - closeInputStream(stream); + IOUtils.close(stream); return b; } @@ -170,7 +217,7 @@ public static Bitmap getBitmapFromUrl(String imageUrl, int readTimeOut) { * @return */ public static Bitmap scaleImageTo(Bitmap org, int newWidth, int newHeight) { - return scaleImage(org, (float)newWidth / org.getWidth(), (float)newHeight / org.getHeight()); + return scaleImage(org, (float) newWidth / org.getWidth(), (float) newHeight / org.getHeight()); } /** @@ -191,18 +238,4 @@ public static Bitmap scaleImage(Bitmap org, float scaleWidth, float scaleHeight) return Bitmap.createBitmap(org, 0, 0, org.getWidth(), org.getHeight(), matrix, true); } - /** - * close inputStream - * - * @param s - */ - private static void closeInputStream(InputStream s) { - if (s != null) { - try { - s.close(); - } catch (IOException e) { - throw new RuntimeException("IOException occurred. ", e); - } - } - } } diff --git a/src/main/java/cn/trinea/android/common/util/JSONUtils.java b/src/main/java/cn/trinea/android/common/util/JSONUtils.java new file mode 100644 index 0000000..5a476a2 --- /dev/null +++ b/src/main/java/cn/trinea/android/common/util/JSONUtils.java @@ -0,0 +1,821 @@ +package cn.trinea.android.common.util; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +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 { + + public static boolean isPrintException = true; + + private JSONUtils() { + throw new AssertionError(); + } + + /** + * 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) { + if (isPrintException) { + 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) { + if (isPrintException) { + e.printStackTrace(); + } + return defaultValue; + } + } + + /** + * @param jsonObject + * @param key + * @param defaultValue + * @return + * @see 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 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) { + if (isPrintException) { + 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) { + if (isPrintException) { + e.printStackTrace(); + } + return defaultValue; + } + } + + /** + * @param jsonObject + * @param key + * @param defaultValue + * @return + * @see 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 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) { + if (isPrintException) { + 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) { + if (isPrintException) { + e.printStackTrace(); + } + return defaultValue; + } + } + + /** + * @param jsonObject + * @param key + * @param defaultValue + * @return + * @see 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 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) { + if (isPrintException) { + 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) { + if (isPrintException) { + e.printStackTrace(); + } + return defaultValue; + } + } + + /** + * get String from jsonObject + * + * @param jsonObject + * @param defaultValue + * @param keyArray + * @return
      + *
    • if jsonObject is null, return defaultValue
    • + *
    • if keyArray is null or empty, return defaultValue
    • + *
    • get {@link #getJSONObject(JSONObject, String, JSONObject)} by recursion, return it. if anyone is + * null, return directly
    • + *
    + */ + public static String getStringCascade(JSONObject jsonObject, String defaultValue, String... keyArray) { + if (jsonObject == null || ArrayUtils.isEmpty(keyArray)) { + return defaultValue; + } + + String data = jsonObject.toString(); + for (String key : keyArray) { + data = getStringCascade(data, key, defaultValue); + if (data == null) { + return defaultValue; + } + } + return data; + } + + /** + * get String from jsonData + * + * @param jsonData + * @param defaultValue + * @param keyArray + * @return
      + *
    • if jsonData is null, return defaultValue
    • + *
    • if keyArray is null or empty, return defaultValue
    • + *
    • get {@link #getJSONObject(JSONObject, String, JSONObject)} by recursion, return it. if anyone is + * null, return directly
    • + *
    + */ + public static String getStringCascade(String jsonData, String defaultValue, String... keyArray) { + if (StringUtils.isEmpty(jsonData)) { + return defaultValue; + } + + String data = jsonData; + for (String key : keyArray) { + data = getString(data, key, defaultValue); + if (data == null) { + return defaultValue; + } + } + return data; + } + + /** + * 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) { + if (isPrintException) { + 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) { + if (isPrintException) { + e.printStackTrace(); + } + return defaultValue; + } + } + + /** + * get String list 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 List getStringList(JSONObject jsonObject, String key, List defaultValue) { + if (jsonObject == null || StringUtils.isEmpty(key)) { + return defaultValue; + } + + try { + JSONArray statusArray = jsonObject.getJSONArray(key); + if (statusArray != null) { + List list = new ArrayList(); + for (int i = 0; i < statusArray.length(); i++) { + list.add(statusArray.getString(i)); + } + return list; + } + } catch (JSONException e) { + if (isPrintException) { + e.printStackTrace(); + } + return defaultValue; + } + return defaultValue; + } + + /** + * get String list 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#getStringList(JSONObject, String, List)}
    • + *
    + */ + public static List getStringList(String jsonData, String key, List defaultValue) { + if (StringUtils.isEmpty(jsonData)) { + return defaultValue; + } + + try { + JSONObject jsonObject = new JSONObject(jsonData); + return getStringList(jsonObject, key, defaultValue); + } catch (JSONException e) { + if (isPrintException) { + 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) { + if (isPrintException) { + e.printStackTrace(); + } + return defaultValue; + } + } + + /** + * get JSONObject from jsonData + * + * @param jsonData + * @param key + * @param defaultValue + * @return
      + *
    • if jsonData 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) { + if (isPrintException) { + e.printStackTrace(); + } + return defaultValue; + } + } + + /** + * get JSONObject from jsonObject + * + * @param jsonObject + * @param defaultValue + * @param keyArray + * @return
      + *
    • if jsonObject is null, return defaultValue
    • + *
    • if keyArray is null or empty, return defaultValue
    • + *
    • get {@link #getJSONObject(JSONObject, String, JSONObject)} by recursion, return it. if anyone is + * null, return directly
    • + *
    + */ + public static JSONObject getJSONObjectCascade(JSONObject jsonObject, JSONObject defaultValue, String... keyArray) { + if (jsonObject == null || ArrayUtils.isEmpty(keyArray)) { + return defaultValue; + } + + JSONObject js = jsonObject; + for (String key : keyArray) { + js = getJSONObject(js, key, defaultValue); + if (js == null) { + return defaultValue; + } + } + return js; + } + + /** + * get JSONObject from jsonData + * + * @param jsonData + * @param defaultValue + * @param keyArray + * @return
      + *
    • if jsonData is null, return defaultValue
    • + *
    • if keyArray is null or empty, return defaultValue
    • + *
    • get {@link #getJSONObject(JSONObject, String, JSONObject)} by recursion, return it. if anyone is + * null, return directly
    • + *
    + */ + public static JSONObject getJSONObjectCascade(String jsonData, JSONObject defaultValue, String... keyArray) { + if (StringUtils.isEmpty(jsonData)) { + return defaultValue; + } + + try { + JSONObject jsonObject = new JSONObject(jsonData); + return getJSONObjectCascade(jsonObject, defaultValue, keyArray); + } catch (JSONException e) { + if (isPrintException) { + 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) { + if (isPrintException) { + 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) { + if (isPrintException) { + 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) { + if (isPrintException) { + 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) { + if (isPrintException) { + 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; + } + if (jsonData.length() == 0) { + return new HashMap(); + } + + try { + JSONObject jsonObject = new JSONObject(jsonData); + return getMap(jsonObject, key); + } catch (JSONException e) { + if (isPrintException) { + 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) { + if (isPrintException) { + e.printStackTrace(); + } + return null; + } + } +} diff --git a/src/cn/trinea/android/common/util/ListUtils.java b/src/main/java/cn/trinea/android/common/util/ListUtils.java similarity index 82% rename from src/cn/trinea/android/common/util/ListUtils.java rename to src/main/java/cn/trinea/android/common/util/ListUtils.java index 6ec581b..3521294 100644 --- a/src/cn/trinea/android/common/util/ListUtils.java +++ b/src/main/java/cn/trinea/android/common/util/ListUtils.java @@ -3,16 +3,39 @@ import java.util.ArrayList; import java.util.List; +import android.text.TextUtils; + /** * List Utils * - * @author Trinea 2011-7-22 + * @author Trinea 2011-7-22 */ public class ListUtils { /** default join separator **/ public static final String DEFAULT_JOIN_SEPARATOR = ","; + private ListUtils() { + throw new AssertionError(); + } + + /** + * get size of list + * + *
    +     * getSize(null)   =   0;
    +     * getSize({})     =   0;
    +     * getSize({1})    =   1;
    +     * 
    + * + * @param + * @param sourceList + * @return if list is null or empty, return 0, else return {@link List#size()}. + */ + public static int getSize(List sourceList) { + return sourceList == null ? 0 : sourceList.size(); + } + /** * is null or its size is 0 * @@ -48,20 +71,20 @@ public static boolean isEmpty(List sourceList) { public static boolean isEquals(ArrayList actual, ArrayList expected) { if (actual == null) { return expected == null; - } else if (expected == null) { + } + if (expected == null) { return false; - } else { - if (actual.size() != expected.size()) { - return false; - } + } + if (actual.size() != expected.size()) { + return false; + } - for (int i = 0; i < actual.size(); i++) { - if (!ObjectUtils.isEquals(actual.get(i), expected.get(i))) { - return false; - } + for (int i = 0; i < actual.size(); i++) { + if (!ObjectUtils.isEquals(actual.get(i), expected.get(i))) { + return false; } - return true; } + return true; } /** @@ -95,7 +118,7 @@ public static String join(List list) { * @return join list to string. if list is empty, return "" */ public static String join(List list, char separator) { - return join(list, new String(new char[] { separator })); + return join(list, new String(new char[] {separator})); } /** @@ -115,22 +138,7 @@ public static String join(List list, char separator) { * @return join list to string with separator. if list is empty, return "" */ public static String join(List list, String separator) { - if (isEmpty(list)) { - return ""; - } - if (separator == null) { - separator = DEFAULT_JOIN_SEPARATOR; - } - - StringBuilder joinStr = new StringBuilder(); - for (int i = 0; i < list.size(); i++) { - joinStr.append(list.get(i)); - if (i != list.size() - 1) { - joinStr.append(separator); - } - } - - return joinStr.toString(); + return list == null ? "" : TextUtils.join(separator, list); } /** @@ -159,11 +167,11 @@ public static int addDistinctList(List sourceList, List entryList) { } int sourceCount = sourceList.size(); - for (V entry : entryList) + for (V entry : entryList) { if (!sourceList.contains(entry)) { sourceList.add(entry); } - + } return sourceList.size() - sourceCount; } @@ -181,7 +189,7 @@ public static int distinctList(List sourceList) { int sourceCount = sourceList.size(); int sourceListSize = sourceList.size(); - for (int i = 0; i < sourceListSize; i++) + for (int i = 0; i < sourceListSize; i++) { for (int j = (i + 1); j < sourceListSize; j++) { if (sourceList.get(i).equals(sourceList.get(j))) { sourceList.remove(j); @@ -189,7 +197,7 @@ public static int distinctList(List sourceList) { j--; } } - + } return sourceCount - sourceList.size(); } @@ -199,10 +207,10 @@ public static int distinctList(List sourceList) { * @param sourceList * @param value * @return
      - *
    • if sourceList is null, return false
    • - *
    • if value is null, return false
    • - *
    • return {@link List#add(Object)}
    • - *
    + *
  • if sourceList is null, return false
  • + *
  • if value is null, return false
  • + *
  • return {@link List#add(Object)}
  • + * */ public static boolean addListNotNullValue(List sourceList, V value) { return (sourceList != null && value != null) ? sourceList.add(value) : false; diff --git a/src/cn/trinea/android/common/util/MapUtils.java b/src/main/java/cn/trinea/android/common/util/MapUtils.java similarity index 64% rename from src/cn/trinea/android/common/util/MapUtils.java rename to src/main/java/cn/trinea/android/common/util/MapUtils.java index 84a6ccb..55f937b 100644 --- a/src/cn/trinea/android/common/util/MapUtils.java +++ b/src/main/java/cn/trinea/android/common/util/MapUtils.java @@ -1,13 +1,14 @@ package cn.trinea.android.common.util; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; /** * Map Utils * - * @author Trinea 2011-7-22 + * @author Trinea 2011-7-22 */ public class MapUtils { @@ -15,7 +16,10 @@ public class MapUtils { public static final String DEFAULT_KEY_AND_VALUE_SEPARATOR = ":"; /** default separator between key-value pairs **/ public static final String DEFAULT_KEY_AND_VALUE_PAIR_SEPARATOR = ","; - + + private MapUtils() { + throw new AssertionError(); + } /** * is null or its size is 0 * @@ -25,7 +29,7 @@ public class MapUtils { * isEmpty({1, 2}) = false; * * - * @param str + * @param sourceMap * @return if map is null or its size is 0, return true, else return false. */ public static boolean isEmpty(Map sourceMap) { @@ -39,10 +43,10 @@ public static boolean isEmpty(Map sourceMap) { * @param key * @param value * @return
      - *
    • if map is null, return false
    • - *
    • if key is null or empty, return false
    • - *
    • return {@link Map#put(Object, Object)}
    • - *
    + *
  • if map is null, return false
  • + *
  • if key is null or empty, return false
  • + *
  • return {@link Map#put(Object, Object)}
  • + * */ public static boolean putMapNotEmptyKey(Map map, String key, String value) { if (map == null || StringUtils.isEmpty(key)) { @@ -60,11 +64,11 @@ public static boolean putMapNotEmptyKey(Map map, String key, Str * @param key * @param value * @return
      - *
    • if map is null, return false
    • - *
    • if key is null or empty, return false
    • - *
    • if value is null or empty, return false
    • - *
    • return {@link Map#put(Object, Object)}
    • - *
    + *
  • if map is null, return false
  • + *
  • if key is null or empty, return false
  • + *
  • if value is null or empty, return false
  • + *
  • return {@link Map#put(Object, Object)}
  • + * */ public static boolean putMapNotEmptyKeyAndValue(Map map, String key, String value) { if (map == null || StringUtils.isEmpty(key) || StringUtils.isEmpty(value)) { @@ -83,14 +87,14 @@ public static boolean putMapNotEmptyKeyAndValue(Map map, String * @param value * @param defaultValue * @return
      - *
    • if map is null, return false
    • - *
    • if key is null or empty, return false
    • - *
    • if value is null or empty, put defaultValue, return true
    • - *
    • if value is neither null nor empty,put value, return true
    • - *
    + *
  • if map is null, return false
  • + *
  • if key is null or empty, return false
  • + *
  • if value is null or empty, put defaultValue, return true
  • + *
  • if value is neither null nor empty,put value, return true
  • + * */ public static boolean putMapNotEmptyKeyAndValue(Map map, String key, String value, - String defaultValue) { + String defaultValue) { if (map == null || StringUtils.isEmpty(key)) { return false; } @@ -106,10 +110,10 @@ public static boolean putMapNotEmptyKeyAndValue(Map map, String * @param key * @param value * @return
      - *
    • if map is null, return false
    • - *
    • if key is null, return false
    • - *
    • return {@link Map#put(Object, Object)}
    • - *
    + *
  • if map is null, return false
  • + *
  • if key is null, return false
  • + *
  • return {@link Map#put(Object, Object)}
  • + * */ public static boolean putMapNotNullKey(Map map, K key, V value) { if (map == null || key == null) { @@ -127,11 +131,11 @@ public static boolean putMapNotNullKey(Map map, K key, V value) { * @param key * @param value * @return
      - *
    • if map is null, return false
    • - *
    • if key is null, return false
    • - *
    • if value is null, return false
    • - *
    • return {@link Map#put(Object, Object)}
    • - *
    + *
  • if map is null, return false
  • + *
  • if key is null, return false
  • + *
  • if value is null, return false
  • + *
  • return {@link Map#put(Object, Object)}
  • + * */ public static boolean putMapNotNullKeyAndValue(Map map, K key, V value) { if (map == null || key == null || value == null) { @@ -145,7 +149,7 @@ public static boolean putMapNotNullKeyAndValue(Map map, K key, V va /** * get key by value, match the first entry front to back *
      - * Attentions: + * Attentions: *
    • for HashMap, the order of entry not same to put order, so you may need to use TreeMap
    • *
    * @@ -153,10 +157,10 @@ public static boolean putMapNotNullKeyAndValue(Map map, K key, V va * @param map * @param value * @return
      - *
    • if map is null, return null
    • - *
    • if value exist, return key
    • - *
    • return null
    • - *
    + *
  • if map is null, return null
  • + *
  • if value exist, return key
  • + *
  • return null
  • + * */ public static K getKeyByValue(Map map, V value) { if (isEmpty(map)) { @@ -168,7 +172,6 @@ public static K getKeyByValue(Map map, V value) { return entry.getKey(); } } - return null; } @@ -195,7 +198,7 @@ public static K getKeyByValue(Map map, V value) { * @return */ public static Map parseKeyAndValueToMap(String source, String keyAndValueSeparator, - String keyAndValuePairSeparator, boolean ignoreSpace) { + String keyAndValuePairSeparator, boolean ignoreSpace) { if (StringUtils.isEmpty(source)) { return null; } @@ -208,19 +211,21 @@ public static Map parseKeyAndValueToMap(String source, String ke } Map keyAndValueMap = new HashMap(); String[] keyAndValueArray = source.split(keyAndValuePairSeparator); - if (keyAndValueArray != null) { - int seperator; - for (String valueEntity : keyAndValueArray) { - if (!StringUtils.isEmpty(valueEntity)) { - seperator = valueEntity.indexOf(keyAndValueSeparator); - if (seperator != -1) { - if (ignoreSpace) { - MapUtils.putMapNotEmptyKey(keyAndValueMap, valueEntity.substring(0, seperator).trim(), - valueEntity.substring(seperator + 1).trim()); - } else { - MapUtils.putMapNotEmptyKey(keyAndValueMap, valueEntity.substring(0, seperator), - valueEntity.substring(seperator + 1)); - } + if (keyAndValueArray == null) { + return null; + } + + int seperator; + for (String valueEntity : keyAndValueArray) { + if (!StringUtils.isEmpty(valueEntity)) { + seperator = valueEntity.indexOf(keyAndValueSeparator); + if (seperator != -1) { + if (ignoreSpace) { + MapUtils.putMapNotEmptyKey(keyAndValueMap, valueEntity.substring(0, seperator).trim(), + valueEntity.substring(seperator + 1).trim()); + } else { + MapUtils.putMapNotEmptyKey(keyAndValueMap, valueEntity.substring(0, seperator), + valueEntity.substring(seperator + 1)); } } } @@ -235,12 +240,12 @@ public static Map parseKeyAndValueToMap(String source, String ke * @param ignoreSpace whether ignore space at the begging or end of key and value * @return * @see {@link MapUtils#parseKeyAndValueToMap(String, String, String, boolean)}, keyAndValueSeparator is - * {@link #DEFAULT_KEY_AND_VALUE_SEPARATOR}, keyAndValuePairSeparator is - * {@link #DEFAULT_KEY_AND_VALUE_PAIR_SEPARATOR} + * {@link #DEFAULT_KEY_AND_VALUE_SEPARATOR}, keyAndValuePairSeparator is + * {@link #DEFAULT_KEY_AND_VALUE_PAIR_SEPARATOR} */ public static Map parseKeyAndValueToMap(String source, boolean ignoreSpace) { return parseKeyAndValueToMap(source, DEFAULT_KEY_AND_VALUE_SEPARATOR, DEFAULT_KEY_AND_VALUE_PAIR_SEPARATOR, - ignoreSpace); + ignoreSpace); } /** @@ -249,11 +254,36 @@ public static Map parseKeyAndValueToMap(String source, boolean i * @param source key-value pairs * @return * @see {@link MapUtils#parseKeyAndValueToMap(String, String, String, boolean)}, keyAndValueSeparator is - * {@link #DEFAULT_KEY_AND_VALUE_SEPARATOR}, keyAndValuePairSeparator is - * {@link #DEFAULT_KEY_AND_VALUE_PAIR_SEPARATOR}, ignoreSpace is true + * {@link #DEFAULT_KEY_AND_VALUE_SEPARATOR}, keyAndValuePairSeparator is + * {@link #DEFAULT_KEY_AND_VALUE_PAIR_SEPARATOR}, ignoreSpace is true */ public static Map parseKeyAndValueToMap(String source) { return parseKeyAndValueToMap(source, DEFAULT_KEY_AND_VALUE_SEPARATOR, DEFAULT_KEY_AND_VALUE_PAIR_SEPARATOR, - true); + true); + } + + /** + * join map + * + * @param map + * @return + */ + public static String toJson(Map map) { + if (map == null || map.size() == 0) { + return null; + } + + StringBuilder paras = new StringBuilder(); + paras.append("{"); + Iterator> ite = map.entrySet().iterator(); + while (ite.hasNext()) { + Map.Entry entry = (Map.Entry)ite.next(); + paras.append("\"").append(entry.getKey()).append("\":\"").append(entry.getValue()).append("\""); + if (ite.hasNext()) { + paras.append(","); + } + } + paras.append("}"); + return paras.toString(); } } diff --git a/src/main/java/cn/trinea/android/common/util/NetWorkUtils.java b/src/main/java/cn/trinea/android/common/util/NetWorkUtils.java new file mode 100644 index 0000000..b966ff1 --- /dev/null +++ b/src/main/java/cn/trinea/android/common/util/NetWorkUtils.java @@ -0,0 +1,118 @@ +package cn.trinea.android.common.util; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.telephony.TelephonyManager; +import android.text.TextUtils; + +/** + * NetWork Utils + *
      + * Attentions + *
    • You should add android.permission.ACCESS_NETWORK_STATE in manifest, to get network status.
    • + *
    + * + * @author Trinea 2014-11-03 + */ +public class NetWorkUtils { + + public static final String NETWORK_TYPE_WIFI = "wifi"; + public static final String NETWORK_TYPE_3G = "eg"; + public static final String NETWORK_TYPE_2G = "2g"; + public static final String NETWORK_TYPE_WAP = "wap"; + public static final String NETWORK_TYPE_UNKNOWN = "unknown"; + public static final String NETWORK_TYPE_DISCONNECT = "disconnect"; + + /** + * Get network type + * + * @param context + * @return + */ + public static int getNetworkType(Context context) { + ConnectivityManager connectivityManager = (ConnectivityManager)context + .getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo networkInfo = connectivityManager == null ? null : connectivityManager.getActiveNetworkInfo(); + return networkInfo == null ? -1 : networkInfo.getType(); + } + + /** + * Get network type name + * + * @param context + * @return + */ + public static String getNetworkTypeName(Context context) { + ConnectivityManager manager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo networkInfo; + String type = NETWORK_TYPE_DISCONNECT; + if (manager == null || (networkInfo = manager.getActiveNetworkInfo()) == null) { + return type; + }; + + if (networkInfo.isConnected()) { + String typeName = networkInfo.getTypeName(); + if ("WIFI".equalsIgnoreCase(typeName)) { + type = NETWORK_TYPE_WIFI; + } else if ("MOBILE".equalsIgnoreCase(typeName)) { + String proxyHost = android.net.Proxy.getDefaultHost(); + type = TextUtils.isEmpty(proxyHost) ? (isFastMobileNetwork(context) ? NETWORK_TYPE_3G : NETWORK_TYPE_2G) + : NETWORK_TYPE_WAP; + } else { + type = NETWORK_TYPE_UNKNOWN; + } + } + return type; + } + + /** + * Whether is fast mobile network + * + * @param context + * @return + */ + private static boolean isFastMobileNetwork(Context context) { + TelephonyManager telephonyManager = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE); + if (telephonyManager == null) { + return false; + } + + switch (telephonyManager.getNetworkType()) { + case TelephonyManager.NETWORK_TYPE_1xRTT: + return false; + case TelephonyManager.NETWORK_TYPE_CDMA: + return false; + case TelephonyManager.NETWORK_TYPE_EDGE: + return false; + case TelephonyManager.NETWORK_TYPE_EVDO_0: + return true; + case TelephonyManager.NETWORK_TYPE_EVDO_A: + return true; + case TelephonyManager.NETWORK_TYPE_GPRS: + return false; + case TelephonyManager.NETWORK_TYPE_HSDPA: + return true; + case TelephonyManager.NETWORK_TYPE_HSPA: + return true; + case TelephonyManager.NETWORK_TYPE_HSUPA: + return true; + case TelephonyManager.NETWORK_TYPE_UMTS: + return true; + case TelephonyManager.NETWORK_TYPE_EHRPD: + return true; + case TelephonyManager.NETWORK_TYPE_EVDO_B: + return true; + case TelephonyManager.NETWORK_TYPE_HSPAP: + return true; + case TelephonyManager.NETWORK_TYPE_IDEN: + return false; + case TelephonyManager.NETWORK_TYPE_LTE: + return true; + case TelephonyManager.NETWORK_TYPE_UNKNOWN: + return false; + default: + return false; + } + } +} diff --git a/src/cn/trinea/android/common/util/ObjectUtils.java b/src/main/java/cn/trinea/android/common/util/ObjectUtils.java similarity index 73% rename from src/cn/trinea/android/common/util/ObjectUtils.java rename to src/main/java/cn/trinea/android/common/util/ObjectUtils.java index 20a5641..d3c77fe 100644 --- a/src/cn/trinea/android/common/util/ObjectUtils.java +++ b/src/main/java/cn/trinea/android/common/util/ObjectUtils.java @@ -3,22 +3,42 @@ /** * Object Utils * - * @author Trinea 2011-10-24 + * @author Trinea 2011-10-24 */ public class ObjectUtils { + private ObjectUtils() { + throw new AssertionError(); + } + /** * compare two object * * @param actual * @param expected * @return
      - *
    • if both are null, return true
    • - *
    • return actual.{@link Object#equals(Object)}
    • - *
    + *
  • if both are null, return true
  • + *
  • return actual.{@link Object#equals(Object)}
  • + * */ public static boolean isEquals(Object actual, Object expected) { - return actual == null ? expected == null : actual.equals(expected); + return actual == expected || (actual == null ? expected == null : actual.equals(expected)); + } + + /** + * null Object to empty string + * + *
    +     * nullStrToEmpty(null) = "";
    +     * nullStrToEmpty("") = "";
    +     * nullStrToEmpty("aa") = "aa";
    +     * 
    + * + * @param str + * @return + */ + public static String nullStrToEmpty(Object str) { + return (str == null ? "" : (str instanceof String ? (String)str : str.toString())); } /** @@ -97,7 +117,7 @@ public static int[] transformIntArray(Integer[] source) { * @param v2 * @return */ - @SuppressWarnings({ "unchecked", "rawtypes" }) + @SuppressWarnings({"unchecked", "rawtypes"}) public static int compare(V v1, V v2) { return v1 == null ? (v2 == null ? 0 : -1) : (v2 == null ? 1 : ((Comparable)v1).compareTo(v2)); } diff --git a/src/main/java/cn/trinea/android/common/util/PackageUtils.java b/src/main/java/cn/trinea/android/common/util/PackageUtils.java new file mode 100644 index 0000000..7297a71 --- /dev/null +++ b/src/main/java/cn/trinea/android/common/util/PackageUtils.java @@ -0,0 +1,799 @@ +package cn.trinea.android.common.util; + +import java.io.File; +import java.util.List; + +import android.app.ActivityManager; +import android.app.ActivityManager.RunningTaskInfo; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.net.Uri; +import android.os.Build; +import android.provider.Settings; +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)}
    • + *
    + *
      + * Uninstall package + *
    • {@link PackageUtils#uninstallNormal(Context, String)}
    • + *
    • {@link PackageUtils#uninstallSilent(Context, String)}
    • + *
    • {@link PackageUtils#uninstall(Context, String)}
    • + *
    + *
      + * Is system application + *
    • {@link PackageUtils#isSystemApplication(Context)}
    • + *
    • {@link PackageUtils#isSystemApplication(Context, String)}
    • + *
    • {@link PackageUtils#isSystemApplication(PackageManager, String)}
    • + *
    + *
      + * Others + *
    • {@link PackageUtils#getInstallLocation()} get system install location
    • + *
    • {@link PackageUtils#isTopActivity(Context, String)} whether the app whost package's name is packageName is on the + * top of the stack
    • + *
    • {@link PackageUtils#startInstalledAppDetails(Context, String)} start InstalledAppDetails Activity
    • + *
    + * + * @author Trinea 2013-5-15 + */ +public class PackageUtils { + + public static final String TAG = "PackageUtils"; + + private PackageUtils() { + throw new AssertionError(); + } + + /** + * App installation location settings values, same to {@link #PackageHelper} + */ + public static final int APP_INSTALL_AUTO = 0; + public static final int APP_INSTALL_INTERNAL = 1; + public static final int APP_INSTALL_EXTERNAL = 2; + + /** + * 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) || ShellUtils.checkRootPermission()) { + return installSilent(context, filePath); + } + return installNormal(context, filePath) ? INSTALL_SUCCEEDED : INSTALL_FAILED_INVALID_URI; + } + + /** + * 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.exists() || !file.isFile() || file.length() <= 0) { + return false; + } + + i.setDataAndType(Uri.parse("file://" + filePath), "application/vnd.android.package-archive"); + i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(i); + return true; + } + + /** + * install package silent by root + *
      + * Attentions: + *
    • Don't call this on the ui thread, it may costs some times.
    • + *
    • You should add android.permission.INSTALL_PACKAGES in manifest, so no need to request root + * permission, if you are system app.
    • + *
    • Default pm install params is "-r".
    • + *
    + * + * @param context + * @param filePath file path of package + * @return {@link PackageUtils#INSTALL_SUCCEEDED} means install success, other means failed. details see + * {@link PackageUtils}.INSTALL_FAILED_*. same to {@link PackageManager}.INSTALL_* + * @see #installSilent(Context, String, String) + */ + public static int installSilent(Context context, String filePath) { + return installSilent(context, filePath, " -r " + getInstallLocationParams()); + } + + /** + * install package silent by root + *
      + * Attentions: + *
    • Don't call this on the ui thread, it may 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 + * @param filePath file path of package + * @param pmParams pm install params + * @return {@link PackageUtils#INSTALL_SUCCEEDED} means install success, other means failed. details see + * {@link PackageUtils}.INSTALL_FAILED_*. same to {@link PackageManager}.INSTALL_* + */ + public static int installSilent(Context context, String filePath, String pmParams) { + 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("LD_LIBRARY_PATH=/vendor/lib*:/system/lib* pm install ") + .append(pmParams == null ? "" : pmParams).append(" ").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("installSilent successMsg:").append(commandResult.successMsg) + .append(", ErrorMsg:").append(commandResult.errorMsg).toString()); + if (commandResult.errorMsg == null) { + return INSTALL_FAILED_OTHER; + } + if (commandResult.errorMsg.contains("INSTALL_FAILED_ALREADY_EXISTS")) { + return INSTALL_FAILED_ALREADY_EXISTS; + } + if (commandResult.errorMsg.contains("INSTALL_FAILED_INVALID_APK")) { + return INSTALL_FAILED_INVALID_APK; + } + if (commandResult.errorMsg.contains("INSTALL_FAILED_INVALID_URI")) { + return INSTALL_FAILED_INVALID_URI; + } + if (commandResult.errorMsg.contains("INSTALL_FAILED_INSUFFICIENT_STORAGE")) { + return INSTALL_FAILED_INSUFFICIENT_STORAGE; + } + if (commandResult.errorMsg.contains("INSTALL_FAILED_DUPLICATE_PACKAGE")) { + return INSTALL_FAILED_DUPLICATE_PACKAGE; + } + if (commandResult.errorMsg.contains("INSTALL_FAILED_NO_SHARED_USER")) { + return INSTALL_FAILED_NO_SHARED_USER; + } + if (commandResult.errorMsg.contains("INSTALL_FAILED_UPDATE_INCOMPATIBLE")) { + return INSTALL_FAILED_UPDATE_INCOMPATIBLE; + } + if (commandResult.errorMsg.contains("INSTALL_FAILED_SHARED_USER_INCOMPATIBLE")) { + return INSTALL_FAILED_SHARED_USER_INCOMPATIBLE; + } + if (commandResult.errorMsg.contains("INSTALL_FAILED_MISSING_SHARED_LIBRARY")) { + return INSTALL_FAILED_MISSING_SHARED_LIBRARY; + } + if (commandResult.errorMsg.contains("INSTALL_FAILED_REPLACE_COULDNT_DELETE")) { + return INSTALL_FAILED_REPLACE_COULDNT_DELETE; + } + if (commandResult.errorMsg.contains("INSTALL_FAILED_DEXOPT")) { + return INSTALL_FAILED_DEXOPT; + } + if (commandResult.errorMsg.contains("INSTALL_FAILED_OLDER_SDK")) { + return INSTALL_FAILED_OLDER_SDK; + } + if (commandResult.errorMsg.contains("INSTALL_FAILED_CONFLICTING_PROVIDER")) { + return INSTALL_FAILED_CONFLICTING_PROVIDER; + } + if (commandResult.errorMsg.contains("INSTALL_FAILED_NEWER_SDK")) { + return INSTALL_FAILED_NEWER_SDK; + } + if (commandResult.errorMsg.contains("INSTALL_FAILED_TEST_ONLY")) { + return INSTALL_FAILED_TEST_ONLY; + } + if (commandResult.errorMsg.contains("INSTALL_FAILED_CPU_ABI_INCOMPATIBLE")) { + return INSTALL_FAILED_CPU_ABI_INCOMPATIBLE; + } + if (commandResult.errorMsg.contains("INSTALL_FAILED_MISSING_FEATURE")) { + return INSTALL_FAILED_MISSING_FEATURE; + } + if (commandResult.errorMsg.contains("INSTALL_FAILED_CONTAINER_ERROR")) { + return INSTALL_FAILED_CONTAINER_ERROR; + } + if (commandResult.errorMsg.contains("INSTALL_FAILED_INVALID_INSTALL_LOCATION")) { + return INSTALL_FAILED_INVALID_INSTALL_LOCATION; + } + if (commandResult.errorMsg.contains("INSTALL_FAILED_MEDIA_UNAVAILABLE")) { + return INSTALL_FAILED_MEDIA_UNAVAILABLE; + } + if (commandResult.errorMsg.contains("INSTALL_FAILED_VERIFICATION_TIMEOUT")) { + return INSTALL_FAILED_VERIFICATION_TIMEOUT; + } + if (commandResult.errorMsg.contains("INSTALL_FAILED_VERIFICATION_FAILURE")) { + return INSTALL_FAILED_VERIFICATION_FAILURE; + } + if (commandResult.errorMsg.contains("INSTALL_FAILED_PACKAGE_CHANGED")) { + return INSTALL_FAILED_PACKAGE_CHANGED; + } + if (commandResult.errorMsg.contains("INSTALL_FAILED_UID_CHANGED")) { + return INSTALL_FAILED_UID_CHANGED; + } + if (commandResult.errorMsg.contains("INSTALL_PARSE_FAILED_NOT_APK")) { + return INSTALL_PARSE_FAILED_NOT_APK; + } + if (commandResult.errorMsg.contains("INSTALL_PARSE_FAILED_BAD_MANIFEST")) { + return INSTALL_PARSE_FAILED_BAD_MANIFEST; + } + if (commandResult.errorMsg.contains("INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION")) { + return INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION; + } + if (commandResult.errorMsg.contains("INSTALL_PARSE_FAILED_NO_CERTIFICATES")) { + return INSTALL_PARSE_FAILED_NO_CERTIFICATES; + } + if (commandResult.errorMsg.contains("INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES")) { + return INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; + } + if (commandResult.errorMsg.contains("INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING")) { + return INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING; + } + if (commandResult.errorMsg.contains("INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME")) { + return INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME; + } + if (commandResult.errorMsg.contains("INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID")) { + return INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID; + } + if (commandResult.errorMsg.contains("INSTALL_PARSE_FAILED_MANIFEST_MALFORMED")) { + return INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; + } + if (commandResult.errorMsg.contains("INSTALL_PARSE_FAILED_MANIFEST_EMPTY")) { + return INSTALL_PARSE_FAILED_MANIFEST_EMPTY; + } + if (commandResult.errorMsg.contains("INSTALL_FAILED_INTERNAL_ERROR")) { + return INSTALL_FAILED_INTERNAL_ERROR; + } + return INSTALL_FAILED_OTHER; + } + + /** + * uninstall according conditions + *
      + *
    • if system application or rooted, see {@link #uninstallSilent(Context, String)}
    • + *
    • else see {@link #uninstallNormal(Context, String)}
    • + *
    + * + * @param context + * @param packageName package name of app + * @return whether package name is empty + * @return + */ + public static final int uninstall(Context context, String packageName) { + if (PackageUtils.isSystemApplication(context) || ShellUtils.checkRootPermission()) { + return uninstallSilent(context, packageName); + } + return uninstallNormal(context, packageName) ? DELETE_SUCCEEDED : DELETE_FAILED_INVALID_PACKAGE; + } + + /** + * uninstall package normal by system intent + * + * @param context + * @param packageName package name of app + * @return whether package name is empty + */ + public static boolean uninstallNormal(Context context, String packageName) { + if (packageName == null || packageName.length() == 0) { + return false; + } + + Intent i = new Intent(Intent.ACTION_DELETE, Uri.parse(new StringBuilder(32).append("package:") + .append(packageName).toString())); + i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(i); + return true; + } + + /** + * uninstall package and clear data of app silent by root + * + * @param context + * @param packageName package name of app + * @return + * @see #uninstallSilent(Context, String, boolean) + */ + public static int uninstallSilent(Context context, String packageName) { + return uninstallSilent(context, packageName, true); + } + + /** + * uninstall package silent by root + *
      + * Attentions: + *
    • Don't call this on the ui thread, it may costs some times.
    • + *
    • You should add android.permission.DELETE_PACKAGES in manifest, so no need to request root + * permission, if you are system app.
    • + *
    + * + * @param context file path of package + * @param packageName package name of app + * @param isKeepData whether keep the data and cache directories around after package removal + * @return
      + *
    • {@link #DELETE_SUCCEEDED} means uninstall success
    • + *
    • {@link #DELETE_FAILED_INTERNAL_ERROR} means internal error
    • + *
    • {@link #DELETE_FAILED_INVALID_PACKAGE} means package name error
    • + *
    • {@link #DELETE_FAILED_PERMISSION_DENIED} means permission denied
    • + */ + public static int uninstallSilent(Context context, String packageName, boolean isKeepData) { + if (packageName == null || packageName.length() == 0) { + return DELETE_FAILED_INVALID_PACKAGE; + } + + /** + * if context is system app, don't need root permission, but should add in mainfest + **/ + StringBuilder command = new StringBuilder().append("LD_LIBRARY_PATH=/vendor/lib*:/system/lib* pm uninstall") + .append(isKeepData ? " -k " : " ").append(packageName.replace(" ", "\\ ")); + CommandResult commandResult = ShellUtils.execCommand(command.toString(), !isSystemApplication(context), true); + if (commandResult.successMsg != null + && (commandResult.successMsg.contains("Success") || commandResult.successMsg.contains("success"))) { + return DELETE_SUCCEEDED; + } + Log.e(TAG, + new StringBuilder().append("uninstallSilent successMsg:").append(commandResult.successMsg) + .append(", ErrorMsg:").append(commandResult.errorMsg).toString()); + if (commandResult.errorMsg == null) { + return DELETE_FAILED_INTERNAL_ERROR; + } + if (commandResult.errorMsg.contains("Permission denied")) { + return DELETE_FAILED_PERMISSION_DENIED; + } + return DELETE_FAILED_INTERNAL_ERROR; + } + + /** + * 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; + } + + /** + * whether the app whost package's name is packageName is on the top of the stack + *
        + * Attentions: + *
      • You should add android.permission.GET_TASKS in manifest
      • + *
      + * + * @param context + * @param packageName + * @return if params error or task stack is null, return null, otherwise retun whether the app is on the top of + * stack + */ + public static Boolean isTopActivity(Context context, String packageName) { + if (context == null || StringUtils.isEmpty(packageName)) { + return null; + } + + ActivityManager activityManager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE); + List tasksInfo = activityManager.getRunningTasks(1); + if (ListUtils.isEmpty(tasksInfo)) { + return null; + } + try { + return packageName.equals(tasksInfo.get(0).topActivity.getPackageName()); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * get app version code + * + * @param context + * @return + */ + public static int getAppVersionCode(Context context) { + if (context != null) { + PackageManager pm = context.getPackageManager(); + if (pm != null) { + PackageInfo pi; + try { + pi = pm.getPackageInfo(context.getPackageName(), 0); + if (pi != null) { + return pi.versionCode; + } + } catch (NameNotFoundException e) { + e.printStackTrace(); + } + } + } + return -1; + } + + /** + * get system install location
      + * can be set by System Menu Setting->Storage->Prefered install location + * + * @return + * @see {@link IPackageManager#getInstallLocation()} + */ + public static int getInstallLocation() { + CommandResult commandResult = ShellUtils.execCommand( + "LD_LIBRARY_PATH=/vendor/lib*:/system/lib* pm get-install-location", false, true); + if (commandResult.result == 0 && commandResult.successMsg != null && commandResult.successMsg.length() > 0) { + try { + int location = Integer.parseInt(commandResult.successMsg.substring(0, 1)); + switch (location) { + case APP_INSTALL_INTERNAL: + return APP_INSTALL_INTERNAL; + case APP_INSTALL_EXTERNAL: + return APP_INSTALL_EXTERNAL; + } + } catch (NumberFormatException e) { + e.printStackTrace(); + Log.e(TAG, "pm get-install-location error"); + } + } + return APP_INSTALL_AUTO; + } + + /** + * get params for pm install location + * + * @return + */ + private static String getInstallLocationParams() { + int location = getInstallLocation(); + switch (location) { + case APP_INSTALL_INTERNAL: + return "-f"; + case APP_INSTALL_EXTERNAL: + return "-s"; + } + return ""; + } + + /** + * start InstalledAppDetails Activity + * + * @param context + * @param packageName + */ + public static void startInstalledAppDetails(Context context, String packageName) { + Intent intent = new Intent(); + int sdkVersion = Build.VERSION.SDK_INT; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { + intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + intent.setData(Uri.fromParts("package", packageName, null)); + } else { + intent.setAction(Intent.ACTION_VIEW); + intent.setClassName("com.android.settings", "com.android.settings.InstalledAppDetails"); + intent.putExtra((sdkVersion == Build.VERSION_CODES.FROYO ? "pkg" + : "com.android.settings.ApplicationPkgName"), packageName); + } + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } + + /** + * 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
      + * if the parser was given a path that is not a file, or does not end with the expected '.apk' extension. + */ + public static final int INSTALL_PARSE_FAILED_NOT_APK = -100; + + /** + * Installation return code
      + * if the parser was unable to retrieve the AndroidManifest.xml file. + */ + public static final int INSTALL_PARSE_FAILED_BAD_MANIFEST = -101; + + /** + * Installation return code
      + * if the parser encountered an unexpected exception. + */ + public static final int INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION = -102; + + /** + * Installation return code
      + * if the parser did not find any certificates in the .apk. + */ + public static final int INSTALL_PARSE_FAILED_NO_CERTIFICATES = -103; + + /** + * Installation return code
      + * if the parser found inconsistent certificates on the files in the .apk. + */ + public static final int INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES = -104; + + /** + * Installation return code
      + * if the parser encountered a CertificateEncodingException in one of the files in the .apk. + */ + public static final int INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING = -105; + + /** + * Installation return code
      + * if the parser encountered a bad or missing package name in the manifest. + */ + public static final int INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME = -106; + + /** + * Installation return code
      + * if the parser encountered a bad shared user id name in the manifest. + */ + public static final int INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID = -107; + + /** + * Installation return code
      + * if the parser encountered some structural problem in the manifest. + */ + public static final int INSTALL_PARSE_FAILED_MANIFEST_MALFORMED = -108; + + /** + * Installation return code
      + * if the parser did not find any actionable tags (instrumentation or application) in the manifest. + */ + public static final int INSTALL_PARSE_FAILED_MANIFEST_EMPTY = -109; + + /** + * Installation return code
      + * if the system failed to install the package because of system issues. + */ + public static final int INSTALL_FAILED_INTERNAL_ERROR = -110; + /** + * Installation return code
      + * other reason + */ + public static final int INSTALL_FAILED_OTHER = -1000000; + + /** + * Uninstall return code
      + * uninstall success. + */ + public static final int DELETE_SUCCEEDED = 1; + + /** + * Uninstall return code
      + * uninstall fail if the system failed to delete the package for an unspecified reason. + */ + public static final int DELETE_FAILED_INTERNAL_ERROR = -1; + + /** + * Uninstall return code
      + * uninstall fail if the system failed to delete the package because it is the active DevicePolicy manager. + */ + public static final int DELETE_FAILED_DEVICE_POLICY_MANAGER = -2; + + /** + * Uninstall return code
      + * uninstall fail if pcakge name is invalid + */ + public static final int DELETE_FAILED_INVALID_PACKAGE = -3; + + /** + * Uninstall return code
      + * uninstall fail if permission denied + */ + public static final int DELETE_FAILED_PERMISSION_DENIED = -4; +} diff --git a/src/cn/trinea/android/common/util/ParcelUtils.java b/src/main/java/cn/trinea/android/common/util/ParcelUtils.java similarity index 96% rename from src/cn/trinea/android/common/util/ParcelUtils.java rename to src/main/java/cn/trinea/android/common/util/ParcelUtils.java index 321667c..a78cb99 100644 --- a/src/cn/trinea/android/common/util/ParcelUtils.java +++ b/src/main/java/cn/trinea/android/common/util/ParcelUtils.java @@ -10,10 +10,14 @@ /** * ParcelUtils * - * @author Trinea 2013-5-27 + * @author Trinea 2013-5-27 */ public class ParcelUtils { + private ParcelUtils() { + throw new AssertionError(); + } + /** * read boolean * @@ -41,7 +45,6 @@ public static void writeBoolean(boolean b, Parcel out) { * @return */ public static Map readHashMapStringAndString(Parcel in) { - if (in == null) { return null; } @@ -67,7 +70,6 @@ public static Map readHashMapStringAndString(Parcel in) { * @param flags */ public static void writeHashMapStringAndString(Map map, Parcel out, int flags) { - if (map != null) { out.writeInt(map.size()); for (Entry entry : map.entrySet()) { @@ -89,7 +91,6 @@ public static void writeHashMapStringAndString(Map map, Parcel o */ @SuppressWarnings("unchecked") public static Map readHashMapStringKey(Parcel in, ClassLoader loader) { - if (in == null) { return null; } @@ -115,7 +116,6 @@ public static Map readHashMapStringKey(Parcel * @param flags */ public static void writeHashMapStringKey(Map map, Parcel out, int flags) { - if (map != null) { out.writeInt(map.size()); @@ -138,7 +138,6 @@ public static void writeHashMapStringKey(Map m */ @SuppressWarnings("unchecked") public static Map readHashMap(Parcel in, ClassLoader loader) { - if (in == null) { return null; } @@ -163,7 +162,6 @@ public static Map readHashMap * @param flags */ public static void writeHashMap(Map map, Parcel out, int flags) { - if (map != null) { out.writeInt(map.size()); diff --git a/src/main/java/cn/trinea/android/common/util/PatchUtils.java b/src/main/java/cn/trinea/android/common/util/PatchUtils.java new file mode 100644 index 0000000..5cce745 --- /dev/null +++ b/src/main/java/cn/trinea/android/common/util/PatchUtils.java @@ -0,0 +1,21 @@ +package cn.trinea.android.common.util; + +import cn.trinea.android.common.entity.PatchResult; + +/** + * PatchUtils + * + * @author Trinea 2013-12-10 + */ +public class PatchUtils { + + /** + * patch old apk and patch file to new apk + * + * @param oldApkPath + * @param patchPath + * @param newApkPath + * @return + */ + public static native PatchResult patch(String oldApkPath, String patchPath, String newApkPath); +} diff --git a/src/main/java/cn/trinea/android/common/util/PreferencesUtils.java b/src/main/java/cn/trinea/android/common/util/PreferencesUtils.java new file mode 100644 index 0000000..974cde7 --- /dev/null +++ b/src/main/java/cn/trinea/android/common/util/PreferencesUtils.java @@ -0,0 +1,248 @@ +package cn.trinea.android.common.util; + +import android.content.Context; +import android.content.SharedPreferences; + +/** + * PreferencesUtils, easy to get or put data + *
        + * Preference Name + *
      • you can change preference name by {@link #PREFERENCE_NAME}
      • + *
      + *
        + * Put Value + *
      • put string {@link #putString(Context, String, String)}
      • + *
      • put int {@link #putInt(Context, String, int)}
      • + *
      • put long {@link #putLong(Context, String, long)}
      • + *
      • put float {@link #putFloat(Context, String, float)}
      • + *
      • put boolean {@link #putBoolean(Context, String, boolean)}
      • + *
      + *
        + * Get Value + *
      • get string {@link #getString(Context, String)}, {@link #getString(Context, String, String)}
      • + *
      • get int {@link #getInt(Context, String)}, {@link #getInt(Context, String, int)}
      • + *
      • get long {@link #getLong(Context, String)}, {@link #getLong(Context, String, long)}
      • + *
      • get float {@link #getFloat(Context, String)}, {@link #getFloat(Context, String, float)}
      • + *
      • get boolean {@link #getBoolean(Context, String)}, {@link #getBoolean(Context, String, boolean)}
      • + *
      + * + * @author Trinea 2013-3-6 + */ +public class PreferencesUtils { + + public static String PREFERENCE_NAME = "TrineaAndroidCommon"; + + private PreferencesUtils() { + throw new AssertionError(); + } + + /** + * put string preferences + * + * @param context + * @param key The name of the preference to modify + * @param value The new value for the preference + * @return True if the new values were successfully written to persistent storage. + */ + public static boolean putString(Context context, String key, String value) { + SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = settings.edit(); + editor.putString(key, value); + return editor.commit(); + } + + /** + * get string preferences + * + * @param context + * @param key The name of the preference to retrieve + * @return The preference value if it exists, or null. Throws ClassCastException if there is a preference with this + * name that is not a string + * @see #getString(Context, String, String) + */ + public static String getString(Context context, String key) { + return getString(context, key, null); + } + + /** + * get string preferences + * + * @param context + * @param key The name of the preference to retrieve + * @param defaultValue Value to return if this preference does not exist + * @return The preference value if it exists, or defValue. Throws ClassCastException if there is a preference with + * this name that is not a string + */ + public static String getString(Context context, String key, String defaultValue) { + SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); + return settings.getString(key, defaultValue); + } + + /** + * put int preferences + * + * @param context + * @param key The name of the preference to modify + * @param value The new value for the preference + * @return True if the new values were successfully written to persistent storage. + */ + public static boolean putInt(Context context, String key, int value) { + SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = settings.edit(); + editor.putInt(key, value); + return editor.commit(); + } + + /** + * get int preferences + * + * @param context + * @param key The name of the preference to retrieve + * @return The preference value if it exists, or -1. Throws ClassCastException if there is a preference with this + * name that is not a int + * @see #getInt(Context, String, int) + */ + public static int getInt(Context context, String key) { + return getInt(context, key, -1); + } + + /** + * get int preferences + * + * @param context + * @param key The name of the preference to retrieve + * @param defaultValue Value to return if this preference does not exist + * @return The preference value if it exists, or defValue. Throws ClassCastException if there is a preference with + * this name that is not a int + */ + public static int getInt(Context context, String key, int defaultValue) { + SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); + return settings.getInt(key, defaultValue); + } + + /** + * put long preferences + * + * @param context + * @param key The name of the preference to modify + * @param value The new value for the preference + * @return True if the new values were successfully written to persistent storage. + */ + public static boolean putLong(Context context, String key, long value) { + SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = settings.edit(); + editor.putLong(key, value); + return editor.commit(); + } + + /** + * get long preferences + * + * @param context + * @param key The name of the preference to retrieve + * @return The preference value if it exists, or -1. Throws ClassCastException if there is a preference with this + * name that is not a long + * @see #getLong(Context, String, long) + */ + public static long getLong(Context context, String key) { + return getLong(context, key, -1); + } + + /** + * get long preferences + * + * @param context + * @param key The name of the preference to retrieve + * @param defaultValue Value to return if this preference does not exist + * @return The preference value if it exists, or defValue. Throws ClassCastException if there is a preference with + * this name that is not a long + */ + public static long getLong(Context context, String key, long defaultValue) { + SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); + return settings.getLong(key, defaultValue); + } + + /** + * put float preferences + * + * @param context + * @param key The name of the preference to modify + * @param value The new value for the preference + * @return True if the new values were successfully written to persistent storage. + */ + public static boolean putFloat(Context context, String key, float value) { + SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = settings.edit(); + editor.putFloat(key, value); + return editor.commit(); + } + + /** + * get float preferences + * + * @param context + * @param key The name of the preference to retrieve + * @return The preference value if it exists, or -1. Throws ClassCastException if there is a preference with this + * name that is not a float + * @see #getFloat(Context, String, float) + */ + public static float getFloat(Context context, String key) { + return getFloat(context, key, -1); + } + + /** + * get float preferences + * + * @param context + * @param key The name of the preference to retrieve + * @param defaultValue Value to return if this preference does not exist + * @return The preference value if it exists, or defValue. Throws ClassCastException if there is a preference with + * this name that is not a float + */ + public static float getFloat(Context context, String key, float defaultValue) { + SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); + return settings.getFloat(key, defaultValue); + } + + /** + * put boolean preferences + * + * @param context + * @param key The name of the preference to modify + * @param value The new value for the preference + * @return True if the new values were successfully written to persistent storage. + */ + public static boolean putBoolean(Context context, String key, boolean value) { + SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = settings.edit(); + editor.putBoolean(key, value); + return editor.commit(); + } + + /** + * get boolean preferences, default is false + * + * @param context + * @param key The name of the preference to retrieve + * @return The preference value if it exists, or false. Throws ClassCastException if there is a preference with this + * name that is not a boolean + * @see #getBoolean(Context, String, boolean) + */ + public static boolean getBoolean(Context context, String key) { + return getBoolean(context, key, false); + } + + /** + * get boolean preferences + * + * @param context + * @param key The name of the preference to retrieve + * @param defaultValue Value to return if this preference does not exist + * @return The preference value if it exists, or defValue. Throws ClassCastException if there is a preference with + * this name that is not a boolean + */ + public static boolean getBoolean(Context context, String key, boolean defaultValue) { + SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); + return settings.getBoolean(key, defaultValue); + } +} diff --git a/src/main/java/cn/trinea/android/common/util/RandomUtils.java b/src/main/java/cn/trinea/android/common/util/RandomUtils.java new file mode 100644 index 0000000..103ad3d --- /dev/null +++ b/src/main/java/cn/trinea/android/common/util/RandomUtils.java @@ -0,0 +1,247 @@ +package cn.trinea.android.common.util; + +import java.util.Random; + +/** + * Random Utils + *
        + * Shuffling algorithm + *
      • {@link #shuffle(Object[])} Shuffling algorithm, Randomly permutes the specified array using a default source of + * randomness
      • + *
      • {@link #shuffle(Object[], int)} Shuffling algorithm, Randomly permutes the specified array
      • + *
      • {@link #shuffle(int[])} Shuffling algorithm, Randomly permutes the specified int array using a default source of + * randomness
      • + *
      • {@link #shuffle(int[], int)} Shuffling algorithm, Randomly permutes the specified int array
      • + *
      + *
        + * get random int + *
      • {@link #getRandom(int)} get random int between 0 and max
      • + *
      • {@link #getRandom(int, int)} get random int between min and max
      • + *
      + *
        + * get random numbers or letters + *
      • {@link #getRandomCapitalLetters(int)} get a fixed-length random string, its a mixture of uppercase letters
      • + *
      • {@link #getRandomLetters(int)} get a fixed-length random string, its a mixture of uppercase and lowercase letters + *
      • + *
      • {@link #getRandomLowerCaseLetters(int)} get a fixed-length random string, its a mixture of lowercase letters
      • + *
      • {@link #getRandomNumbers(int)} get a fixed-length random string, its a mixture of numbers
      • + *
      • {@link #getRandomNumbersAndLetters(int)} get a fixed-length random string, its a mixture of uppercase, lowercase + * letters and numbers
      • + *
      • {@link #getRandom(String, int)} get a fixed-length random string, its a mixture of chars in source
      • + *
      • {@link #getRandom(char[], int)} get a fixed-length random string, its a mixture of chars in sourceChar
      • + *
      + * + * @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"; + + private RandomUtils() { + throw new AssertionError(); + } + + /** + * get a fixed-length random string, its a mixture of uppercase, lowercase letters and numbers + * + * @param length + * @return + * @see 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 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 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 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 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(); + } + + /** + * get random int between 0 and max + * + * @param max + * @return
        + *
      • if max <= 0, return 0
      • + *
      • else return random int between 0 and max
      • + *
      + */ + public static int getRandom(int max) { + return getRandom(0, max); + } + + /** + * get random int between min and max + * + * @param min + * @param max + * @return
        + *
      • if min > max, return 0
      • + *
      • if min == max, return min
      • + *
      • else return random int between min and max
      • + *
      + */ + public static int getRandom(int min, int max) { + if (min > max) { + return 0; + } + if (min == max) { + return min; + } + return min + new Random().nextInt(max - min); + } + + /** + * Shuffling algorithm, Randomly permutes the specified array using a default source of randomness + * + * @param objArray + * @return + */ + public static boolean shuffle(Object[] objArray) { + if (objArray == null) { + return false; + } + + return shuffle(objArray, getRandom(objArray.length)); + } + + /** + * Shuffling algorithm, Randomly permutes the specified array + * + * @param objArray + * @param shuffleCount + * @return + */ + public static boolean shuffle(Object[] objArray, int shuffleCount) { + int length; + if (objArray == null || shuffleCount < 0 || (length = objArray.length) < shuffleCount) { + return false; + } + + for (int i = 1; i <= shuffleCount; i++) { + int random = getRandom(length - i); + Object temp = objArray[length - i]; + objArray[length - i] = objArray[random]; + objArray[random] = temp; + } + return true; + } + + /** + * Shuffling algorithm, Randomly permutes the specified int array using a default source of randomness + * + * @param intArray + * @return + */ + public static int[] shuffle(int[] intArray) { + if (intArray == null) { + return null; + } + + return shuffle(intArray, getRandom(intArray.length)); + } + + /** + * Shuffling algorithm, Randomly permutes the specified int array + * + * @param intArray + * @param shuffleCount + * @return + */ + public static int[] shuffle(int[] intArray, int shuffleCount) { + int length; + if (intArray == null || shuffleCount < 0 || (length = intArray.length) < shuffleCount) { + return null; + } + + int[] out = new int[shuffleCount]; + for (int i = 1; i <= shuffleCount; i++) { + int random = getRandom(length - i); + out[i - 1] = intArray[random]; + int temp = intArray[length - i]; + intArray[length - i] = intArray[random]; + intArray[random] = temp; + } + return out; + } +} diff --git a/src/main/java/cn/trinea/android/common/util/ResourceUtils.java b/src/main/java/cn/trinea/android/common/util/ResourceUtils.java new file mode 100644 index 0000000..49be12b --- /dev/null +++ b/src/main/java/cn/trinea/android/common/util/ResourceUtils.java @@ -0,0 +1,135 @@ +package cn.trinea.android.common.util; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + +import android.content.Context; + +/** + * ResourceUtils + * + * @author Trinea 2012-5-26 + */ +public class ResourceUtils { + + private ResourceUtils() { + throw new AssertionError(); + } + + /** + * 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 content from 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; + } + } + + /** + * same to {@link ResourceUtils#geFileFromAssets(Context, String)}, but return type is List + * + * @param context + * @param fileName + * @return + */ + public static List geFileToListFromAssets(Context context, String fileName) { + if (context == null || StringUtils.isEmpty(fileName)) { + return null; + } + + List fileContent = new ArrayList(); + try { + InputStreamReader in = new InputStreamReader(context.getResources().getAssets().open(fileName)); + BufferedReader br = new BufferedReader(in); + String line; + while ((line = br.readLine()) != null) { + fileContent.add(line); + } + br.close(); + return fileContent; + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + /** + * same to {@link ResourceUtils#geFileFromRaw(Context, int)}, but return type is List + * + * @param context + * @param resId + * @return + */ + public static List geFileToListFromRaw(Context context, int resId) { + if (context == null) { + return null; + } + + List fileContent = new ArrayList(); + BufferedReader reader = null; + try { + InputStreamReader in = new InputStreamReader(context.getResources().openRawResource(resId)); + reader = new BufferedReader(in); + String line = null; + while ((line = reader.readLine()) != null) { + fileContent.add(line); + } + reader.close(); + return fileContent; + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/src/main/java/cn/trinea/android/common/util/ScreenUtils.java b/src/main/java/cn/trinea/android/common/util/ScreenUtils.java new file mode 100644 index 0000000..a009471 --- /dev/null +++ b/src/main/java/cn/trinea/android/common/util/ScreenUtils.java @@ -0,0 +1,42 @@ +package cn.trinea.android.common.util; + +import android.content.Context; + +/** + * ScreenUtils + *
        + * Convert between dp and sp + *
      • {@link ScreenUtils#dpToPx(Context, float)}
      • + *
      • {@link ScreenUtils#pxToDp(Context, float)}
      • + *
      + * + * @author Trinea 2014-2-14 + */ +public class ScreenUtils { + + private ScreenUtils() { + throw new AssertionError(); + } + + public static float dpToPx(Context context, float dp) { + if (context == null) { + return -1; + } + return dp * context.getResources().getDisplayMetrics().density; + } + + public static float pxToDp(Context context, float px) { + if (context == null) { + return -1; + } + return px / context.getResources().getDisplayMetrics().density; + } + + public static int dpToPxInt(Context context, float dp) { + return (int)(dpToPx(context, dp) + 0.5f); + } + + public static int pxToDpCeilInt(Context context, float px) { + return (int)(pxToDp(context, px) + 0.5f); + } +} diff --git a/src/cn/trinea/android/common/util/SerializeUtils.java b/src/main/java/cn/trinea/android/common/util/SerializeUtils.java similarity index 71% rename from src/cn/trinea/android/common/util/SerializeUtils.java rename to src/main/java/cn/trinea/android/common/util/SerializeUtils.java index f4c33eb..422c45b 100644 --- a/src/cn/trinea/android/common/util/SerializeUtils.java +++ b/src/main/java/cn/trinea/android/common/util/SerializeUtils.java @@ -10,15 +10,19 @@ /** * Serialize Utils * - * @author Trinea 2012-5-14 + * @author Trinea 2012-5-14 */ public class SerializeUtils { + private SerializeUtils() { + throw new AssertionError(); + } + /** - * deserialization from file + * Deserialization object from file. * - * @param filePath - * @return + * @param filePath file path + * @return de-serialized object * @throws RuntimeException if an error occurs */ public static Object deserialization(String filePath) { @@ -35,22 +39,15 @@ public static Object deserialization(String filePath) { } catch (IOException e) { throw new RuntimeException("IOException occurred. ", e); } finally { - if (in != null) { - try { - in.close(); - } catch (IOException e) { - throw new RuntimeException("IOException occurred. ", e); - } - } + IOUtils.close(in); } } /** - * serialize to file + * Serialize object to file. * - * @param filePath - * @param obj - * @return + * @param filePath file path + * @param obj object * @throws RuntimeException if an error occurs */ public static void serialization(String filePath, Object obj) { @@ -64,13 +61,8 @@ public static void serialization(String filePath, Object obj) { } catch (IOException e) { throw new RuntimeException("IOException occurred. ", e); } finally { - if (out != null) { - try { - out.close(); - } catch (IOException e) { - throw new RuntimeException("IOException occurred. ", e); - } - } + IOUtils.close(out); } } + } diff --git a/src/main/java/cn/trinea/android/common/util/ShellUtils.java b/src/main/java/cn/trinea/android/common/util/ShellUtils.java new file mode 100644 index 0000000..937ce90 --- /dev/null +++ b/src/main/java/cn/trinea/android/common/util/ShellUtils.java @@ -0,0 +1,285 @@ +package cn.trinea.android.common.util; + +import android.os.Build; + +import java.io.BufferedReader; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * ShellUtils + *
        + * Check root + *
      • {@link ShellUtils#checkRootPermission()}
      • + *
      + *
        + * Execte command + *
      • {@link ShellUtils#execCommand(String)}
      • + *
      • {@link ShellUtils#execCommand(String, long)}
      • + *
      • {@link ShellUtils#execCommand(String, boolean)}
      • + *
      • {@link ShellUtils#execCommand(String, boolean, long)}
      • + *
      • {@link ShellUtils#execCommand(String, boolean, boolean)}
      • + *
      • {@link ShellUtils#execCommand(String, boolean, long, boolean)}
      • + *
      • {@link ShellUtils#execCommand(List, boolean)}
      • + *
      • {@link ShellUtils#execCommand(List, boolean, long)}
      • + *
      • {@link ShellUtils#execCommand(List, boolean, long, boolean)}
      • + *
      • {@link ShellUtils#execCommand(String[], boolean)}
      • + *
      • {@link ShellUtils#execCommand(String[], boolean, long)}
      • + *
      • {@link ShellUtils#execCommand(String[], boolean, long, 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"; + + private ShellUtils() { + throw new AssertionError(); + } + + /** + * check whether has root permission + * + * @return + */ + public static boolean checkRootPermission() { + return execCommand("echo root", true, 5000, false).result == 0; + } + + public static CommandResult execCommand(String command) { + return execCommand(new String[]{command}, false, -1, true); + } + + public static CommandResult execCommand(String command, long timeoutInMills) { + return execCommand(new String[]{command}, false, timeoutInMills, true); + } + + public static CommandResult execCommand(String command, boolean isRoot) { + return execCommand(new String[]{command}, isRoot, -1, true); + } + + /** + * execute shell command, default return result msg + * + * @param command command + * @param isRoot whether need to run with root + * @param timeoutInMills timeout in millSeconds, -1 means no timeout, if bigger than 0, you must make sure Android Version is not smaller than Android O + * @return + * @see ShellUtils#execCommand(String[], boolean, long, boolean) + */ + public static CommandResult execCommand(String command, boolean isRoot, long timeoutInMills) { + return execCommand(new String[]{command}, isRoot, timeoutInMills, true); + } + + public static CommandResult execCommand(List commands, boolean isRoot) { + return execCommand(commands == null ? null : commands.toArray(new String[]{}), isRoot, -1, true); + } + + /** + * execute shell commands, default return result msg + * + * @param commands command list + * @param isRoot whether need to run with root + * @param timeoutInMills timeout in millSeconds, -1 means no timeout, if bigger than 0, you must make sure Android Version is not smaller than Android O + * @return + * @see ShellUtils#execCommand(String[], boolean, long, boolean) + */ + public static CommandResult execCommand(List commands, boolean isRoot, long timeoutInMills) { + return execCommand(commands == null ? null : commands.toArray(new String[]{}), isRoot, timeoutInMills, true); + } + + public static CommandResult execCommand(String[] commands, boolean isRoot) { + return execCommand(commands, isRoot, -1, true); + } + + /** + * execute shell commands, default return result msg + * + * @param commands command array + * @param isRoot whether need to run with root + * @param timeoutInMills timeout in millSeconds, -1 means no timeout, if bigger than 0, you must make sure Android Version is not smaller than Android O + * @return + * @see ShellUtils#execCommand(String[], boolean, long, boolean) + */ + public static CommandResult execCommand(String[] commands, boolean isRoot, long timeoutInMills) { + return execCommand(commands, isRoot, timeoutInMills, true); + } + + public static CommandResult execCommand(String command, boolean isRoot, boolean isNeedResultMsg) { + return execCommand(new String[]{command}, isRoot, -1, isNeedResultMsg); + } + + /** + * execute shell command + * + * @param command command + * @param isRoot whether need to run with root + * @param timeoutInMills timeout in millSeconds, -1 means no timeout, if bigger than 0, you must make sure Android Version is not smaller than Android O + * @param isNeedResultMsg whether need result msg + * @return + * @see ShellUtils#execCommand(String[], boolean, long, boolean) + */ + public static CommandResult execCommand(String command, boolean isRoot, long timeoutInMills, boolean isNeedResultMsg) { + return execCommand(new String[]{command}, isRoot, timeoutInMills, isNeedResultMsg); + } + + /** + * execute shell commands + * + * @param commands command list + * @param isRoot whether need to run with root + * @param timeoutInMills timeout in millSeconds, -1 means no timeout, if bigger than 0, you must make sure Android Version is not smaller than Android O + * @param isNeedResultMsg whether need result msg + * @return + * @see ShellUtils#execCommand(String[], boolean, long, boolean) + */ + public static CommandResult execCommand(List commands, boolean isRoot, long timeoutInMills, boolean isNeedResultMsg) { + return execCommand(commands == null ? null : commands.toArray(new String[]{}), isRoot, timeoutInMills, isNeedResultMsg); + } + + /** + * execute shell commands + * + * @param commands command array + * @param isRoot whether need to run with root + * @param timeoutInMills timeout in millSeconds, -1 means no timeout, if bigger than 0, you must make sure Android Version is not smaller than Android O + * @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, long timeoutInMills, 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; + boolean isTimeout = false; + 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(); + + if (timeoutInMills <= 0) { + result = process.waitFor(); + } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + isTimeout = process.waitFor(timeoutInMills, TimeUnit.MILLISECONDS); + result = isTimeout ? 0 : -1; + } + } + // 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).append(COMMAND_LINE_END); + } + while ((s = errorResult.readLine()) != null) { + errorMsg.append(s).append(COMMAND_LINE_END); + } + } + } 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(); + } + } + if (isTimeout && errorMsg.length() == 0) { + errorMsg.append("Timeout, possibly due to lack of permissions."); + } + 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; + } + + @Override + public String toString() { + return "result is: " + result + + "\nsuccessMsg is: '" + successMsg + "'" + + "\nerrorMsg is: '" + errorMsg + "'"; + } + } +} diff --git a/src/main/java/cn/trinea/android/common/util/SingletonUtils.java b/src/main/java/cn/trinea/android/common/util/SingletonUtils.java new file mode 100644 index 0000000..362625a --- /dev/null +++ b/src/main/java/cn/trinea/android/common/util/SingletonUtils.java @@ -0,0 +1,26 @@ +package cn.trinea.android.common.util; + +/** + * Singleton helper class for lazily initialization. + * + * @author Trinea + * + * @param + */ +public abstract class SingletonUtils { + + private T instance; + + protected abstract T newInstance(); + + public final T getInstance() { + if (instance == null) { + synchronized (SingletonUtils.class) { + if (instance == null) { + instance = newInstance(); + } + } + } + return instance; + } +} diff --git a/src/cn/trinea/android/common/util/SizeUtils.java b/src/main/java/cn/trinea/android/common/util/SizeUtils.java similarity index 67% rename from src/cn/trinea/android/common/util/SizeUtils.java rename to src/main/java/cn/trinea/android/common/util/SizeUtils.java index d8ae212..6dd09db 100644 --- a/src/cn/trinea/android/common/util/SizeUtils.java +++ b/src/main/java/cn/trinea/android/common/util/SizeUtils.java @@ -3,7 +3,7 @@ /** * SizeUtils * - * @author Trinea 2013-5-15 + * @author Trinea 2013-5-15 */ public class SizeUtils { @@ -13,4 +13,8 @@ public class SizeUtils { public static final long MB_2_BYTE = 1048576; /** kb to byte **/ public static final long KB_2_BYTE = 1024; + + private SizeUtils() { + throw new AssertionError(); + } } diff --git a/src/main/java/cn/trinea/android/common/util/SqliteUtils.java b/src/main/java/cn/trinea/android/common/util/SqliteUtils.java new file mode 100644 index 0000000..b73cff4 --- /dev/null +++ b/src/main/java/cn/trinea/android/common/util/SqliteUtils.java @@ -0,0 +1,37 @@ +package cn.trinea.android.common.util; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; + +/** + * SqliteUtils + * + * @author Trinea 2013-10-21 + */ +public class SqliteUtils { + + private static volatile SqliteUtils instance; + + private DbHelper dbHelper; + private SQLiteDatabase db; + + private SqliteUtils(Context context) { + dbHelper = new DbHelper(context); + db = dbHelper.getWritableDatabase(); + } + + public static SqliteUtils getInstance(Context context) { + if (instance == null) { + synchronized (SqliteUtils.class) { + if (instance == null) { + instance = new SqliteUtils(context); + } + } + } + return instance; + } + + public SQLiteDatabase getDb() { + return db; + } +} diff --git a/src/cn/trinea/android/common/util/StringUtils.java b/src/main/java/cn/trinea/android/common/util/StringUtils.java similarity index 86% rename from src/cn/trinea/android/common/util/StringUtils.java rename to src/main/java/cn/trinea/android/common/util/StringUtils.java index ae7b6dc..d22b064 100644 --- a/src/cn/trinea/android/common/util/StringUtils.java +++ b/src/main/java/cn/trinea/android/common/util/StringUtils.java @@ -8,10 +8,14 @@ /** * String Utils * - * @author Trinea 2011-7-22 + * @author Trinea 2011-7-22 */ public class StringUtils { + private StringUtils() { + throw new AssertionError(); + } + /** * is null or its length is 0 or it is made by space * @@ -44,7 +48,7 @@ public static boolean isBlank(String str) { * @param str * @return if string is null or its size is 0, return true, else return false. */ - public static boolean isEmpty(String str) { + public static boolean isEmpty(CharSequence str) { return (str == null || str.length() == 0); } @@ -61,7 +65,23 @@ public static boolean isEquals(String actual, String expected) { } /** - * null string to empty string + * get length of CharSequence + * + *
      +     * length(null) = 0;
      +     * length(\"\") = 0;
      +     * length(\"abc\") = 3;
      +     * 
      + * + * @param str + * @return if str is null or empty, return 0, else return {@link CharSequence#length()}. + */ + public static int length(CharSequence str) { + return str == null ? 0 : str.length(); + } + + /** + * null Object to empty string * *
            * nullStrToEmpty(null) = "";
      @@ -72,8 +92,8 @@ public static boolean isEquals(String actual, String expected) {
            * @param str
            * @return
            */
      -    public static String nullStrToEmpty(String str) {
      -        return (str == null ? "" : str);
      +    public static String nullStrToEmpty(Object str) {
      +        return (str == null ? "" : (str instanceof String ? (String)str : str.toString()));
           }
       
           /**
      @@ -97,8 +117,8 @@ public static String capitalizeFirstLetter(String str) {
               }
       
               char c = str.charAt(0);
      -        return (!Character.isLetter(c) || Character.isUpperCase(c)) ? str
      -            : new StringBuilder(str.length()).append(Character.toUpperCase(c)).append(str.substring(1)).toString();
      +        return (!Character.isLetter(c) || Character.isUpperCase(c)) ? str : new StringBuilder(str.length())
      +                .append(Character.toUpperCase(c)).append(str.substring(1)).toString();
           }
       
           /**
      @@ -164,15 +184,16 @@ public static String utf8Encode(String str, String defultReturn) {
            * 
            * @param href
            * @return 
        - *
      • if href is null, return ""
      • - *
      • if not match regx, return source
      • - *
      • return the last string that match regx
      • - *
      + *
    • if href is null, return ""
    • + *
    • if not match regx, return source
    • + *
    • return the last string that match regx
    • + *
    */ public static String getHrefInnerHtml(String href) { if (isEmpty(href)) { return ""; } + String hrefReg = ".*<[\\s]*a[\\s]*.*>(.+?)<[\\s]*/a[\\s]*>.*"; Pattern hrefPattern = Pattern.compile(hrefReg, Pattern.CASE_INSENSITIVE); Matcher hrefMatcher = hrefPattern.matcher(href); @@ -200,12 +221,8 @@ public static String getHrefInnerHtml(String href) { * @return */ public static String htmlEscapeCharsToString(String source) { - if (StringUtils.isEmpty(source)) { - return source; - } else { - return source.replaceAll("<", "<").replaceAll(">", ">").replaceAll("&", "&") - .replaceAll(""", "\""); - } + return StringUtils.isEmpty(source) ? source : source.replaceAll("<", "<").replaceAll(">", ">") + .replaceAll("&", "&").replaceAll(""", "\""); } /** diff --git a/src/cn/trinea/android/common/util/SystemUtils.java b/src/main/java/cn/trinea/android/common/util/SystemUtils.java similarity index 87% rename from src/cn/trinea/android/common/util/SystemUtils.java rename to src/main/java/cn/trinea/android/common/util/SystemUtils.java index 1d5ef3b..e4306b6 100644 --- a/src/cn/trinea/android/common/util/SystemUtils.java +++ b/src/main/java/cn/trinea/android/common/util/SystemUtils.java @@ -3,13 +3,17 @@ /** * SystemUtils * - * @author Trinea 2013-5-15 + * @author Trinea 2013-5-15 */ public class SystemUtils { /** recommend default thread pool size according to system available processors, {@link #getDefaultThreadPoolSize()} **/ public static final int DEFAULT_THREAD_POOL_SIZE = getDefaultThreadPoolSize(); + private SystemUtils() { + throw new AssertionError(); + } + /** * get recommend default thread pool size * diff --git a/src/main/java/cn/trinea/android/common/util/TimeUtils.java b/src/main/java/cn/trinea/android/common/util/TimeUtils.java new file mode 100644 index 0000000..3dbdcf4 --- /dev/null +++ b/src/main/java/cn/trinea/android/common/util/TimeUtils.java @@ -0,0 +1,67 @@ +package cn.trinea.android.common.util; + +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * TimeUtils + * + * @author Trinea 2013-8-24 + */ +public class TimeUtils { + + public static final SimpleDateFormat DEFAULT_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + public static final SimpleDateFormat DATE_FORMAT_DATE = new SimpleDateFormat("yyyy-MM-dd"); + + private TimeUtils() { + throw new AssertionError(); + } + + /** + * long time to string + * + * @param timeInMillis + * @param dateFormat + * @return + */ + public static String getTime(long timeInMillis, SimpleDateFormat dateFormat) { + return dateFormat.format(new Date(timeInMillis)); + } + + /** + * long time to string, format is {@link #DEFAULT_DATE_FORMAT} + * + * @param timeInMillis + * @return + */ + public static String getTime(long timeInMillis) { + return getTime(timeInMillis, DEFAULT_DATE_FORMAT); + } + + /** + * get current time in milliseconds + * + * @return + */ + public static long getCurrentTimeInLong() { + return System.currentTimeMillis(); + } + + /** + * get current time in milliseconds, format is {@link #DEFAULT_DATE_FORMAT} + * + * @return + */ + public static String getCurrentTimeInString() { + return getTime(getCurrentTimeInLong()); + } + + /** + * get current time in milliseconds + * + * @return + */ + public static String getCurrentTimeInString(SimpleDateFormat dateFormat) { + return getTime(getCurrentTimeInLong(), dateFormat); + } +} diff --git a/src/main/java/cn/trinea/android/common/util/ToastUtils.java b/src/main/java/cn/trinea/android/common/util/ToastUtils.java new file mode 100644 index 0000000..e5ebb80 --- /dev/null +++ b/src/main/java/cn/trinea/android/common/util/ToastUtils.java @@ -0,0 +1,48 @@ +package cn.trinea.android.common.util; + +import android.content.Context; +import android.widget.Toast; + +/** + * ToastUtils + * + * @author Trinea 2013-12-9 + */ +public class ToastUtils { + + private ToastUtils() { + throw new AssertionError(); + } + + public static void show(Context context, int resId) { + show(context, context.getResources().getText(resId), Toast.LENGTH_SHORT); + } + + public static void show(Context context, int resId, int duration) { + show(context, context.getResources().getText(resId), duration); + } + + public static void show(Context context, CharSequence text) { + show(context, text, Toast.LENGTH_SHORT); + } + + public static void show(Context context, CharSequence text, int duration) { + Toast.makeText(context, text, duration).show(); + } + + public static void show(Context context, int resId, Object... args) { + show(context, String.format(context.getResources().getString(resId), args), Toast.LENGTH_SHORT); + } + + public static void show(Context context, String format, Object... args) { + show(context, String.format(format, args), Toast.LENGTH_SHORT); + } + + public static void show(Context context, int resId, int duration, Object... args) { + show(context, String.format(context.getResources().getString(resId), args), duration); + } + + public static void show(Context context, String format, int duration, Object... args) { + show(context, String.format(format, args), duration); + } +} diff --git a/src/main/java/cn/trinea/android/common/util/ViewUtils.java b/src/main/java/cn/trinea/android/common/util/ViewUtils.java new file mode 100644 index 0000000..acdb8e7 --- /dev/null +++ b/src/main/java/cn/trinea/android/common/util/ViewUtils.java @@ -0,0 +1,262 @@ +package cn.trinea.android.common.util; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.AbsListView; +import android.widget.GridView; +import android.widget.LinearLayout; +import android.widget.ListAdapter; +import android.widget.ListView; +import android.widget.RelativeLayout; +import android.widget.RelativeLayout.LayoutParams; +import android.widget.TextView; + +/** + * ViewUtils + *
      + * get view height + *
    • {@link ViewUtils#getListViewHeightBasedOnChildren(ListView)}
    • + *
    • {@link ViewUtils#getAbsListViewHeightBasedOnChildren(AbsListView)}
    • + *
    + *
      + * set view height + *
    • {@link ViewUtils#setViewHeight(View, int)} set view height
    • + *
    • {@link ViewUtils#setListViewHeightBasedOnChildren(ListView)}
    • + *
    • {@link ViewUtils#setAbsListViewHeightBasedOnChildren(AbsListView)}
    • + *
    + *
      + * get other info + *
    • {@link ViewUtils#getGridViewVerticalSpacing(GridView)} get GridView vertical spacing
    • + *
    + *
      + * set other info + *
    • {@link ViewUtils#setSearchViewOnClickListener(View, OnClickListener)}
    • + *
    + * + * @author Trinea 2013-12-24 + */ +public class ViewUtils { + + private ViewUtils() { + throw new AssertionError(); + } + + /** + * get ListView height according to every children + * + * @param view + * @return + */ + public static int getListViewHeightBasedOnChildren(ListView view) { + int height = getAbsListViewHeightBasedOnChildren(view); + ListAdapter adapter; + int adapterCount; + if (view != null && (adapter = view.getAdapter()) != null && (adapterCount = adapter.getCount()) > 0) { + height += view.getDividerHeight() * (adapterCount - 1); + } + return height; + } + + // /** + // * get GridView height according to every children + // * + // * @param view + // * @return + // */ + // public static int getGridViewHeightBasedOnChildren(GridView view) { + // int height = getAbsListViewHeightBasedOnChildren(view); + // ListAdapter adapter; + // int adapterCount, numColumns = getGridViewNumColumns(view); + // if (view != null && (adapter = view.getAdapter()) != null && (adapterCount = adapter.getCount()) > 0 + // && numColumns > 0) { + // int rowCount = (int)Math.ceil(adapterCount / (double)numColumns); + // height = rowCount * (height / adapterCount + getGridViewVerticalSpacing(view)); + // } + // return height; + // } + // + // /** + // * get GridView columns number + // * + // * @param view + // * @return + // */ + // public static int getGridViewNumColumns(GridView view) { + // if (view == null || view.getChildCount() <= 0) { + // return 0; + // } + // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + // return getNumColumnsCompat11(view); + // + // } else { + // int columns = 0; + // int children = view.getChildCount(); + // if (children > 0) { + // int width = view.getChildAt(0).getMeasuredWidth(); + // if (width > 0) { + // columns = view.getWidth() / width; + // } + // } + // return columns; + // } + // } + // + // @TargetApi(11) + // private static int getNumColumnsCompat11(GridView view) { + // return view.getNumColumns(); + // } + + private static final String CLASS_NAME_GRID_VIEW = "android.widget.GridView"; + private static final String FIELD_NAME_VERTICAL_SPACING = "mVerticalSpacing"; + + /** + * get GridView vertical spacing + * + * @param view + * @return + */ + public static int getGridViewVerticalSpacing(GridView view) { + // get mVerticalSpacing by android.widget.GridView + Class demo = null; + int verticalSpacing = 0; + try { + demo = Class.forName(CLASS_NAME_GRID_VIEW); + Field field = demo.getDeclaredField(FIELD_NAME_VERTICAL_SPACING); + field.setAccessible(true); + verticalSpacing = (Integer)field.get(view); + return verticalSpacing; + } catch (Exception e) { + /** + * accept all exception, include ClassNotFoundException, NoSuchFieldException, InstantiationException, + * IllegalArgumentException, IllegalAccessException, NullPointException + */ + e.printStackTrace(); + } + return verticalSpacing; + } + + /** + * get AbsListView height according to every children + * + * @param view + * @return + */ + public static int getAbsListViewHeightBasedOnChildren(AbsListView view) { + ListAdapter adapter; + if (view == null || (adapter = view.getAdapter()) == null) { + return 0; + } + + int height = 0; + for (int i = 0; i < adapter.getCount(); i++) { + View item = adapter.getView(i, null, view); + if (item instanceof ViewGroup) { + item.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); + } + item.measure(0, 0); + height += item.getMeasuredHeight(); + } + height += view.getPaddingTop() + view.getPaddingBottom(); + return height; + } + + /** + * set view height + * + * @param view + * @param height + */ + public static void setViewHeight(View view, int height) { + if (view == null) { + return; + } + + ViewGroup.LayoutParams params = view.getLayoutParams(); + params.height = height; + } + + // /** + // * set GistView height which is calculated by {@link # getGridViewHeightBasedOnChildren(GridView)} + // * + // * @param view + // * @return + // */ + // public static void setGridViewHeightBasedOnChildren(GridView view) { + // setViewHeight(view, getGridViewHeightBasedOnChildren(view)); + // } + + /** + * set ListView height which is calculated by {@link # getListViewHeightBasedOnChildren(ListView)} + * + * @param view + * @return + */ + public static void setListViewHeightBasedOnChildren(ListView view) { + setViewHeight(view, getListViewHeightBasedOnChildren(view)); + } + + /** + * set AbsListView height which is calculated by {@link # getAbsListViewHeightBasedOnChildren(AbsListView)} + * + * @param view + * @return + */ + public static void setAbsListViewHeightBasedOnChildren(AbsListView view) { + setViewHeight(view, getAbsListViewHeightBasedOnChildren(view)); + } + + /** + * set SearchView OnClickListener + * + * @param v + * @param listener + */ + public static void setSearchViewOnClickListener(View v, OnClickListener listener) { + if (v instanceof ViewGroup) { + ViewGroup group = (ViewGroup)v; + int count = group.getChildCount(); + for (int i = 0; i < count; i++) { + View child = group.getChildAt(i); + if (child instanceof LinearLayout || child instanceof RelativeLayout) { + setSearchViewOnClickListener(child, listener); + } + + if (child instanceof TextView) { + TextView text = (TextView)child; + text.setFocusable(false); + } + child.setOnClickListener(listener); + } + } + } + + /** + * get descended views from parent. + * + * @param parent + * @param filter Type of views which will be returned. + * @param includeSubClass Whether returned list will include views which are subclass of filter or not. + * @return + */ + public static List getDescendants(ViewGroup parent, Class filter, boolean includeSubClass) { + List descendedViewList = new ArrayList(); + int childCount = parent.getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = parent.getChildAt(i); + Class childsClass = child.getClass(); + if ((includeSubClass && filter.isAssignableFrom(childsClass)) + || (!includeSubClass && childsClass == filter)) { + descendedViewList.add(filter.cast(child)); + } + if (child instanceof ViewGroup) { + descendedViewList.addAll(getDescendants((ViewGroup)child, filter, includeSubClass)); + } + } + return descendedViewList; + } +} diff --git a/src/cn/trinea/android/common/view/BorderScrollView.java b/src/main/java/cn/trinea/android/common/view/BorderScrollView.java similarity index 92% rename from src/cn/trinea/android/common/view/BorderScrollView.java rename to src/main/java/cn/trinea/android/common/view/BorderScrollView.java index 6fdeb45..6f31a9e 100644 --- a/src/cn/trinea/android/common/view/BorderScrollView.java +++ b/src/main/java/cn/trinea/android/common/view/BorderScrollView.java @@ -19,15 +19,15 @@ public class BorderScrollView extends ScrollView { private OnBorderListener onBorderListener; private View contentView; - public BorderScrollView(Context context){ + public BorderScrollView(Context context) { super(context); } - public BorderScrollView(Context context, AttributeSet attrs){ + public BorderScrollView(Context context, AttributeSet attrs) { super(context, attrs); } - public BorderScrollView(Context context, AttributeSet attrs, int defStyle){ + public BorderScrollView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @@ -51,7 +51,7 @@ public void setOnBorderListener(final OnBorderListener onBorderListener) { /** * OnBorderListener, Called when scroll to top or bottom * - * @author Trinea 2013-5-22 + * @author Trinea 2013-5-22 */ public static interface OnBorderListener { diff --git a/src/cn/trinea/android/common/view/DropDownListView.java b/src/main/java/cn/trinea/android/common/view/DropDownListView.java similarity index 83% rename from src/cn/trinea/android/common/view/DropDownListView.java rename to src/main/java/cn/trinea/android/common/view/DropDownListView.java index 0435ae3..ef9851f 100644 --- a/src/cn/trinea/android/common/view/DropDownListView.java +++ b/src/main/java/cn/trinea/android/common/view/DropDownListView.java @@ -21,14 +21,14 @@ import cn.trinea.android.common.R; /** - * Listview whick can do something when drop down or scroll to bottom. for example, drop down to refresh and + * Listview which can do something when drop down or scroll to bottom. for example, drop down to refresh and * load more when scroll to bottom *
      * DropDown Listener *
    • define isDropDownStyle="true" at layout xml or call {@link #setDropDownStyle(boolean)} to enable drop down style * before
    • *
    • {@link #setOnDropDownListener(OnDropDownListener)} set listener which will be excuted when drop down, but you - * should call {@link #onDropDownComplete()} manual at the end of listener to reinstate status.
    • \ + * should call {@link #onDropDownComplete()} manual at the end of listener to reinstate status. *
    • {@link #setHeaderDefaultText(String)}, {@link #setHeaderLoadingText(String)}, {@link #setHeaderPullText(String)}, * {@link #setHeaderReleaseText(String)}, {@link #setHeaderSecondText(CharSequence)} to set text
    • *
    @@ -50,13 +50,13 @@ * principle see http://trinea.iteye.com/blog/1562281 * * - * @author Trinea 2012-5-20 + * @author Trinea 2012-5-20 */ public class DropDownListView extends ListView implements OnScrollListener { - private boolean isDropDownStyle = true; - private boolean isOnBottomStyle = true; - private boolean isAutoLoadOnBottom = false; + private boolean isDropDownStyle = true; + private boolean isOnBottomStyle = true; + private boolean isAutoLoadOnBottom = false; private String headerDefaultText; private String headerPullText; @@ -84,20 +84,22 @@ public class DropDownListView extends ListView implements OnScrollListener { private OnScrollListener onScrollListener; /** rate about drop down distance and header padding top when drop down **/ - private float headerPaddingTopRate = 1.5f; + private float headerPaddingTopRate = 1.5f; /** min distance which header can release to loading **/ - private int headerReleaseMinDistance = 30; + private int headerReleaseMinDistance; /** whether bottom listener has more **/ - private boolean hasMore = true; + private boolean hasMore = true; /** whether show footer loading progress bar when loading **/ - private boolean isShowFooterProgressBar = true; + private boolean isShowFooterProgressBar = true; + /** whether show footer when no more data **/ + private boolean isShowFooterWhenNoMore = false; private int currentScrollState; private int currentHeaderStatus; /** whether reached top, when has reached top, don't show header layout **/ - private boolean hasReachedTop = false; + private boolean hasReachedTop = false; /** image flip animation **/ private RotateAnimation flipAnimation; @@ -111,18 +113,21 @@ public class DropDownListView extends ListView implements OnScrollListener { /** y of point which user touch down **/ private float actionDownPointY; - public DropDownListView(Context context){ + /** whether is on bottom loading **/ + private boolean isOnBottomLoading = false; + + public DropDownListView(Context context) { super(context); init(context); } - public DropDownListView(Context context, AttributeSet attrs){ + public DropDownListView(Context context, AttributeSet attrs) { super(context, attrs); getAttrs(context, attrs); init(context); } - public DropDownListView(Context context, AttributeSet attrs, int defStyle){ + public DropDownListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); getAttrs(context, attrs); init(context); @@ -153,13 +158,15 @@ private void initDropDownStyle() { return; } + headerReleaseMinDistance = context.getResources().getDimensionPixelSize( + R.dimen.drop_down_list_header_release_min_distance); flipAnimation = new RotateAnimation(0, 180, RotateAnimation.RELATIVE_TO_SELF, 0.5f, - RotateAnimation.RELATIVE_TO_SELF, 0.5f); + RotateAnimation.RELATIVE_TO_SELF, 0.5f); flipAnimation.setInterpolator(new LinearInterpolator()); flipAnimation.setDuration(250); flipAnimation.setFillAfter(true); reverseFlipAnimation = new RotateAnimation(-180, 0, RotateAnimation.RELATIVE_TO_SELF, 0.5f, - RotateAnimation.RELATIVE_TO_SELF, 0.5f); + RotateAnimation.RELATIVE_TO_SELF, 0.5f); reverseFlipAnimation.setInterpolator(new LinearInterpolator()); reverseFlipAnimation.setDuration(250); reverseFlipAnimation.setFillAfter(true); @@ -290,6 +297,24 @@ public void setShowFooterProgressBar(boolean isShowFooterProgressBar) { this.isShowFooterProgressBar = isShowFooterProgressBar; } + /** + * get isShowFooterWhenNoMore + * + * @return the isShowFooterWhenNoMore + */ + public boolean isShowFooterWhenNoMore() { + return isShowFooterWhenNoMore; + } + + /** + * set isShowFooterWhenNoMore + * + * @param isShowFooterWhenNoMore the isShowFooterWhenNoMore to set + */ + public void setShowFooterWhenNoMore(boolean isShowFooterWhenNoMore) { + this.isShowFooterWhenNoMore = isShowFooterWhenNoMore; + } + /** * get footer button * @@ -323,50 +348,55 @@ public void setOnDropDownListener(OnDropDownListener onDropDownListener) { * @param onBottomListener */ public void setOnBottomListener(OnClickListener onBottomListener) { + if (!isDropDownStyle) { + throw new RuntimeException( + "isDropDownStyle is false, cannot call setOnBottomListener, you can call setDropDownStyle(true) at fitst."); + } footerButton.setOnClickListener(onBottomListener); } @Override public boolean onTouchEvent(MotionEvent event) { - if (isDropDownStyle) { - hasReachedTop = false; - - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - actionDownPointY = event.getY(); - break; - case MotionEvent.ACTION_MOVE: - adjustHeaderPadding(event); - break; - case MotionEvent.ACTION_UP: - if (!isVerticalScrollBarEnabled()) { - setVerticalScrollBarEnabled(true); - } - /** - * set status when finger leave screen if first item visible and header status is not - * HEADER_STATUS_LOADING - *
      - *
    • if current header status is HEADER_STATUS_RELEASE_TO_LOAD, call onDropDown.
    • - *
    • if current header status is HEADER_STATUS_DROP_DOWN_TO_LOAD, then set header status to - * HEADER_STATUS_CLICK_TO_LOAD and hide header layout.
    • - *
    - */ - if (getFirstVisiblePosition() == 0 && currentHeaderStatus != HEADER_STATUS_LOADING) { - switch (currentHeaderStatus) { - case HEADER_STATUS_RELEASE_TO_LOAD: - onDropDown(); - break; - case HEADER_STATUS_DROP_DOWN_TO_LOAD: - setHeaderStatusClickToLoad(); - setSecondPositionVisible(); - break; - case HEADER_STATUS_CLICK_TO_LOAD: - default: - break; - } + if (!isDropDownStyle) { + return super.onTouchEvent(event); + } + + hasReachedTop = false; + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + actionDownPointY = event.getY(); + break; + case MotionEvent.ACTION_MOVE: + adjustHeaderPadding(event); + break; + case MotionEvent.ACTION_UP: + if (!isVerticalScrollBarEnabled()) { + setVerticalScrollBarEnabled(true); + } + /** + * set status when finger leave screen if first item visible and header status is not + * HEADER_STATUS_LOADING + *
      + *
    • if current header status is HEADER_STATUS_RELEASE_TO_LOAD, call onDropDown.
    • + *
    • if current header status is HEADER_STATUS_DROP_DOWN_TO_LOAD, then set header status to + * HEADER_STATUS_CLICK_TO_LOAD and hide header layout.
    • + *
    + */ + if (getFirstVisiblePosition() == 0 && currentHeaderStatus != HEADER_STATUS_LOADING) { + switch (currentHeaderStatus) { + case HEADER_STATUS_RELEASE_TO_LOAD: + onDropDown(); + break; + case HEADER_STATUS_DROP_DOWN_TO_LOAD: + setHeaderStatusClickToLoad(); + setSecondPositionVisible(); + break; + case HEADER_STATUS_CLICK_TO_LOAD: + default: + break; } - break; - } + } + break; } return super.onTouchEvent(event); } @@ -380,8 +410,9 @@ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCoun * and header status is not HEADER_STATUS_LOADING *
      * if header layout is visiable, - *
    • if height of header is higher than a fixed value, then set header status to RELEASE_TO_REFRESH.
    • - *
    • else set header status to DROP_DOWN_TO_REFRESH.
    • + *
    • if height of header is higher than a fixed value, then set header status to + * HEADER_STATUS_RELEASE_TO_LOAD.
    • + *
    • else set header status to HEADER_STATUS_DROP_DOWN_TO_LOAD.
    • *
    *
      * if header layout is not visiable, @@ -390,17 +421,17 @@ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCoun */ if (firstVisibleItem == 0) { headerImage.setVisibility(View.VISIBLE); - if (headerLayout.getBottom() >= headerOriginalHeight + headerReleaseMinDistance - || headerLayout.getTop() >= 0) { + int pointBottom = headerOriginalHeight + headerReleaseMinDistance; + if (headerLayout.getBottom() >= pointBottom) { setHeaderStatusReleaseToLoad(); - } else if (headerLayout.getBottom() < headerOriginalHeight + headerReleaseMinDistance) { + } else if (headerLayout.getBottom() < pointBottom) { setHeaderStatusDropDownToLoad(); } } else { setHeaderStatusClickToLoad(); } } else if (currentScrollState == SCROLL_STATE_FLING && firstVisibleItem == 0 - && currentHeaderStatus != HEADER_STATUS_LOADING) { + && currentHeaderStatus != HEADER_STATUS_LOADING) { /** * when state of ListView is SCROLL_STATE_FLING(ListView is scrolling but finger has leave screen) and * first item(header layout) is visible and header status is not HEADER_STATUS_LOADING, then hide first @@ -521,7 +552,8 @@ private void onBottomBegin() { * on bottom loading, you can call it by manual, but you should manual call onBottomComplete at the same time. */ public void onBottom() { - if (isOnBottomStyle) { + if (isOnBottomStyle && !isOnBottomLoading) { + isOnBottomLoading = true; onBottomBegin(); footerButton.performClick(); } @@ -535,19 +567,24 @@ public void onBottomComplete() { if (isShowFooterProgressBar) { footerProgressBar.setVisibility(View.GONE); } - footerButton.setEnabled(true); if (!hasMore) { footerButton.setText(footerNoMoreText); + footerButton.setEnabled(false); + if (!isShowFooterWhenNoMore) { + removeFooterView(footerLayout); + } } else { footerButton.setText(footerDefaultText); + footerButton.setEnabled(true); } + isOnBottomLoading = false; } } /** * OnDropDownListener, called when header released * - * @author Trinea 2012-5-31 + * @author Trinea 2012-5-31 */ public interface OnDropDownListener { @@ -584,6 +621,24 @@ public boolean isHasMore() { return hasMore; } + /** + * get header layout view + * + * @return + */ + public RelativeLayout getHeaderLayout() { + return headerLayout; + } + + /** + * get footer layout view + * + * @return + */ + public RelativeLayout getFooterLayout() { + return footerLayout; + } + /** * get rate about drop down distance and header padding top when drop down * @@ -841,11 +896,17 @@ private void setHeaderStatusLoading() { private void adjustHeaderPadding(MotionEvent ev) { // adjust header padding according to motion event history int pointerCount = ev.getHistorySize(); + if (isVerticalFadingEdgeEnabled()) { + setVerticalScrollBarEnabled(false); + } for (int i = 0; i < pointerCount; i++) { - if (currentHeaderStatus == HEADER_STATUS_RELEASE_TO_LOAD) { - headerLayout.setPadding(headerLayout.getPaddingLeft(), - (int)(((ev.getHistoricalY(i) - actionDownPointY) - headerOriginalHeight) / headerPaddingTopRate), - headerLayout.getPaddingRight(), headerLayout.getPaddingBottom()); + if (currentHeaderStatus == HEADER_STATUS_DROP_DOWN_TO_LOAD + || currentHeaderStatus == HEADER_STATUS_RELEASE_TO_LOAD) { + headerLayout + .setPadding( + headerLayout.getPaddingLeft(), + (int)(((ev.getHistoricalY(i) - actionDownPointY) - headerOriginalHeight) / headerPaddingTopRate), + headerLayout.getPaddingRight(), headerLayout.getPaddingBottom()); } } } @@ -855,7 +916,7 @@ private void adjustHeaderPadding(MotionEvent ev) { */ private void resetHeaderPadding() { headerLayout.setPadding(headerLayout.getPaddingLeft(), headerOriginalTopPadding, - headerLayout.getPaddingRight(), headerLayout.getPaddingBottom()); + headerLayout.getPaddingRight(), headerLayout.getPaddingBottom()); } /** @@ -888,8 +949,8 @@ private void measureHeaderLayout(View child) { */ private void getAttrs(Context context, AttributeSet attrs) { TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.drop_down_list_attr); - isDropDownStyle = ta.getBoolean(R.styleable.drop_down_list_attr_isDropDownStyle, true); - isOnBottomStyle = ta.getBoolean(R.styleable.drop_down_list_attr_isOnBottomStyle, true); + isDropDownStyle = ta.getBoolean(R.styleable.drop_down_list_attr_isDropDownStyle, false); + isOnBottomStyle = ta.getBoolean(R.styleable.drop_down_list_attr_isOnBottomStyle, false); isAutoLoadOnBottom = ta.getBoolean(R.styleable.drop_down_list_attr_isAutoLoadOnBottom, false); ta.recycle(); } diff --git a/src/main/java/cn/trinea/android/common/view/HorizontalListView.java b/src/main/java/cn/trinea/android/common/view/HorizontalListView.java new file mode 100644 index 0000000..13d30fd --- /dev/null +++ b/src/main/java/cn/trinea/android/common/view/HorizontalListView.java @@ -0,0 +1,383 @@ +/* + * HorizontalListView.java v1.5 + * + * + * The MIT License Copyright (c) 2011 Paul Soucy (paul@dev-smart.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package cn.trinea.android.common.view; + +import java.util.LinkedList; +import java.util.Queue; + +import android.content.Context; +import android.database.DataSetObserver; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.GestureDetector; +import android.view.GestureDetector.OnGestureListener; +import android.view.MotionEvent; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ListAdapter; +import android.widget.Scroller; + +public class HorizontalListView extends AdapterView { + + public boolean mAlwaysOverrideTouch = true; + protected ListAdapter mAdapter; + private int mLeftViewIndex = -1; + private int mRightViewIndex = 0; + protected int mCurrentX; + protected int mNextX; + private int mMaxX = Integer.MAX_VALUE; + private int mDisplayOffset = 0; + protected Scroller mScroller; + private GestureDetector mGesture; + private Queue mRemovedViewQueue = new LinkedList(); + private OnItemSelectedListener mOnItemSelected; + private OnItemClickListener mOnItemClicked; + private OnItemLongClickListener mOnItemLongClicked; + private boolean mDataChanged = false; + + public HorizontalListView(Context context, AttributeSet attrs) { + super(context, attrs); + initView(); + } + + private synchronized void initView() { + mLeftViewIndex = -1; + mRightViewIndex = 0; + mDisplayOffset = 0; + mCurrentX = 0; + mNextX = 0; + mMaxX = Integer.MAX_VALUE; + mScroller = new Scroller(getContext()); + mGesture = new GestureDetector(getContext(), mOnGesture); + } + + @Override + public void setOnItemSelectedListener(AdapterView.OnItemSelectedListener listener) { + mOnItemSelected = listener; + } + + @Override + public void setOnItemClickListener(AdapterView.OnItemClickListener listener) { + mOnItemClicked = listener; + } + + @Override + public void setOnItemLongClickListener(AdapterView.OnItemLongClickListener listener) { + mOnItemLongClicked = listener; + } + + private DataSetObserver mDataObserver = new DataSetObserver() { + + @Override + public void onChanged() { + synchronized (HorizontalListView.this) { + mDataChanged = true; + } + invalidate(); + requestLayout(); + } + + @Override + public void onInvalidated() { + reset(); + invalidate(); + requestLayout(); + } + + }; + + @Override + public ListAdapter getAdapter() { + return mAdapter; + } + + @Override + public View getSelectedView() { + // TODO: implement + return null; + } + + @Override + public void setAdapter(ListAdapter adapter) { + if (mAdapter != null) { + mAdapter.unregisterDataSetObserver(mDataObserver); + } + mAdapter = adapter; + mAdapter.registerDataSetObserver(mDataObserver); + reset(); + } + + private synchronized void reset() { + initView(); + removeAllViewsInLayout(); + requestLayout(); + } + + @Override + public void setSelection(int position) { + // TODO: implement + } + + private void addAndMeasureChild(final View child, int viewPos) { + LayoutParams params = child.getLayoutParams(); + if (params == null) { + params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); + } + + addViewInLayout(child, viewPos, params, true); + child.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST), + MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST)); + } + + @Override + protected synchronized void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + + if (mAdapter == null) { + return; + } + + if (mDataChanged) { + int oldCurrentX = mCurrentX; + initView(); + removeAllViewsInLayout(); + mNextX = oldCurrentX; + mDataChanged = false; + } + + if (mScroller.computeScrollOffset()) { + int scrollx = mScroller.getCurrX(); + mNextX = scrollx; + } + + if (mNextX <= 0) { + mNextX = 0; + mScroller.forceFinished(true); + } + if (mNextX >= mMaxX) { + mNextX = mMaxX; + mScroller.forceFinished(true); + } + + int dx = mCurrentX - mNextX; + + removeNonVisibleItems(dx); + fillList(dx); + positionItems(dx); + + mCurrentX = mNextX; + + if (!mScroller.isFinished()) { + post(new Runnable() { + + @Override + public void run() { + requestLayout(); + } + }); + + } + } + + private void fillList(final int dx) { + int edge = 0; + View child = getChildAt(getChildCount() - 1); + if (child != null) { + edge = child.getRight(); + } + fillListRight(edge, dx); + + edge = 0; + child = getChildAt(0); + if (child != null) { + edge = child.getLeft(); + } + fillListLeft(edge, dx); + + } + + private void fillListRight(int rightEdge, final int dx) { + while (rightEdge + dx < getWidth() && mRightViewIndex < mAdapter.getCount()) { + + View child = mAdapter.getView(mRightViewIndex, mRemovedViewQueue.poll(), this); + addAndMeasureChild(child, -1); + rightEdge += child.getMeasuredWidth(); + + if (mRightViewIndex == mAdapter.getCount() - 1) { + mMaxX = mCurrentX + rightEdge - getWidth(); + } + + if (mMaxX < 0) { + mMaxX = 0; + } + mRightViewIndex++; + } + + } + + private void fillListLeft(int leftEdge, final int dx) { + while (leftEdge + dx > 0 && mLeftViewIndex >= 0) { + View child = mAdapter.getView(mLeftViewIndex, mRemovedViewQueue.poll(), this); + addAndMeasureChild(child, 0); + leftEdge -= child.getMeasuredWidth(); + mLeftViewIndex--; + mDisplayOffset -= child.getMeasuredWidth(); + } + } + + private void removeNonVisibleItems(final int dx) { + View child = getChildAt(0); + while (child != null && child.getRight() + dx <= 0) { + mDisplayOffset += child.getMeasuredWidth(); + mRemovedViewQueue.offer(child); + removeViewInLayout(child); + mLeftViewIndex++; + child = getChildAt(0); + + } + + child = getChildAt(getChildCount() - 1); + while (child != null && child.getLeft() + dx >= getWidth()) { + mRemovedViewQueue.offer(child); + removeViewInLayout(child); + mRightViewIndex--; + child = getChildAt(getChildCount() - 1); + } + } + + private void positionItems(final int dx) { + if (getChildCount() > 0) { + mDisplayOffset += dx; + int left = mDisplayOffset; + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + int childWidth = child.getMeasuredWidth(); + child.layout(left, 0, left + childWidth, child.getMeasuredHeight()); + left += childWidth + child.getPaddingRight(); + } + } + } + + public synchronized void scrollTo(int x) { + mScroller.startScroll(mNextX, 0, x - mNextX, 0); + requestLayout(); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + boolean handled = super.dispatchTouchEvent(ev); + handled |= mGesture.onTouchEvent(ev); + return handled; + } + + protected boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + synchronized (HorizontalListView.this) { + mScroller.fling(mNextX, 0, (int)-velocityX, 0, 0, mMaxX, 0, 0); + } + requestLayout(); + + return true; + } + + protected boolean onDown(MotionEvent e) { + mScroller.forceFinished(true); + return true; + } + + private OnGestureListener mOnGesture = new GestureDetector.SimpleOnGestureListener() { + + @Override + public boolean onDown(MotionEvent e) { + return HorizontalListView.this.onDown(e); + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, + float velocityY) { + return HorizontalListView.this.onFling(e1, e2, velocityX, velocityY); + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, + float distanceY) { + + synchronized (HorizontalListView.this) { + mNextX += (int)distanceX; + } + requestLayout(); + + return true; + } + + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + if (isEventWithinView(e, child)) { + if (mOnItemClicked != null) { + mOnItemClicked.onItemClick(HorizontalListView.this, child, + mLeftViewIndex + 1 + i, + mAdapter.getItemId(mLeftViewIndex + 1 + i)); + } + if (mOnItemSelected != null) { + mOnItemSelected.onItemSelected(HorizontalListView.this, + child, mLeftViewIndex + 1 + i, + mAdapter.getItemId(mLeftViewIndex + 1 + i)); + } + break; + } + + } + return true; + } + + @Override + public void onLongPress(MotionEvent e) { + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = getChildAt(i); + if (isEventWithinView(e, child)) { + if (mOnItemLongClicked != null) { + mOnItemLongClicked.onItemLongClick( + HorizontalListView.this, child, mLeftViewIndex + 1 + + i, + mAdapter.getItemId(mLeftViewIndex + 1 + i)); + } + break; + } + + } + } + + private boolean isEventWithinView(MotionEvent e, View child) { + Rect viewRect = new Rect(); + int[] childPosition = new int[2]; + child.getLocationOnScreen(childPosition); + int left = childPosition[0]; + int right = left + child.getWidth(); + int top = childPosition[1]; + int bottom = top + child.getHeight(); + viewRect.set(left, top, right, bottom); + return viewRect.contains((int)e.getRawX(), (int)e.getRawY()); + } + }; + +} diff --git a/src/cn/trinea/android/common/view/SlideOnePageGallery.java b/src/main/java/cn/trinea/android/common/view/SlideOnePageGallery.java similarity index 87% rename from src/cn/trinea/android/common/view/SlideOnePageGallery.java rename to src/main/java/cn/trinea/android/common/view/SlideOnePageGallery.java index 85725bc..c5d8485 100644 --- a/src/cn/trinea/android/common/view/SlideOnePageGallery.java +++ b/src/main/java/cn/trinea/android/common/view/SlideOnePageGallery.java @@ -9,19 +9,19 @@ /** * SlideOnePageGallery, only slide one page every slide * - * @author Trinea 2013-3-22 + * @author Trinea 2013-3-22 */ public class SlideOnePageGallery extends Gallery { - public SlideOnePageGallery(Context context, AttributeSet attrs, int defStyle){ + public SlideOnePageGallery(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } - public SlideOnePageGallery(Context context, AttributeSet attrs){ + public SlideOnePageGallery(Context context, AttributeSet attrs) { super(context, attrs); } - public SlideOnePageGallery(Context context){ + public SlideOnePageGallery(Context context) { super(context); } @@ -31,7 +31,6 @@ private boolean isScrollingLeft(MotionEvent e1, MotionEvent e2) { @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - int kEvent; if (isScrollingLeft(e1, e2)) { // Check if scrolling left diff --git a/res/drawable-hdpi/drop_down_list_arrow.png b/src/main/res/drawable-hdpi/drop_down_list_arrow.png similarity index 100% rename from res/drawable-hdpi/drop_down_list_arrow.png rename to src/main/res/drawable-hdpi/drop_down_list_arrow.png diff --git a/res/drawable-ldpi/drop_down_list_arrow.png b/src/main/res/drawable-ldpi/drop_down_list_arrow.png similarity index 100% rename from res/drawable-ldpi/drop_down_list_arrow.png rename to src/main/res/drawable-ldpi/drop_down_list_arrow.png diff --git a/res/drawable-mdpi/drop_down_list_arrow.png b/src/main/res/drawable-mdpi/drop_down_list_arrow.png similarity index 100% rename from res/drawable-mdpi/drop_down_list_arrow.png rename to src/main/res/drawable-mdpi/drop_down_list_arrow.png diff --git a/res/layout/drop_down_list_footer.xml b/src/main/res/layout/drop_down_list_footer.xml similarity index 86% rename from res/layout/drop_down_list_footer.xml rename to src/main/res/layout/drop_down_list_footer.xml index e46d3bc..94df385 100644 --- a/res/layout/drop_down_list_footer.xml +++ b/src/main/res/layout/drop_down_list_footer.xml @@ -17,8 +17,9 @@ android:id="@+id/drop_down_list_footer_button" style="@style/drop_down_list_footer_font_style" android:layout_width="wrap_content" - android:layout_height="match_parent" + android:layout_height="@dimen/drop_down_list_footer_button_height" android:layout_centerInParent="true" + android:layout_marginLeft="@dimen/drop_down_list_footer_button_margin_left" android:background="@android:color/transparent" android:gravity="center" android:text="@string/drop_down_list_footer_default_text" /> diff --git a/res/layout/drop_down_list_header.xml b/src/main/res/layout/drop_down_list_header.xml similarity index 100% rename from res/layout/drop_down_list_header.xml rename to src/main/res/layout/drop_down_list_header.xml diff --git a/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml similarity index 92% rename from res/values-zh-rCN/strings.xml rename to src/main/res/values-zh-rCN/strings.xml index 56bcae0..8c4a8cc 100644 --- a/res/values-zh-rCN/strings.xml +++ b/src/main/res/values-zh-rCN/strings.xml @@ -9,5 +9,4 @@ 加载中… 没有更多了 - Image \ No newline at end of file diff --git a/src/main/res/values-zh-rTW/strings.xml b/src/main/res/values-zh-rTW/strings.xml new file mode 100644 index 0000000..1df3775 --- /dev/null +++ b/src/main/res/values-zh-rTW/strings.xml @@ -0,0 +1,12 @@ + + + + 點擊可以刷新 + 下拉可以刷新 + 松開可以刷新 + 加載中… + 更多 + 加載中… + 沒有更多了 + + \ No newline at end of file diff --git a/res/values/attrs.xml b/src/main/res/values/attrs.xml similarity index 100% rename from res/values/attrs.xml rename to src/main/res/values/attrs.xml diff --git a/res/values/colors.xml b/src/main/res/values/colors.xml similarity index 100% rename from res/values/colors.xml rename to src/main/res/values/colors.xml diff --git a/res/values/dimens.xml b/src/main/res/values/dimens.xml similarity index 60% rename from res/values/dimens.xml rename to src/main/res/values/dimens.xml index c18263e..3e2a2de 100644 --- a/res/values/dimens.xml +++ b/src/main/res/values/dimens.xml @@ -2,7 +2,10 @@ 12dp 15dp + 20dp 36dp 36dp + 8dp + 48dp \ No newline at end of file diff --git a/res/values/strings.xml b/src/main/res/values/strings.xml similarity index 100% rename from res/values/strings.xml rename to src/main/res/values/strings.xml diff --git a/res/values/styles.xml b/src/main/res/values/styles.xml similarity index 100% rename from res/values/styles.xml rename to src/main/res/values/styles.xml