Android应用继续瘦身,以及一些注意事项

自上次对应用瘦身过后,经历的若干功能的迭代,很快的,安装包大小又到了15MB,老大说要控制在10MB之内,于是便开始了新一轮的瘦身之旅。

如果你还没读过我的前面一篇瘦身文章,可以先去看看:

Android应用瘦身,从18MB到12.5MB

已经看过上文的朋友,可以直接阅读本文,文章接下来将会为你带来更多瘦身方案,以及一些需要注意的问题。

WebP局限性

在我的上一篇文章提到,让大家谨慎使用WebP格式的图片,并且提及到Android Studio不支持预览WebP图片的问题,而最新的2.3版本已经支持预览WebP文件,同时还能够帮我们将JPG和PNG转换成为WebP图片,虽然IDE的升级让我们喜出望外,但你仍然要关心WebP的一下几个问题:

  • WebP的编码时间为PNG的5倍以上,解码速度与PNG差不多,甚至更快;
  • WebP编码时占用内存比PNG高25%,解码时比PNG低30%;
  • Android 4.0+开始支持WebP图片,但是带透明度的WebP图片是在4.2.1+之后才支持的,在4.2.1之前则无法显示;

针对以上一二点,你可能需要考虑一下自家用户实际机型分布情况,如果大部分都是中低端配置机型,使用了WebP后对体验可能会有所下降,对于高配设备,完全可以忽略。而针对第三点,则需要考虑用户的系统分布,相信绝大部分App的用户已经满足4.0+这个条件,但对于满足4.2.1+条件就很难说了,至少我们这款有几百万用户的产品仍有不少用户不满足此条件,因此也不敢随便提升最低兼容版本,但是你仍然可以进行一些WebP的处理,主要如下:

  • 对于不需要透明度的PNG图片,先转成JPG格式后,再使用IDE进行WebP转换。不要直接用PNG转换成WebP,否则转换后的WebP仍然带了透明度,在4.2.1之前的设备任然无法显示,先转换成JPG就是为了去除PNG的透明度;
  • 将PNG转换成JPG时,建议采用专业的图片处理工具,因为我在使用一些在线转换工具转换成为JPG时,发现有少量的图片转换成JPG后再转成WebP时在4.2.1之前依然无法正常显示,但经过专业的处理的图片处理工具转换后的图片则全部没有问题;
  • 在将JPG转换成WebP图片时,采用IDE默认75的有损压缩质量,超过75之后,压缩效果反而会变差;

虽然IDE给了我们WebP更方便的支持,但实际上受限于Android系统版本的问题,所以,我们只能对有限的图片做处理,对于项目中有大量留白的透明像素的图片,转换成JPG效果会失真,所以,也只能暂时忽略掉。最后,推荐一个腾讯出品的WebP在线工具——智图:https://zhitu.isux.us/ 。关于WebP的知识,我非常建议大家阅读以下两篇腾讯出品的文章,你会收获很多

继续阅读全文 »

关于Android RecyclerView的那些开源LayoutManager

Google默认为RecyclerView提供了LinearLayoutManager、StaggeredGridLayoutManager、GridLayoutManager,已经可以满足很多开发需求了,但是实际开发过程中,免不了出现一些更加新颖的交互设计。于是,便从Github上整理了一波关于RecyclerView可以使用的LayoutManager,在实际开发中需要用到时,遇到相似的效果时即可随手拈来,提高效率。

FanLayoutManager

https://github.com/Cleveroad/FanLayoutManager

来自国外一家名为Cleveroad的科技公司的开源作品,因其效果就像风扇扇叶旋转一样,所以名中带Fan。效果不错,API上也有很多的配置参数,用起来还是挺灵活的。

CarouselLayoutManager

https://github.com/Azoft/CarouselLayoutManager

让你的RecyclerView秀出传送带效果,支持横向和纵向两种选择。

ChipsLayoutManager

https://github.com/BelooS/ChipsLayoutManager

一种流式布局的效果,很像我们平时看到的标签云。该库同样提供了很多可配置效果的API,并且支持多种多花效果。

继续阅读全文 »

几个不错的Android开源音视频播放器

整理了一下Github上几个开源的音视频播放器项目,有兴趣的同学可以clone代码去研究学习。

UniversalMusicPlayer

https://github.com/googlesamples/android-UniversalMusicPlayer

