App经过优化后好处多多。优化会使app加载快、打开快、响应快、占内存变小、省电等等等。
Memory Monitor(Profiler)是Android studio自带的一个内存监视工具,他可以很好的帮助我们进行内存实时分析。通过点击as下方的Memory Monitor(Profiler)
标签**(或者View->Tool Windows->Profiler)**,打开工具:较浅蓝色代表free的内存、深色代表使用的内存用内从变换的走势图变换,可以判断关于内存的使用状态,例如当内存持续增高时,可能发生内存泄漏;当内存突然减少时,可能发生GC等。
MAT是一个快速,功能丰富的Java Heap分析工具,通过分析Java进程的内存快照HPROF分析,从众多的对象中分析,快速计算出在内存中对象占用的大小,查看哪些对象不能被卡及收集器回收,并可以通过试图直观的查看可能造成这种结果的对象。
LeakCanary是一个内存检测工具。github文档
Android Lint是安装继承的一个代码提示工具,它可以给布局、代码提供非常强大的帮助。比如在布局文件中写了三层冗余的LinearLayout布局就会在编辑器右边看到提示。
使用:
含义:
如腾讯的perfdog、阿里的mobileperf等等。
布局优化就是减少界面的卡顿、提高流畅度,造成界面卡顿的最重要的原因就是丢帧。
丢帧:比如现在很多手机都是60帧,在一秒中画面需要更新60帧,但是如果着一秒钟只更新了50帧,那么就是丢帧,在人的感觉当中就是卡顿。引起丢真的原因非常多,有硬件层面,有软件层面,也有App自身的问题。
这里的 uploadBitmap 主要是 upload bitmap to gpu 的操作 , 如果 bitmap 过大 , 或者每一帧内容都在变化 , 那么就需要频繁 upload , 导致渲染线程耗时.
应用本身频繁调用 buildDrawingCache 会导致主线程执行耗时从而导致卡顿 , 从下图来看, 主线程每一帧明显超过了 Vsync 周期
微信对话框有多个动态表情的时候, 也会出现这种情况导致的卡顿
如果应用在 Activity 中设置了软件渲染, 那么就不会走 hwui , 直接走 skia, 纯 cpu 进程渲染, 由于这么做会加重 UI Thread 的负载, 所以大部分情况下这种写法都会导致卡顿
Activity resume 的时候, 与 AMS 通信要持有 AMS 锁, 这时候如果碰到后台比较繁忙的时候, 等锁操作就会比较耗时, 导致部分场景因为这个卡顿, 比如多任务手势操作
这一项指的是游戏自身的绘制问题, 会导致总是不能满帧去跑, 如下图, 红框部分是SurfaceFlinger 显示掉帧, 原因是底下的游戏在绘制的时候, 刚好这一帧超过了 Vsync SF 的信号.这种一般是游戏自身的问题
应用里面涉及到 WebView 的时候, 如果页面比较复杂, WebView 的性能就会比较差, 从而造成卡顿
如果屏幕帧率和系统的 fps 不相符 , 那么有可能会导致画面不是那么顺畅. 比如使用 90 Hz 的屏幕搭配 60 fps 的动画
部分应用由于设计比较复杂, 每一帧绘制的耗时都比较长 , 这么做的话在 60 fps 的机器上可能没有问题 , 但是在 90 fps 的机器上就会很卡, 因为从 60 -> 90 , 每帧留给应用的绘制时间从 16.6 ms 变成了 11.1 ms , 如果没有在 11.1 ms 内完成, 就会出现掉帧的情况.
主线程操作数据库
使用 SharedPerforence 的 Commit 而不是 Apply
与 WebView 进行交互的时候, 如果 WebView 出现问题, 那么也会出现卡顿
RenderThread 自身比较耗时, 导致一帧的时长超过 Vsync 间隔
渲染线程耗时过长阻塞了主线程的下一次 sync
有的 App 会产生多个 RenderThread ,在某些场景下 RenderThread 在 sync 的时候花费比较多的时间,导致主线程卡顿
标签重用layout(布局复用)
延时加载(提高显示速度)
标签替换父布局(merge用于删除不必要的布局,即可用来减少层级)
实现布局变化较快的界面。warp_context
(会增加measure计算成本)Android手机对于每个APP可用的内存是有一定限制的,同时每部手机的运行内存也是不一样的,所以当有的手机出现内存泄漏的时候会产生OOM导致APP崩溃。
我们就要传入ACTIVITY_SERVICE这个参数,这样我们就可以通过ActivityManager中的getMemoryClass
方法获取到该APP在该部手机上能获取到的最大的内存空间,单位是M,或者可以通过getLargeMemoryClass
方法获取到设置了最大堆的应用的能获得到的最大的内存空间。但是这两个获取到的值一般都是相同的,因为一般的应用都不支持最大堆的申明,而且也不这么去做。
StringBuilder str = new StringBuilder();
ActivityManager activityManager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
int memClass = activityManager.getMemoryClass();//以m为单位
int LargememClass = activityManager.getLargeMemoryClass();//以m为单位
str.append("memClass:"+memClass+"\n");
str.append("LargememClass:"+LargememClass+"\n");
mTv.setText(str.toString());//最大内存数。(M为单位)
生命最大堆:在清单文件中application标签下定义
android:largeHeap="true"
通过Android Studio的Android Profiler来动态的查看我们APP的内存使用情况
内存泄漏:由于代码的瑕疵,导致这块内存虽然是停止不用的,但是依然还是被其他东西引用着,使得GC没法对它进行回收。
OOM:内存溢出
OOM的必然性:Android手机对于每个APP可用的内存是有一定限制的。
OOM的可解决性:Android手机的生产厂家对于手机内存的设置是有过考量的,一般来说只要是有对内存的优化是不会出现OOM问题的。
OOM问题绝大部分发生在图片上。
1)强引用(StrongReference)
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。
如下:
Object o=new Object(); // 强引用
当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。如果不使用时,要通过如下方式来弱化引用,如下:
o=null; // 帮助垃圾收集器回收此对象
显式地设置o为null,或超出对象的生命周期范围,则gc认为该对象不存在引用,这时就可以回收这个对象。具体什么时候收集这要取决于gc的算法。
举例:
public void test(){
Object o=new Object();
// 省略其他操作
}
在一个方法的内部有一个强引用,这个引用保存在栈中,而真正的引用内容(Object)保存在堆中。当这个方法运行完成后就会退出方法栈,则引用内容的引用不存在,这个Object会被回收。
但是如果这个o是全局的变量时,就需要在不用这个对象时赋值为null,因为强引用不会被垃圾回收。
强引用在实际中有非常重要的用处,举个ArrayList的实现源代码:
private transient Object[] elementData;
public void clear() {
modCount++;
// Let gc do its work
for (int i = 0; i < size; i++){
elementData[i] = null;
}
size = 0;
}
在ArrayList类中定义了一个私有的变量elementData数组,在调用方法清空数组时可以看到为每个数组内容赋值为null。不同于elementData=null,强引用仍然存在,避免在后续调用 add()等方法添加元素时进行重新的内存分配。使用如clear()方法中释放内存的方法对数组中存放的引用类型特别适用,这样就可以及时释放内存。
2)软引用(SoftReference)
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
String str=new String("abc"); // 强引用
SoftReference<String> softRef=new SoftReference<String>(str); // 软引用
软引用在实际中有重要的应用,例如浏览器的后退按钮。按后退时,这个后退时显示的网页内容是重新进行请求还是从缓存中取出呢?这就要看具体的实现策略了。
(1)如果一个网页在浏览结束时就进行内容的回收,则按后退查看前面浏览过的页面时,需要重新构建
(2)如果将浏览过的网页存储到内存中会造成内存的大量浪费,甚至会造成内存溢出
这时候就可以使用软引用
Browser prev = new Browser(); // 获取页面进行浏览
SoftReference sr = new SoftReference(prev); // 浏览完毕后置为软引用
if(sr.get()!=null){
rev = (Browser) sr.get(); // 还没有被回收器回收,直接获取
}else{
prev = new Browser(); // 由于内存吃紧,所以对软引用的对象回收了
sr = new SoftReference(prev); // 重新构建
}
这样就很好的解决了实际的问题。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
其实对应的还有弱引用和虚引用,这个对于内存的优化没有什么用处就不在这里说了。
优化OOM
1)缩小图片
private void changePicOpti() {
if (file == null) {
//file图片文件
return;
}
try {
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;//只获得文件的宽和高
BitmapFactory.decodeStream(new FileInputStream(file), null, o);
int width_tmp = o.outWidth;
int height_tmp = o.outHeight;
int scale = 2;
while (true) {
if (width_tmp / scale < SCREEN_WIDTH && height_tmp / scale < SCREEN_HEIGHT) {
//SCREEN_WIDTH屏幕宽度,SCREEN_HEIGHT屏幕宽度
break;
}
scale += 2;
}
scale /= 2;//inSampleSize=1会将原始图片放大2倍
o.inJustDecodeBounds = false;
o.inSampleSize = scale;
FileInputStream fin = new FileInputStream(file);
Bitmap bitmap = BitmapFactory.decodeStream(fin, null, o);
img.setImageBitmap(bitmap);
} catch (IOException e) {
e.printStackTrace();
}
}
2)压缩像素
private void changeRGB() {
if (file == null) {
//file图片文件
return;
}
try {
BitmapFactory.Options o = new BitmapFactory.Options();
o.inPreferredConfig = Bitmap.Config.RGB_565;
FileInputStream fin = new FileInputStream(file);
Bitmap bitmap = BitmapFactory.decodeStream(fin, null, o);
img.setImageBitmap(bitmap);
} catch (IOException e) {
e.printStackTrace();
}
}
3)显示部分高清图片
private void partLoad() {
if (file == null) {
//file图片文件
return;
}
try {
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;//只获得文件的宽和高
FileInputStream fin = new FileInputStream(file);
BitmapFactory.decodeStream(fin, null, o);
int width_tmp = o.outWidth;
int height_tmp = o.outHeight;
fin = new FileInputStream(file);
BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder.newInstance(fin, false);
BitmapFactory.Options options = new BitmapFactory.Options();
//SCREEN_WIDTH屏幕宽度,SCREEN_HEIGHT屏幕宽度,nextPx垂直方向变动的,nextPy水平方向的变动,默认中心点
int x = width_tmp / 2 - SCREEN_WIDTH / 2 + nextPx;
int y = height_tmp / 2 - SCREEN_HEIGHT / 2 + nextPy;
//保证在图片范围以内
if (x < 0) {
x = 0;
} else if (x > width_tmp - SCREEN_WIDTH) {
x = width_tmp - SCREEN_WIDTH;
}
if (y < 0) {
y = 0;
} else if (y > height_tmp - SCREEN_HEIGHT) {
y = height_tmp - SCREEN_HEIGHT;
}
Bitmap bitmap = bitmapRegionDecoder.decodeRegion(new Rect(x, y, x + SCREEN_WIDTH, y + SCREEN_HEIGHT), options);
img.setImageBitmap(bitmap);
} catch (IOException e) {
e.printStackTrace();
}
}
应用启动过程中,最重要的两个进程就是 SystemServer 和 App Process . 其职责划分如下:
IntentService
public class StartIntentService extends IntentService {
private static final String ACTION_INIT_WHEN_APP_CREATE = "SERVICE_ACTION_INIT";
public StartIntentService() {
super("StartIntentService");
}
public static void start(Context context) {
Intent intent = new Intent(context, StartIntentService.class);
intent.setAction(ACTION_INIT_WHEN_APP_CREATE);
context.startService(intent);
}
@Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
final String action = intent.getAction();
if (ACTION_INIT_WHEN_APP_CREATE.equals(action)) {
//初始化
init();
}
}
}
Application中的onCreate添加:InitializeService.start(this);
splash_layer.xml
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/white" />
<item>
<bitmap
android:gravity="center"
android:src="@drawable/ic_splash" />
item>
layer-list>
添加一个主题:
<style name="SplashTheme" parent="AppTheme">
- "android:windowBackground"
>@drawable/splash_layer
style>
添加一个Activity:SplashActivity。
public class LogoSplashActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
startActivity(new Intent(this,MainActivity.class));
finish();
}
}
在AndroidManifest.xml中的application下添加
<activity
android:name=".SplashActivity"
android:screenOrientation="portrait"
android:theme="@style/SplashTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
intent-filter>
activity>
线程优化主要是减少 CPU 调度带来的波动,让启动时间更稳定。如果启动过程中有太多的线程一起启动,会给 CPU 带来非常大的压力,尤其是比较低端的机器。过多的线程同时跑会让主线程的 Sleep 和 Runnable 状态变多, 增加了应用的启动速度,优化的过程中要注意:
启动过程中减少 GC 的次数
将一些IO操作写进子线程中
在App使用过程中,你可能会遇到App没有响应您是否关闭它的提示。这就是ANR
ANR全名(Application Not Responding), 也就是"应用无响应". 当操作在一段时间内系统无法处理时, 系统层面会弹出上图那样的ANR对话框.
在Android里, App的响应能力是由Activity Manager和Window Manager系统服务来监控的. 通常在如下两种情况下会弹出ANR对话框:
不要在主线程里面做繁重的操作
特别注意:
使用Thread和HandlerThread时, 为了使效果更好, 建议设置Thread的优先级偏低一点:
Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND);
因为如果没有做任何优先级设置的话, 你创建的Thread默认和UI Thread是具有同样的优先级的, 同样的优先级的Thread, CPU调度上还是可能会阻塞掉你的UI Thread, 导致ANR的.
Android应用开发中,需要考虑的情况是,如何优化电量使用,让我们的app不会因为电量消耗过高被用户排斥,或者被其他安全应用报告
对于移动设备而言,有以下几种行为会导致设备电量的消耗增加
分析工具:Network Monitor
网络代理工具:Wireshark, Fiddler, Charles等
任务调度JobScheduler,该工具继承了常见的集中运行方式,开发者只需添加少数几行代码,即可完成原来要多种组件配合的工作
相关:JobScheduler: 任务调度器、JobInfo : 任务概要信息、JobService: 任务服务,描述具体逻辑
具体介绍请看:官网文档,使用请看:官方Demo