Android优化一共分为几个部分:
1、布局UI的加载的优化
2、APP启动的优化
3、内存的优化
4、电量的优化
5、APP瘦身的优化
一、布局UI的加载的优化
当一个页面非常复杂的时候,比如首页,有很复杂的UI视图,和复杂的动画效果,那么我们进入这个页面的时候,中间会卡顿几秒(时间不定),根本原因就是因为,UI层级嵌套太复杂,和一些自定义View太复杂导致的。所以这也是我们解决问题的核心。
解决方案:
1、减少层级的套用,可以使加载速度变快,采用<include>和<merge>标签。
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
2 xmlns:tools="http://schemas.android.com/tools"
3 android:id="@+id/container"
4 android:layout_width="match_parent"
5 android:layout_height="match_parent"
6 android:orientation="vertical" >
7
8 <include layout="@layout/fragment_main" />
9
10 </LinearLayout>
1 <merge xmlns:android="http://schemas.android.com/apk/res/android"
2 xmlns:tools="http://schemas.android.com/tools"
3 android:layout_width="match_parent"
4 android:layout_height="match_parent" >
5
6 <TextView
7 android:layout_width="match_parent"
8 android:layout_height="wrap_content"
9 android:text="我是button3" />
10
11 <Button
12 android:layout_width="match_parent"
13 android:layout_height="wrap_content"
14 android:text="我是button2" />
15
16 </merge>
2、对于比较复杂的自定义View,可以使用<ViewSub>标签, 它是非常轻量级且宽高都是0,因此他本身不会参与任何绘制。然后在你想要加载的时候在初始化viewsub布局即可
<ViewStub
android:id="@+id/viewSubArrange"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:inflatedId="@+id/viewInfalatedRootId"
android:layout="@layout/layout_arrange_view12"/>
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<com.biocare.project.ecg.arrangeview.Arrange12View
android:id="@+id/arrangeview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
这里为什么要用if (mArrangeView==null)判断呢,因为ViewSub只能实例化一次,不然会报如下异常:
java.lang.IllegalStateException: ViewStub must have a non-null
ViewGroup viewParent
经过上面的优化处理, 打开页面会变得非常迅速!!!
二、APP冷启动的优化:系统不存在该应用的进程,启动应用才创建出应用的进程
随着项目的庞大,打开APP的速度会越来越慢,这时候我们需要做的就是优化。优化主要是两个方面,第一个方面是UI的显示(不然会显示白屏一段时间),第二个方面是速度的提升。
冷启动和热启动的区别: 热启动不会再初始化Application,只会初始化MainActivity的测量绘制即可。
影响启动速度的原因
- -高耗时任务 : 数据库初始化、某些第三方框架初始化、大文件读取、MultiDex加载等,导致CPU阻塞
- -复杂的View层级: 使用的嵌套Layout过多,层级加深,导致View在渲染过程中,递归加深,占用CPU资源,影响Measure、Layout等方法的速度
- -类过于复杂: Java对象的创建也是需要一定时间的,如果一个类中结构特别复杂,new一个对象将消耗较高的资源,特别是一些单例的初始化,需要特别注意其中的结构
- 冷启动的时间计算: 从进程的创建开始计算,到完成视图的第一次绘制(既MainActivity的内容对用户可见)为止!,所以优化主要还是MainActivity的复杂程度和Application初始化第三方的时间
1)默认情况下
如果我们对App没有做处理(设置了默认主题),并且在 Application 初始化了其它第三方的服务(假设需要加载2000ms),那么冷启动过程就会先白屏,然后跳转到启动页。
白屏原因: 系统默认会在启动应用程序的时候 启动空白窗口 ,直到 App 应用程序的入口 Activity 创建成功,视图绘制完毕。( 大概是onWindowFocusChanged方法回调的时候 )。
白屏原因:
当我们在点击手机应用时候,系统首先后创建一个进程(应用进程):需要时间去创建,所以这个期间是白屏的也可能是黑色的,根据系统默认的主题定。
APP启动过程
* ActivityManagerService组织回退栈时以ActivityRecord为基本单位,所有的ActivityRecord放在同一个ArrayList里,可以将mHistory看作一个栈对象,索引0所指的对象位于栈底,索引mHistory.size()-1所指的对象位于栈顶
*
Zygote进程孵化出新的应用进程后,会执行ActivityThread类的main方法.在该方法里会先准备好Looper和消息队列,然后调用attach方法将应用进程绑定到ActivityManagerService,然后进入loop循环,不断地读取消息队列里的消息,并分发消息。
*
ActivityThread的main方法执行后,应用进程接下来通知ActivityManagerService应用进程已启动,ActivityManagerService保存应用进程的一个代理对象,这样ActivityManagerService可以通过这个代理对象控制应用进程,然后ActivityManagerService通知应用进程创建入口Activity的实例,并执行它的生命周期方法
问题一:启动UI的优化
我们可以添加一个闪屏页面,就是在冷启动的时候加载一个我们的页面,来作为闪屏页面。然后Activity初始化完成之后,在跳转页面,这样就可以无缝衔接了。做法如下:
<activity
android:name=".business.launcher.LauncherActivity"
android:screenOrientation="portrait"
android:theme="@style/LauncherTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<style name="LauncherTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowNoTitle">true</item>
<item name="android:windowFullscreen">true</item>
<item name="android:windowBackground">@drawable/layer_splash</item>
</style>
问题二:代码的优化
冷启动耗时统计
adb命令 : adb shell am start -S -W 包名/启动类的全限定名 , -S 表示重启当前应用
C:\Android\Demo>adb shell am start -S -W com.example.moneyqian.demo/com.example.moneyqian.demo.MainActivity
Stopping: com.example.moneyqian.demo
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.example.moneyqian.demo/.MainActivity }
Status: ok
Activity: com.example.moneyqian.demo/.MainActivity
ThisTime: 2247
TotalTime: 2247
WaitTime: 2278
ThisTime : 最后一个 Activity 的启动耗时(例如从 LaunchActivity - >MainActivity「adb命令输入的Activity」 , 只统计 MainActivity 的启动耗时)
TotalTime : 启动一连串的 Activity 总耗时.(有几个Activity 就统计几个)
WaitTime : 应用进程的创建过程 + TotalTime .
1) Application 初始化开销大
Application的onCreate方法中,启动IntentService做初始化第三方的操作,和一些耗时的操作。
2) Activity 初始化开销大
Activity 的创建中除了要避免 Application 创建中提到的问题,还需要注意以下问题:
- 加载极其复杂的布局
- 主线程中出现磁盘或网络 I/O
- 加载和解码 Bitmap
- 渲染多个 VectorDrawable 对象。
解决问题的方法
1、视图层次过深:
- 减少冗余、嵌套的布局层次。
- 不布局绘制不可见的 UI,而是使用 ViewStub 对象在适当的时间布局绘制。
2、大量的资源初始化:
- 调整资源初始化的位置,可以在不同的线程执行懒加载。
- 加载部分视图,然后再加载大的位图和其他资源。
- 第一个MainActivity的onCreate()减少工作量
- 不要让Application进行业务、耗时的操作
3、通过 DDMS或者TraceView在确定每个方法的耗时操作。
三、APP的内存优化:
这一个优化在之前的文章有专门介绍, 根本核心就是避免内存泄漏问题,
减小对象的内存占用
1、使用更加轻量级的数据结构:例如,我们可以考虑使用ArrayMap/SparseArray而不是HashMap等传统数据结构,相比起Android系统专门为移动操作系统编写的ArrayMap容器,在大多数情况下,HashMap都显示效率低下,更占内存。另外,SparseArray更加高效在于,避免了对key与value的自动装箱,并且避免了装箱后的解箱。
2、避免使用Enum:在Android中应该尽量使用int来代替Enum,因为使用Enum会导致编译后的dex文件大小增大,并且使用Enum时,其运行时还会产生额外的内存占用。
3、减小Bitmap对象的内存占用:
inBitmap:如果设置了这个字段,Bitmap在加载数据时可以复用这个字段所指向的bitmap的内存空间。但是,内存能够复用也是有条件的。比如,在Android 4.4(API level 19)之前,只有新旧两个Bitmap的尺寸一样才能复用内存空间。Android 4.4开始只要旧 Bitmap 的尺寸大于等于新的 Bitmap 就可以复用了。
4、inSampleSize:缩放比例,在把图片载入内存之前,我们需要先计算出一个合适的缩放比例,避免不必要的大图载入。
5、decode format:解码格式,选择ARGB_8888 RBG_565 ARGB_4444 ALPHA_8,存在很大差异。
ARGB_4444:每个像素占四位,即A=4,R=4,G=4,B=4,那么一个像素点占4+4+4+4=16位 ARGB_8888:每个像素占四位,即A=8,R=8,G=8,B=8,那么一个像素点占8+8+8+8=32位 RGB_565:每个像素占四位,即R=5,G=6,B=5,没有透明度,那么一个像素点占5+6+5=16位 ALPHA_8:每个像素占四位,只有透明度,没有颜色。
6、使用更小的图片:在设计给到资源图片的时候,我们需要特别留意这张图片是否存在可以压缩的空间,是否可以使用一张更小的图片。尽量使用更小的图片不仅仅可以减少内存的使用,还可以避免出现大量的InflationException。假设有一张很大的图片被XML文件直接引用,很有可能在初始化视图的时候就会因为内存不足而发生InflationException,这个问题的根本原因其实是发生了OOM。
7、内存对象的重复使用
大多数对象的复用,最终实施的方案都是利用对象池技术,要么是在编写代码的时候显式的在程序里面去创建对象池,然后处理好复用的实现逻辑,要么就是利用系统框架既有的某些复用特性达到减少对象的重复创建,从而减少内存的分配与回收。
8、复用系统自带资源:Android系统本身内置了很多的资源,例如字符串/颜色/图片/动画/样式以及简单布局等等,这些资源都可以在应用程序中直接引用。这样做不仅仅可以减少应用程序的自身负重,减小APK的大小,另外还可以一定程度上减少内存的开销,复用性更好。但是也有必要留意Android系统的版本差异性,对那些不同系统版本上表现存在很大差异,不符合需求的情况,还是需要应用程序自身内置进去。
9、ListView ViewHodler
10、Bitmap对象的复用:在ListView与GridView等显示大量图片的控件里面需要使用LRU的机制来缓存处理好的Bitmap。
inBitmap:使用inBitmap属性可以告知Bitmap解码器去尝试使用已经存在的内存区域,新解码的bitmap会尝试去使用之前那张bitmap在heap中所占据的pixel data内存区域,而不是去问内存重新申请一块区域来存放bitmap。
使用inBitmap,在4.4之前,只能重用相同大小的bitmap的内存区域,而4.4之后你可以重用任何bitmap的内存区域,只要这块内存比将要分配内存的bitmap大就可以。这里最好的方法就是使用LRUCache来缓存bitmap,后面来了新的bitmap,可以从cache中按照api版本找到最适合重用的bitmap,来重用它的内存区域。
新申请的bitmap与旧的bitmap必须有相同的解码格式
11、避免在onDraw方法里面执行对象的创建:类似onDraw等频繁调用的方法,一定需要注意避免在这里做创建对象的操作,因为他会迅速增加内存的使用,而且很容易引起频繁的gc,甚至是内存抖动。
12、StringBuilder:在有些时候,代码中会需要使用到大量的字符串拼接的操作,这种时候有必要考虑使用StringBuilder来替代频繁的“+”。
四、APP电量的优化:
- 减少网络的请求
- 减少定位的频率
- 减少socket的通信
- 减少动画的UI
五、APK瘦身
1、应用里面的图片可以在熊猫压缩网站进行压缩后,进行使用
2、无用的资源及时删除(可以通过命令查出无用资源)
3、无用的代码及时删除
4、减少帧动画的使用
5、减少第三库Modle的使用
6、so库尽量用小的