这是Google开源的一个音频播放器项目,它向我们展示了如何在不同的设备上(Android phones, tablets, Android Auto, Android Wear, Android TV and Google Cast)提供一致的用户体验。

StylishMusicPlayer

https://github.com/ryanhoo/StylishMusicPlayer

采用RxJava开发的本地音乐播放器,UI图标都是Material Design上找的,最终作品颜值如下。

继续阅读全文 »

搭建个人博客,你需要知道这些

一个多月前开始筹备搭建自己的个人博客,由于要等国内备案完成,因此直到这两天才算完全搞定。今天把个人博客搭建需要了解的相关知识整理一下,希望可以给同样想建站的同学提供一点参考。

博客生成器

一个网站最基础的部分就是网页,如果想从HTML页面写起,显然成本太高,好在大牛们已经做好了博客生成器来解决网页编写的问题。一般来说,博客生成器分为动态和静态两种。其中,动态博客生成器典型代表有:WordPress、FarBox、Ghost等,静态的博客生成器典型代表有:Hexo、Jekyll、Octopress、Hugo等。关于动态和静态的区别主要有以下几点:

  • 资源占用上,静态的相比动态占用服务器资源少,还可以托管在Github Pages上;
  • 发布更新操作上,由于静态博客没有管理后台,所以发布更新内容要比动态博客繁琐;
  • 访问速度上,由于静态博客没有数据库,所以访问速度更快;
  • 安全性上,静态博客相比动态博客免疫了很多Web攻击套路;

我的博客便是选择了Hexo这个静态博客生成器。

Web服务器

有了博客生成器后,只需按照对应官方文档配置即可生成一个最简单的本地博客站点,如果想要让网络上人能够访问,你还需要将将其部署上服务器才行。对于服务器的选择,肯定要选择知名的服务器提供商,相对来说会比较稳定靠谱,国内的UCloud、阿里云、腾讯云、七牛云,国外的有亚马逊。如果使用静态博客生成器,还可以考虑用Github Pages,这样可以节省一笔租服务器的开销,对于还没有收入的在校学生,是个很不错的选择。不过,Github偶尔在国内无法访问,这点得做好心理准备。

继续阅读全文 »

Android开发时,那些相见恨晚的工具或网站!

本文来我在知乎话题Android开发时你遇到过什么相见恨晚的工具或网站?下的回答!

Android开发时你遇到过什么相见恨晚的工具或网站?

在实际Android开发过程确实会有很多相见恨晚的工具或网站出现,下面是我自己的一些分享。

源码网站

https://github.com/googlesamples

Android系统每次推出一些新特性,Google都会写一些Demo放在Github上,对于想要了解新特性怎么玩的同学,肯定不能错过它。

https://www.codota.com/

如果你不知道一个Android的类怎么用,可以在Codota上面快速的找到很多不错的示例代码。

https://android-arsenal.com/

你是否还在为找不到合适的开源库而苦恼,Android Arsenal这个网站已经帮你做了一定的分类,可以帮你提高不少效率。

https://android.googlesource.com/

Android所有的源代码都在这里,只需找到对应想要的模块,用Git克隆下来即可。比如,我想要的framework代码

http://androidxref.com/

克隆Android一个模块的代码量是很多的,有时候我只想要几个类的代码怎么办?AndroidXRef这个网站可以让你单独搜索某个类,要哪几个下载哪几个即可。

http://grepcode.com/

除了AndroidXRef可以查看某个类的源代码外,GrepCode同样也能做到。而且GrepCode不限于Android的源码,这里也推荐一下。

源码分析

源码分析的网站很多,这里举几个比较经典的网站。

继续阅读全文 »

Android应用安全风险与防范

近段时间在做Android应用安全方面的功课,本文进行简单梳理方便以后Review,有错误和遗漏之处还请大家指出。

代码混淆

Android开发除了部分功能采用C/C++编码外,其余主要都是采用Java进行编码开发功能。Java应用非常容易被反编译,Android自然也不例外。只要利用apktool等类似的反编译工具,就可以通过安装包获取源代码。Google为了保护开发者的知识产权,为Android提供了ProGuard混淆方案,以增加反编译后源码阅读,但对于Android开发老司机和逆向工程师来说,解读还原出源代码只是时间问题。

ProGuard是针对Java应用的保护,并不是专门针对Android应用的,Android虽然使用Java开发,但是毕竟不是跑在JVM上,所以安装包结构和普通的Java应用还是区别多多。如果你对免费的ProGuard放心不下,可考虑试试付费的混淆方案DexGuard,除了拥有ProGuard的功能外,还包含资源混淆,字符串加密,类加密和dex文件分割等。

