版权声明:本文为博主原创文章,未经博主允许不得转载。
最近整理了几篇在乐视实习时写的文章,都是一些简单的技术调研,Android夜间模式是当时做技术分享的内容,供大家参考,欢迎一起讨论~
一.夜间模式的简单介绍
随着人们夜间阅读的需求越来越扩大化,许多应用也都在设置中增加了“夜间模式”这个选项。关于夜间模式的实现,有很多种方法。在此介绍几种比较常见的夜间模式的实现方法。
首先,夜间模式是Android换肤的一种,如果应用中的夜间模式是多种皮肤的其中之一,则可以从apk外部加载皮肤资源,通过下载额外的apk文件,然后获取该apk文件中的资源文件。从实现难度上来讲,换肤的实现会比夜间模式更复杂些,但是实现方式思路也已经比较成熟。两个比较常见的应用的夜间模式实现效果如下:
可以有一些APP是深蓝底色,有些则是黑色,字体一般为灰色。总体来说都要以暗色为底色,降低亮度和对比度。
二.夜间模式的实现方案——应用换肤技术
1.zip压缩包式皮肤
应用可设置一个默认路径。如果用户选择某个皮肤,则解压该皮肤.zip到这个文件夹中。该皮肤也可以自定义扩展名,但是都为zip格式(例如墨迹天气皮肤扩展名是mja,搜狗输入法的皮肤扩展名是sga)
实现技术: 该技术重点在于如何去读取zip文件中的资源以及皮肤文件存放策略。
实现方案:
如果每次启动都读取SD卡上的皮肤文件,就会影响APP执行速度。最好是提供设置皮肤的界面,把用户选择的皮肤文件解压缩到皮肤路径(例如”/data/data/[package name]/skin” )下,这样不需要跨存储器读取,速度较快,而且不需要每次都去zip压缩包中读取
下面是一些关于处理zip文件的方法
public void doZip(String srcFile, String destFile) {
// zipDirectoryPath:需要压缩的文件夹名
File zipDir;
String dirName;
zipDir = new File(srcFile);
dirName = zipDir.getName();
try {
this.zipOut = new ZipOutputStream(
new BufferedOutputStream(
new FileOutputStream(destFile)));
//设置压缩的注释
zipOut.setComment("comment");
//设置压缩的编码,如果要压缩的路径中有中文,就用下面的编码
zipOut.setEncoding("GBK");
//启用压缩
zipOut.setMethod(ZipOutputStream.DEFLATED);
//压缩级别为最强压缩
zipOut.setLevel(Deflater.BEST_COMPRESSION);
handleDir(zipDir, this.zipOut,dirName);
this.zipOut.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
public void unZip(String unZipfile, String destFile) {
FileOutputStream fileOut;
File file;
InputStream inputStream;
try {
this.zipFile = new ZipFile(unZipfile);
for (Enumeration entries = this.zipFile.getEntries();
entries .hasMoreElements();) {
ZipEntry entry = (ZipEntry) entries.nextElement();
file = new File(destFile+File.separator+entry.getName());
if (entry.isDirectory()) {
file.mkdirs();
} else {
// 如果指定文件的目录不存在,则创建之.
File parent = file.getParentFile();
if (!parent.exists()) {
parent.mkdirs();
}
inputStream = zipFile.getInputStream(entry);
fileOut = new FileOutputStream(file);
while ((this.readedBytes = inputStream.read(
this.buf)) > 0) {
fileOut.write(this.buf, 0, this.readedBytes);
}
fileOut.close();
inputStream.close();
}
}
this.zipFile.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
/**
* 获得压缩文件内文件列表
*
* @param zipFile 压缩文件
* @return 压缩文件内文件名称
* @throws ZipException 压缩文件格式有误时抛出
* @throws IOException 当解压缩过程出错时抛出
*/
public static ArrayList getEntriesNames(File zipFile) throws ZipException, IOException {
ArrayList entryNames = new ArrayList();
Enumeration> entries = getEntriesEnumeration(zipFile);
while (entries.hasMoreElements()) {
ZipEntry entry = ((ZipEntry) entries.nextElement());
entryNames.add(new String(getEntryName(entry).getBytes("GB2312"), "8859_1"));
}
return entryNames;
}
/**
* 获得压缩文件内压缩文件对象以取得其属性
*
* @param zipFile 压缩文件
* @return 返回一个压缩文件列表
* @throws ZipException 压缩文件格式有误时抛出
* @throws IOException IO操作有误时抛出
*/
public static Enumeration> getEntriesEnumeration(File zipFile) throws ZipException,
IOException {
ZipFile zf = new ZipFile(zipFile);
return zf.entries();
}
具体demo可见 https://github.com/luozheng1985/skin_demo
这种方式的优点是:皮肤资源的格式定义很随意可以是zip也可以是自定义的格式,只要程序中能够解析到资源就行,缺点是需要读取并解析文件,导致效率上会比较差。
2.apk文件换肤
这种方法通过获取其他程序的context来获取皮肤资源。我们知道android程序中要获取drawable、layout等资源,都要通过context.getResources().getXXX的方式。那么如果我们可以拿到其他程序的context,那么那个程序就可以作为皮肤程序来提供资源给主程序使用了。android中两个程序相互读取数据的条件是:两个程序的共享用户id相同,通过AndroidManifest.xml中的android:sharedUserId属性配置;两个程序签名相同。想要改变皮肤时,改变提供资源的context为皮肤程序的context,然后刷新即可。
具体实现步骤如下:
- 在主应用程序 和 皮肤资源程序的AndroidManifest.xml中配置相同sharedUserId。
android:sharedUserId是指共用一个uid,也就是,凡是这个属性相同的工程,都会共用同一个uid
*为了让用户无感知,需要安装后皮肤APk后,让自己不可以打开,且不生成桌面图标,因此要去掉AndroidManifest.xml的如下代码:
皮肤资源apk 与 主应用apk中对同一功能的皮肤资源文件名要一致。
在主程序中 获取到皮肤资源apk对应的Context,
Context skinContext = createPackageContext("皮肤资源包名",Context.CONTEXT_IGNORE_SECURITY);
通过返回的context对象就可以访问到皮肤资源apk中的任何资源
如 :
Drawable drawable =skinContext.getResources().getDrawable(R.drawable.bg_title)
(皮肤资源Apk 无需包含任何UI Activity,只需要包含需要更换皮肤的res资源文件)
优点:可定期提供换肤包供下载,换肤方式灵活,同时效率比较高。
缺点:如需使用某个皮肤,必须安装该皮肤。但其实现起来还是用代码的方式来提供置换的。同时,让两个工程来共享一个进程,这样做十分的危险。此外,安装了很多皮肤后,应用程序列表里面会有很多皮肤程序。在主程序卸载后,皮肤工程不能同样的卸载。如卸载腾讯微博之后,安装的皮肤apk没有被卸载。