本篇分为代码优化 布局优化 图片优化 网络优化 电量优化
一 、代码优化
避免创建不必要的对象
因为创建一个对象就意味着垃圾回收器需要回收一个对象,都回耗费时间,
①拼接字符串时,那么可以优先考虑使用StringBuffer或者StringBuilder来进行拼接,而不是加号连接符,因为使用加号连接符会创建多余的对象,拼接的字符串越长,加号连接符的性能越低。
②在没有特殊原因的情况下,尽量使用基本数据类来代替封装数据类型,int比Integer要更加高效,其它数据类型也是一样
静态优于抽象
如果你并不需要访问一个对象中的某些字段,只是想调用它的某个方法来去完成一项通用的功能,那么可以将这个方法设置成静态方法,这会让调用的速度提升15%-20%,同时也不用为了调用这个方法而去专门创建对象了,这样还满足了上面的一条原则。另外这也是一种好的编程习惯,因为我们可以放心地调用静态方法,而不用担心调用这个方法后是否会改变对象的状态(静态方法内无法访问非静态字段)。
对常量使用static final修饰符
注意,这种优化方式只对基本数据类型以及String类型的常量有效,对于其它数据类型的常量是无效的。不过,对于任何常量都是用static final的关键字来进行声明仍然是一种非常好的习惯。
使用增强型for循环语法
对于ArrayList这种集合,自己手写的循环要比增强型for循环更快,而其他的集合就没有这种情况
一般的for循环写法
-
int sum = 0;
-
Counter[] localArray = mArray;
-
int len = localArray.length;
-
for (int i = 0; i < len; ++i) {
-
sum += localArray[i].mCount;
-
}
不要写成这样:for (int i = 0; i < mArray.length; ++i)因为这样在每次循环时都会执行 mArray.length
多使用系统封装好的API
如说String类当中提供的好多API都是拥有极高的效率的,像indexOf()方法和一些其它相关的API
系统中提供的System.arraycopy()方法
避免在内部调用Getters/Setters方法
-
public class Calculate {
-
private int one = 1;
-
private int two = 2;
-
public int getOne() {
-
return one;
-
}
-
public int getTwo() {
-
return two;
-
}
-
public int getSum() {
-
//return getOne() + getTwo();//尽量不要这样写
-
return one+two
-
}
}
节制地使用Service
当界面不可见时释放内存
避免在Bitmap上浪费内存
知晓内存的开支情况
我们还应当清楚我们所使用语言的内存开支和消耗情况,并且在整个软件的设计和开发当中都应该将这些信息考虑在内。可能有一些看起来无关痛痒的写法,结果却会导致很大一部分的内存开支,例如:
- 使用枚举通常会比使用静态常量要消耗两倍以上的内存,在Android开发当中我们应当尽可能地不使用枚举。
- 任何一个Java类,包括内部类、匿名类,都要占用大概500字节的内存空间。
- 任何一个类的实例要消耗12-16字节的内存开支,因此频繁创建实例也是会一定程序上影响内存的。
- 在使用HashMap时,即使你只设置了一个基本数据类型的键,比如说int,但是也会按照对象的大小来分配内存,大概是32字节,而不是4字节。因此最好的办法就是像上面所说的一样,使用优化过的数据集合。
尽量避免使用依赖注入框架
如:可以将findViewById()省去的框架等
使用ProGuard简化代码(暂时没用过)
ProGuard相信大家都不会陌生,很多人都会使用这个工具来混淆代码,但是除了混淆之外,它还具有压缩和优化代码的功能。ProGuard会对我们的代码进行检索,删除一些无用的代码,并且会对类、字段、方法等进行重命名,重命名之后的类、字段和方法名都会比原来简短很多,这样的话也就对内存的占用变得更少了。
二、布局优化技巧
<include>
<include>标签可以允许在一个布局当中引入另外一个布局,如公共的头布局
<include>标签当中的属性会覆盖layout属性,如给include标签若指定了ID属性,而你的layout也定义了ID,则你的layout的ID会被覆盖。如果findViewById()查找layout的Id来查找子控件,子控件会报空指针
需要注意的是,如果我们想要在<include>标签当中覆写layout属性,必须要将layout_width和layout_height这两个属性也进行覆写,否则覆写效果将不会生效。
<merge>
<merge>标签是作为<include>标签的一种辅助扩展来使用的,它的主要作用是为了防止在引用布局文件时产生多余的布局嵌套。
使用场景:<merge/>多用于替换FrameLayout或者当一个布局包含另一个时,<merge/>标签消除视图层次结构中多余的视图组。例如你的主布局文件是垂直布局,引入了一个垂直布局的include,这是如果include布局使用的LinearLayout就没意义了,使用的话反而减慢你的UI表现。这时可以使用<merge/>标签优化。
如:
-
<merge xmlns:android="http://schemas.android.com/apk/res/android">
-
<Button
-
android:layout_width="fill_parent"
-
android:layout_height="wrap_content"
-
android:text="@string/add"/>
-
<Button
-
android:layout_width="fill_parent"
-
android:layout_height="wrap_content"
-
android:text="@string/delete"/>
-
</merge>
<ViewStub />
仅在需要时加载布局,ViewStub是View的子类,它不可见且大小为0,可以延迟加载布局资源
.ViewStub只能Inflate一次,之后ViewStub对象会被置为空。 而且只能用来Inflate一个布局文件,而不是某个具体的View
使用场景:各种不常用的布局像进度条、显示错误消息等可以使用<ViewStub />标签,以减少内存使用量,加快渲染速度
注意:①还不支持 <merge /> 标签②:如果这个根布局是个View,比如说是个ImagView,那么找出来的id为null,得必须注意这一点
写法:
-
ViewStub viewStub = (ViewStub) findViewById(R.id.view_stub);
-
if (viewStub != null) {
-
View inflatedView = viewStub.inflate();
-
editExtra1 = (EditText) inflatedView.findViewById(R.id.edit_extra1);
-
//或者是下面的形式加载
-
//myViewStub.setVisibility(View.VISIBLE);
-
}
三、图片优化
四种图片格式
-
JPEG
-
是一种广泛使用的有损压缩图像标准格式,它不支持透明和多帧动画,一般摄影的作品是JEPG格式的,通过控制压缩比,可以调整图片的大小
-
-
PNG
-
是一种无损压缩的图片格式,他支持完整的透明通道,从图片处理的领域来讲,JEPG只有RGB三个通道,而PNG有ARGB四个通道,因此PNG图片占用空间一般比较大,会无形的增加app的大小,在做app瘦身时一般都要对PNG图片进行梳理以减小其占用的体积
-
-
GIF
* 是一种古老的图片的格式,诞生于1987年,随着初代互联网流行开来,他的特别是支持多帧动画,表情图, -
Webp
-
google于2010年发布,支持有损和无损、支持完整的透明通道、也支持多帧动画,目前主流的APP都已经使用了Webp,淘宝,微信,即保证了图片的大小和质量
-
在安卓应用开发中能够使用编解码格式的只有三种 JEPG PNG WEBP
推荐几种图片处理网站
-
无损压缩ImageOptin,在不牺牲图片质量的前提下,即减下来PNG图片占用的空间,又提高了图片的加载速度 https://imageoptim.com/api
-
有损压缩ImageAlpha,图片大小得到极大的缩小,如果需要使用的话,一定要ui设计师看能否使用 https://pngmini.com/
-
有损压缩TinyPNG 比较知名的png压缩的工具,也需要ui设计师看能够使用不 https://tinypng.com/
-
PNG/JPEG 转化为 wepb :智图 :http://zhitu.isux.us/
如果ui设计师工作量不饱和的话,可以推荐, 尽量使用 .9.png 点9图 小黑点表示 可拉伸区域,黑边表示纵向显示内容的范围
四、网络优化
移动端对额App几乎都是联网的,网络延迟等会对App的性能产生较大的影响,网络优化可以节约网络流量和电量
上网过程的说明流程:对于普通的上网,系统是这样做的:浏览器本身就是一个客户端,当你输入URL的时候,首先浏览器会去请求DNS服务器,通过DNS获取相应域名的对应的Ip地址,通过IP地址找到对应Ip对应的服务器,要求建立TCP连接,等浏览器发送完HTTP Request包后,服务器接受到请求包之后才开始处理请求包,服务器调用自身服务,返回Http Response (响应包):客户端收到来自服务器的响应后开始渲染这个Response包里的主体(body),等收到全部的内容随后断开与该服务器之间的TCP连接。具体的文章在这里
Web工作的方式
https://www.jianshu.com/p/84aa55a8a7eb
DNS域名的系统,主要的功能根据应用请求所用的域名URL去网络上面映射表中查相对应的IP地址,这个过程有可能会消耗上百毫秒,而且可能存在着DNS劫持的危险,可以替换为Ip直接连接的方式来代替域名访问的方法,从而达到更快的网络请求,但是使用Ip地址不够灵活,当后台变换了Ip地址的话,会出现访问不了,前段的App需要发包,解决方法是增加Ip地址动态更新的能力,或者是在IP地址访问失败了,切换到域名的访问.
Demo--->ping 一个地址,不正确的话,切换到备用的地址
boolean ping = ping("wwww.baidu.com");
/**
* 测试主域名是否可用
*
* @param ip
* @return
*/
private final int PING_TIME_OUT = 1000; // ping 超时时间
private boolean ping(String ip) {
try {
Integer status = executeCommandIp( ip, PING_TIME_OUT );
if ( status != null && status == 0 ) {
return true;
} else {
return false;
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
return false;
}
/**
* 执行域名是否可通
* @param command
* @param timeout
* @return
* @throws IOException
* @throws InterruptedException
* @throws TimeoutException
*/
private int executeCommandIp( final String command, final long timeout )
throws IOException, InterruptedException, TimeoutException {
Process process = Runtime.getRuntime().exec(
"ping -c 1 -w 100 " + command);
mWorker = new PingWorker(process);
mWorker.start();
try {
mWorker.join(timeout);
if (mWorker.exit != null) {
return mWorker.exit;
} else {
//throw new TimeoutException();
return -1;
}
} catch (InterruptedException ex) {
mWorker.interrupt();
Thread.currentThread().interrupt();
throw ex;
} finally {
process.destroy();
}
}
class PingWorker extends Thread {
private final Process process;
private Integer exit;
private String ip;
public PingWorker(Process process) {
this.process = process;
}
@Override
public void run() {
try {
exit = process.waitFor();
if (exit == 0) {
BufferedReader buf = new BufferedReader(new InputStreamReader(process.getInputStream()));
String str = new String();
StringBuffer ipInfo = new StringBuffer();
//读出所有信息并显示
while((str=buf.readLine())!=null) {
ipInfo.append(str);
}
/*
PING sni1st.dtwscache.ourwebcdn.com (14.215.228.4) 56(84) bytes of data.64 bytes from 14.215.228.4: icmp_seq=1 ttl=57 time=16.6 ms--- sni1st.dtwscache.ourwebcdn.com ping statistics ---1 packets transmitted, 1 received, 0% packet loss, time 0msrtt min/avg/max/mdev = 16.656/16.656/16.656/0.000 ms
*/
System.out.println("shiming ipInfo----->"+ipInfo);
Pattern mPattern = Pattern.compile("\\((.*?)\\)");
Matcher matcher = mPattern.matcher(ipInfo.toString());
if ( matcher.find() ) {
ip = matcher.group( 1 );
}
}
else {
ip = " process.waitFor()==="+exit;
}
}
catch (IOException e) {
e.printStackTrace();
ip="java.io.IOException: Stream closed";
return;
}
catch (InterruptedException e) {
ip="java.io.InterruptedException: Stream closed";
return;
}
}
}
合并网络请求,一次完整的Http请求,首先进行的是DNS查找,通过TCP三次握手,从而建立连接,如果是https请求的话,还要经过TLS握手成功后才可以进行连接,对于网络请求,减少接口,能够合并的网络请求就尽量合并
SSL(Secure Sockets Layer 安全套接层),及其继任者传输层安全(Transport Layer Security,TLS)是为网络通信提供安全及数据完整性的一种安全协议。TLS与SSL在传输层对网络连接进行加密。
HTTPS和HTTP的区别主要为以下四点:
-
一、https协议需要到ca申请证书,一般免费证书很少,需要交费。
-
二、http是超文本传输协议,信息是明文传输,https 则是具有安全性的ssl加密传输协议。
-
三、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
-
四、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
-
预先获取数据能够将网络请求集中在一次,这样其他时间段手机就可以切换到空闲的时间,从而避免经常性的唤醒,从而节约用电
-
避免轮询:如果说每个一段时间需要向服务器发起主动的网络请求,其实不建议在app端做这样的操作,可以使用推送,如果说在不得已的情况下,也要避免使用Thread.sleep()函数来循环等待,建议使用系统的AlarmManager来实现定时轮询,AlarmManager 可以保证在系统休眠的时候,CPU也可以得到休息,在下一次需要发起网络请求的时候才唤醒
-
尽量避免网络请求失败时候,无限制的循环重试连接,参考 :https://www.jianshu.com/p/141ee58eb143 中有提到过
//基于Rxjava 和 RxAndroid Retorfit
o.subscribeOn(Schedulers.io())
.retryWhen(new RetryWhenHandler(1, 5))
.doOnSubscribe(new Action0() {
@Override
public void call() {
s.onBegin();
}
})
.subscribeOn(AndroidSchedulers.mainThread())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(s);
-
离线缓存,对于图片或者文件,内存缓存+磁盘缓存+网络缓存,一般我们本地需要做的是二级缓存,当缓存中存在图片或者是文件,直接从缓存中读取,不会走网络,下载图片,在Android中使用LruCache实现内存缓存,DiskLruCache实现本地缓存
/**
* 图片缓存的核心类
*/
private LruCache<String, Bitmap> mLruCache;
// 缓存大小
private static final int CACHE_MAX_SIZE = 1024;
/**
* LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
*/
private void lruCacheDemo() {
// 获取应用程序最大可用内存
int maxMemory = (int) Runtime.getRuntime().maxMemory();
//设置LruCache缓存的大小,一般为当前进程可用容量的1/8。
int cacheSize = maxMemory / 8;
mLruCache = new LruCache<String, Bitmap>(cacheSize) {
//重写sizeOf方法,计算出要缓存的每张图片的大小
//这个方法要特别注意,跟我们实例化 LruCache 的 maxSize 要呼应,怎么做到呼应呢,比如 maxSize 的大小为缓存的个数,这里就是 return 1就 ok,如果是内存的大小,如果5M,这个就不能是个数 了,这是应该是每个缓存 value 的 size 大小,如果是 Bitmap,这应该是 bitmap.getByteCount();
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
////这里用户可以重写它,实现数据和内存回收操作
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
if (oldValue != newValue) {
oldValue.recycle();
}
}
};
}
/**
* 从LruCache中获取一张图片,如果不存在就返回null。
*/
private Bitmap getBitmapFromLruCache(String key) {
return mLruCache.get(key);
}
/**
* 往LruCache中添加一张图片
*
* @param key
* @param bitmap
*/
private void addBitmapToLruCache(String key, Bitmap bitmap) {
if (getBitmapFromLruCache(key) == null) {
if (bitmap != null)
mLruCache.put(key, bitmap);
}
}
-
压缩数据的大小:可以对发送服务端数据进行zip压缩,同时可以使用更优的数据传输格式,例如二进制的代替Json格式,这个比较牛逼,估计运用的很少,使用webp格式代替图片格式
-
不同的网络环境使用不同的超时策略,常见的网络格式有 2g、3g、4g、wifi,实时的更新当前的网络状态,通过监听来获取最新的网络类型,并动态调整网络超时的时间
private void netWorkDemo() {
TextView netWork = findViewById(R.id.net_work);
boolean networkConnected = NetworkUtils.isNetworkConnected(this);
int networkType = NetworkUtils.getNetworkType(this);
System.out.println("shiming 是否联网了"+networkConnected);
switch (networkType){
case TYPE_UNKNOWN:
System.out.println("shiming 联网的类型---无网络连接");
netWork.setText("是否联网了---》"+networkConnected+" 联网的类型---无网络连接");
break;
case TYPE_2G:
System.out.println("shiming 联网的类型---2G");
netWork.setText("是否联网了---》"+networkConnected+" 联网的类型---2G");
break;
case TYPE_3G:
System.out.println("shiming 联网的类型---TYPE_3G");
netWork.setText("是否联网了---》"+networkConnected+" 联网的类型---TYPE_3G");
break;
case TYPE_4G:
System.out.println("shiming 联网的类型---TYPE_4G");
netWork.setText("是否联网了---》"+networkConnected+" 联网的类型---TYPE_4G");
break;
case TYPE_WIFI:
System.out.println("shiming 联网的类型---TYPE_WIFI");
netWork.setText("是否联网了---》"+networkConnected+" 联网的类型---TYPE_WIFI");
break;
}
}
CDN的全称是Content Delivery Network,即内容分发网络。其基本思路是尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快、更稳定。通过在网络各处放置节点服务器所构成的在现有的互联网基础之上的一层智能虚拟网络,CDN系统能够实时地根据网络流量和各节点的连接、负载状况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上。其目的是使用户可就近取得所需内容,解决 Internet网络拥挤的状况,提高用户访问网站的响应速度。
五、电量优化
①数据传输 蓝牙传输,Wi-Fi传输 移动网络传输 后台数据的管理:根据业务需求,接口尽量避免无效数据的传输 数据传输的频度问题:通过经验值或者是数据统计的方法确定好数据传输的频度,避免冗余重复的数据传输,数据传输过程中要压缩数据的大小,合并网络请求,避免轮询
②位置服务 正确的使用位置复位,是应用耗电的一个关键
Android提供了三种定位
- GPS定位,通过GPS实现定位,精度最高,通常在10米(火星坐标),但是GPS定位在时间和电量上消耗也是最高的
- 网络定位,通过移动通信的基站信号差异来计算出手机所在的位置,精度比GPS差好多
- 被动定位,最省电的定位服务,如果使用被动定位服务。说明它想知道位置更新信息但有不主动获取,等待手机中其他应用或者是服务或者是系统组件发出定位请求,并和这些组件的监听器一起接收位置的信息,实际的开发中,一般使用的是第三方的地图,高德,腾讯,百度,他们做了很好的封装,同时在地图上的表现上更加的优化
③ AlarmManager 也是比较耗电的,通常情况下需要保证两次唤醒操作的时间间隔不要太短了,在不需要使用唤醒功能的情况下,尽早的取消唤醒功能