虽然代码混淆是最为基础的保护措施,不过国内仍有不少应用还是裸奔的,其中还包括一些大厂应用(此处不表)。

签名校验

Android黑产里面,有一个叫做二次打包,也称为重打包。即通过反编译正版应用后,可以获得smali源码,往其中注入代码或者修改相应业务逻辑后,再利用新的签名进行重新打包,并发布到应用市场去,很多无良开发者就是通过这种方式去破解一些付费应用或者往其中注入广告代码来获利。简单梳理一下重打包的基本流程:

  1. 对正版应用用apktool类逆向工具进行解包;
  2. 在某处地方注入smali代码;
  3. 利用IDE生成签名文件,再通过jarsigner进行签名;
  4. 上传应用市场;

为了与二次打包做对抗,可以在应用内的关键功能入口增加校验签名的检测,如果发现应用签名非正版,则强制关闭应用或者限制用户使用。加签名校验代码时,可以考虑:

继续阅读全文 »

Android应用瘦身,从18MB到12.5MB

最近在为我们开发的直播应用做 Android 端的安装包瘦身,花了大概一周的时间把安装包从 18MB 减小到了 12.5MB。原本完全可以优化到 10MB 之下,但由于其他原因的限制,所以目前阶段只到 12.5MB 为止。在此记录一下优化的思路和用到的工具,方便自己以后 Review ,有需要的童鞋也可供参考。

瘦身的目的

从目的导向来看,我们是不会无缘无故去做一件事情的,那我们对应用瘦身的目的是为了什么?答案是:提高下载转化率。什么是下载转化率?举个栗子:你的应用大小是 18MB ,有100个潜在用户想要去下载尝试使用,结果有20个用户嫌弃安装包太大直接扬长而去,有20个用户在等待下载的过程中取消下载,最终只有60个用户真正下载安装,那么应用的下载转化率就是 60/100 = 60% 。

简单的小结便是:安装包越小,用户下载等待的时间越短,对手机存储配置小的设备体验愈佳,应用的下载转化率也就越高。记得以前在腾讯大讲堂听微信大牛说过,微信第一个版本只有差不多 400KB ,瞬间膜拜。

安装包的组成

要对安装包做瘦身,首先需要了解安装包的组成结构,这里简单的梳理了一下组成各个部分及其作用:

其中,在安装包中占比较大的包括:dex文件、res文件夹、assets文件夹、lib文件夹以及resource.arsc文件。所以,接下来的瘦身优化就是让这些文件变小,以此达到瘦身的目的。

在 Android Studio 2.2.3 开始,就加入了浏览 APK 结构的功能,我们直接把安装包拖入 IDE ,就可以直接浏览其组成和对应大小,这样能够很方便的对比分析出每一步优化后的结果。

继续阅读全文 »

走过2016时,我成长些什么

时间过得超快,2016年转眼间就剩下那么几天,每到年尾都要对整整的一年做个总结,本文便是我的2016总结稿。

开发成长篇

一直以来,我主要都是从事 Android 开发这块的工作,对于后端或者 Web 端这块的,很多知识还停留在校园教科书式的时代。原本今年年初计划要重拾 Java 后端、 Web 端和 Python 这块,重新拓展一下自己的技术栈,但是上半年由于个人原因,导致原本想要开始的计划延迟了半年,下半年才开始执行计划,目前有闲暇时间都在看关于 Web 端和 Java 后端的,Python 这块至今还未动,这算是2016最大的遗憾了。

对于我来说,由于一直以来都是做客户端开发工作,很多问题的思考有一定程度局限在客户端视觉。有时候处在客户端的角度看待问题,得出来一些解决方案具有很多局限性。为了弥补自己思考方式上的短板,我在尝试从自己原本的圈子里走出来,站在不同的角度去看待业务需求的实现,这样得到的方案会更加具有可实践性。后端语言众多,为何选择 Java ?主要是当前 Java 是主流,外加 Android 开发也用的是 Java ,可以省去学新语法的时间。就学习来说,语言不是重点,相信如果打通了 Java 后端的任督二脉,换一种后端语言也可以很快入门上手。

