├── .classpath
├── .gitattributes
├── .gitignore
├── .project
├── .settings
└── org.eclipse.jdt.core.prefs
├── AndroidManifest.xml
├── README.md
├── bin
├── AndroidManifest.xml
├── classes
│ └── com
│ │ └── v7lin
│ │ └── android
│ │ ├── BuildConfig.class
│ │ ├── R$attr.class
│ │ ├── R$color.class
│ │ ├── R$drawable.class
│ │ ├── R$id.class
│ │ ├── R$layout.class
│ │ ├── R$string.class
│ │ ├── R$style.class
│ │ ├── R.class
│ │ ├── app
│ │ ├── SuperActivity.class
│ │ ├── SuperAppConfig.class
│ │ ├── SuperReceiver.class
│ │ └── SuperService.class
│ │ ├── content
│ │ ├── EnvRes.class
│ │ ├── EnvResourcesWrapper.class
│ │ ├── dex
│ │ │ ├── EnvDexActivity.class
│ │ │ ├── EnvDexClassLoader.class
│ │ │ ├── EnvDexConst.class
│ │ │ ├── EnvDexContextWrapper.class
│ │ │ ├── EnvDexResourcesManager$EnvDexResourcesManagerHolder.class
│ │ │ ├── EnvDexResourcesManager.class
│ │ │ ├── EnvDexResourcesWrapper.class
│ │ │ ├── EnvDexSetup$EnvDexSetupHolder.class
│ │ │ ├── EnvDexSetup.class
│ │ │ └── EnvDexSetupImpl.class
│ │ ├── idex
│ │ │ └── IDexTest.class
│ │ └── res
│ │ │ ├── EnvActivity.class
│ │ │ ├── EnvAppConfig.class
│ │ │ ├── EnvChecker.class
│ │ │ ├── EnvCheckerCompat$EarlyEnvTextCheckerCompatImpl.class
│ │ │ ├── EnvCheckerCompat$EnvTextCheckerCompatImpl.class
│ │ │ ├── EnvCheckerCompat$IceCreamSandwichEnvTextCheckerCompatImpl.class
│ │ │ ├── EnvCheckerCompat$JellyBeanMr1EnvTextCheckerCompatImpl.class
│ │ │ ├── EnvCheckerCompat$UnknownEnvTextCheckerCompatImpl.class
│ │ │ ├── EnvCheckerCompat.class
│ │ │ ├── EnvCheckerCompatIceCreamSandwich.class
│ │ │ ├── EnvCheckerCompatJellyBeanMr1.class
│ │ │ ├── EnvCheckerCompatUnknown.class
│ │ │ ├── EnvColorChecker.class
│ │ │ ├── EnvContextWrapper.class
│ │ │ ├── EnvDrawableChecker.class
│ │ │ ├── EnvLayoutInflaterWrapper$EnvFactory.class
│ │ │ ├── EnvLayoutInflaterWrapper.class
│ │ │ ├── EnvReceiver.class
│ │ │ ├── EnvService.class
│ │ │ ├── EnvSetup$EnvSetupHolder.class
│ │ │ ├── EnvSetup.class
│ │ │ ├── EnvSetupImpl.class
│ │ │ ├── EnvSkinResClearCompat$BaseEnvResClearCompatImpl.class
│ │ │ ├── EnvSkinResClearCompat$EarlyEnvResClearCompatImpl.class
│ │ │ ├── EnvSkinResClearCompat$EnvResClearCompatImpl.class
│ │ │ ├── EnvSkinResClearCompat$IceCreamSandwichEnvResClearCompatImpl.class
│ │ │ ├── EnvSkinResClearCompat$JellyBeanEnvResClearCompatImpl.class
│ │ │ ├── EnvSkinResClearCompat.class
│ │ │ ├── EnvSkinResourcesManager$EnvResourcesManagerHolder.class
│ │ │ ├── EnvSkinResourcesManager.class
│ │ │ ├── EnvSkinResourcesWrapper.class
│ │ │ ├── EnvTextChecker.class
│ │ │ ├── EnvTypefaceChecker.class
│ │ │ ├── EnvTypefaceManager$EnvTypefaceManagerHolder.class
│ │ │ └── EnvTypefaceManager.class
│ │ ├── crash
│ │ ├── CrashReport.class
│ │ ├── CrashUncaughtExceptionHandler.class
│ │ └── UncaughtExceptionHandlerWrapper.class
│ │ ├── os
│ │ ├── StatFsCompat$EarlyStatFsCompatImpl.class
│ │ ├── StatFsCompat$JellyBeanMr2StatFsCompatImpl.class
│ │ ├── StatFsCompat$StatFsCompatImpl.class
│ │ ├── StatFsCompat.class
│ │ ├── StatFsCompatJellyBeanMr2.class
│ │ ├── env
│ │ │ ├── PathConst.class
│ │ │ └── PathUtils.class
│ │ └── storage
│ │ │ ├── StorageManagerCompat$EarlyStorageManagerCompatImpl.class
│ │ │ ├── StorageManagerCompat$GingerbreadStorageManagerCompatImpl.class
│ │ │ ├── StorageManagerCompat$StorageManagerCompatImpl.class
│ │ │ ├── StorageManagerCompat.class
│ │ │ ├── StorageManagerCompatGingerbread.class
│ │ │ └── StorageUtils.class
│ │ └── test
│ │ ├── TestActivity$1.class
│ │ ├── TestActivity.class
│ │ └── TestDexActivity.class
└── proguard.txt
├── gen
└── com
│ └── v7lin
│ └── android
│ ├── BuildConfig.java
│ └── R.java
├── lint.xml
├── proguard.cfg
├── project.properties
├── res
├── color
│ └── test_selector.xml
├── drawable-hdpi
│ ├── btn_default_normal.9.png
│ ├── btn_default_selected.9.png
│ └── icon.png
├── drawable
│ └── test_selector.xml
├── layout
│ ├── layout_dex.xml
│ └── layout_main.xml
└── values
│ ├── color.xml
│ ├── strings.xml
│ ├── styles.xml
│ └── themes.xml
└── src
└── com
└── v7lin
└── android
├── app
├── SuperActivity.java
├── SuperAppConfig.java
├── SuperReceiver.java
└── SuperService.java
├── content
├── EnvRes.java
├── EnvResourcesWrapper.java
├── dex
│ ├── EnvDexActivity.java
│ ├── EnvDexClassLoader.java
│ ├── EnvDexConst.java
│ ├── EnvDexContextWrapper.java
│ ├── EnvDexResourcesManager.java
│ ├── EnvDexResourcesWrapper.java
│ ├── EnvDexSetup.java
│ └── EnvDexSetupImpl.java
├── idex
│ └── IDexTest.java
└── res
│ ├── EnvActivity.java
│ ├── EnvAppConfig.java
│ ├── EnvChecker.java
│ ├── EnvCheckerCompat.java
│ ├── EnvCheckerCompatIceCreamSandwich.java
│ ├── EnvCheckerCompatJellyBeanMr1.java
│ ├── EnvCheckerCompatUnknown.java
│ ├── EnvColorChecker.java
│ ├── EnvContextWrapper.java
│ ├── EnvDrawableChecker.java
│ ├── EnvLayoutInflaterWrapper.java
│ ├── EnvReceiver.java
│ ├── EnvService.java
│ ├── EnvSetup.java
│ ├── EnvSetupImpl.java
│ ├── EnvSkinResClearCompat.java
│ ├── EnvSkinResourcesManager.java
│ ├── EnvSkinResourcesWrapper.java
│ ├── EnvTextChecker.java
│ ├── EnvTypefaceChecker.java
│ └── EnvTypefaceManager.java
├── crash
├── CrashReport.java
├── CrashUncaughtExceptionHandler.java
└── UncaughtExceptionHandlerWrapper.java
├── os
├── StatFsCompat.java
├── StatFsCompatJellyBeanMr2.java
├── env
│ ├── PathConst.java
│ └── PathUtils.java
└── storage
│ ├── StorageManagerCompat.java
│ ├── StorageManagerCompatGingerbread.java
│ └── StorageUtils.java
└── test
├── TestActivity.java
└── TestDexActivity.java
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 | *.sln merge=union
7 | *.csproj merge=union
8 | *.vbproj merge=union
9 | *.fsproj merge=union
10 | *.dbproj merge=union
11 |
12 | # Standard to msysgit
13 | *.doc diff=astextplain
14 | *.DOC diff=astextplain
15 | *.docx diff=astextplain
16 | *.DOCX diff=astextplain
17 | *.dot diff=astextplain
18 | *.DOT diff=astextplain
19 | *.pdf diff=astextplain
20 | *.PDF diff=astextplain
21 | *.rtf diff=astextplain
22 | *.RTF diff=astextplain
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Windows image file caches
2 | Thumbs.db
3 | ehthumbs.db
4 |
5 | # Folder config file
6 | Desktop.ini
7 |
8 | # Recycle Bin used on file shares
9 | $RECYCLE.BIN/
10 |
11 | # Windows Installer files
12 | *.cab
13 | *.msi
14 | *.msm
15 | *.msp
16 |
17 | # =========================
18 | # Operating System Files
19 | # =========================
20 |
21 | # OSX
22 | # =========================
23 |
24 | .DS_Store
25 | .AppleDouble
26 | .LSOverride
27 |
28 | # Icon must end with two \r
29 | Icon
30 |
31 | # Thumbnails
32 | ._*
33 |
34 | # Files that might appear on external disk
35 | .Spotlight-V100
36 | .Trashes
37 |
38 | # Directories potentially created on remote AFP share
39 | .AppleDB
40 | .AppleDesktop
41 | Network Trash Folder
42 | Temporary Items
43 | .apdisk
44 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | Android_Skin
4 |
5 |
6 |
7 |
8 |
9 | com.android.ide.eclipse.adt.ResourceManagerBuilder
10 |
11 |
12 |
13 |
14 | com.android.ide.eclipse.adt.PreCompilerBuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.jdt.core.javabuilder
20 |
21 |
22 |
23 |
24 | com.android.ide.eclipse.adt.ApkBuilder
25 |
26 |
27 |
28 |
29 |
30 | com.android.ide.eclipse.adt.AndroidNature
31 | org.eclipse.jdt.core.javanature
32 |
33 |
34 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.jdt.core.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
3 | org.eclipse.jdt.core.compiler.compliance=1.6
4 | org.eclipse.jdt.core.compiler.source=1.6
5 |
--------------------------------------------------------------------------------
/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
39 |
40 |
43 |
44 |
45 |
52 |
53 |
54 |
55 |
56 |
59 |
60 |
69 |
70 |
71 |
72 |
73 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Android_Skin
2 | ============
3 |
4 | @Deprecated
5 | 项目已迁移至 https://github.com/v7lin/Android_Skin_2.0
6 |
7 | 前言
8 |
9 | 先来拉下仇恨,看看能不能让让仇恨满级 ...
10 | 使用 ActionBar 的同学,请绕路 ... 我救不了你 ...
11 | 喜欢用 new View 的同学,请谨慎考虑一下 ... 这是一条不归路 ...
12 | 剩下仅使用 layout.xml 的同学 ...
13 |
14 | 介绍
15 |
16 | Android 安装式的插件 ... 我就不介绍了 ... 个人认为花精力去研究这种东西,毫无意义可言 ...
17 | 本文将插件分为两种:皮肤插件和功能插件。相应的同名同类型资源优先级关系:皮肤插件资源 > 宿主应用资源 > 功能插件资源
18 |
19 | 背景
20 |
21 | 虽然本文介绍的是 Android 非安装式的插件,但是,这里的插件都是 apk ... 当然,为了避免用户错误安装了插件 apk,这些插件 apk 发布前,最好嫑签名 ...
22 | 以下三点,让我一直感叹,Google 为何如此坑爹的原因 ...
23 | 1)同一 resid,可能是宿主应用包和插件应用包都有,而对应资源却是不同的 ...
24 | 2)Android SDK Package ID 以 0x01 开头 ...
25 | 3)Android App Package ID 以 0x7f 开头 ...
26 |
27 | 皮肤插件
28 |
29 | 寻寻觅觅,冷冷清清,凄凄惨惨戚戚 ... 一路查找资料和查看Android源码,终于找到突破口了 ...
30 | Android 获取资源,最终都是通过 Resource 这个类来代理 AssetManager 实现的。
31 | 皮肤插件 Apk 只需要编译 res 和 Manifest 即可 ... 不需要任何 Code ...
32 |
33 | 1)接入口
34 | Activity 也是一个 Context ... 更准确的来说,他是一个 ContextThemeWrapper ... 而真正的 Context 是通过 attachBaseContext 设置进来的 ... 那么,我们以此为入口,通过重写 attachBaseContext 代理一下外部设置进来的 ContextImpl ...
35 | 2)插件资源的管理类的选择
36 | 细究 Android 资源管理机制,我们可以轻易发现,Android 资源的管理是通过 Resource 这个类来管理。而APP Resource 在内存中只有一个备份的,以WeakReference 形式。那么我们以这个思路,设计一个皮肤插件管理类,管理皮肤插件 Resource。
37 | 3)如何换非 inflater View 的资源呢?
38 | 这里的资源不一定是以文件形式存在,R 文件里包含的所有内容都是资源。
39 | 也许,有看过插件式开发的同学,会对此嗤之以鼻,可能会认为只要加载了皮肤插件包资源就 OK了。但这皮肤插件资源必须与宿主应用的资源数量、类型要保持一致,多一个少一个都不行。原因是:同一 resid,可能是宿主应用包和插件应用包都有,而对应资源却是不同的 ... 但,后续开发,不会涉及资源这一模块的变化么?那是不可能的 ...
40 | 那么,我们要怎样实现换肤功能呢?首先我们要明确,我们换肤要换啥?换的是同名同类型资源(包含drawable、color、colorstatelist、string、text、dimen等。但坚决不能换 id。不建议换 layout,layout 与代码中的逻辑绑定比较深) ...
41 | 4)如何换 inflater View 的资源呢?
42 | 看到上面那些内容,喜欢用 new View 的同学应该很开心吧 ... 是啊 ... 我曾经也是喜欢用 new View ... 也为此开心了一阵 ... 但随之而来的是打击 ... 当我欢欢喜喜的拿着研究成功给老大看了一下 ... 他就指出,layout.xml 中的资源能够一键搞定么?一键搞定么?搞定么?么? ...
43 | 我勒个去了 ... 还真是,这解决方案还真没啥实施价值,毕竟工程中还有很多 layout ... Toast.makeText 中也有 layout ... 擦了 ... 咋整?
44 | 众里寻她千百度,蓦然回首,那人却在灯火阑珊处 ... 就酱紫 ... 半个月苦苦寻觅,终于找到解决方案了 ... LayoutInflater ...
45 |
46 | 功能插件
47 |
48 | 得空再写这些文档吧 ...
49 |
50 | 详细介绍见:http://www.eoeandroid.com/thread-553503-1-1.html
51 |
52 | 说多了都是泪,就酱紫大家将就着自己读代码吧 ... 如有不理解的,可以发 Email 问我 ... 过两天再学一下怎么传几张图,让大家看看效果 ...
53 |
54 | Author: 林恒龙
55 | Email:v7lin@qq.com
56 |
--------------------------------------------------------------------------------
/bin/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
39 |
40 |
43 |
44 |
45 |
52 |
53 |
54 |
55 |
56 |
59 |
60 |
69 |
70 |
71 |
72 |
73 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/BuildConfig.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/BuildConfig.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/R$attr.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/R$attr.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/R$color.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/R$color.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/R$drawable.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/R$drawable.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/R$id.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/R$id.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/R$layout.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/R$layout.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/R$string.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/R$string.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/R$style.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/R$style.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/R.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/R.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/app/SuperActivity.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/app/SuperActivity.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/app/SuperAppConfig.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/app/SuperAppConfig.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/app/SuperReceiver.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/app/SuperReceiver.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/app/SuperService.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/app/SuperService.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/EnvRes.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/EnvRes.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/EnvResourcesWrapper.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/EnvResourcesWrapper.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/dex/EnvDexActivity.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/dex/EnvDexActivity.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/dex/EnvDexClassLoader.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/dex/EnvDexClassLoader.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/dex/EnvDexConst.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/dex/EnvDexConst.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/dex/EnvDexContextWrapper.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/dex/EnvDexContextWrapper.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/dex/EnvDexResourcesManager$EnvDexResourcesManagerHolder.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/dex/EnvDexResourcesManager$EnvDexResourcesManagerHolder.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/dex/EnvDexResourcesManager.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/dex/EnvDexResourcesManager.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/dex/EnvDexResourcesWrapper.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/dex/EnvDexResourcesWrapper.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/dex/EnvDexSetup$EnvDexSetupHolder.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/dex/EnvDexSetup$EnvDexSetupHolder.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/dex/EnvDexSetup.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/dex/EnvDexSetup.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/dex/EnvDexSetupImpl.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/dex/EnvDexSetupImpl.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/idex/IDexTest.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/idex/IDexTest.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/res/EnvActivity.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/res/EnvActivity.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/res/EnvAppConfig.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/res/EnvAppConfig.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/res/EnvChecker.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/res/EnvChecker.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/res/EnvCheckerCompat$EarlyEnvTextCheckerCompatImpl.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/res/EnvCheckerCompat$EarlyEnvTextCheckerCompatImpl.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/res/EnvCheckerCompat$EnvTextCheckerCompatImpl.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/res/EnvCheckerCompat$EnvTextCheckerCompatImpl.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/res/EnvCheckerCompat$IceCreamSandwichEnvTextCheckerCompatImpl.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/res/EnvCheckerCompat$IceCreamSandwichEnvTextCheckerCompatImpl.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/res/EnvCheckerCompat$JellyBeanMr1EnvTextCheckerCompatImpl.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/res/EnvCheckerCompat$JellyBeanMr1EnvTextCheckerCompatImpl.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/res/EnvCheckerCompat$UnknownEnvTextCheckerCompatImpl.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/res/EnvCheckerCompat$UnknownEnvTextCheckerCompatImpl.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/res/EnvCheckerCompat.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/res/EnvCheckerCompat.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/res/EnvCheckerCompatIceCreamSandwich.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/res/EnvCheckerCompatIceCreamSandwich.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/res/EnvCheckerCompatJellyBeanMr1.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/res/EnvCheckerCompatJellyBeanMr1.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/res/EnvCheckerCompatUnknown.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/res/EnvCheckerCompatUnknown.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/res/EnvColorChecker.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/res/EnvColorChecker.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/res/EnvContextWrapper.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/res/EnvContextWrapper.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/res/EnvDrawableChecker.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/res/EnvDrawableChecker.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/res/EnvLayoutInflaterWrapper$EnvFactory.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/res/EnvLayoutInflaterWrapper$EnvFactory.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/res/EnvLayoutInflaterWrapper.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/res/EnvLayoutInflaterWrapper.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/res/EnvReceiver.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/res/EnvReceiver.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/res/EnvService.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/res/EnvService.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/res/EnvSetup$EnvSetupHolder.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/res/EnvSetup$EnvSetupHolder.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/res/EnvSetup.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/res/EnvSetup.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/res/EnvSetupImpl.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/res/EnvSetupImpl.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/res/EnvSkinResClearCompat$BaseEnvResClearCompatImpl.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/res/EnvSkinResClearCompat$BaseEnvResClearCompatImpl.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/res/EnvSkinResClearCompat$EarlyEnvResClearCompatImpl.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/res/EnvSkinResClearCompat$EarlyEnvResClearCompatImpl.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/res/EnvSkinResClearCompat$EnvResClearCompatImpl.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/res/EnvSkinResClearCompat$EnvResClearCompatImpl.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/res/EnvSkinResClearCompat$IceCreamSandwichEnvResClearCompatImpl.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/res/EnvSkinResClearCompat$IceCreamSandwichEnvResClearCompatImpl.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/res/EnvSkinResClearCompat$JellyBeanEnvResClearCompatImpl.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/res/EnvSkinResClearCompat$JellyBeanEnvResClearCompatImpl.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/res/EnvSkinResClearCompat.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/res/EnvSkinResClearCompat.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/res/EnvSkinResourcesManager$EnvResourcesManagerHolder.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/res/EnvSkinResourcesManager$EnvResourcesManagerHolder.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/res/EnvSkinResourcesManager.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/res/EnvSkinResourcesManager.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/res/EnvSkinResourcesWrapper.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/res/EnvSkinResourcesWrapper.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/res/EnvTextChecker.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/res/EnvTextChecker.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/res/EnvTypefaceChecker.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/res/EnvTypefaceChecker.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/res/EnvTypefaceManager$EnvTypefaceManagerHolder.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/res/EnvTypefaceManager$EnvTypefaceManagerHolder.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/content/res/EnvTypefaceManager.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/content/res/EnvTypefaceManager.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/crash/CrashReport.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/crash/CrashReport.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/crash/CrashUncaughtExceptionHandler.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/crash/CrashUncaughtExceptionHandler.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/crash/UncaughtExceptionHandlerWrapper.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/crash/UncaughtExceptionHandlerWrapper.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/os/StatFsCompat$EarlyStatFsCompatImpl.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/os/StatFsCompat$EarlyStatFsCompatImpl.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/os/StatFsCompat$JellyBeanMr2StatFsCompatImpl.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/os/StatFsCompat$JellyBeanMr2StatFsCompatImpl.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/os/StatFsCompat$StatFsCompatImpl.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/os/StatFsCompat$StatFsCompatImpl.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/os/StatFsCompat.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/os/StatFsCompat.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/os/StatFsCompatJellyBeanMr2.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/os/StatFsCompatJellyBeanMr2.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/os/env/PathConst.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/os/env/PathConst.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/os/env/PathUtils.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/os/env/PathUtils.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/os/storage/StorageManagerCompat$EarlyStorageManagerCompatImpl.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/os/storage/StorageManagerCompat$EarlyStorageManagerCompatImpl.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/os/storage/StorageManagerCompat$GingerbreadStorageManagerCompatImpl.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/os/storage/StorageManagerCompat$GingerbreadStorageManagerCompatImpl.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/os/storage/StorageManagerCompat$StorageManagerCompatImpl.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/os/storage/StorageManagerCompat$StorageManagerCompatImpl.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/os/storage/StorageManagerCompat.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/os/storage/StorageManagerCompat.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/os/storage/StorageManagerCompatGingerbread.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/os/storage/StorageManagerCompatGingerbread.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/os/storage/StorageUtils.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/os/storage/StorageUtils.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/test/TestActivity$1.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/test/TestActivity$1.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/test/TestActivity.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/test/TestActivity.class
--------------------------------------------------------------------------------
/bin/classes/com/v7lin/android/test/TestDexActivity.class:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/bin/classes/com/v7lin/android/test/TestDexActivity.class
--------------------------------------------------------------------------------
/bin/proguard.txt:
--------------------------------------------------------------------------------
1 | # view AndroidManifest.xml #generated:56
2 | -keep class android.test.InstrumentationTestRunner { (...); }
3 |
4 | # view AndroidManifest.xml #generated:60
5 | -keep class com.v7lin.android.content.EnvAppConfig { (...); }
6 |
7 | # view AndroidManifest.xml #generated:73
8 | -keep class com.v7lin.android.test.TestActivity { (...); }
9 |
10 | # view AndroidManifest.xml #generated:85
11 | -keep class com.v7lin.android.test.TestDexActivity { (...); }
12 |
13 |
--------------------------------------------------------------------------------
/gen/com/v7lin/android/BuildConfig.java:
--------------------------------------------------------------------------------
1 | /** Automatically generated file. DO NOT MODIFY */
2 | package com.v7lin.android;
3 |
4 | public final class BuildConfig {
5 | public final static boolean DEBUG = true;
6 | }
--------------------------------------------------------------------------------
/gen/com/v7lin/android/R.java:
--------------------------------------------------------------------------------
1 | /* AUTO-GENERATED FILE. DO NOT MODIFY.
2 | *
3 | * This class was automatically generated by the
4 | * aapt tool from the resource data it found. It
5 | * should not be modified by hand.
6 | */
7 |
8 | package com.v7lin.android;
9 |
10 | public final class R {
11 | public static final class attr {
12 | }
13 | public static final class color {
14 | public static final int black=0x7f040002;
15 | public static final int gray=0x7f040003;
16 | /** 基色
17 | */
18 | public static final int red=0x7f040000;
19 | public static final int test=0x7f040004;
20 | public static final int test_selector=0x7f040005;
21 | public static final int white=0x7f040001;
22 | }
23 | public static final class drawable {
24 | public static final int btn_default_normal=0x7f020000;
25 | public static final int btn_default_selected=0x7f020001;
26 | public static final int icon=0x7f020002;
27 | public static final int test_selector=0x7f020003;
28 | }
29 | public static final class id {
30 | public static final int button1=0x7f070001;
31 | public static final int checkBox1=0x7f070004;
32 | public static final int imageView1=0x7f070000;
33 | public static final int imageView2=0x7f070002;
34 | public static final int radioButton1=0x7f070005;
35 | public static final int textView1=0x7f070003;
36 | }
37 | public static final class layout {
38 | public static final int layout_dex=0x7f030000;
39 | public static final int layout_main=0x7f030001;
40 | }
41 | public static final class string {
42 | public static final int app_description=0x7f050001;
43 | public static final int app_name=0x7f050000;
44 | }
45 | public static final class style {
46 | public static final int Animation=0x7f060000;
47 | public static final int Theme_Normal=0x7f060001;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/lint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/proguard.cfg:
--------------------------------------------------------------------------------
1 | -optimizationpasses 5
2 | -dontusemixedcaseclassnames
3 | -dontskipnonpubliclibraryclasses
4 | -dontpreverify
5 | -dontoptimize
6 | -verbose
7 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
8 |
9 | # 注:做 SDK 混淆 Jar 时,不能添加优化属性
10 |
11 | # 为了方便查看外部崩溃 LOG 信息,便于定位修正 BUG
12 | -keepattributes Exceptions
13 | -keepattributes InnerClasses
14 | -keepattributes Signature
15 | -keepattributes Deprecated
16 | -keepattributes SourceFile
17 | -keepattributes LineNumberTable
18 | -keepattributes *Annotation*
19 | -keepattributes EnclosingMethod
20 |
21 | # 关闭 Log日志输出
22 | -assumenosideeffects class android.util.Log {
23 | public static *** d(...);
24 | public static *** v(...);
25 | public static *** i(...);
26 | }
27 |
28 | -keep public class * extends android.app.Activity
29 | -keep public class * extends android.app.Application
30 | -keep public class * extends android.app.Service
31 | -keep public class * extends android.content.BroadcastReceiver
32 | -keep public class * extends android.content.ContentProvider
33 | -keep public class * extends android.app.backup.BackupAgentHelper
34 | -keep public class * extends android.preference.Preference
35 | -keep public class com.android.vending.licensing.ILicensingService
36 |
37 | -keep public class * extends android.view.View {
38 | public (android.content.Context);
39 | public (android.content.Context, android.util.AttributeSet);
40 | public (android.content.Context, android.util.AttributeSet, int);
41 | public void set*(...);
42 | }
43 |
44 | -keepclasseswithmembernames class * {
45 | native ;
46 | }
47 |
48 | -keepclassmembers enum * {
49 | public static **[] values();
50 | public static ** valueOf(java.lang.String);
51 | }
52 |
53 | -keep class * implements android.os.Parcelable {
54 | public static final android.os.Parcelable$Creator *;
55 | }
56 |
57 | -keep public class org.apache.commons.logging.** {
58 | public protected *;
59 | }
60 | -keep public class android.os.** {
61 | public protected *;
62 | }
63 | -keep public class android.text.** {
64 | public protected *;
65 | }
66 | -keep public class android.net.http.** {
67 | public protected *;
68 | }
69 |
70 | -dontwarn org.apache.commons.logging.**
71 | -dontwarn android.os.**
72 | -dontwarn android.text.**
73 | -dontwarn android.net.http.**
74 | -dontwarn android.content.**
75 | -dontwarn android.view.**
76 | -dontwarn android.widget.**
77 |
78 | -dontwarn com.android.internal.widget.**
79 | -keep class com.android.internal.widget.* { *;}
80 |
--------------------------------------------------------------------------------
/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Indicates whether an apk should be generated for each density.
14 | split.density=false
15 | # Project target.
16 | target=android-20
17 | proguard.config=proguard.cfg
18 | android.library=false
19 |
--------------------------------------------------------------------------------
/res/color/test_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/res/drawable-hdpi/btn_default_normal.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/res/drawable-hdpi/btn_default_normal.9.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/btn_default_selected.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/res/drawable-hdpi/btn_default_selected.9.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/droplet-js/Android_Skin/45af06425c4c084feff9e471c6df2f83c678553a/res/drawable-hdpi/icon.png
--------------------------------------------------------------------------------
/res/drawable/test_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/res/layout/layout_dex.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/res/layout/layout_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
18 |
19 |
25 |
26 |
33 |
34 |
40 |
41 |
48 |
49 |
--------------------------------------------------------------------------------
/res/values/color.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | #b13332
6 | #fff
7 | #161616
8 | #787878
9 |
10 | #fff
11 |
12 |
--------------------------------------------------------------------------------
/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Android
4 | Android Library
5 |
6 |
7 |
--------------------------------------------------------------------------------
/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
--------------------------------------------------------------------------------
/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/app/SuperActivity.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.app;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.content.pm.ActivityInfo;
6 | import android.content.res.Configuration;
7 | import android.os.Bundle;
8 | import android.view.KeyEvent;
9 | import android.view.Menu;
10 | import android.view.MenuItem;
11 | import android.view.Window;
12 | import android.view.WindowManager;
13 |
14 | /**
15 | * 基础 Activity ,方便对 Activity 进行管理
16 | * 各种源码下载:http://grepcode.com/ http://rgruet.free.fr/public/
17 | * 手机测试平台:http://mc.sigma-rt.com/mc/doHomepageU.do
18 | *
19 | * 其他: Progress Wheel PhotoView Nine Old Androids
20 | */
21 | /**
22 | * 参考文献:
23 | * http://www.eoeandroid.com/forum.php?mod=viewthread&tid=272453&reltid=35453
24 | * &pre_thread_id=0&pre_pos=2&ext=CB
25 | * http://www.oschina.net/translate/40-developer
26 | * -tips-for-android-optimization?from=20130922
27 | * http://www.eoeandroid.com/thread-311194-1-1.html
28 | * https://github.com/Trinea/AndroidCommon http://www.23code.com/
29 | * https://github.com/yangfuhai/afinal https://github.com/wyouflf/xUtils
30 | * http://my.eoe.cn/1181897/archive/21230.html?f=nge
31 | * http://blog.csdn.net/cuiweijie3/article/details/9464925
32 | * https://github.com/davidleen/android-BaseAdapter-enhance 混淆文献:
33 | * http://blog.csdn.net/dianyueneo/article/details/7221323
34 | *
35 | * 2.3 的新类 StrictMode 捕捉发生在应用程序主线程中耗时调用
36 | *
37 | * @author v7lin E-mail:v7lin@qq.com
38 | * @since 2014-11-7 23:26:40
39 | */
40 | public class SuperActivity extends Activity {
41 |
42 | @Override
43 | protected void onCreate(Bundle savedInstanceState) {
44 | super.onCreate(savedInstanceState);
45 | }
46 |
47 | @Override
48 | public boolean onCreateOptionsMenu(Menu menu) {
49 | return super.onCreateOptionsMenu(menu);
50 | }
51 |
52 | @Override
53 | public boolean onOptionsItemSelected(MenuItem item) {
54 | return super.onOptionsItemSelected(item);
55 | }
56 |
57 | @Override
58 | protected void onRestart() {
59 | super.onRestart();
60 | }
61 |
62 | @Override
63 | protected void onStart() {
64 | super.onStart();
65 | }
66 |
67 | @Override
68 | public void onConfigurationChanged(Configuration newConfig) {
69 | super.onConfigurationChanged(newConfig);
70 | }
71 |
72 | @Override
73 | protected void onRestoreInstanceState(Bundle savedInstanceState) {
74 | super.onRestoreInstanceState(savedInstanceState);
75 | }
76 |
77 | /**
78 | * 初始化最好在这里执行
79 | */
80 | @Override
81 | protected void onPostCreate(Bundle savedInstanceState) {
82 | super.onPostCreate(savedInstanceState);
83 | }
84 |
85 | @Override
86 | protected void onResume() {
87 | super.onResume();
88 | }
89 |
90 | @Override
91 | protected void onPostResume() {
92 | super.onPostResume();
93 | }
94 |
95 | @Override
96 | protected void onNewIntent(Intent intent) {
97 | super.onNewIntent(intent);
98 | }
99 |
100 | @Override
101 | protected void onPause() {
102 | super.onPause();
103 | }
104 |
105 | /**
106 | * 一定是在 onStop 之前调用 可能是在 onPause 之前调用,也有可能在 onPause 之后调用
107 | */
108 | @Override
109 | protected void onSaveInstanceState(Bundle outState) {
110 | super.onSaveInstanceState(outState);
111 | }
112 |
113 | @Override
114 | protected void onStop() {
115 | super.onStop();
116 | }
117 |
118 | @Override
119 | protected void onDestroy() {
120 | super.onDestroy();
121 | }
122 |
123 | @Override
124 | public void finish() {
125 | super.finish();
126 | }
127 |
128 | @Override
129 | public void startActivityForResult(Intent intent, int requestCode) {
130 | super.startActivityForResult(intent, requestCode);
131 | }
132 |
133 | @Override
134 | protected void onActivityResult(int requestCode, int resultCode, Intent data) {
135 | super.onActivityResult(requestCode, resultCode, data);
136 | }
137 |
138 | @Override
139 | public boolean onKeyDown(int keyCode, KeyEvent event) {
140 | return super.onKeyDown(keyCode, event);
141 | }
142 |
143 | /**
144 | * 设置屏幕全屏与否
145 | */
146 | protected final void requestFullScreen(boolean fullScreen) {
147 | if (fullScreen) {// 全屏
148 | Window window = getWindow();
149 | if (window != null) {
150 | WindowManager.LayoutParams params = window.getAttributes();
151 | if (params != null) {
152 | params.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN;
153 | window.setAttributes(params);
154 | window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
155 | }
156 | }
157 | } else {// 非全屏
158 | Window window = getWindow();
159 | if (window != null) {
160 | WindowManager.LayoutParams params = window.getAttributes();
161 | if (params != null) {
162 | params.flags &= (~WindowManager.LayoutParams.FLAG_FULLSCREEN);
163 | window.setAttributes(params);
164 | window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
165 | }
166 | }
167 | }
168 | }
169 |
170 | /**
171 | * 设置屏幕待机与否
172 | */
173 | protected final void requestKeepScreenOn(boolean keepScreenOn) {
174 | if (keepScreenOn) {// 不待机(常亮)
175 | Window window = getWindow();
176 | if (window != null) {
177 | window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
178 | }
179 | } else {// 待机(不常亮)
180 | Window window = getWindow();
181 | if (window != null) {
182 | window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
183 | }
184 | }
185 | }
186 |
187 | /**
188 | * 设置屏幕旋转属性 {@link ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED etc.}
189 | */
190 | protected final void requestOrientation(int requestedOrientation) {
191 | setRequestedOrientation(requestedOrientation);
192 | }
193 | }
194 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/app/SuperAppConfig.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.app;
2 |
3 | import java.lang.Thread.UncaughtExceptionHandler;
4 |
5 | import android.app.Application;
6 |
7 | import com.v7lin.android.crash.CrashUncaughtExceptionHandler;
8 |
9 | /**
10 | *
11 | * @author v7lin E-mail:v7lin@qq.com
12 | * @since 2014-11-7 23:26:40
13 | */
14 | public class SuperAppConfig extends Application {
15 |
16 | @Override
17 | public void onCreate() {
18 | super.onCreate();
19 |
20 | // 设置堆利用率,优化 Dalvik 虚拟机的堆内存分配。适用:大型游戏或耗资源的应用
21 | // VMRuntime.getRuntime().setTargetHeapUtilization(0.75f);
22 | // 设置最小 heap 内存为 6MB 大小。
23 | // VMRuntime.getRuntime().setMinimumHeapSize(6* 1024* 1024);
24 |
25 | // 注册 App 异常崩溃处理器
26 | UncaughtExceptionHandler handler = new CrashUncaughtExceptionHandler(getApplicationContext(), Thread.getDefaultUncaughtExceptionHandler());
27 | Thread.setDefaultUncaughtExceptionHandler(handler);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/app/SuperReceiver.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.app;
2 |
3 | import android.content.BroadcastReceiver;
4 |
5 | /**
6 | *
7 | *
8 | * @author v7lin E-mail:v7lin@qq.com
9 | * @since 2014-11-7 23:26:40
10 | */
11 | public abstract class SuperReceiver extends BroadcastReceiver {
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/app/SuperService.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.app;
2 |
3 | import android.app.Service;
4 | import android.content.Intent;
5 | import android.os.IBinder;
6 |
7 | /**
8 | *
9 | * @author v7lin E-mail:v7lin@qq.com
10 | * @since 2014-11-7 23:26:40
11 | */
12 | public class SuperService extends Service {
13 |
14 | @Override
15 | public void onCreate() {
16 | super.onCreate();
17 | }
18 |
19 | @Override
20 | public IBinder onBind(Intent intent) {
21 | return null;
22 | }
23 |
24 | @Override
25 | public boolean onUnbind(Intent intent) {
26 | return super.onUnbind(intent);
27 | }
28 |
29 | @Override
30 | public void onDestroy() {
31 | super.onDestroy();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/content/EnvRes.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.content;
2 |
3 | /**
4 | *
5 | *
6 | * @author v7lin E-mail:v7lin@qq.com
7 | * @since 2014-11-7 23:26:40
8 | */
9 | public class EnvRes {
10 |
11 | private final int resid;
12 |
13 | public EnvRes(int resid) {
14 | super();
15 | this.resid = resid;
16 | }
17 |
18 | public int getResid() {
19 | return resid;
20 | }
21 |
22 | /**
23 | * 判断资源映射结果是否有效
24 | */
25 | public boolean isValid() {
26 | return resid > 0;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/content/EnvResourcesWrapper.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.content;
2 |
3 | import android.content.Context;
4 | import android.content.res.Configuration;
5 | import android.content.res.Resources;
6 | import android.util.DisplayMetrics;
7 |
8 | /**
9 | *
10 | *
11 | * @author v7lin E-mail:v7lin@qq.com
12 | * @since 2014-11-12 下午8:03:24
13 | */
14 | public abstract class EnvResourcesWrapper extends Resources {
15 |
16 | public EnvResourcesWrapper(Context context, Resources res) {
17 | super(res.getAssets(), res.getDisplayMetrics(), res.getConfiguration());
18 |
19 | checkSystemConfig();
20 | }
21 |
22 | private void checkSystemConfig() {
23 | // 设置字体大小不随系统设置而改变
24 | Configuration defConfig = new Configuration();
25 | Configuration config = getConfiguration();
26 | config.fontScale = defConfig.fontScale;
27 |
28 | // 暂未作处理
29 | // 有些机器(如:三星 N7100)屏幕达到 xhdpi 标准,却还是 hdpi,故而在此做一次校正
30 | DisplayMetrics metrics = new DisplayMetrics();
31 | metrics.setTo(getDisplayMetrics());
32 |
33 | updateConfiguration(config, metrics);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/content/dex/EnvDexActivity.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.content.dex;
2 |
3 | import android.content.Context;
4 | import android.os.Bundle;
5 |
6 | import com.v7lin.android.app.SuperActivity;
7 | import com.v7lin.android.content.res.EnvContextWrapper;
8 |
9 | /**
10 | *
11 | *
12 | * @author v7lin E-mail:v7lin@qq.com
13 | * @since 2014-11-7 23:26:40
14 | */
15 | public class EnvDexActivity extends SuperActivity implements EnvDexConst {
16 |
17 | /**
18 | * 这里无法取到插件信息,这真是蛋碎啊 ...
19 | */
20 | @Override
21 | protected void attachBaseContext(Context newBase) {
22 | super.attachBaseContext(new EnvDexContextWrapper(new EnvContextWrapper(newBase)));
23 | }
24 |
25 | @Override
26 | public Context getBaseContext() {
27 | Context context = super.getBaseContext();
28 | if (context instanceof EnvDexContextWrapper) {
29 | context = ((EnvDexContextWrapper) context).getBaseContext();
30 | }
31 | if (context instanceof EnvContextWrapper) {
32 | context = ((EnvContextWrapper) context).getBaseContext();
33 | }
34 | return context;
35 | }
36 |
37 | /**
38 | * 蛋碎,这里竟是最早能获取插件信息的地方
39 | */
40 | @Override
41 | protected void onCreate(Bundle savedInstanceState) {
42 | String dexName = getIntent().getStringExtra(KEY_DEX_APP);
43 | Context context = super.getBaseContext();
44 | if (context instanceof EnvDexContextWrapper) {
45 | ((EnvDexContextWrapper) context).setDexName(dexName);
46 | }
47 | super.onCreate(savedInstanceState);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/content/dex/EnvDexClassLoader.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.content.dex;
2 |
3 | import android.content.Context;
4 |
5 | import com.v7lin.android.os.env.PathUtils;
6 |
7 | import dalvik.system.DexClassLoader;
8 |
9 | /**
10 | * Android 插件式开发要领
11 | *
12 | * 1.反射式:
13 | * 绝对的万能神器 ... 不过,据说 ... 反射需要点时间 ...
14 | * 2.接口式:
15 | * 主工程和插件工程必须能引用到公共的接口。
16 | * 接口式中的接口限制:
17 | * 要么是存在于 SDK 的 jar 包中 ...
18 | * 要么是存在于主工程的 jar 包中,且不存在于插件工程的 jar 包中(如:我们不能将 Android SDK 的 jar 包放入工程中 libs 文件夹下) ...
19 | *
20 | * @author v7lin E-mail:v7lin@qq.com
21 | * @since 2014-11-7 23:26:40
22 | */
23 | public class EnvDexClassLoader extends DexClassLoader {
24 |
25 | /**
26 | * 耗时动作,请在主线程中执行
27 | */
28 | public static EnvDexClassLoader newInstance(Context context, String dexPath, ClassLoader parent) {
29 | // ClassLoader 会报 java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation 错误,即有两个同包名同类名接口,可避免
30 | // ClassLoader.getSystemClassLoader();
31 | // VMStack.getCallingClassLoader(); 高级 API 中被废弃了
32 | // ClassLoader.getSystemClassLoader().getParent(); 强制类型转换错误
33 | return new EnvDexClassLoader(dexPath, PathUtils.getDexDir(context).getAbsolutePath(), null, parent);
34 | }
35 |
36 | private EnvDexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) {
37 | super(dexPath, optimizedDirectory, libraryPath, parent);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/content/dex/EnvDexConst.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.content.dex;
2 |
3 | /**
4 | *
5 | *
6 | * @author v7lin E-mail:v7lin@qq.com
7 | * @since 2014-11-7 23:26:40
8 | */
9 | public interface EnvDexConst {
10 |
11 | public static final String KEY_DEX_APP = "dex_app";
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/content/dex/EnvDexContextWrapper.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.content.dex;
2 |
3 | import android.content.Context;
4 | import android.content.ContextWrapper;
5 | import android.content.res.Resources;
6 | import android.content.res.Resources.Theme;
7 | import android.view.LayoutInflater;
8 |
9 | import com.v7lin.android.content.res.EnvLayoutInflaterWrapper;
10 |
11 | /**
12 | * 功能插件 APK 就嫑签名,防止用户错误安装插件 APK ...
13 | *
14 | * @author v7lin E-mail:v7lin@qq.com
15 | * @since 2014-11-7 23:26:40
16 | */
17 | public class EnvDexContextWrapper extends ContextWrapper {
18 |
19 | private Resources mResources;
20 | private Theme mTheme;
21 | private LayoutInflater mLayoutInflater;
22 |
23 | public EnvDexContextWrapper(Context base) {
24 | super(base);
25 | }
26 |
27 | @Override
28 | public Resources getResources() {
29 | if (mResources == null) {
30 | mResources = new EnvDexResourcesWrapper(this, getBaseContext().getResources());
31 | }
32 | return mResources;
33 | }
34 |
35 | public void setDexName(String dexName) {
36 | Resources res = getResources();
37 | if (res != null && res instanceof EnvDexResourcesWrapper) {
38 | EnvDexResourcesWrapper wrapper = (EnvDexResourcesWrapper) res;
39 | wrapper.setDexName(dexName);
40 | }
41 | }
42 |
43 | @Override
44 | public Theme getTheme() {
45 | if (mTheme == null) {
46 | mTheme = getResources().newTheme();
47 | mTheme.setTo(getBaseContext().getTheme());
48 | }
49 | return mTheme;
50 | }
51 |
52 | @Override
53 | public Object getSystemService(String name) {
54 | if (Context.LAYOUT_INFLATER_SERVICE.equals(name)) {
55 | if (mLayoutInflater == null) {
56 | mLayoutInflater = new EnvLayoutInflaterWrapper(LayoutInflater.from(getBaseContext()), this);
57 | }
58 | return mLayoutInflater;
59 | }
60 | return super.getSystemService(name);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/content/dex/EnvDexResourcesManager.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.content.dex;
2 |
3 | import java.io.File;
4 | import java.lang.ref.WeakReference;
5 | import java.lang.reflect.Method;
6 | import java.util.HashMap;
7 | import java.util.Map;
8 |
9 | import android.content.Context;
10 | import android.content.pm.PackageInfo;
11 | import android.content.pm.PackageManager;
12 | import android.content.res.AssetManager;
13 | import android.content.res.Resources;
14 | import android.text.TextUtils;
15 |
16 | import com.v7lin.android.os.env.PathUtils;
17 |
18 | /**
19 | *
20 | *
21 | * @author v7lin E-mail:v7lin@qq.com
22 | * @since 2014-11-9 22:55:20
23 | */
24 | public class EnvDexResourcesManager {
25 |
26 | private final Map> mActiveResources = new HashMap>();
27 |
28 | public static EnvDexResourcesManager getInstance() {
29 | return EnvDexResourcesManagerHolder.INSTANCE;
30 | }
31 |
32 | private EnvDexResourcesManager() {
33 | super();
34 | }
35 |
36 | public String getDexPackageName(Context context, String dexName) {
37 | String packageName = null;
38 | if (!TextUtils.isEmpty(dexName)) {
39 | File skinFile = new File(PathUtils.getDexAppDir(context), dexName);
40 | if (skinFile.exists() && skinFile.isFile()) {
41 | PackageManager manager = context.getPackageManager();
42 | PackageInfo info = manager.getPackageArchiveInfo(skinFile.getAbsolutePath(), PackageManager.GET_ACTIVITIES);
43 | packageName = info.packageName;
44 | }
45 | }
46 | return packageName;
47 | }
48 |
49 | public synchronized Resources getTopLevelResources(Context context, String dexName) {
50 | Resources res = null;
51 | try {
52 | if (!TextUtils.isEmpty(dexName)) {
53 | WeakReference wr = mActiveResources.get(dexName);
54 | res = wr != null ? wr.get() : null;
55 | boolean isValid = false;
56 | if (res != null && res.getAssets() != null) {
57 | AssetManager assets = res.getAssets();
58 | Class> clazz = assets.getClass();
59 | Method method = clazz.getDeclaredMethod("isUpToDate");
60 | Object object = method.invoke(assets);
61 | isValid = Boolean.valueOf(String.valueOf(object)).booleanValue();
62 | }
63 | if (!isValid) {
64 | File dexFile = new File(PathUtils.getDexAppDir(context), dexName);
65 | if (dexFile.exists() && dexFile.isFile()) {
66 | Class> clazz = AssetManager.class;
67 | AssetManager dexAsset = (AssetManager) clazz.newInstance();
68 | Method method = clazz.getDeclaredMethod("addAssetPath", String.class);
69 | method.invoke(dexAsset, dexFile.getAbsolutePath());
70 | res = new Resources(dexAsset, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());
71 | mActiveResources.put(dexName, new WeakReference(res));
72 | }
73 | }
74 | }
75 | } catch (NoSuchMethodException e) {
76 | e.printStackTrace();
77 | } catch (Exception e) {
78 | e.printStackTrace();
79 | }
80 | return res;
81 | }
82 |
83 | private static class EnvDexResourcesManagerHolder {
84 | private static final EnvDexResourcesManager INSTANCE = new EnvDexResourcesManager();
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/content/dex/EnvDexResourcesWrapper.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.content.dex;
2 |
3 | import java.io.InputStream;
4 | import java.util.concurrent.atomic.AtomicBoolean;
5 |
6 | import com.v7lin.android.content.EnvRes;
7 | import com.v7lin.android.content.EnvResourcesWrapper;
8 |
9 | import android.content.Context;
10 | import android.content.res.AssetFileDescriptor;
11 | import android.content.res.ColorStateList;
12 | import android.content.res.Resources;
13 | import android.content.res.TypedArray;
14 | import android.content.res.XmlResourceParser;
15 | import android.graphics.drawable.Drawable;
16 | import android.text.TextUtils;
17 | import android.util.TypedValue;
18 |
19 | /**
20 | * 同名同类型资源
21 | * 优先级:应用自带资源 > 功能插件资源
22 | *
23 | * 同一 resid,可能是主工程包和插件包都有,而对应资源却是不同的
24 | *
25 | * 由于无法绕过 R 文件的编译,无法想 Android SDK 那样将资源文件按包名区分
26 | * 所以,使用同名资源替换发,有插件 Resource ID 解析得出资源的名称和类型
27 | * 然后到主工程中寻出同名资源
28 | * 主工程有同名资源,则以主工程为主
29 | * 主工程木有同名资源,则继续使用插件工程资源
30 | *
31 | * Android SDK Package ID 以 0x01 开头
32 | * Android App Package ID 以 0x7f 开头
33 | *
34 | * @author v7lin E-mail:v7lin@qq.com
35 | * @since 2014-11-9 22:55:29
36 | */
37 | public class EnvDexResourcesWrapper extends EnvResourcesWrapper {
38 |
39 | private final Context mContext;
40 | private final AtomicBoolean mInitDexRes = new AtomicBoolean(false);
41 | private String mDexName;
42 | private String mDexPackageName;
43 | private Resources mDexRes;
44 |
45 | public EnvDexResourcesWrapper(Context context, Resources res) {
46 | super(context, res);
47 | this.mContext = context;
48 | }
49 |
50 | public void setDexName(String dexName) {
51 | this.mDexName = dexName;
52 | }
53 |
54 | private synchronized void ensureDexRes(Context context) {
55 | if (!TextUtils.isEmpty(mDexName) && mInitDexRes.compareAndSet(false, true)) {
56 | mDexRes = EnvDexResourcesManager.getInstance().getTopLevelResources(context, mDexName);
57 | mDexPackageName = EnvDexResourcesManager.getInstance().getDexPackageName(context, mDexName);
58 | }
59 | }
60 |
61 | /**
62 | * 映射同名同类型资源
63 | *
64 | * 皮肤插件包中并不包含所有资源,这导致 R 文件上的资源 id 无法一一对应。
65 | * 所以这里需要做一次资源映射
66 | */
67 | private EnvRes mappingEnvRes(int id) {
68 | EnvRes mapping = null;
69 | if (mDexRes != null) {
70 | String packageName = mDexRes.getResourcePackageName(id);
71 | if (TextUtils.equals(mDexPackageName, packageName)) {
72 | String typeName = mDexRes.getResourceTypeName(id);
73 | String entryName = mDexRes.getResourceEntryName(id);
74 | final int mappingid = super.getIdentifier(entryName, typeName, packageName);
75 | mapping = new EnvRes(mappingid);
76 | }
77 | }
78 | return mapping;
79 | }
80 |
81 | @Override
82 | public int getIdentifier(String name, String defType, String defPackage) {
83 | return TextUtils.equals(mDexPackageName, defPackage) ? mDexRes.getIdentifier(name, defType, defPackage) : super.getIdentifier(name, defType, defPackage);
84 | }
85 |
86 | @Override
87 | public String getResourceName(int resid) throws NotFoundException {
88 | ensureDexRes(mContext);
89 | EnvRes mapping = mappingEnvRes(resid);
90 | if (mapping != null && mapping.isValid()) {
91 | try {
92 | return super.getResourceName(mapping.getResid());
93 | } catch (NotFoundException e) {
94 | }
95 | }
96 | return mDexRes != null ? mDexRes.getResourceName(resid) : super.getResourceName(resid);
97 | }
98 |
99 | @Override
100 | public String getResourcePackageName(int resid) throws NotFoundException {
101 | ensureDexRes(mContext);
102 | EnvRes mapping = mappingEnvRes(resid);
103 | if (mapping != null && mapping.isValid()) {
104 | try {
105 | return super.getResourcePackageName(mapping.getResid());
106 | } catch (NotFoundException e) {
107 | }
108 | }
109 | return mDexRes != null ? mDexRes.getResourcePackageName(resid) : super.getResourcePackageName(resid);
110 | }
111 |
112 | @Override
113 | public String getResourceTypeName(int resid) throws NotFoundException {
114 | ensureDexRes(mContext);
115 | EnvRes mapping = mappingEnvRes(resid);
116 | if (mapping != null && mapping.isValid()) {
117 | try {
118 | return super.getResourceTypeName(mapping.getResid());
119 | } catch (NotFoundException e) {
120 | }
121 | }
122 | return mDexRes != null ? mDexRes.getResourceTypeName(resid) : super.getResourceTypeName(resid);
123 | }
124 |
125 | @Override
126 | public String getResourceEntryName(int resid) throws NotFoundException {
127 | ensureDexRes(mContext);
128 | EnvRes mapping = mappingEnvRes(resid);
129 | if (mapping != null && mapping.isValid()) {
130 | try {
131 | return super.getResourceEntryName(mapping.getResid());
132 | } catch (NotFoundException e) {
133 | }
134 | }
135 | return mDexRes != null ? mDexRes.getResourceEntryName(resid) : super.getResourceEntryName(resid);
136 | }
137 |
138 | @Override
139 | public Drawable getDrawable(int id) throws NotFoundException {
140 | ensureDexRes(mContext);
141 | EnvRes mapping = mappingEnvRes(id);
142 | if (mapping != null && mapping.isValid()) {
143 | try {
144 | return super.getDrawable(mapping.getResid());
145 | } catch (NotFoundException e) {
146 | }
147 | }
148 | return mDexRes != null ? mDexRes.getDrawable(id) : super.getDrawable(id);
149 | }
150 |
151 | @Override
152 | public int getColor(int id) throws NotFoundException {
153 | ensureDexRes(mContext);
154 | EnvRes mapping = mappingEnvRes(id);
155 | if (mapping != null && mapping.isValid()) {
156 | try {
157 | return super.getColor(mapping.getResid());
158 | } catch (NotFoundException e) {
159 | }
160 | }
161 | return mDexRes != null ? mDexRes.getColor(id) : super.getColor(id);
162 | }
163 |
164 | @Override
165 | public ColorStateList getColorStateList(int id) throws NotFoundException {
166 | ensureDexRes(mContext);
167 | EnvRes mapping = mappingEnvRes(id);
168 | if (mapping != null && mapping.isValid()) {
169 | try {
170 | return super.getColorStateList(mapping.getResid());
171 | } catch (NotFoundException e) {
172 | }
173 | }
174 | return mDexRes != null ? mDexRes.getColorStateList(id) : super.getColorStateList(id);
175 | }
176 |
177 | @Override
178 | public CharSequence getText(int id) throws NotFoundException {
179 | ensureDexRes(mContext);
180 | EnvRes mapping = mappingEnvRes(id);
181 | if (mapping != null && mapping.isValid()) {
182 | try {
183 | return super.getText(mapping.getResid());
184 | } catch (NotFoundException e) {
185 | }
186 | }
187 | return mDexRes != null ? mDexRes.getText(id) : super.getText(id);
188 | }
189 |
190 | @Override
191 | public CharSequence getQuantityText(int id, int quantity) throws NotFoundException {
192 | ensureDexRes(mContext);
193 | EnvRes mapping = mappingEnvRes(id);
194 | if (mapping != null && mapping.isValid()) {
195 | try {
196 | return super.getQuantityText(mapping.getResid(), quantity);
197 | } catch (NotFoundException e) {
198 | }
199 | }
200 | return mDexRes != null ? mDexRes.getQuantityText(id, quantity) : super.getQuantityText(id, quantity);
201 | }
202 |
203 | @Override
204 | public CharSequence getText(int id, CharSequence def) {
205 | ensureDexRes(mContext);
206 | EnvRes mapping = mappingEnvRes(id);
207 | if (mapping != null && mapping.isValid()) {
208 | try {
209 | return super.getText(mapping.getResid(), def);
210 | } catch (NotFoundException e) {
211 | }
212 | }
213 | return mDexRes != null ? mDexRes.getText(id, def) : super.getText(id, def);
214 | }
215 |
216 | @Override
217 | public CharSequence[] getTextArray(int id) throws NotFoundException {
218 | ensureDexRes(mContext);
219 | EnvRes mapping = mappingEnvRes(id);
220 | if (mapping != null && mapping.isValid()) {
221 | try {
222 | return super.getTextArray(mapping.getResid());
223 | } catch (NotFoundException e) {
224 | }
225 | }
226 | return mDexRes != null ? mDexRes.getTextArray(id) : super.getTextArray(id);
227 | }
228 |
229 | @Override
230 | public String[] getStringArray(int id) throws NotFoundException {
231 | ensureDexRes(mContext);
232 | EnvRes mapping = mappingEnvRes(id);
233 | if (mapping != null && mapping.isValid()) {
234 | try {
235 | return super.getStringArray(mapping.getResid());
236 | } catch (NotFoundException e) {
237 | }
238 | }
239 | return mDexRes != null ? mDexRes.getStringArray(id) : super.getStringArray(id);
240 | }
241 |
242 | @Override
243 | public int[] getIntArray(int id) throws NotFoundException {
244 | ensureDexRes(mContext);
245 | EnvRes mapping = mappingEnvRes(id);
246 | if (mapping != null && mapping.isValid()) {
247 | try {
248 | return super.getIntArray(mapping.getResid());
249 | } catch (NotFoundException e) {
250 | }
251 | }
252 | return mDexRes != null ? mDexRes.getIntArray(id) : super.getIntArray(id);
253 | }
254 |
255 | @Override
256 | public float getDimension(int id) throws NotFoundException {
257 | ensureDexRes(mContext);
258 | EnvRes mapping = mappingEnvRes(id);
259 | if (mapping != null && mapping.isValid()) {
260 | try {
261 | return super.getDimension(mapping.getResid());
262 | } catch (NotFoundException e) {
263 | }
264 | }
265 | return mDexRes != null ? mDexRes.getDimension(id) : super.getDimension(id);
266 | }
267 |
268 | @Override
269 | public int getDimensionPixelOffset(int id) throws NotFoundException {
270 | ensureDexRes(mContext);
271 | EnvRes mapping = mappingEnvRes(id);
272 | if (mapping != null && mapping.isValid()) {
273 | try {
274 | return super.getDimensionPixelOffset(mapping.getResid());
275 | } catch (NotFoundException e) {
276 | }
277 | }
278 | return mDexRes != null ? mDexRes.getDimensionPixelOffset(id) : super.getDimensionPixelOffset(id);
279 | }
280 |
281 | @Override
282 | public int getDimensionPixelSize(int id) throws NotFoundException {
283 | ensureDexRes(mContext);
284 | EnvRes mapping = mappingEnvRes(id);
285 | if (mapping != null && mapping.isValid()) {
286 | try {
287 | return super.getDimensionPixelSize(mapping.getResid());
288 | } catch (NotFoundException e) {
289 | }
290 | }
291 | return mDexRes != null ? mDexRes.getDimensionPixelSize(id) : super.getDimensionPixelSize(id);
292 | }
293 |
294 | @Override
295 | public float getFraction(int id, int base, int pbase) {
296 | ensureDexRes(mContext);
297 | EnvRes mapping = mappingEnvRes(id);
298 | if (mapping != null && mapping.isValid()) {
299 | try {
300 | return super.getFraction(mapping.getResid(), base, pbase);
301 | } catch (NotFoundException e) {
302 | }
303 | }
304 | return mDexRes != null ? mDexRes.getFraction(id, base, pbase) : super.getFraction(id, base, pbase);
305 | }
306 |
307 | @Override
308 | public boolean getBoolean(int id) throws NotFoundException {
309 | ensureDexRes(mContext);
310 | EnvRes mapping = mappingEnvRes(id);
311 | if (mapping != null && mapping.isValid()) {
312 | try {
313 | return super.getBoolean(mapping.getResid());
314 | } catch (NotFoundException e) {
315 | }
316 | }
317 | return mDexRes != null ? mDexRes.getBoolean(id) : super.getBoolean(id);
318 | }
319 |
320 | @Override
321 | public int getInteger(int id) throws NotFoundException {
322 | ensureDexRes(mContext);
323 | EnvRes mapping = mappingEnvRes(id);
324 | if (mapping != null && mapping.isValid()) {
325 | try {
326 | return super.getInteger(mapping.getResid());
327 | } catch (NotFoundException e) {
328 | }
329 | }
330 | return mDexRes != null ? mDexRes.getInteger(id) : super.getInteger(id);
331 | }
332 |
333 | @Override
334 | public XmlResourceParser getAnimation(int id) throws NotFoundException {
335 | ensureDexRes(mContext);
336 | EnvRes mapping = mappingEnvRes(id);
337 | if (mapping != null && mapping.isValid()) {
338 | try {
339 | return super.getAnimation(mapping.getResid());
340 | } catch (NotFoundException e) {
341 | }
342 | }
343 | return mDexRes != null ? mDexRes.getAnimation(id) : super.getAnimation(id);
344 | }
345 |
346 | @Override
347 | public XmlResourceParser getXml(int id) throws NotFoundException {
348 | ensureDexRes(mContext);
349 | EnvRes mapping = mappingEnvRes(id);
350 | if (mapping != null && mapping.isValid()) {
351 | try {
352 | return super.getXml(mapping.getResid());
353 | } catch (NotFoundException e) {
354 | }
355 | }
356 | return mDexRes != null ? mDexRes.getXml(id) : super.getXml(id);
357 | }
358 |
359 | @Override
360 | public TypedArray obtainTypedArray(int id) throws NotFoundException {
361 | ensureDexRes(mContext);
362 | EnvRes mapping = mappingEnvRes(id);
363 | if (mapping != null && mapping.isValid()) {
364 | try {
365 | return super.obtainTypedArray(mapping.getResid());
366 | } catch (NotFoundException e) {
367 | }
368 | }
369 | return mDexRes != null ? mDexRes.obtainTypedArray(id) : super.obtainTypedArray(id);
370 | }
371 |
372 | @Override
373 | public InputStream openRawResource(int id, TypedValue value) throws NotFoundException {
374 | ensureDexRes(mContext);
375 | EnvRes mapping = mappingEnvRes(id);
376 | if (mapping != null && mapping.isValid()) {
377 | try {
378 | return super.openRawResource(mapping.getResid(), value);
379 | } catch (NotFoundException e) {
380 | }
381 | }
382 | return mDexRes != null ? mDexRes.openRawResource(id, value) : super.openRawResource(id, value);
383 | }
384 |
385 | @Override
386 | public AssetFileDescriptor openRawResourceFd(int id) throws NotFoundException {
387 | ensureDexRes(mContext);
388 | EnvRes mapping = mappingEnvRes(id);
389 | if (mapping != null && mapping.isValid()) {
390 | try {
391 | return super.openRawResourceFd(mapping.getResid());
392 | } catch (NotFoundException e) {
393 | }
394 | }
395 | return mDexRes != null ? mDexRes.openRawResourceFd(id) : super.openRawResourceFd(id);
396 | }
397 | }
398 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/content/dex/EnvDexSetup.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.content.dex;
2 |
3 | /**
4 | *
5 | *
6 | * @author v7lin E-mail:v7lin@qq.com
7 | * @since 2014-11-7 23:26:40
8 | */
9 | public abstract class EnvDexSetup {
10 |
11 | public static EnvDexSetup getInstance() {
12 | return EnvDexSetupHolder.INSTANCE;
13 | }
14 |
15 | private static class EnvDexSetupHolder {
16 | private static final EnvDexSetup INSTANCE = new EnvDexSetupImpl();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/content/dex/EnvDexSetupImpl.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.content.dex;
2 |
3 | /**
4 | *
5 | *
6 | * @author v7lin E-mail:v7lin@qq.com
7 | * @since 2014-11-7 23:26:40
8 | */
9 | public class EnvDexSetupImpl extends EnvDexSetup {
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/content/idex/IDexTest.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.content.idex;
2 |
3 | import android.graphics.drawable.Drawable;
4 |
5 | /**
6 | *
7 | *
8 | * @author v7lin Email:v7lin@qq.com
9 | * @since 2014-11-8 19:54:17
10 | */
11 | public interface IDexTest {
12 |
13 | public Drawable getDrawable();
14 |
15 | public Drawable getDexDrawable();
16 |
17 | public CharSequence getText();
18 |
19 | public CharSequence getDexText();
20 | }
21 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/content/res/EnvActivity.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.content.res;
2 |
3 | import android.content.Context;
4 |
5 | import com.v7lin.android.app.SuperActivity;
6 |
7 | /**
8 | *
9 | *
10 | * @author v7lin E-mail:v7lin@qq.com
11 | * @since 2014-11-7 23:26:40
12 | */
13 | public class EnvActivity extends SuperActivity {
14 |
15 | @Override
16 | protected void attachBaseContext(Context newBase) {
17 | super.attachBaseContext(new EnvContextWrapper(newBase));
18 | }
19 |
20 | @Override
21 | public Context getBaseContext() {
22 | Context context = super.getBaseContext();
23 | if (context instanceof EnvContextWrapper) {
24 | context = ((EnvContextWrapper) context).getBaseContext();
25 | }
26 | return context;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/content/res/EnvAppConfig.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.content.res;
2 |
3 | import android.content.Context;
4 |
5 | import com.v7lin.android.app.SuperAppConfig;
6 |
7 | /**
8 | *
9 | *
10 | * @author v7lin E-mail:v7lin@qq.com
11 | * @since 2014-11-7 23:26:40
12 | */
13 | public class EnvAppConfig extends SuperAppConfig {
14 |
15 | @Override
16 | protected void attachBaseContext(Context base) {
17 | super.attachBaseContext(new EnvContextWrapper(base));
18 | }
19 |
20 | /**
21 | * 在 ActivityThread 接收广播时,会取出 baseContext作一次强制类型转换,转换为 ContextImpl
22 | */
23 | @Override
24 | public Context getBaseContext() {
25 | Context context = super.getBaseContext();
26 | if (context instanceof EnvContextWrapper) {
27 | context = ((EnvContextWrapper) context).getBaseContext();
28 | }
29 | return context;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/content/res/EnvChecker.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.content.res;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.View;
6 |
7 | /**
8 | *
9 | *
10 | * @author v7lin E-mail:v7lin@qq.com
11 | * @since 2014-11-7 23:26:40
12 | */
13 | interface EnvChecker {
14 |
15 | public void check(Context context, AttributeSet attrs, View view);
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/content/res/EnvCheckerCompat.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.content.res;
2 |
3 | import android.content.Context;
4 | import android.graphics.Typeface;
5 | import android.os.Build;
6 | import android.util.AttributeSet;
7 | import android.view.View;
8 |
9 | /**
10 | *
11 | *
12 | * @author v7lin E-mail:v7lin@qq.com
13 | * @since 2014-11-7 23:26:40
14 | */
15 | class EnvCheckerCompat {
16 |
17 | interface EnvTextCheckerCompatImpl {
18 | public void checkSwitchText(Context context, AttributeSet attrs, View view);
19 |
20 | public void checkSwitchTypeface(Context context, AttributeSet attrs, View view, Typeface typeface);
21 |
22 | public void checkTextClockText(Context context, AttributeSet attrs, View view);
23 |
24 | public void checkSubtitleViewText(Context context, AttributeSet attrs, View view);
25 |
26 | public void checkSubtitleViewTypeface(Context context, AttributeSet attrs, View view, Typeface typeface);
27 |
28 | public void checkMediaRouteButtonDrawable(Context context, AttributeSet attrs, View view);
29 | }
30 |
31 | static class EarlyEnvTextCheckerCompatImpl implements EnvTextCheckerCompatImpl {
32 |
33 | @Override
34 | public void checkSwitchText(Context context, AttributeSet attrs, View view) {
35 |
36 | }
37 |
38 | @Override
39 | public void checkSwitchTypeface(Context context, AttributeSet attrs, View view, Typeface typeface) {
40 |
41 | }
42 |
43 | @Override
44 | public void checkTextClockText(Context context, AttributeSet attrs, View view) {
45 |
46 | }
47 |
48 | @Override
49 | public void checkSubtitleViewText(Context context, AttributeSet attrs, View view) {
50 |
51 | }
52 |
53 | @Override
54 | public void checkSubtitleViewTypeface(Context context, AttributeSet attrs, View view, Typeface typeface) {
55 |
56 | }
57 |
58 | @Override
59 | public void checkMediaRouteButtonDrawable(Context context, AttributeSet attrs, View view) {
60 |
61 | }
62 | }
63 |
64 | static class IceCreamSandwichEnvTextCheckerCompatImpl extends EarlyEnvTextCheckerCompatImpl {
65 |
66 | @Override
67 | public void checkSwitchText(Context context, AttributeSet attrs, View view) {
68 | EnvCheckerCompatIceCreamSandwich.checkSwitchText(context, attrs, view);
69 | }
70 |
71 | @Override
72 | public void checkSwitchTypeface(Context context, AttributeSet attrs, View view, Typeface typeface) {
73 | EnvCheckerCompatIceCreamSandwich.checkSwitchTypeface(context, attrs, view, typeface);
74 | }
75 | }
76 |
77 | static class JellyBeanMr1EnvTextCheckerCompatImpl extends IceCreamSandwichEnvTextCheckerCompatImpl {
78 |
79 | @Override
80 | public void checkTextClockText(Context context, AttributeSet attrs, View view) {
81 | EnvCheckerCompatJellyBeanMr1.checkTextClockText(context, attrs, view);
82 | }
83 | }
84 |
85 | static class UnknownEnvTextCheckerCompatImpl extends JellyBeanMr1EnvTextCheckerCompatImpl {
86 |
87 | @Override
88 | public void checkSubtitleViewText(Context context, AttributeSet attrs, View view) {
89 | EnvCheckerCompatUnknown.checkSubtitleViewText(context, attrs, view);
90 | }
91 |
92 | @Override
93 | public void checkSubtitleViewTypeface(Context context, AttributeSet attrs, View view, Typeface typeface) {
94 | EnvCheckerCompatUnknown.checkSubtitleViewTypeface(context, attrs, view, typeface);
95 | }
96 |
97 | @Override
98 | public void checkMediaRouteButtonDrawable(Context context, AttributeSet attrs, View view) {
99 | EnvCheckerCompatUnknown.checkMediaRouteButtonDrawable(context, attrs, view);
100 | }
101 | }
102 |
103 | private static final EnvTextCheckerCompatImpl IMPL;
104 |
105 | static {
106 | final int apiVersion = Build.VERSION.SDK_INT;
107 | if (apiVersion >= Build.VERSION_CODES.KITKAT) {
108 | IMPL = new UnknownEnvTextCheckerCompatImpl();
109 | } else if (apiVersion >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
110 | IMPL = new JellyBeanMr1EnvTextCheckerCompatImpl();
111 | } else if (apiVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
112 | IMPL = new IceCreamSandwichEnvTextCheckerCompatImpl();
113 | } else {
114 | IMPL = new EarlyEnvTextCheckerCompatImpl();
115 | }
116 | }
117 |
118 | public static void checkSwitchText(Context context, AttributeSet attrs, View view) {
119 | IMPL.checkSwitchText(context, attrs, view);
120 | }
121 |
122 | public static void checkSwitchTypeface(Context context, AttributeSet attrs, View view, Typeface typeface) {
123 | IMPL.checkSwitchTypeface(context, attrs, view, typeface);
124 | }
125 |
126 | public static void checkTextClockText(Context context, AttributeSet attrs, View view) {
127 | IMPL.checkTextClockText(context, attrs, view);
128 | }
129 |
130 | public static void checkSubtitleViewText(Context context, AttributeSet attrs, View view) {
131 | IMPL.checkSubtitleViewText(context, attrs, view);
132 | }
133 |
134 | public static void checkSubtitleViewTypeface(Context context, AttributeSet attrs, View view, Typeface typeface) {
135 | IMPL.checkSubtitleViewTypeface(context, attrs, view, typeface);
136 | }
137 |
138 | public static void checkMediaRouteButtonDrawable(Context context, AttributeSet attrs, View view) {
139 | IMPL.checkMediaRouteButtonDrawable(context, attrs, view);
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/content/res/EnvCheckerCompatIceCreamSandwich.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.content.res;
2 |
3 | import com.v7lin.android.content.EnvRes;
4 |
5 | import android.annotation.TargetApi;
6 | import android.content.Context;
7 | import android.content.res.TypedArray;
8 | import android.graphics.Typeface;
9 | import android.os.Build;
10 | import android.util.AttributeSet;
11 | import android.util.TypedValue;
12 | import android.view.View;
13 | import android.widget.Switch;
14 |
15 | /**
16 | *
17 | *
18 | * @author v7lin E-mail:v7lin@qq.com
19 | * @since 2014-11-7 23:26:40
20 | */
21 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
22 | class EnvCheckerCompatIceCreamSandwich {
23 |
24 | public static void checkSwitchText(Context context, AttributeSet attrs, View view) {
25 | if (view instanceof Switch) {
26 | Switch switchView = (Switch) view;
27 | final TypedValue value = new TypedValue();
28 | TypedArray array = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Switch, 0, 0);
29 | array.getValue(com.android.internal.R.styleable.Switch_textOn, value);
30 | EnvRes textOnRes = new EnvRes(value.resourceId);
31 | if (textOnRes.isValid()) {
32 | switchView.setTextOn(context.getText(textOnRes.getResid()));
33 | }
34 | array.getValue(com.android.internal.R.styleable.Switch_textOff, value);
35 | EnvRes textOffRes = new EnvRes(value.resourceId);
36 | if (textOffRes.isValid()) {
37 | switchView.setTextOff(context.getText(textOffRes.getResid()));
38 | }
39 | array.recycle();
40 | }
41 | }
42 |
43 | public static void checkSwitchTypeface(Context context, AttributeSet attrs, View view, Typeface typeface) {
44 | if (view instanceof Switch) {
45 | Switch switchView = (Switch) view;
46 | switchView.setSwitchTypeface(typeface);
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/content/res/EnvCheckerCompatJellyBeanMr1.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.content.res;
2 |
3 | import com.v7lin.android.content.EnvRes;
4 |
5 | import android.annotation.TargetApi;
6 | import android.content.Context;
7 | import android.content.res.TypedArray;
8 | import android.os.Build;
9 | import android.util.AttributeSet;
10 | import android.util.TypedValue;
11 | import android.view.View;
12 | import android.widget.TextClock;
13 |
14 | /**
15 | *
16 | *
17 | * @author v7lin E-mail:v7lin@qq.com
18 | * @since 2014-11-7 23:26:40
19 | */
20 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
21 | class EnvCheckerCompatJellyBeanMr1 {
22 |
23 | public static void checkTextClockText(Context context, AttributeSet attrs, View view) {
24 | if (view instanceof TextClock) {
25 | TextClock clock = (TextClock) view;
26 | final TypedValue value = new TypedValue();
27 | TypedArray array = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.TextClock, 0, 0);
28 | array.getValue(com.android.internal.R.styleable.TextClock_format12Hour, value);
29 | EnvRes format12HourRes = new EnvRes(value.resourceId);
30 | if (format12HourRes.isValid()) {
31 | clock.setFormat12Hour(context.getText(format12HourRes.getResid()));
32 | }
33 | array.getValue(com.android.internal.R.styleable.TextClock_format24Hour, value);
34 | EnvRes format24HourRes = new EnvRes(value.resourceId);
35 | if (format24HourRes.isValid()) {
36 | clock.setFormat24Hour(context.getText(format24HourRes.getResid()));
37 | }
38 | array.recycle();
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/content/res/EnvCheckerCompatUnknown.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.content.res;
2 |
3 | import java.lang.reflect.InvocationTargetException;
4 | import java.lang.reflect.Method;
5 |
6 | import android.annotation.TargetApi;
7 | import android.app.MediaRouteButton;
8 | import android.content.Context;
9 | import android.content.res.Resources.NotFoundException;
10 | import android.content.res.TypedArray;
11 | import android.graphics.Typeface;
12 | import android.graphics.drawable.Drawable;
13 | import android.os.Build;
14 | import android.util.AttributeSet;
15 | import android.util.TypedValue;
16 | import android.view.View;
17 |
18 | import com.android.internal.widget.SubtitleView;
19 | import com.v7lin.android.content.EnvRes;
20 |
21 | /**
22 | * 未知 View 所在 API,所以暂定 API 级别为当前最高
23 | *
24 | * @author v7lin E-mail:v7lin@qq.com
25 | * @since 2014-11-7 23:26:40
26 | */
27 | @TargetApi(Build.VERSION_CODES.KITKAT)
28 | class EnvCheckerCompatUnknown {
29 |
30 | public static void checkSubtitleViewText(Context context, AttributeSet attrs, View view) {
31 | if (view instanceof SubtitleView) {
32 | SubtitleView subtitleView = (SubtitleView) view;
33 | final TypedValue value = new TypedValue();
34 | TypedArray array = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.TextView, 0, 0);
35 | array.getValue(com.android.internal.R.styleable.TextView_text, value);
36 | EnvRes textRes = new EnvRes(value.resourceId);
37 | if (textRes.isValid()) {
38 | subtitleView.setText(textRes.getResid());
39 | }
40 | array.recycle();
41 | }
42 | }
43 |
44 | public static void checkSubtitleViewTypeface(Context context, AttributeSet attrs, View view, Typeface typeface) {
45 | if (view instanceof SubtitleView) {
46 | SubtitleView subtitleView = (SubtitleView) view;
47 | subtitleView.setTypeface(typeface);
48 | }
49 | }
50 |
51 | /**
52 | * 需要反射来设置
53 | */
54 | public static void checkMediaRouteButtonDrawable(Context context, AttributeSet attrs, View view) {
55 | if (view instanceof MediaRouteButton) {
56 | MediaRouteButton button = (MediaRouteButton) view;
57 | final TypedValue value = new TypedValue();
58 | TypedArray array = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.MediaRouteButton, 0, 0);
59 | array.getValue(com.android.internal.R.styleable.MediaRouteButton_externalRouteEnabledDrawable, value);
60 | EnvRes externalRouteEnabledDrawableRes = new EnvRes(value.resourceId);
61 | if (externalRouteEnabledDrawableRes.isValid()) {
62 | try {
63 | Method method = MediaRouteButton.class.getDeclaredMethod("setRemoteIndicatorDrawable", Drawable.class);
64 | if (method != null) {
65 | method.setAccessible(true);
66 | method.invoke(button, context.getResources().getDrawable(externalRouteEnabledDrawableRes.getResid()));
67 | }
68 | } catch (NoSuchMethodException e) {
69 | e.printStackTrace();
70 | } catch (IllegalAccessException e) {
71 | e.printStackTrace();
72 | } catch (IllegalArgumentException e) {
73 | e.printStackTrace();
74 | } catch (InvocationTargetException e) {
75 | e.printStackTrace();
76 | } catch (NotFoundException e) {
77 | e.printStackTrace();
78 | }
79 | }
80 | array.recycle();
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/content/res/EnvColorChecker.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.content.res;
2 |
3 | import com.v7lin.android.content.EnvRes;
4 |
5 | import android.content.Context;
6 | import android.content.res.TypedArray;
7 | import android.os.Build;
8 | import android.util.AttributeSet;
9 | import android.util.TypedValue;
10 | import android.view.View;
11 | import android.widget.AbsListView;
12 | import android.widget.TextView;
13 |
14 | /**
15 | *
16 | *
17 | * @author v7lin E-mail:v7lin@qq.com
18 | * @since 2014-11-7 23:26:40
19 | */
20 | class EnvColorChecker implements EnvChecker {
21 |
22 | @Override
23 | public void check(Context context, AttributeSet attrs, View view) {
24 | checkTextColor(context, attrs, view);
25 | checkAbsListView(context, attrs, view);
26 | }
27 |
28 | private void checkTextColor(Context context, AttributeSet attrs, View view) {
29 | if (view instanceof TextView) {
30 | TextView textView = (TextView) view;
31 | final TypedValue value = new TypedValue();
32 |
33 | int textColorHighlightResid = 0; // color
34 | int textColorResid = 0; // colorstatelist
35 | int textColorHintResid = 0; // colorstatelist
36 | int textColorLinkResid = 0; // colorstatelist
37 |
38 | // 高版本 API
39 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
40 | TypedArray textViewAppearanceArray = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.TextViewAppearance, 0, 0);
41 | int textAppearance = textViewAppearanceArray.getResourceId(com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
42 | if (textAppearance != -1) {
43 | TypedArray textAppearanceArray = context.getTheme().obtainStyledAttributes(textAppearance, com.android.internal.R.styleable.TextAppearance);
44 | final int count = textAppearanceArray.getIndexCount();
45 | for (int i = 0; i < count; i++) {
46 | int attr = textAppearanceArray.getIndex(i);
47 | textAppearanceArray.getValue(attr, value);
48 | switch (attr) {
49 | case com.android.internal.R.styleable.TextAppearance_textColorHighlight: {
50 | textColorHighlightResid = value.resourceId;
51 | break;
52 | }
53 | case com.android.internal.R.styleable.TextAppearance_textColor: {
54 | textColorResid = value.resourceId;
55 | break;
56 | }
57 | case com.android.internal.R.styleable.TextAppearance_textColorHint: {
58 | textColorHintResid = value.resourceId;
59 | break;
60 | }
61 | case com.android.internal.R.styleable.TextAppearance_textColorLink: {
62 | textColorLinkResid = value.resourceId;
63 | break;
64 | }
65 | default: {
66 | break;
67 | }
68 | }
69 | }
70 | textAppearanceArray.recycle();
71 | }
72 | textViewAppearanceArray.recycle();
73 | }
74 |
75 | TypedArray textViewArray = context.getTheme().obtainStyledAttributes(attrs, com.android.internal.R.styleable.TextView, 0, 0);
76 | final int count = textViewArray.getIndexCount();
77 | for (int i = 0; i < count; i++) {
78 | int attr = textViewArray.getIndex(i);
79 | textViewArray.getValue(attr, value);
80 | switch (attr) {
81 | case com.android.internal.R.styleable.TextView_textColorHighlight: {
82 | textColorHighlightResid = value.resourceId;
83 | break;
84 | }
85 | case com.android.internal.R.styleable.TextView_textColor: {
86 | textColorResid = value.resourceId;
87 | break;
88 | }
89 | case com.android.internal.R.styleable.TextView_textColorHint: {
90 | textColorHintResid = value.resourceId;
91 | break;
92 | }
93 | case com.android.internal.R.styleable.TextView_textColorLink: {
94 | textColorLinkResid = value.resourceId;
95 | break;
96 | }
97 | default: {
98 | break;
99 | }
100 | }
101 | }
102 | textViewArray.recycle();
103 |
104 | EnvRes textColorHighlightRes = new EnvRes(textColorHighlightResid);
105 | if (textColorHighlightRes.isValid()) {
106 | textView.setHighlightColor(context.getResources().getColor(textColorHighlightRes.getResid()));
107 | }
108 | EnvRes textColorRes = new EnvRes(textColorResid);
109 | if (textColorRes.isValid()) {
110 | textView.setTextColor(context.getResources().getColorStateList(textColorRes.getResid()));
111 | }
112 | EnvRes textColorHintRes = new EnvRes(textColorHintResid);
113 | if (textColorHintRes.isValid()) {
114 | textView.setHintTextColor(context.getResources().getColorStateList(textColorHintRes.getResid()));
115 | }
116 | EnvRes textColorLinkRes = new EnvRes(textColorLinkResid);
117 | if (textColorLinkRes.isValid()) {
118 | textView.setLinkTextColor(context.getResources().getColorStateList(textColorLinkRes.getResid()));
119 | }
120 | }
121 | }
122 |
123 | private void checkAbsListView(Context context, AttributeSet attrs, View view) {
124 | if (view instanceof AbsListView) {
125 | AbsListView listView = (AbsListView) view;
126 | final TypedValue value = new TypedValue();
127 | TypedArray array = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.AbsListView, 0, 0);
128 | array.getValue(com.android.internal.R.styleable.AbsListView_cacheColorHint, value);
129 | EnvRes cacheColorHintRes = new EnvRes(value.resourceId);
130 | if (cacheColorHintRes.isValid()) {
131 | listView.setCacheColorHint(context.getResources().getColor(cacheColorHintRes.getResid()));
132 | }
133 | array.recycle();
134 | }
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/content/res/EnvContextWrapper.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.content.res;
2 |
3 | import android.content.Context;
4 | import android.content.ContextWrapper;
5 | import android.content.res.Resources;
6 | import android.content.res.Resources.Theme;
7 | import android.view.LayoutInflater;
8 |
9 |
10 | /**
11 | * 皮肤插件 APK 就嫑签名,防止用户错误安装皮肤插件 APK ...
12 | *
13 | * Window 层级的 View,救不了 ...
14 | * 非 inflater 的 View,救不了 ...
15 | *
16 | * 特别的,要注意
17 | * @color/white 取到的 resid 为 white,不会是 white_delegate
18 | *
19 | * @author v7lin E-mail:v7lin@qq.com
20 | * @since 2014-11-7 23:26:40
21 | */
22 | public class EnvContextWrapper extends ContextWrapper {
23 |
24 | private Resources mResources;
25 | private Theme mTheme;
26 | private LayoutInflater mLayoutInflater;
27 |
28 | public EnvContextWrapper(Context base) {
29 | super(base);
30 | }
31 |
32 | @Override
33 | public Resources getResources() {
34 | if (mResources == null) {
35 | mResources = new EnvSkinResourcesWrapper(this, getBaseContext().getResources());
36 | }
37 | return mResources;
38 | }
39 |
40 | @Override
41 | public Theme getTheme() {
42 | if (mTheme == null) {
43 | mTheme = getResources().newTheme();
44 | mTheme.setTo(getBaseContext().getTheme());
45 | }
46 | return mTheme;
47 | }
48 |
49 | @Override
50 | public Object getSystemService(String name) {
51 | if (Context.LAYOUT_INFLATER_SERVICE.equals(name)) {
52 | if (mLayoutInflater == null) {
53 | mLayoutInflater = new EnvLayoutInflaterWrapper(LayoutInflater.from(getBaseContext()), this);
54 | }
55 | return mLayoutInflater;
56 | }
57 | return super.getSystemService(name);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/content/res/EnvDrawableChecker.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.content.res;
2 |
3 | import java.lang.reflect.Field;
4 | import java.lang.reflect.InvocationTargetException;
5 | import java.lang.reflect.Method;
6 |
7 | import com.v7lin.android.content.EnvRes;
8 |
9 |
10 | import android.content.Context;
11 | import android.content.res.Resources.NotFoundException;
12 | import android.content.res.TypedArray;
13 | import android.graphics.drawable.Drawable;
14 | import android.util.AttributeSet;
15 | import android.util.TypedValue;
16 | import android.view.View;
17 | import android.widget.AbsListView;
18 | import android.widget.AbsSeekBar;
19 | import android.widget.CompoundButton;
20 | import android.widget.ImageView;
21 | import android.widget.TextView;
22 |
23 | /**
24 | *
25 | *
26 | * @author v7lin E-mail:v7lin@qq.com
27 | * @since 2014-11-7 23:26:40
28 | */
29 | class EnvDrawableChecker implements EnvChecker {
30 |
31 | @Override
32 | public void check(Context context, AttributeSet attrs, View view) {
33 | checkBackground(context, attrs, view);
34 | checkScrollBar(context, attrs, view);
35 | checkCompoundDrawable(context, attrs, view);
36 | checkCompoundButton(context, attrs, view);
37 | checkImageView(context, attrs, view);
38 | checkAbsListView(context, attrs, view);
39 | checkAbsSeekBar(context, attrs, view);
40 | checkMediaRouteButton(context, attrs, view);
41 | }
42 |
43 | /**
44 | * @see View#setBackgroundResource(int)
45 | */
46 | private void checkBackground(Context context, AttributeSet attrs, View view) {
47 | final TypedValue value = new TypedValue();
48 | TypedArray array = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View, 0, 0);
49 | array.getValue(com.android.internal.R.styleable.View_background, value);
50 | EnvRes backgroundRes = new EnvRes(value.resourceId);
51 | if (backgroundRes.isValid()) {
52 | view.setBackgroundResource(backgroundRes.getResid());
53 | }
54 | array.recycle();
55 | }
56 |
57 | private void checkScrollBar(Context context, AttributeSet attrs, View view) {
58 | try {
59 | Field mScrollCacheField = View.class.getDeclaredField("mScrollCache");
60 | if (mScrollCacheField != null) {
61 | mScrollCacheField.setAccessible(true);
62 | Object mScrollCache = mScrollCacheField.get(view);
63 | if (mScrollCache != null) {
64 | Field scrollBarField = mScrollCache.getClass().getDeclaredField("scrollBar");
65 | if (scrollBarField != null) {
66 | Object scrollBar = scrollBarField.get(mScrollCache);
67 | if (scrollBar != null) {
68 | final TypedValue value = new TypedValue();
69 | TypedArray array = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View, 0, 0);
70 | array.getValue(com.android.internal.R.styleable.View_scrollbarTrackHorizontal, value);
71 | EnvRes scrollbarTrackHorizontalRes = new EnvRes(value.resourceId);
72 | if (scrollbarTrackHorizontalRes.isValid()) {
73 | Method setHorizontalTrackDrawableMethod = scrollBar.getClass().getDeclaredMethod("setHorizontalTrackDrawable", Drawable.class);
74 | if (setHorizontalTrackDrawableMethod != null) {
75 | setHorizontalTrackDrawableMethod.setAccessible(true);
76 | setHorizontalTrackDrawableMethod.invoke(scrollBar, context.getResources().getDrawable(scrollbarTrackHorizontalRes.getResid()));
77 | }
78 | }
79 | array.getValue(com.android.internal.R.styleable.View_scrollbarThumbHorizontal, value);
80 | EnvRes scrollbarThumbHorizontalRes = new EnvRes(value.resourceId);
81 | if (scrollbarThumbHorizontalRes.isValid()) {
82 | Method setHorizontalThumbDrawableMethod = scrollBar.getClass().getDeclaredMethod("setHorizontalThumbDrawable", Drawable.class);
83 | if (setHorizontalThumbDrawableMethod != null) {
84 | setHorizontalThumbDrawableMethod.setAccessible(true);
85 | setHorizontalThumbDrawableMethod.invoke(scrollBar, context.getResources().getDrawable(scrollbarThumbHorizontalRes.getResid()));
86 | }
87 | }
88 | array.getValue(com.android.internal.R.styleable.View_scrollbarTrackVertical, value);
89 | EnvRes scrollbarTrackVerticalRes = new EnvRes(value.resourceId);
90 | if (scrollbarTrackVerticalRes.isValid()) {
91 | Method setVerticalTrackDrawableMethod = scrollBar.getClass().getDeclaredMethod("setVerticalTrackDrawable", Drawable.class);
92 | if (setVerticalTrackDrawableMethod != null) {
93 | setVerticalTrackDrawableMethod.setAccessible(true);
94 | setVerticalTrackDrawableMethod.invoke(scrollBar, context.getResources().getDrawable(scrollbarTrackVerticalRes.getResid()));
95 | }
96 | }
97 | array.getValue(com.android.internal.R.styleable.View_scrollbarThumbVertical, value);
98 | EnvRes scrollbarThumbVerticalRes = new EnvRes(value.resourceId);
99 | if (scrollbarThumbVerticalRes.isValid()) {
100 | Method setVerticalThumbDrawableMethod = scrollBar.getClass().getDeclaredMethod("setVerticalThumbDrawable", Drawable.class);
101 | if (setVerticalThumbDrawableMethod != null) {
102 | setVerticalThumbDrawableMethod.setAccessible(true);
103 | setVerticalThumbDrawableMethod.invoke(scrollBar, context.getResources().getDrawable(scrollbarThumbVerticalRes.getResid()));
104 | }
105 | }
106 | array.recycle();
107 | }
108 | }
109 | }
110 | }
111 | } catch (NoSuchFieldException e) {
112 | e.printStackTrace();
113 | } catch (IllegalAccessException e) {
114 | e.printStackTrace();
115 | } catch (IllegalArgumentException e) {
116 | e.printStackTrace();
117 | } catch (NoSuchMethodException e) {
118 | e.printStackTrace();
119 | } catch (InvocationTargetException e) {
120 | e.printStackTrace();
121 | } catch (NotFoundException e) {
122 | e.printStackTrace();
123 | }
124 | }
125 |
126 | /**
127 | * @see TextView#setCompoundDrawablesWithIntrinsicBounds(int, int, int, int)
128 | */
129 | private void checkCompoundDrawable(Context context, AttributeSet attrs, View view) {
130 | if (view instanceof TextView) {
131 | TextView textView = (TextView) view;
132 | final int drawablePadding = textView.getCompoundDrawablePadding();
133 |
134 | final TypedValue value = new TypedValue();
135 | TypedArray array = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.TextView, 0, 0);
136 | array.getValue(com.android.internal.R.styleable.TextView_drawableLeft, value);
137 | final int drawableLeftResId = value.resourceId;
138 | array.getValue(com.android.internal.R.styleable.TextView_drawableTop, value);
139 | final int drawableTopResId = value.resourceId;
140 | array.getValue(com.android.internal.R.styleable.TextView_drawableRight, value);
141 | final int drawableRightResId = value.resourceId;
142 | array.getValue(com.android.internal.R.styleable.TextView_drawableBottom, value);
143 | final int drawableBottomResId = value.resourceId;
144 | array.recycle();
145 |
146 | EnvRes drawableLeftRes = new EnvRes(drawableLeftResId);
147 | EnvRes drawableTopRes = new EnvRes(drawableTopResId);
148 | EnvRes drawableRightRes = new EnvRes(drawableRightResId);
149 | EnvRes drawableBottomRes = new EnvRes(drawableBottomResId);
150 | if (drawableLeftRes.isValid() || drawableTopRes.isValid() || drawableRightRes.isValid() || drawableBottomRes.isValid()) {
151 | textView.setCompoundDrawablesWithIntrinsicBounds(drawableLeftRes.getResid(), drawableTopRes.getResid(), drawableRightRes.getResid(), drawableBottomRes.getResid());
152 | textView.setCompoundDrawablePadding(drawablePadding);
153 | }
154 | }
155 | }
156 |
157 | /**
158 | * @see CompoundButton#setButtonDrawable(int)
159 | */
160 | private void checkCompoundButton(Context context, AttributeSet attrs, View view) {
161 | if (view instanceof CompoundButton) {
162 | CompoundButton button = (CompoundButton) view;
163 | final TypedValue value = new TypedValue();
164 | TypedArray array = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.CompoundButton, 0, 0);
165 | array.getValue(com.android.internal.R.styleable.CompoundButton_button, value);
166 | EnvRes buttonRes = new EnvRes(value.resourceId);
167 | if (buttonRes.isValid()) {
168 | button.setButtonDrawable(buttonRes.getResid());
169 | }
170 | array.recycle();
171 | }
172 | }
173 |
174 | /**
175 | * @see ImageView#setImageResource(int)
176 | */
177 | private void checkImageView(Context context, AttributeSet attrs, View view) {
178 | if (view instanceof ImageView) {
179 | ImageView imageView = (ImageView) view;
180 | final TypedValue value = new TypedValue();
181 | TypedArray array = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ImageView, 0, 0);
182 | array.getValue(com.android.internal.R.styleable.ImageView_src, value);
183 | EnvRes srcRes = new EnvRes(value.resourceId);
184 | if (srcRes.isValid()) {
185 | imageView.setImageResource(srcRes.getResid());
186 | }
187 | array.recycle();
188 | }
189 | }
190 |
191 | private void checkAbsListView(Context context, AttributeSet attrs, View view) {
192 | if (view instanceof AbsListView) {
193 | AbsListView listView = (AbsListView) view;
194 | final TypedValue value = new TypedValue();
195 | TypedArray array = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.AbsListView, 0, 0);
196 | array.getValue(com.android.internal.R.styleable.AbsListView_listSelector, value);
197 | EnvRes listSelectorRes = new EnvRes(value.resourceId);
198 | if (listSelectorRes.isValid()) {
199 | listView.setSelector(context.getResources().getDrawable(listSelectorRes.getResid()));
200 | }
201 | array.recycle();
202 | }
203 | }
204 |
205 | private void checkAbsSeekBar(Context context, AttributeSet attrs, View view) {
206 | if (view instanceof AbsSeekBar) {
207 | AbsSeekBar seekBar = (AbsSeekBar) view;
208 | final TypedValue value = new TypedValue();
209 | TypedArray array = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.SeekBar, 0, 0);
210 | array.getValue(com.android.internal.R.styleable.SeekBar_thumb, value);
211 | EnvRes thumbRes = new EnvRes(value.resourceId);
212 | if (thumbRes.isValid()) {
213 | seekBar.setThumb(context.getResources().getDrawable(thumbRes.getResid()));
214 | }
215 | array.recycle();
216 | }
217 | }
218 |
219 | private void checkMediaRouteButton(Context context, AttributeSet attrs, View view) {
220 | EnvCheckerCompat.checkMediaRouteButtonDrawable(context, attrs, view);
221 | }
222 | }
223 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/content/res/EnvLayoutInflaterWrapper.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.content.res;
2 |
3 | import java.util.Arrays;
4 | import java.util.List;
5 |
6 | import android.content.Context;
7 | import android.content.res.TypedArray;
8 | import android.util.AttributeSet;
9 | import android.view.LayoutInflater;
10 | import android.view.View;
11 |
12 | /**
13 | *
14 | *
15 | * @author v7lin E-mail:v7lin@qq.com
16 | * @since 2014-11-7 23:26:40
17 | */
18 | public class EnvLayoutInflaterWrapper extends LayoutInflater {
19 |
20 | private static final String[] sClassPrefixArray = {
21 | // widget
22 | "android.widget.",
23 | // webkit
24 | "android.webkit.",
25 | // app
26 | "android.app." };
27 |
28 | private static final List sClassPrefixList = Arrays.asList(sClassPrefixArray);
29 |
30 | public EnvLayoutInflaterWrapper(LayoutInflater original, Context newContext) {
31 | super(original, newContext);
32 |
33 | setup();
34 | }
35 |
36 | private void setup() {
37 | Factory factory = getFactory();
38 | if (!(factory instanceof EnvFactory)) {
39 | setFactory(new EnvFactory(factory));
40 | }
41 | }
42 |
43 | @Override
44 | public LayoutInflater cloneInContext(Context newContext) {
45 | return new EnvLayoutInflaterWrapper(this, newContext);
46 | }
47 |
48 | static class EnvFactory implements Factory {
49 |
50 | private final LayoutInflater.Factory wrapped;
51 |
52 | private final EnvChecker mEnvTypefaceChecker = new EnvTypefaceChecker();
53 | private final EnvChecker mEnvTextChecker = new EnvTextChecker();
54 | private final EnvChecker mEnvColorChecker = new EnvColorChecker();
55 | private final EnvChecker mEnvDrawableChecker = new EnvDrawableChecker();
56 |
57 | public EnvFactory(Factory wrapped) {
58 | super();
59 | this.wrapped = wrapped;
60 | }
61 |
62 | @Override
63 | public View onCreateView(String name, Context context, AttributeSet attrs) {
64 | View view = null;
65 | if (context instanceof LayoutInflater.Factory) {
66 | view = ((LayoutInflater.Factory) context).onCreateView(name, context, attrs);
67 | }
68 | if (view == null && wrapped != null) {
69 | view = wrapped.onCreateView(name, context, attrs);
70 | }
71 | if (view == null) {
72 | view = createViewOrFailQuietly(name, context, attrs);
73 | }
74 | if (view != null) {
75 | checkEnvConfig(context, attrs, view);
76 | }
77 | return view;
78 | }
79 |
80 | private View createViewOrFailQuietly(String name, Context context, AttributeSet attrs) {
81 | if (name.contains(".")) {
82 | return createViewOrFailQuietly(name, null, context, attrs);
83 | }
84 | for (final String prefix : sClassPrefixList) {
85 | final View view = createViewOrFailQuietly(name, prefix, context, attrs);
86 | if (view != null) {
87 | return view;
88 | }
89 | }
90 | return null;
91 | }
92 |
93 | private View createViewOrFailQuietly(String name, String prefix, Context context, AttributeSet attrs) {
94 | try {
95 | return LayoutInflater.from(context).createView(name, prefix, attrs);
96 | } catch (Exception ignore) {
97 | return null;
98 | }
99 | }
100 |
101 | /**
102 | * 关于系统性换肤的 View 还未完善,待续 ...
103 | *
104 | * 这里强调,所有视图必须使用
105 | * {@link LayoutInflater#inflate(int, android.view.ViewGroup)} 或者
106 | * {@link View#inflate(Context, int, android.view.ViewGroup)} 创建
107 | *
108 | * 换 Typeface,有一个 BUG,就是直接 new 的对象无法自动换字体,所以有上述强调 换 Drawable,避免
109 | * {@link TypedArray#getDrawable(int)} 直接取资源,绕过既定换肤流程
110 | */
111 | private void checkEnvConfig(Context context, AttributeSet attrs, View view) {
112 | // change typeface
113 | mEnvTypefaceChecker.check(context, attrs, view);
114 | // change text
115 | mEnvTextChecker.check(context, attrs, view);
116 | // change color
117 | mEnvColorChecker.check(context, attrs, view);
118 | // change drawable
119 | mEnvDrawableChecker.check(context, attrs, view);
120 | }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/content/res/EnvReceiver.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.content.res;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 |
6 | import com.v7lin.android.app.SuperReceiver;
7 |
8 | /**
9 | *
10 | *
11 | * @author v7lin E-mail:v7lin@qq.com
12 | * @since 2014-11-7 23:26:40
13 | */
14 | public abstract class EnvReceiver extends SuperReceiver {
15 |
16 | @Override
17 | public final void onReceive(Context context, Intent intent) {
18 | onReceiveEnv(new EnvContextWrapper(context), intent);
19 | }
20 |
21 | public abstract void onReceiveEnv(Context context, Intent intent);
22 | }
23 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/content/res/EnvService.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.content.res;
2 |
3 | import android.content.Context;
4 |
5 | import com.v7lin.android.app.SuperService;
6 |
7 | /**
8 | *
9 | *
10 | * @author v7lin E-mail:v7lin@qq.com
11 | * @since 2014-11-7 23:26:40
12 | */
13 | public class EnvService extends SuperService {
14 |
15 | @Override
16 | protected void attachBaseContext(Context base) {
17 | super.attachBaseContext(new EnvContextWrapper(base));
18 | }
19 |
20 | @Override
21 | public Context getBaseContext() {
22 | Context context = super.getBaseContext();
23 | if (context instanceof EnvContextWrapper) {
24 | context = ((EnvContextWrapper) context).getBaseContext();
25 | }
26 | return context;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/content/res/EnvSetup.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.content.res;
2 |
3 | import android.content.Context;
4 |
5 | /**
6 | *
7 | *
8 | * @author v7lin E-mail:v7lin@qq.com
9 | * @since 2014-11-7 23:26:40
10 | */
11 | public abstract class EnvSetup {
12 |
13 | public static EnvSetup getInstance() {
14 | return EnvSetupHolder.INSTANCE;
15 | }
16 |
17 | public abstract String getSkinName(Context context);
18 |
19 | public abstract void setSkinName(Context context, String skinName);
20 |
21 | public abstract String getFontName(Context context);
22 |
23 | public abstract void setFontName(Context context, String fontName);
24 |
25 | private static class EnvSetupHolder {
26 | private static final EnvSetup INSTANCE = new EnvSetupImpl();
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/content/res/EnvSetupImpl.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.content.res;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 |
6 | /**
7 | *
8 | *
9 | * @author v7lin E-mail:v7lin@qq.com
10 | * @since 2014-11-7 23:26:40
11 | */
12 | class EnvSetupImpl extends EnvSetup {
13 |
14 | private static final String ENV_SETUP = "env_setup";
15 |
16 | private static final String KEY_SKIN_NAME = "skin_name";
17 | private static final String KEY_FONT_NAME = "font_name";
18 |
19 | @Override
20 | public String getSkinName(Context context) {
21 | SharedPreferences preferences = context.getSharedPreferences(ENV_SETUP, Context.MODE_PRIVATE);
22 | return preferences.getString(KEY_SKIN_NAME, "");
23 | }
24 |
25 | @Override
26 | public void setSkinName(Context context, String skinName) {
27 | SharedPreferences preferences = context.getSharedPreferences(ENV_SETUP, Context.MODE_PRIVATE);
28 | preferences.edit().putString(KEY_SKIN_NAME, skinName).commit();
29 | }
30 |
31 | @Override
32 | public String getFontName(Context context) {
33 | SharedPreferences preferences = context.getSharedPreferences(ENV_SETUP, Context.MODE_PRIVATE);
34 | return preferences.getString(KEY_FONT_NAME, "");
35 | }
36 |
37 | @Override
38 | public void setFontName(Context context, String fontName) {
39 | SharedPreferences preferences = context.getSharedPreferences(ENV_SETUP, Context.MODE_PRIVATE);
40 | preferences.edit().putString(KEY_FONT_NAME, fontName).commit();
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/content/res/EnvSkinResClearCompat.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.content.res;
2 |
3 | import java.lang.reflect.Field;
4 | import java.lang.reflect.InvocationTargetException;
5 | import java.lang.reflect.Method;
6 | import java.lang.reflect.Modifier;
7 |
8 | import android.content.res.ColorStateList;
9 | import android.content.res.Resources;
10 | import android.graphics.drawable.ColorDrawable;
11 | import android.graphics.drawable.Drawable;
12 | import android.graphics.drawable.Drawable.ConstantState;
13 | import android.os.Build;
14 | import android.util.LongSparseArray;
15 |
16 | /**
17 | * 清理 {@link Resources} 中有关 {@link ColorStateList}、{@link Drawable}的
18 | * {@link ConstantState} 的缓存
19 | *
20 | * 虽然,{@link Drawable}的{@link ConstantState} 大多是不需要清理的 但是,谁让它的子类中,出现了
21 | * {@link ColorDrawable}的{@link ColorState}呢
22 | *
23 | * @author v7lin E-mail:v7lin@qq.com
24 | * @since 2014-11-7 23:26:40
25 | */
26 | class EnvSkinResClearCompat {
27 |
28 | interface EnvResClearCompatImpl {
29 | public void clear(Resources res);
30 | }
31 |
32 | static abstract class BaseEnvResClearCompatImpl implements EnvResClearCompatImpl {
33 |
34 | protected void clearLongSparseArray(Resources res, String fieldName) {
35 | try {
36 | Class> clazz = Resources.class;
37 | Field field = clazz.getDeclaredField(fieldName);
38 | if (field != null) {
39 | field.setAccessible(true);
40 | Object object = null;
41 | if (Modifier.isStatic(field.getModifiers())) {
42 | object = field.get(null);
43 | } else {
44 | object = field.get(res);
45 | }
46 | if (object != null && object instanceof LongSparseArray>) {
47 | LongSparseArray> array = (LongSparseArray>) object;
48 | Method method = LongSparseArray.class.getDeclaredMethod("clear");
49 | if (method != null) {
50 | method.setAccessible(true);
51 | method.invoke(array);
52 | }
53 | }
54 | }
55 | } catch (NoSuchFieldException e) {
56 | e.printStackTrace();
57 | } catch (IllegalAccessException e) {
58 | e.printStackTrace();
59 | } catch (IllegalArgumentException e) {
60 | e.printStackTrace();
61 | } catch (NoSuchMethodException e) {
62 | e.printStackTrace();
63 | } catch (InvocationTargetException e) {
64 | e.printStackTrace();
65 | }
66 | }
67 | }
68 |
69 | static class EarlyEnvResClearCompatImpl extends BaseEnvResClearCompatImpl {
70 |
71 | @Override
72 | public void clear(Resources res) {
73 | // static
74 | // sPreloadedDrawables
75 | // mPreloadedColorStateLists
76 | clearLongSparseArray(res, "sPreloadedDrawables");
77 | clearLongSparseArray(res, "mPreloadedColorStateLists");
78 |
79 | // member
80 | // mDrawableCache
81 | // mColorStateListCache
82 | clearLongSparseArray(res, "mDrawableCache");
83 | clearLongSparseArray(res, "mColorStateListCache");
84 | }
85 | }
86 |
87 | /**
88 | * 未知 自己找 Resource 源码,去看看这些成员的改变是从哪个 API 开始的
89 | */
90 | static class IceCreamSandwichEnvResClearCompatImpl extends BaseEnvResClearCompatImpl {
91 |
92 | @Override
93 | public void clear(Resources res) {
94 | // static
95 | // sPreloadedDrawables
96 | // mPreloadedColorStateLists
97 | // sPreloadedColorDrawables
98 | clearLongSparseArray(res, "sPreloadedDrawables");
99 | clearLongSparseArray(res, "mPreloadedColorStateLists");
100 | clearLongSparseArray(res, "sPreloadedColorDrawables");
101 |
102 | // member
103 | // mDrawableCache
104 | // mColorStateListCache
105 | // mColorDrawableCache
106 | clearLongSparseArray(res, "mDrawableCache");
107 | clearLongSparseArray(res, "mColorStateListCache");
108 | clearLongSparseArray(res, "mColorDrawableCache");
109 | }
110 | }
111 |
112 | static class JellyBeanEnvResClearCompatImpl extends BaseEnvResClearCompatImpl {
113 |
114 | @Override
115 | public void clear(Resources res) {
116 | // static
117 | // sPreloadedDrawables
118 | // sPreloadedColorStateLists
119 | // sPreloadedColorDrawables
120 | clearLongSparseArray(res, "sPreloadedDrawables");
121 | clearLongSparseArray(res, "sPreloadedColorStateLists");
122 | clearLongSparseArray(res, "sPreloadedColorDrawables");
123 |
124 | // member
125 | // mDrawableCache
126 | // mColorStateListCache
127 | // mColorDrawableCache
128 | clearLongSparseArray(res, "mDrawableCache");
129 | clearLongSparseArray(res, "mColorStateListCache");
130 | clearLongSparseArray(res, "mColorDrawableCache");
131 | }
132 | }
133 |
134 | private static final EnvResClearCompatImpl IMPL;
135 |
136 | static {
137 | final int apiVersion = Build.VERSION.SDK_INT;
138 | if (apiVersion >= Build.VERSION_CODES.JELLY_BEAN) {
139 | IMPL = new JellyBeanEnvResClearCompatImpl();
140 | } else if (apiVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
141 | IMPL = new IceCreamSandwichEnvResClearCompatImpl();
142 | } else {
143 | IMPL = new EarlyEnvResClearCompatImpl();
144 | }
145 | }
146 |
147 | public static void clear(Resources res) {
148 | IMPL.clear(res);
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/content/res/EnvSkinResourcesManager.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.content.res;
2 |
3 | import java.io.File;
4 | import java.lang.ref.WeakReference;
5 | import java.lang.reflect.Method;
6 | import java.util.HashMap;
7 | import java.util.Map;
8 |
9 | import android.content.Context;
10 | import android.content.pm.PackageInfo;
11 | import android.content.pm.PackageManager;
12 | import android.content.res.AssetManager;
13 | import android.content.res.Resources;
14 | import android.text.TextUtils;
15 |
16 | import com.v7lin.android.os.env.PathUtils;
17 |
18 | /**
19 | *
20 | *
21 | * @author v7lin E-mail:v7lin@qq.com
22 | * @since 2014-11-7 23:26:40
23 | */
24 | public class EnvSkinResourcesManager {
25 |
26 | private final Map> mActiveResources = new HashMap>();
27 |
28 | public static EnvSkinResourcesManager getInstance() {
29 | return EnvResourcesManagerHolder.INSTANCE;
30 | }
31 |
32 | private EnvSkinResourcesManager() {
33 | super();
34 | }
35 |
36 | public String getCurSkinName(Context context) {
37 | String skinName = EnvSetup.getInstance().getSkinName(context);
38 | return checkSkinLegal(context, skinName) ? skinName : "";
39 | }
40 |
41 | private boolean checkSkinLegal(Context context, String skinName) {
42 | boolean legal = false;
43 | if (!TextUtils.isEmpty(skinName)) {
44 | File skinFile = new File(PathUtils.getSkinDir(context), skinName);
45 | if (skinFile.exists() && skinFile.isFile()) {
46 | PackageManager manager = context.getPackageManager();
47 | PackageInfo info = manager.getPackageArchiveInfo(skinFile.getAbsolutePath(), PackageManager.GET_ACTIVITIES);
48 | legal = TextUtils.equals(context.getPackageName(), info.packageName);
49 | }
50 | }
51 | return legal;
52 | }
53 |
54 | public boolean isSkinChanged(Context context, String skinName) {
55 | return !TextUtils.equals(getCurSkinName(context), skinName);
56 | }
57 |
58 | public synchronized Resources getTopLevelResources(Context context) {
59 | Resources res = null;
60 | try {
61 | String skinName = getCurSkinName(context);
62 | if (!TextUtils.isEmpty(skinName)) {
63 | WeakReference wr = mActiveResources.get(skinName);
64 | res = wr != null ? wr.get() : null;
65 | boolean isValid = false;
66 | if (res != null && res.getAssets() != null) {
67 | AssetManager assets = res.getAssets();
68 | Class> clazz = assets.getClass();
69 | Method method = clazz.getDeclaredMethod("isUpToDate");
70 | Object object = method.invoke(assets);
71 | isValid = Boolean.valueOf(String.valueOf(object)).booleanValue();
72 | }
73 | if (!isValid) {
74 | File skinFile = new File(PathUtils.getSkinDir(context), skinName);
75 | if (skinFile.exists() && skinFile.isFile()) {
76 | Class> clazz = AssetManager.class;
77 | AssetManager skinAsset = (AssetManager) clazz.newInstance();
78 | Method method = clazz.getDeclaredMethod("addAssetPath", String.class);
79 | method.invoke(skinAsset, skinFile.getAbsolutePath());
80 | res = new Resources(skinAsset, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());
81 | mActiveResources.put(skinName, new WeakReference(res));
82 | }
83 | }
84 | }
85 | } catch (NoSuchMethodException e) {
86 | e.printStackTrace();
87 | } catch (Exception e) {
88 | e.printStackTrace();
89 | }
90 | return res;
91 | }
92 |
93 | private static class EnvResourcesManagerHolder {
94 | private static final EnvSkinResourcesManager INSTANCE = new EnvSkinResourcesManager();
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/content/res/EnvSkinResourcesWrapper.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.content.res;
2 |
3 | import java.io.InputStream;
4 | import java.util.concurrent.atomic.AtomicBoolean;
5 |
6 | import com.v7lin.android.content.EnvResourcesWrapper;
7 | import com.v7lin.android.content.EnvRes;
8 |
9 | import android.content.Context;
10 | import android.content.res.AssetFileDescriptor;
11 | import android.content.res.ColorStateList;
12 | import android.content.res.Resources;
13 | import android.content.res.TypedArray;
14 | import android.content.res.XmlResourceParser;
15 | import android.graphics.drawable.Drawable;
16 | import android.text.TextUtils;
17 | import android.util.TypedValue;
18 |
19 | /**
20 | * 同名同类型资源
21 | * 优先级:皮肤插件资源 > 应用自带资源
22 | *
23 | * 皮肤插件,不建议换布局,所以这里不重写 {@link Resources#getLayout(int)} 方法
24 | *
25 | * @author v7lin E-mail:v7lin@qq.com
26 | * @since 2014-11-7 23:26:40
27 | */
28 | public class EnvSkinResourcesWrapper extends EnvResourcesWrapper {
29 |
30 | private final Context mContext;
31 | private final String mPackageName;
32 | private final AtomicBoolean mInitSkinRes = new AtomicBoolean(false);
33 | private String mSkinName;
34 | private Resources mSkinRes;
35 |
36 | public EnvSkinResourcesWrapper(Context context, Resources res) {
37 | super(context, res);
38 | this.mContext = context;
39 | this.mPackageName = context.getPackageName();
40 | }
41 |
42 | private synchronized void ensureSkinRes(Context context) {
43 | if (mInitSkinRes.compareAndSet(false, true) || EnvSkinResourcesManager.getInstance().isSkinChanged(context, mSkinName)) {
44 | mSkinName = EnvSkinResourcesManager.getInstance().getCurSkinName(context);
45 | mSkinRes = EnvSkinResourcesManager.getInstance().getTopLevelResources(context);
46 | // 这里需要清理一下 Resources 中的缓存,避免影响 Drawable.ConstantState 和 ColorStateList
47 | EnvSkinResClearCompat.clear(this);
48 | }
49 | }
50 |
51 | /**
52 | * 映射同名同类型资源
53 | *
54 | * 皮肤插件包中并不包含所有资源,这导致 R 文件上的资源 id 无法一一对应。
55 | * 所以这里需要做一次资源映射
56 | */
57 | private EnvRes mappingEnvRes(int id) {
58 | EnvRes mapping = null;
59 | if (mSkinRes != null) {
60 | String packageName = getResourcePackageName(id);
61 | if (TextUtils.equals(mPackageName, packageName)) {
62 | String typeName = getResourceTypeName(id);
63 | String entryName = getResourceEntryName(id);
64 | final int mappingid = mSkinRes.getIdentifier(entryName, typeName, packageName);
65 | mapping = new EnvRes(mappingid);
66 | }
67 | }
68 | return mapping;
69 | }
70 |
71 | @Override
72 | public Drawable getDrawable(int id) throws NotFoundException {
73 | ensureSkinRes(mContext);
74 | EnvRes mapping = mappingEnvRes(id);
75 | if (mapping != null && mapping.isValid() && mSkinRes != null) {
76 | try {
77 | return mSkinRes.getDrawable(mapping.getResid());
78 | } catch (NotFoundException e) {
79 | }
80 | }
81 | return super.getDrawable(id);
82 | }
83 |
84 | @Override
85 | public int getColor(int id) throws NotFoundException {
86 | ensureSkinRes(mContext);
87 | EnvRes mapping = mappingEnvRes(id);
88 | if (mapping != null && mapping.isValid() && mSkinRes != null) {
89 | try {
90 | return mSkinRes.getColor(mapping.getResid());
91 | } catch (NotFoundException e) {
92 | }
93 | }
94 | return super.getColor(id);
95 | }
96 |
97 | @Override
98 | public ColorStateList getColorStateList(int id) throws NotFoundException {
99 | ensureSkinRes(mContext);
100 | EnvRes mapping = mappingEnvRes(id);
101 | if (mapping != null && mapping.isValid() && mSkinRes != null) {
102 | try {
103 | return mSkinRes.getColorStateList(mapping.getResid());
104 | } catch (NotFoundException e) {
105 | }
106 | }
107 | return super.getColorStateList(id);
108 | }
109 |
110 | @Override
111 | public CharSequence getText(int id) throws NotFoundException {
112 | ensureSkinRes(mContext);
113 | EnvRes mapping = mappingEnvRes(id);
114 | if (mapping != null && mapping.isValid() && mSkinRes != null) {
115 | try {
116 | return mSkinRes.getText(mapping.getResid());
117 | } catch (NotFoundException e) {
118 | }
119 | }
120 | return super.getText(id);
121 | }
122 |
123 | @Override
124 | public CharSequence getQuantityText(int id, int quantity) throws NotFoundException {
125 | ensureSkinRes(mContext);
126 | EnvRes mapping = mappingEnvRes(id);
127 | if (mapping != null && mapping.isValid() && mSkinRes != null) {
128 | try {
129 | return mSkinRes.getQuantityText(mapping.getResid(), quantity);
130 | } catch (NotFoundException e) {
131 | }
132 | }
133 | return super.getQuantityText(id, quantity);
134 | }
135 |
136 | @Override
137 | public CharSequence getText(int id, CharSequence def) {
138 | ensureSkinRes(mContext);
139 | EnvRes mapping = mappingEnvRes(id);
140 | if (mapping != null && mapping.isValid() && mSkinRes != null) {
141 | try {
142 | return mSkinRes.getText(mapping.getResid());
143 | } catch (NotFoundException e) {
144 | }
145 | }
146 | return super.getText(id, def);
147 | }
148 |
149 | @Override
150 | public CharSequence[] getTextArray(int id) throws NotFoundException {
151 | ensureSkinRes(mContext);
152 | EnvRes mapping = mappingEnvRes(id);
153 | if (mapping != null && mapping.isValid() && mSkinRes != null) {
154 | try {
155 | return mSkinRes.getTextArray(mapping.getResid());
156 | } catch (NotFoundException e) {
157 | }
158 | }
159 | return super.getTextArray(id);
160 | }
161 |
162 | @Override
163 | public String[] getStringArray(int id) throws NotFoundException {
164 | ensureSkinRes(mContext);
165 | EnvRes mapping = mappingEnvRes(id);
166 | if (mapping != null && mapping.isValid() && mSkinRes != null) {
167 | try {
168 | return mSkinRes.getStringArray(mapping.getResid());
169 | } catch (NotFoundException e) {
170 | }
171 | }
172 | return super.getStringArray(id);
173 | }
174 |
175 | @Override
176 | public int[] getIntArray(int id) throws NotFoundException {
177 | ensureSkinRes(mContext);
178 | EnvRes mapping = mappingEnvRes(id);
179 | if (mapping != null && mapping.isValid() && mSkinRes != null) {
180 | try {
181 | return mSkinRes.getIntArray(mapping.getResid());
182 | } catch (NotFoundException e) {
183 | }
184 | }
185 | return super.getIntArray(id);
186 | }
187 |
188 | @Override
189 | public float getDimension(int id) throws NotFoundException {
190 | ensureSkinRes(mContext);
191 | EnvRes mapping = mappingEnvRes(id);
192 | if (mapping != null && mapping.isValid() && mSkinRes != null) {
193 | try {
194 | return mSkinRes.getDimension(mapping.getResid());
195 | } catch (NotFoundException e) {
196 | }
197 | }
198 | return super.getDimension(id);
199 | }
200 |
201 | @Override
202 | public int getDimensionPixelOffset(int id) throws NotFoundException {
203 | ensureSkinRes(mContext);
204 | EnvRes mapping = mappingEnvRes(id);
205 | if (mapping != null && mapping.isValid() && mSkinRes != null) {
206 | try {
207 | return mSkinRes.getDimensionPixelOffset(mapping.getResid());
208 | } catch (NotFoundException e) {
209 | }
210 | }
211 | return super.getDimensionPixelOffset(id);
212 | }
213 |
214 | @Override
215 | public int getDimensionPixelSize(int id) throws NotFoundException {
216 | ensureSkinRes(mContext);
217 | EnvRes mapping = mappingEnvRes(id);
218 | if (mapping != null && mapping.isValid() && mSkinRes != null) {
219 | try {
220 | return mSkinRes.getDimensionPixelSize(mapping.getResid());
221 | } catch (NotFoundException e) {
222 | }
223 | }
224 | return super.getDimensionPixelSize(id);
225 | }
226 |
227 | @Override
228 | public float getFraction(int id, int base, int pbase) {
229 | ensureSkinRes(mContext);
230 | EnvRes mapping = mappingEnvRes(id);
231 | if (mapping != null && mapping.isValid() && mSkinRes != null) {
232 | try {
233 | return mSkinRes.getFraction(mapping.getResid(), base, pbase);
234 | } catch (NotFoundException e) {
235 | }
236 | }
237 | return super.getFraction(id, base, pbase);
238 | }
239 |
240 | @Override
241 | public boolean getBoolean(int id) throws NotFoundException {
242 | ensureSkinRes(mContext);
243 | EnvRes mapping = mappingEnvRes(id);
244 | if (mapping != null && mapping.isValid() && mSkinRes != null) {
245 | try {
246 | return mSkinRes.getBoolean(mapping.getResid());
247 | } catch (NotFoundException e) {
248 | }
249 | }
250 | return super.getBoolean(id);
251 | }
252 |
253 | @Override
254 | public int getInteger(int id) throws NotFoundException {
255 | ensureSkinRes(mContext);
256 | EnvRes mapping = mappingEnvRes(id);
257 | if (mapping != null && mapping.isValid() && mSkinRes != null) {
258 | try {
259 | return mSkinRes.getInteger(mapping.getResid());
260 | } catch (NotFoundException e) {
261 | }
262 | }
263 | return super.getInteger(id);
264 | }
265 |
266 | @Override
267 | public XmlResourceParser getAnimation(int id) throws NotFoundException {
268 | ensureSkinRes(mContext);
269 | EnvRes mapping = mappingEnvRes(id);
270 | if (mapping != null && mapping.isValid() && mSkinRes != null) {
271 | try {
272 | return mSkinRes.getAnimation(mapping.getResid());
273 | } catch (NotFoundException e) {
274 | }
275 | }
276 | return super.getAnimation(id);
277 | }
278 |
279 | @Override
280 | public XmlResourceParser getXml(int id) throws NotFoundException {
281 | ensureSkinRes(mContext);
282 | EnvRes mapping = mappingEnvRes(id);
283 | if (mapping != null && mapping.isValid() && mSkinRes != null) {
284 | try {
285 | return mSkinRes.getXml(mapping.getResid());
286 | } catch (NotFoundException e) {
287 | }
288 | }
289 | return super.getXml(id);
290 | }
291 |
292 | @Override
293 | public TypedArray obtainTypedArray(int id) throws NotFoundException {
294 | ensureSkinRes(mContext);
295 | EnvRes mapping = mappingEnvRes(id);
296 | if (mapping != null && mapping.isValid() && mSkinRes != null) {
297 | try {
298 | return mSkinRes.obtainTypedArray(mapping.getResid());
299 | } catch (NotFoundException e) {
300 | }
301 | }
302 | return super.obtainTypedArray(id);
303 | }
304 |
305 | @Override
306 | public InputStream openRawResource(int id, TypedValue value) throws NotFoundException {
307 | ensureSkinRes(mContext);
308 | EnvRes mapping = mappingEnvRes(id);
309 | if (mapping != null && mapping.isValid() && mSkinRes != null) {
310 | try {
311 | return mSkinRes.openRawResource(mapping.getResid(), value);
312 | } catch (NotFoundException e) {
313 | }
314 | }
315 | return super.openRawResource(id, value);
316 | }
317 |
318 | @Override
319 | public AssetFileDescriptor openRawResourceFd(int id) throws NotFoundException {
320 | ensureSkinRes(mContext);
321 | EnvRes mapping = mappingEnvRes(id);
322 | if (mapping != null && mapping.isValid() && mSkinRes != null) {
323 | try {
324 | return mSkinRes.openRawResourceFd(mapping.getResid());
325 | } catch (NotFoundException e) {
326 | }
327 | }
328 | return super.openRawResourceFd(id);
329 | }
330 | }
331 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/content/res/EnvTextChecker.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.content.res;
2 |
3 | import com.v7lin.android.content.EnvRes;
4 |
5 | import android.content.Context;
6 | import android.content.res.TypedArray;
7 | import android.util.AttributeSet;
8 | import android.util.TypedValue;
9 | import android.view.View;
10 | import android.widget.AutoCompleteTextView;
11 | import android.widget.TextView;
12 | import android.widget.ToggleButton;
13 |
14 | /**
15 | *
16 | *
17 | * @author v7lin E-mail:v7lin@qq.com
18 | * @since 2014-11-7 23:26:40
19 | */
20 | class EnvTextChecker implements EnvChecker {
21 |
22 | @Override
23 | public void check(Context context, AttributeSet attrs, View view) {
24 | checkTag(context, attrs, view);
25 | checkText(context, attrs, view);
26 | checkCompletionHint(context, attrs, view);
27 | checkToggleButton(context, attrs, view);
28 | checkSwitch(context, attrs, view);
29 | checkSubtitleView(context, attrs, view);
30 | }
31 |
32 | private void checkTag(Context context, AttributeSet attrs, View view) {
33 | final TypedValue value = new TypedValue();
34 | TypedArray array = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View, 0, 0);
35 | array.getValue(com.android.internal.R.styleable.View_tag, value);
36 | EnvRes tagRes = new EnvRes(value.resourceId);
37 | if (tagRes.isValid()) {
38 | view.setTag(context.getText(tagRes.getResid()));
39 | }
40 | array.recycle();
41 | }
42 |
43 | private void checkText(Context context, AttributeSet attrs, View view) {
44 | if (view instanceof TextView) {
45 | TextView textView = (TextView) view;
46 | final TypedValue value = new TypedValue();
47 | TypedArray array = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.TextView, 0, 0);
48 | array.getValue(com.android.internal.R.styleable.TextView_hint, value);
49 | EnvRes hintRes = new EnvRes(value.resourceId);
50 | if (hintRes.isValid()) {
51 | textView.setHint(hintRes.getResid());
52 | }
53 | array.getValue(com.android.internal.R.styleable.TextView_text, value);
54 | EnvRes textRes = new EnvRes(value.resourceId);
55 | if (textRes.isValid()) {
56 | textView.setText(textRes.getResid());
57 | }
58 | array.recycle();
59 | }
60 | }
61 |
62 | private void checkCompletionHint(Context context, AttributeSet attrs, View view) {
63 | if (view instanceof AutoCompleteTextView) {
64 | AutoCompleteTextView textView = (AutoCompleteTextView) view;
65 | final TypedValue value = new TypedValue();
66 | TypedArray array = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.AutoCompleteTextView, 0, 0);
67 | array.getValue(com.android.internal.R.styleable.AutoCompleteTextView_completionHint, value);
68 | EnvRes completionHintRes = new EnvRes(value.resourceId);
69 | if (completionHintRes.isValid()) {
70 | textView.setCompletionHint(context.getText(completionHintRes.getResid()));
71 | }
72 | array.recycle();
73 | }
74 | }
75 |
76 | private void checkToggleButton(Context context, AttributeSet attrs, View view) {
77 | if (view instanceof ToggleButton) {
78 | ToggleButton button = (ToggleButton) view;
79 | final TypedValue value = new TypedValue();
80 | TypedArray array = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ToggleButton, 0, 0);
81 | array.getValue(com.android.internal.R.styleable.ToggleButton_textOn, value);
82 | EnvRes textOnRes = new EnvRes(value.resourceId);
83 | if (textOnRes.isValid()) {
84 | button.setTextOn(context.getText(textOnRes.getResid()));
85 | }
86 | array.getValue(com.android.internal.R.styleable.ToggleButton_textOff, value);
87 | EnvRes textOffRes = new EnvRes(value.resourceId);
88 | if (textOffRes.isValid()) {
89 | button.setTextOff(context.getText(textOffRes.getResid()));
90 | }
91 | array.recycle();
92 | }
93 | }
94 |
95 | private void checkSwitch(Context context, AttributeSet attrs, View view) {
96 | EnvCheckerCompat.checkSwitchText(context, attrs, view);
97 | }
98 |
99 | private void checkSubtitleView(Context context, AttributeSet attrs, View view) {
100 | EnvCheckerCompat.checkSubtitleViewText(context, attrs, view);
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/content/res/EnvTypefaceChecker.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.content.res;
2 |
3 | import java.util.concurrent.atomic.AtomicBoolean;
4 |
5 |
6 | import android.content.Context;
7 | import android.graphics.Typeface;
8 | import android.util.AttributeSet;
9 | import android.view.View;
10 | import android.widget.TextView;
11 |
12 | /**
13 | *
14 | *
15 | * @author v7lin E-mail:v7lin@qq.com
16 | * @since 2014-11-7 23:26:40
17 | */
18 | class EnvTypefaceChecker implements EnvChecker {
19 |
20 | private final AtomicBoolean mInitTypeface = new AtomicBoolean(false);
21 | private String mCurFontName;
22 | private Typeface mTypeface;
23 |
24 | private void ensureTypeface(Context context) {
25 | if (mInitTypeface.compareAndSet(false, true) || EnvTypefaceManager.getInstance().isFontChanged(context, mCurFontName)) {
26 | mCurFontName = EnvTypefaceManager.getInstance().getCurFontName(context);
27 | mTypeface = EnvTypefaceManager.getInstance().getTopLevelTypeface(context);
28 | }
29 | }
30 |
31 | /**
32 | * @see TextView#setTypeface(Typeface)
33 | */
34 | @Override
35 | public void check(Context context, AttributeSet attrs, View view) {
36 | ensureTypeface(context);
37 |
38 | checkTextView(context, attrs, view);
39 | checkSwitch(context, attrs, view);
40 | checkSubtitleView(context, attrs, view);
41 | }
42 |
43 | private void checkTextView(Context context, AttributeSet attrs, View view) {
44 | if (view instanceof TextView) {
45 | TextView textView = (TextView) view;
46 | textView.setTypeface(mTypeface);
47 | }
48 | }
49 |
50 | private void checkSwitch(Context context, AttributeSet attrs, View view) {
51 | EnvCheckerCompat.checkSwitchTypeface(context, attrs, view, mTypeface);
52 | }
53 |
54 | private void checkSubtitleView(Context context, AttributeSet attrs, View view) {
55 | EnvCheckerCompat.checkSubtitleViewTypeface(context, attrs, view, mTypeface);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/content/res/EnvTypefaceManager.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.content.res;
2 |
3 | import java.io.File;
4 | import java.lang.ref.WeakReference;
5 | import java.util.HashMap;
6 | import java.util.Map;
7 |
8 | import android.content.Context;
9 | import android.graphics.Typeface;
10 | import android.text.TextUtils;
11 |
12 | import com.v7lin.android.os.env.PathUtils;
13 |
14 | /**
15 | *
16 | *
17 | * @author v7lin E-mail:v7lin@qq.com
18 | * @since 2014-11-7 23:26:40
19 | */
20 | public class EnvTypefaceManager {
21 |
22 | private final Map> mActiveTypeface = new HashMap>();
23 |
24 | public static EnvTypefaceManager getInstance() {
25 | return EnvTypefaceManagerHolder.INSTANCE;
26 | }
27 |
28 | private EnvTypefaceManager() {
29 | super();
30 | }
31 |
32 | public String getCurFontName(Context context) {
33 | String fontName = EnvSetup.getInstance().getFontName(context);
34 | return checkFontExist(context, fontName) ? fontName : "";
35 | }
36 |
37 | private boolean checkFontExist(Context context, String fontName) {
38 | boolean exist = false;
39 | if (!TextUtils.isEmpty(fontName)) {
40 | File resFile = new File(PathUtils.getFontDir(context), fontName);
41 | exist = resFile.exists() && resFile.isFile();
42 | }
43 | return exist;
44 | }
45 |
46 | public boolean isFontChanged(Context context, String fontName) {
47 | return !TextUtils.equals(getCurFontName(context), fontName);
48 | }
49 |
50 | public synchronized Typeface getTopLevelTypeface(Context context) {
51 | Typeface typeface = null;
52 | String fontName = getCurFontName(context);
53 | if (!TextUtils.isEmpty(fontName)) {
54 | WeakReference wr = mActiveTypeface.get(fontName);
55 | typeface = wr != null ? wr.get() : null;
56 | if (typeface == null) {
57 | File fontFile = new File(PathUtils.getFontDir(context), fontName);
58 | if (fontFile.exists() && fontFile.isFile()) {
59 | typeface = Typeface.createFromFile(fontFile);
60 | mActiveTypeface.put(fontName, new WeakReference(typeface));
61 | }
62 | }
63 | }
64 | return typeface;
65 | }
66 |
67 | private static class EnvTypefaceManagerHolder {
68 | private static final EnvTypefaceManager INSTANCE = new EnvTypefaceManager();
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/crash/CrashReport.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.crash;
2 |
3 | import java.util.Date;
4 |
5 | import android.os.Build;
6 |
7 | /**
8 | *
9 | *
10 | * @author v7lin E-mail:v7lin@qq.com
11 | * @since 2014-11-7 23:26:40
12 | */
13 | class CrashReport {
14 |
15 | CrashReport() {
16 | super();
17 | }
18 |
19 | public String getCrashReport(Throwable ex) {
20 | StringBuilder builder = new StringBuilder();
21 | if (ex != null) {
22 | builder.append("------ " + new Date().toString() + " ------" + "\r\n");
23 | builder.append("Android: " + Build.VERSION.RELEASE + "(" + Build.MODEL + ")" + "\r\n");
24 | builder.append("\r\n");
25 | builder.append("Throwable: " + ex.getMessage() + "\r\n");
26 | builder.append("\r\n");
27 | StackTraceElement[] elements = ex.getStackTrace();
28 | for (int i = 0; i < elements.length; i++) {
29 | builder.append(elements[i].toString() + "\r\n");
30 | }
31 | builder.append("\r\n\r\n");
32 | }
33 | return builder.toString();
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/crash/CrashUncaughtExceptionHandler.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.crash;
2 |
3 | import java.io.File;
4 | import java.io.FileNotFoundException;
5 | import java.io.FileOutputStream;
6 | import java.io.IOException;
7 | import java.lang.Thread.UncaughtExceptionHandler;
8 | import java.text.SimpleDateFormat;
9 | import java.util.Date;
10 | import java.util.Locale;
11 |
12 | import android.content.Context;
13 | import android.text.TextUtils;
14 |
15 | import com.v7lin.android.os.env.PathUtils;
16 |
17 | /**
18 | * 崩溃异常捕获
19 | *
20 | * @author v7lin E-mail:v7lin@qq.com
21 | * @since 2014-11-7 23:26:40
22 | */
23 | public class CrashUncaughtExceptionHandler extends UncaughtExceptionHandlerWrapper {
24 |
25 | private Context mContext;
26 |
27 | public CrashUncaughtExceptionHandler(Context context, UncaughtExceptionHandler wrapped) {
28 | super(wrapped);
29 | this.mContext = context;
30 | }
31 |
32 | @Override
33 | public void uncaughtException(Thread thread, Throwable ex) {
34 | readAndWriteCrashReport(ex);
35 | super.uncaughtException(thread, ex);
36 | }
37 |
38 | private void readAndWriteCrashReport(Throwable ex) {
39 | String report = new CrashReport().getCrashReport(ex);
40 | if (!TextUtils.isEmpty(report)) {
41 | File crashDir = PathUtils.getCrashDir(mContext);
42 |
43 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy_MM_dd", Locale.getDefault());
44 | String crashFileName = sdf.format(new Date());
45 |
46 | FileOutputStream fos = null;
47 | try {
48 | File crashFile = new File(crashDir, crashFileName);
49 | if (!crashFile.exists() || !crashFile.isFile()) {
50 | crashFile.createNewFile();
51 | }
52 |
53 | fos = new FileOutputStream(crashFile, true);
54 | fos.write(report.getBytes());
55 | fos.flush();
56 | } catch (FileNotFoundException e) {
57 | e.printStackTrace();
58 | } catch (IOException e) {
59 | e.printStackTrace();
60 | } finally {
61 | try {
62 | if (fos != null) {
63 | fos.close();
64 | }
65 | } catch (IOException e) {
66 | e.printStackTrace();
67 | }
68 | }
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/crash/UncaughtExceptionHandlerWrapper.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.crash;
2 |
3 | import java.lang.Thread.UncaughtExceptionHandler;
4 |
5 | /**
6 | * 全局异常捕获
7 | *
8 | * @author v7lin E-mail:v7lin@qq.com
9 | * @since 2014-11-7 23:26:40
10 | */
11 | public class UncaughtExceptionHandlerWrapper implements UncaughtExceptionHandler {
12 |
13 | private UncaughtExceptionHandler wrappedHandler;
14 |
15 | public UncaughtExceptionHandlerWrapper(UncaughtExceptionHandler wrapped) {
16 | super();
17 |
18 | if (wrapped == null) {
19 | throw new IllegalArgumentException("wrapped handler must not be null");
20 | }
21 |
22 | this.wrappedHandler = wrapped;
23 | }
24 |
25 | @Override
26 | public void uncaughtException(Thread thread, Throwable ex) {
27 | // 交还给默认的异常崩溃处理器处理
28 | wrappedHandler.uncaughtException(thread, ex);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/os/StatFsCompat.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.os;
2 |
3 | import android.os.Build;
4 | import android.os.StatFs;
5 |
6 | /**
7 | *
8 | *
9 | * @author v7lin E-mail:v7lin@qq.com
10 | * @since 2014-11-7 23:26:40
11 | */
12 | public class StatFsCompat {
13 |
14 | interface StatFsCompatImpl {
15 | public long getBlockSizeLong(StatFs statfs);
16 |
17 | public long getBlockCountLong(StatFs statfs);
18 |
19 | public long getAvailableBlocksLong(StatFs statfs);
20 | }
21 |
22 | static class EarlyStatFsCompatImpl implements StatFsCompatImpl {
23 |
24 | @SuppressWarnings("deprecation")
25 | @Override
26 | public long getBlockSizeLong(StatFs statfs) {
27 | return statfs.getBlockSize();
28 | }
29 |
30 | @SuppressWarnings("deprecation")
31 | @Override
32 | public long getBlockCountLong(StatFs statfs) {
33 | return statfs.getBlockCount();
34 | }
35 |
36 | @SuppressWarnings("deprecation")
37 | @Override
38 | public long getAvailableBlocksLong(StatFs statfs) {
39 | return statfs.getAvailableBlocks();
40 | }
41 | }
42 |
43 | static class JellyBeanMr2StatFsCompatImpl implements StatFsCompatImpl {
44 |
45 | @Override
46 | public long getBlockSizeLong(StatFs statfs) {
47 | return StatFsCompatJellyBeanMr2.getBlockSizeLong(statfs);
48 | }
49 |
50 | @Override
51 | public long getBlockCountLong(StatFs statfs) {
52 | return StatFsCompatJellyBeanMr2.getBlockCountLong(statfs);
53 | }
54 |
55 | @Override
56 | public long getAvailableBlocksLong(StatFs statfs) {
57 | return StatFsCompatJellyBeanMr2.getAvailableBlocksLong(statfs);
58 | }
59 | }
60 |
61 | public static StatFsCompat get(String path) {
62 | return new StatFsCompat(path);
63 | }
64 |
65 | private StatFs statFs;
66 | private final StatFsCompatImpl impl;
67 |
68 | private StatFsCompat(String path) {
69 | this(Build.VERSION.SDK_INT, path);
70 | }
71 |
72 | private StatFsCompat(int apiVersion, String path) {
73 | super();
74 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
75 | impl = new JellyBeanMr2StatFsCompatImpl();
76 | } else {
77 | impl = new EarlyStatFsCompatImpl();
78 | }
79 | statFs = new StatFs(path);
80 | }
81 |
82 | public long getBlockSizeLong() {
83 | return impl.getBlockSizeLong(statFs);
84 | }
85 |
86 | public long getBlockCountLong() {
87 | return impl.getBlockCountLong(statFs);
88 | }
89 |
90 | public long getAvailableBlocksLong() {
91 | return impl.getAvailableBlocksLong(statFs);
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/os/StatFsCompatJellyBeanMr2.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.os;
2 |
3 | import android.annotation.TargetApi;
4 | import android.os.Build;
5 | import android.os.StatFs;
6 |
7 | /**
8 | *
9 | *
10 | * @author v7lin E-mail:v7lin@qq.com
11 | * @since 2014-11-7 23:26:40
12 | */
13 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
14 | public class StatFsCompatJellyBeanMr2 {
15 |
16 | private StatFsCompatJellyBeanMr2() {
17 | super();
18 | }
19 |
20 | public static long getBlockSizeLong(StatFs statfs) {
21 | return statfs.getBlockSizeLong();
22 | }
23 |
24 | public static long getBlockCountLong(StatFs statfs) {
25 | return statfs.getBlockCountLong();
26 | }
27 |
28 | public static long getAvailableBlocksLong(StatFs statfs) {
29 | return statfs.getAvailableBlocksLong();
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/os/env/PathConst.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.os.env;
2 |
3 | /**
4 | *
5 | *
6 | * @author v7lin E-mail:v7lin@qq.com
7 | * @since 2014-11-7 23:26:40
8 | */
9 | public interface PathConst {
10 |
11 | public static final String PATH_APP_DIR = "v7lin";
12 |
13 | public static final String PATH_SUB_DIR_SKIN = "skin";
14 | public static final String PATH_SUB_DIR_FONT = "font";
15 | public static final String PATH_SUB_DIR_CRASH = "crash";
16 | public static final String PATH_SUB_DIR_CACHE = "cache";
17 |
18 | public static final String PATH_SUB_DIR_DEX = "dex";
19 | public static final String PATH_SUB_DIR_DEX_APP = "dex.app";
20 |
21 | public static final String PATH_SUB_DIR_APP = "app";
22 | }
23 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/os/env/PathUtils.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.os.env;
2 |
3 | import java.io.File;
4 |
5 | import android.content.Context;
6 |
7 | import com.v7lin.android.os.storage.StorageUtils;
8 |
9 | /**
10 | *
11 | *
12 | * @author v7lin E-mail:v7lin@qq.com
13 | * @since 2014-11-7 23:26:40
14 | */
15 | public final class PathUtils implements PathConst {
16 |
17 | private PathUtils() {
18 | super();
19 | }
20 |
21 | private static File makeDir(Context context, boolean isPrivate) {
22 | File storageDir = StorageUtils.getStorageDir(context, isPrivate);
23 | File appDir = new File(storageDir, PATH_APP_DIR);
24 | if (!appDir.exists()) {
25 | appDir.mkdirs();
26 | }
27 | return appDir;
28 | }
29 |
30 | private static File makeSubDir(Context context, String subPath, boolean isPrivate) {
31 | File appDir = PathUtils.makeDir(context, isPrivate);
32 | File subDir = new File(appDir, subPath);
33 | if (!subDir.exists()) {
34 | subDir.mkdirs();
35 | }
36 | return subDir;
37 | }
38 |
39 | /**
40 | * 为了测试方便,暂时先放在SD卡上
41 | *
42 | * 这个需要保护起来,避免用户误删
43 | */
44 | public static File getSkinDir(Context context) {
45 | return PathUtils.makeSubDir(context, PATH_SUB_DIR_SKIN, false);
46 | }
47 |
48 | public static File getFontDir(Context context) {
49 | return PathUtils.makeSubDir(context, PATH_SUB_DIR_FONT, false);
50 | }
51 |
52 | public static File getCrashDir(Context context) {
53 | return PathUtils.makeSubDir(context, PATH_SUB_DIR_CRASH, false);
54 | }
55 |
56 | public static File getCacheDir(Context context) {
57 | return PathUtils.makeSubDir(context, PATH_SUB_DIR_CACHE, false);
58 | }
59 |
60 | /**
61 | * Android 4.1.2 Api 中有对 Dex 做了限制,Dex 文件只能放在私有目录下
62 | */
63 | public static File getDexDir(Context context) {
64 | return PathUtils.makeSubDir(context, PATH_SUB_DIR_DEX, true);
65 | }
66 |
67 | /**
68 | * 为了测试方便,暂时先放在SD卡上
69 | *
70 | * 这个需要保护起来,避免用户误删
71 | */
72 | public static File getDexAppDir(Context context) {
73 | return PathUtils.makeSubDir(context, PATH_SUB_DIR_DEX_APP, false);
74 | }
75 |
76 | public static File getAppDir(Context context) {
77 | return PathUtils.makeSubDir(context, PATH_SUB_DIR_APP, false);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/os/storage/StorageManagerCompat.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.os.storage;
2 |
3 | import android.Manifest;
4 | import android.content.Context;
5 | import android.content.pm.PackageManager;
6 | import android.os.Build;
7 |
8 | /**
9 | *
10 | *
11 | * @author v7lin E-mail:v7lin@qq.com
12 | * @since 2014-11-7 23:26:40
13 | */
14 | class StorageManagerCompat {
15 |
16 | interface StorageManagerCompatImpl {
17 | public Object getStorageManager(Context context);
18 |
19 | public String[] getVolumePaths(Object manager);
20 | }
21 |
22 | static class EarlyStorageManagerCompatImpl implements StorageManagerCompatImpl {
23 |
24 | @Override
25 | public Object getStorageManager(Context context) {
26 | return null;
27 | }
28 |
29 | @Override
30 | public String[] getVolumePaths(Object manager) {
31 | return null;
32 | }
33 | }
34 |
35 | static class GingerbreadStorageManagerCompatImpl implements StorageManagerCompatImpl {
36 |
37 | @Override
38 | public Object getStorageManager(Context context) {
39 | return StorageManagerCompatGingerbread.getStorageManager(context);
40 | }
41 |
42 | @Override
43 | public String[] getVolumePaths(Object manager) {
44 | return StorageManagerCompatGingerbread.getVolumePaths(manager);
45 | }
46 | }
47 |
48 | public static StorageManagerCompat get(Context context) {
49 | return new StorageManagerCompat(context);
50 | }
51 |
52 | private final Object manager;
53 | private final StorageManagerCompatImpl impl;
54 |
55 | private StorageManagerCompat(Context context) {
56 | this(Build.VERSION.SDK_INT, context);
57 | }
58 |
59 | private StorageManagerCompat(int apiVersion, Context context) {
60 | super();
61 |
62 | checkPermission(context);
63 |
64 | if (apiVersion >= Build.VERSION_CODES.GINGERBREAD) {
65 | impl = new GingerbreadStorageManagerCompatImpl();
66 | } else {
67 | impl = new EarlyStorageManagerCompatImpl();
68 | }
69 | manager = impl.getStorageManager(context);
70 | }
71 |
72 | private void checkPermission(Context context) {
73 | if (context.checkCallingOrSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
74 | && context.checkCallingOrSelfPermission(Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS) != PackageManager.PERMISSION_GRANTED) {
75 | // throw new IllegalArgumentException("StorageManagerCompat Usage Error.");
76 | }
77 | }
78 |
79 | public String[] getVolumePaths() {
80 | return impl.getVolumePaths(manager);
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/os/storage/StorageManagerCompatGingerbread.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.os.storage;
2 |
3 | import java.lang.reflect.Method;
4 |
5 | import android.annotation.TargetApi;
6 | import android.content.Context;
7 | import android.os.Build;
8 | import android.os.storage.StorageManager;
9 |
10 | /**
11 | *
12 | *
13 | * @author v7lin E-mail:v7lin@qq.com
14 | * @since 2014-11-7 23:26:40
15 | */
16 | @TargetApi(Build.VERSION_CODES.GINGERBREAD)
17 | class StorageManagerCompatGingerbread {
18 |
19 | public static Object getStorageManager(Context context) {
20 | StorageManager manager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
21 | return manager;
22 | }
23 |
24 | public static String[] getVolumePaths(Object manager) {
25 | String[] paths = null;
26 | try {
27 | Class> clazz = StorageManager.class;
28 | Method method = clazz.getMethod("getVolumePaths");
29 | method.setAccessible(true);
30 | Object invoke = method.invoke(manager);
31 | if (invoke != null && invoke instanceof String[]) {
32 | paths = (String[]) invoke;
33 | }
34 | } catch (NoSuchMethodException e) {
35 | e.printStackTrace();
36 | } catch (Exception e) {
37 | e.printStackTrace();
38 | }
39 | return paths;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/os/storage/StorageUtils.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.os.storage;
2 |
3 | import java.io.File;
4 |
5 | import android.content.Context;
6 | import android.os.Environment;
7 | import android.text.TextUtils;
8 |
9 | import com.v7lin.android.os.StatFsCompat;
10 |
11 | /**
12 | * 存储设备管理管理 优先级:SdCard > Memory
13 | *
14 | * @author v7lin E-mail:v7lin@qq.com
15 | * @since 2014-11-7 23:26:40
16 | */
17 | public final class StorageUtils {
18 |
19 | /** 存储器最小存储空间为 50M */
20 | private static final long MIN_SIZE = 50 * 1024 * 1024;
21 |
22 | private StorageUtils() {
23 | super();
24 | }
25 |
26 | /**
27 | * 获取绝对路径
28 | */
29 | public static File getStorageDir(Context context, boolean isPrivate) {
30 | File storageDir = null;
31 | if (!isPrivate) {
32 | StorageManagerCompat manager = StorageManagerCompat.get(context);
33 | String[] paths = manager.getVolumePaths();
34 | if (paths != null) {
35 | String storageDirPath = null;
36 | final int length = paths.length;
37 | for (int i = 0; i < length; i++) {
38 | final String path = paths[i];
39 | final long totalSpace = StorageUtils.getTotalSpace(path);
40 | if (totalSpace >= MIN_SIZE) {
41 | storageDirPath = path;
42 | break;
43 | }
44 | }
45 | if (TextUtils.isEmpty(storageDirPath) && length > 0) {
46 | storageDirPath = paths[0];
47 | }
48 | if (!TextUtils.isEmpty(storageDirPath)) {
49 | storageDir = new File(storageDirPath);
50 | }
51 | }
52 | if (storageDir == null) {
53 | storageDir = StorageUtils.hasExternalStorage() ? StorageUtils.getSDCardStorageDir(context) : StorageUtils.getMemoryStorageDir(context);
54 | }
55 | } else {
56 | storageDir = StorageUtils.getMemoryStorageDir(context);
57 | }
58 | return storageDir;
59 | }
60 |
61 | /**
62 | * 获取 basePath 总空间大小
63 | */
64 | private static long getTotalSpace(String path) {
65 | StatFsCompat statFs = StatFsCompat.get(path);
66 | final long blockCount = statFs.getBlockCountLong();
67 | final long blockSize = statFs.getBlockSizeLong();
68 | return blockCount * blockSize;
69 | }
70 |
71 | /**
72 | * 是否有正在挂载的多媒体设备
73 | */
74 | private static boolean hasExternalStorage() {
75 | return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
76 | }
77 |
78 | /**
79 | * 获取SD卡上的绝对路径
80 | */
81 | private static File getSDCardStorageDir(Context context) {
82 | return Environment.getExternalStorageDirectory();
83 | }
84 |
85 | /**
86 | * 获取内存上的绝对路径
87 | */
88 | private static File getMemoryStorageDir(Context context) {
89 | return context.getFilesDir();
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/test/TestActivity.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.test;
2 |
3 | import android.content.Context;
4 | import android.os.Bundle;
5 | import android.os.Parcelable;
6 | import android.util.SparseArray;
7 | import android.view.View;
8 | import android.view.View.OnClickListener;
9 | import android.view.ViewGroup;
10 |
11 | import com.v7lin.android.R;
12 | import com.v7lin.android.content.dex.EnvDexConst;
13 | import com.v7lin.android.content.res.EnvActivity;
14 | import com.v7lin.android.content.res.EnvSetup;
15 |
16 | /**
17 | *
18 | *
19 | * @author v7lin E-mail:v7lin@qq.com
20 | * @since 2014-11-7 23:26:40
21 | */
22 | public class TestActivity extends EnvActivity implements EnvDexConst {
23 |
24 | private static final String SKIN_TEST = "LibAndroid.apk";
25 | private static final String FONT_TEST = "Oswald-Stencbab.ttf";
26 | private static final String DEX_APP_TEST = "StyleDex.apk";
27 |
28 | private final int mLayoutResID = R.layout.layout_main;
29 | private View mContentView;
30 |
31 | @Override
32 | protected void attachBaseContext(Context newBase) {
33 | EnvSetup.getInstance().setSkinName(newBase, "");
34 | EnvSetup.getInstance().setFontName(newBase, "");
35 | super.attachBaseContext(newBase);
36 | }
37 |
38 | @Override
39 | protected void onCreate(Bundle savedInstanceState) {
40 | super.onCreate(savedInstanceState);
41 |
42 | mContentView = View.inflate(TestActivity.this, mLayoutResID, null);
43 |
44 | mContentView.setOnClickListener(new OnClickListener() {
45 |
46 | @Override
47 | public void onClick(View v) {
48 | if (mContentView != null) {
49 | EnvSetup.getInstance().setSkinName(TestActivity.this, SKIN_TEST);
50 | EnvSetup.getInstance().setFontName(TestActivity.this, FONT_TEST);
51 |
52 | SparseArray container = new SparseArray();
53 | mContentView.saveHierarchyState(container);
54 | ((ViewGroup) mContentView.getParent()).removeView(mContentView);
55 |
56 | mContentView = View.inflate(TestActivity.this, mLayoutResID, null);
57 | mContentView.restoreHierarchyState(container);
58 | setContentView(mContentView);
59 | }
60 |
61 | // // 插件式开发
62 | // File dexFile = new File(PathUtils.getDexAppDir(TestActivity.this), DEX_APP_TEST);
63 | // if (dexFile.exists()) {
64 | // try {
65 | // EnvDexContextWrapper context = new EnvDexContextWrapper(TestActivity.this);
66 | // context.setDexName(DEX_APP_TEST);
67 | //
68 | // ClassLoader parent = TestActivity.this.getClassLoader();// ClassLoader.getSystemClassLoader()
69 | // EnvDexClassLoader loader = EnvDexClassLoader.newInstance(TestActivity.this, dexFile.getAbsolutePath(), parent);
70 | // Class> clazz = loader.loadClass("com.v7lin.android.dex.style.StyleDexTest");
71 | // Object object = clazz.getConstructor(Context.class).newInstance(context);
72 | // if (object != null && object instanceof IDexTest) {
73 | // IDexTest test = (IDexTest) object;
74 | // LogCat.e("Get Text:" + test.getText());
75 | // LogCat.e("Get Dex Text:" + test.getDexText());
76 | // }
77 | // } catch (ClassNotFoundException e) {
78 | // e.printStackTrace();
79 | // } catch (InstantiationException e) {
80 | // e.printStackTrace();
81 | // } catch (IllegalAccessException e) {
82 | // e.printStackTrace();
83 | // } catch (IllegalArgumentException e) {
84 | // e.printStackTrace();
85 | // } catch (InvocationTargetException e) {
86 | // e.printStackTrace();
87 | // } catch (NoSuchMethodException e) {
88 | // e.printStackTrace();
89 | // }
90 | // }
91 | }
92 | });
93 | setContentView(mContentView);
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/com/v7lin/android/test/TestDexActivity.java:
--------------------------------------------------------------------------------
1 | package com.v7lin.android.test;
2 |
3 | import android.graphics.Color;
4 | import android.os.Bundle;
5 | import android.view.View;
6 |
7 | import com.v7lin.android.R;
8 | import com.v7lin.android.content.dex.EnvDexActivity;
9 |
10 | /**
11 | *
12 | *
13 | * @author v7lin E-mail:v7lin@qq.com
14 | * @since 2014-11-7 23:26:40
15 | */
16 | public class TestDexActivity extends EnvDexActivity {
17 |
18 | @Override
19 | protected void onCreate(Bundle savedInstanceState) {
20 | super.onCreate(savedInstanceState);
21 |
22 | View contentView = View.inflate(this, R.layout.layout_dex, null);
23 | contentView.setBackgroundColor(Color.BLUE);
24 | setContentView(contentView);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------