随着 H5 、 HyBird 、 RN 、 Weex 、 微信小程序以及 Google 在 GDD 上提出的 Progressive Web AMPs 的出现,很多原本要原生 App 实现的功能,都交给了 Web 端的童鞋,甚至有些小公司,客户端的童鞋同时还要搞 Web 端的事。说实话,我对 Web 端的开发很久前就非常感兴趣,这些新技术的出现更加触发了我跃跃欲试的冲动。从实际应用场景来说,这种移动端的 Web 开发确实有很多很好的应用场景,但并不是所有的场景都适合使用。所以,我从来都不信 Web 即将取代原生应用的鬼话。站在开发者的角度来说,关注技术发展的趋势并学习一些新技术是非常必要的,而那些死守着自己一亩三分地的开发者是注定要被时代淘汰的。

继续阅读全文 »

Android应用内存泄漏的定位、分析与解决策略

最近一直在为项目做内存泄漏的优化工作,本文用于记录一些排查优化的思路和方案,以及介绍一些辅助工具。

什么是内存泄漏

对于不同的语言平台来说,进行标记回收内存的算法是不一样的,像 Android(Java)则采用 GC-Root 的标记回收算法。下面这张图就展示了 Android 内存的回收管理策略(图来自Google 2011的IO大会)

图中的每个圆节点代表对象的内存资源,箭头代表可达路径。当圆节点与 GC Roots 存在可达路径时,表示当前资源正被引用,虚拟机是无法对其进行回收的(如图中的黄色节点)。反过来,如果圆节点与 GC Roots 不存在可达路径,则意味着这块对象的内存资源不再被程序引用,系统虚拟机可以在 GC 过程中将其回收掉。

有了上面的内存回收的栗子,那么接下来就可以说说什么是内存泄漏了。从定义上讲,Android(Java)平台的内存泄漏是指没有用的对象资源任与GC-Root保持可达路径,导致系统无法进行回收。举一个最简单的栗子,我们在 Activity 的 onCreate 函数中注册一个广播接收者,但是在 onDestory 函数中并没有执行反注册,当 Activity 被 finish 掉时,Activity 对象已经走完了自身的生命周期,应该被资源回收释放掉,但由于没有反注册, 此时 Activity 和 GC-Root 间任然有可达路径存在,导致 Activity 虽然被销毁,但是所占用的内存资源却无法被回收掉。类似的栗子其实有很多,不一一例举了。对于 Android(Java)内存回收管理想要再深入了解的童鞋,可以看看下面资源:

泄漏的源头

了解完内存泄漏的理论知识后,再来归类一下内存泄漏的源头。这里我将其归位以下三类:

继续阅读全文 »

深入了解 Messenger 的实现细节

本文是阅读 Messenger 源码的一点记录,看这篇文章前,需要对 Android 的进程间通讯方式有所了解,不然可能会云里雾里。

从使用 Messenger 说起

Android 上实现 IPC (进程间通讯)的方式有好几种,其中有一种就是使用 AIDL 方式实现,对使用 AIDL 不了解的童鞋可以看下方的官方文档(需要梯子)。

https://developer.android.com/guide/components/aidl.html

对于使用 AIDL 方式通讯,其关键就在于创建 aidl 文件,系统会自动为 aidl 文件生成相应的 Java 类,其关键实现在于生成的 Java 类中。

系统提供了一个更方便我们进行 IPC 的类 —— Messenger,先来看看如何使用 Messenger(熟悉的童鞋完全可以跳过这一部分)。

  • 第一步:客户端进程创建两个 Messenger,一个 Sender ,一个 Receiver;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//客户端进程发消息给服务进程
private Messenger mSender;
//客户端进程接收服务进程回调
private Messenger mReceiver = new Messenger(new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Bundle data = msg.getData();
if (data != null) {
String response = data.getString("body");
Toast.makeText(MainActivity.this, response, Toast.LENGTH_SHORT).show();
}
}
});
  • 第二步:编写Service类,并需要在 AndroidManifest.xml 配置多进程;
1
2
3
4
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
public class IPCService extends Service {
private Messenger messenger = new Messenger(new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
try {
Thread.sleep(2 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Message response = Message.obtain();
Bundle data = new Bundle();
data.putString("body", "response");
response.setData(data);
try {
msg.replyTo.send(response);//回调客户端
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
@Override
public IBinder onBind(Intent intent) {
return messenger.getBinder();//将Binder返回给请求绑定的进程
}
}
  • 第三步:绑定Service进程,并在 ServiceConnection 中初始化 Sender;

继续阅读全文 »