2019年5月30号:
更新内存泄漏相关内容,新增使用系统服务引发的内存泄漏相关内容。
更新内存泄漏未关闭资源对象内存泄露,新增WebView扩展,介绍WebView的内存分配并提出解决方案。
2019年5月29号:
更新内存优化相关内容,新增内存管理介绍、内存抖动。
2019年5月28号:
用户zhangkai2811指出Fresco拼写错误,现已修改完毕。
View的绘制流程有3个步骤,分别是measure、layout和draw,它们主要运行在系统的应用框架层,而真正将数据渲染到屏幕上的则是系统Native层的SurfaceFlinger服务来完成的。
绘制过程主要由CPU来进行Measure、Layout、Record、Execute的数据计算工作,GPU负责栅格化、渲染。CPU和GPU是通过图形驱动层来进行连接的,图形驱动层维护了一个队列,CPU将display list添加到该队列中,这样GPU就可以从这个队列中取出数据进行绘制。
Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染,如果每次渲染都成功,这样就能够达到流畅的画面所需要的60fps,VSYNC是Vertical Synchronization(垂直同步)的缩写,是一种定时中断,一旦收到VSYNC信号,CPU就开始处理各帧数据。如果某个操作要花费30ms,这样系统在得到VSYNC信号时无法进行正常的渲染,会发生丢帧。
产生卡顿原因有很多,主要有以下几点:
1、Profile GPU Rendering
Profile GPU Rendering是Android 4.1系统提供的开发辅助功能,可以在开发者选项中打开这一功能,如下图:
单击Profile GPU Rendering选项并开启Profile GPU Rendering功能,如下图:
上面的彩色的图的横轴代表时间,纵轴表示某一帧的耗时。绿色的横线为警戒线,超过这条线则意味着时长超过了16m,尽量要保证垂直的彩色柱状图保持在绿线下面。这些垂直的彩色柱状图代表着一帧,不同颜色的彩色柱状图代表不同的含义:
在Android 6.0中,有更多的颜色被加了进来,如下图所示:
下面来分别介绍它们的含义:
Profile GPU Rendering可以找到渲染有问题的界面,但是想要修复的话,只依赖Profile GPU Rendering是不够的,可以用另一个工具Hierarchy Viewer来查看布局层次和每个View所花的时间。
2、Systrace
Systrace是Android4.1中新增的性能数据采样和分析工具。它可帮助开发者收集Android关键子系统(SurfaceFlinger、WindowManagerService等Framework部分关键模块、服务,View体系系统等)的运行信息。Systrace的功能包括跟踪系统的I/O操作、内核工作队列、CPU负载以及Android各个子系统的运行状况等。对于UI显示性能,比如动画播放不流畅、渲染卡顿等问题提供了分析数据。在android-sdk/tools/目录的命令行中输入‘monitor’,会打开Android Device Monitor。
3、Traceview
TraceView是Android SDK中自带的数据采集和分析工具。一般来说,通过TraceView我们可以得到以下两种数据:
在android-sdk/tools/目录的命令行中输入‘monitor’,会打开Android Device Monitor,选择相应的进程,并单击Start Method Profiling按钮,对应用中需要监控的点进行操作,单击Stop Method Profiling按钮,会自动跳到TraceView视图。
也可以代码中添加TraceView监控语句,代码如下所示。
Debug.startMethodTracing();
...
Debug.stopMethodTracing();
在开始监控的地方调用startMethodTracing方法,在需要结束监控的地方调用stopMethodTracing方法。系统会在SD卡中生成trace文件,将trace文件导出并用SDK中的Traceview打开即可。当然不要忘了在manifest中加入
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>权限。
4、GPU过度绘制
Android手机上面的开发者选项提供了工具来检测过度绘制,可以按如下步骤来打开:
开发者选项->调试GPU过度绘制->显示过度绘制区域
如下图所示:
可以看到,界面上出现了一堆红绿蓝的区域,我们来看下这些区域代表什么意思:
需要注意的是,有些过度绘制是无法避免的。因此在优化界面时,应该尽量让大部分的界面显示为真彩色(即无过度绘制)或者为蓝色(仅有 1 次过度绘制)。尽量避免出现粉色或者红色。
知识扫盲
OOM:
系统分配给app的堆内存是有上限的,不是系统空闲多少内存app就可以用多少,getMemoryClass()可以获取到这个值。
可以在manifest文件中设置largeHeap为true,这样会增大堆内存上限,getLargeMemoryClass()可以获取到这个值。
超出虚拟机堆内存上限会造成OOM。
Low Memory Killer:
android内存管理使用了分页(paging)和内存映射(memory-mapping)技术,但是没有使用swap,而是使用Low Memory Killer策略来提升物理内存的利用率 ,导致除了gc和杀死进程回收物理内存之外没有其他方式来利用已经被占用的内存。
当前台应用切换到后台后,系统并不结束它的进程,而是把它缓存起来,供下次启动。当系统内存不足时,按最近最少使用+优先释放内存使用密集的策略释放缓存进程。
GC:
内存使用的多也会造成GC速度变慢,造成卡顿。
内存占用过高,在创建对象时内存不足,很容易造成触发GC影响APP性能。
目前 Android 端支持的图片格式有JPEG、GIF、PNG、BMP、WebP,但是在 Android中能够使用编解码使用的只有其中的三种:JPEG、PNG、WebP。
Android中Bitmap所占内存大小计算方式:图片长度 x 图片宽度 x 一个像素点占用的字节数
影响Bitmap占用内存的因素:
1、Bitmap的Compress方法(质量压缩):
public boolean compress(CompressFormat format, int quality, OutputStream stream)
参数format:表示图像的压缩格式,目前有CompressFormat.JPEG、CompressFormat.PNG、CompressFormat.WEBP。
参数quality: 图像压缩率,0-100。 0 压缩100%,100意味着不压缩。
参数stream: 写入压缩数据的输出流。
常用的用法:
public static Bitmap compress(Bitmap bitmap){
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, baos);
byte[] bytes = baos.toByteArray();
return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
}
上面方法中通过bitmap的compress方法对bitmap进行质量压缩,10%压缩,90%不压缩。
图片的大小是没有变的,因为质量压缩不会减少图片的像素,它是在保持像素的前提下改变图片的位深及透明度等,来达到压缩图片的目的,这也是为什么该方法叫质量压缩方法。图片的长,宽,像素都不变,那么bitmap所占内存大小是不会变的。
quality值越小压缩后的baos越小(使用场景:在微信分享时,需要对图片的字节数组大小进行限制,这时可以使用bitmap的compress方法对图片进行质量压缩)。
2、BitmapFactory.Options的inJustDecodeBounds和inSampleSize参数(采样率压缩):
inJustDecodeBounds:当inJustDecodeBounds设置为true的时候,BitmapFactory通过decodeXXXX解码图片时,将会返回空(null)的Bitmap对象,这样可以避免Bitmap的内存分配,但是它可以返回Bitmap的宽度、高度以及MimeType。
inSampleSize: 当它小于1的时候,将会被当做1处理,如果大于1,那么就会按照比例(1 / inSampleSize)缩小bitmap的宽和高、降低分辨率,大于1时这个值将会被处置为2的倍数。例如,width=100,height=100,inSampleSize=2,那么就会将bitmap处理为,width=50,height=50,宽高降为1 / 2,像素数降为1 / 4。
常用用法:
public static Bitmap inSampleSize(byte[] data,int reqWidth,int reqHeight){
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(data, 0, data.length, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeByteArray(data, 0, data.length, options);
}
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
final int picheight = options.outHeight;
final int picwidth = options.outWidth;
int targetheight = picheight;
int targetwidth = picwidth;
int inSampleSize = 1;
if (targetheight > reqHeight || targetwidth > reqWidth) {
while (targetheight >= reqHeight
&& targetwidth >= reqWidth) {
inSampleSize += 1;
targetheight = picheight / inSampleSize;
targetwidth = picwidth / inSampleSize;
}
}
return inSampleSize;
}
}
inSampleSize方法中先将inJustDecodeBounds设置为false,在通过BitmapFactory的decodeXXXX方法解码图片,返回空(null)的Bitmap对象,同时获取了bitmap的宽高,再通过calculateInSampleSize方法根据原bitmap的 宽高和目标宽高计算出合适的inSampleSize,最后将inJustDecodeBounds设置为true,通过BitmapFactory的decodeXXXX方法解码图片(使用场景:比如读取本地图片时,防止Bitmap过大导致内存溢出)。
3、通过Matrix压缩图片
Matrix matrix = new Matrix();
matrix.setScale(0.5f, 0.5f);
bm = Bitmap.createBitmap(bit, 0, 0, bit.getWidth(),bit.getHeight(), matrix, true);
}
使用场景:自定义View时,对图片进行缩放、旋转、位移以及倾斜等操作,常见的就是对图片进行缩放处理,以及圆角图片等。
inBitmap: 如果设置,在加载Bitmap的时候会尝试去重用这块内存(内存复用),不能重用的时候会返回null,否则返回bitmap。
复用内存:BitmapFactory.Options 参数inBitmap的使用。inMutable设置为true,并且配合SoftReference软引用使用(内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些软引用对象的内存)。有一点要注意Android4.4以下的平台,需要保证inBitmap和即将要得到decode的Bitmap的尺寸规格一致,Android4.4及其以上的平台,只需要满足inBitmap的尺寸大于要decode得到的Bitmap的尺寸规格即可。
常见的图片加载缓存库有 Picasso、Glide、Fresco。
根据 App 对图片显示和缓存的需求从低到高的选择顺序:Picasso < Glide < Fresco
1、使用完毕后释放图片资源
Android编程中,往往最容易出现OOM的地方就是在图片处理的时候,我们先上个数据:一个像素的显示需要4字节(R、G、B、A各占一个字节),所以一个1080x720像素的手机一个满屏幕画面就需要近3M内存,而开发一个轻量应用的安装包大小也差不多就3M左右,所以说图片很占内存。在Android中,图片的资源文件叫做Drawable,存储在硬盘上,不耗内存,但我们并无法对其进行处理,最多只能进行展示。而如果想对该图片资源进行处理,我们需要把这个Drawable解析为Bitmap形式装载入内存中。其中Android的不同版本对Bitmap的存储方式还有所不同。下面是Android官方文档中对此描述的一段话
On Android 2.3.3 (API level 10) and lower, the backing pixel data for a bitmap is stored in native memory. It is separate from the bitmap itself, which is stored in the Dalvik heap. The pixel data in native memory is not released in a predictable manner, potentially causing an application to briefly exceed its memory limits and crash. As of Android 3.0 (API level 11), the pixel data is stored on the Dalvik heap along with the associated bitmap.
bitmap分成两个部分,一部分为bitmap对象,用以存储此图片的长、宽、透明度等信息;另一部分为bitmap数据,用以存储bitmap的(A)RGB字节数据。在2.3.3及以前版本中bitmap对象和bitmap数据是存储在不同的内存空间上的,bitmap数据部分存储在native内存中,GC无法涉及。所以之前我们需要调用bitmap的recycle方法来显示的告诉系统此处内存可回收,而在3.0版本开始,bitmap的的这两部分都存储在了Dalvik堆中,可以被GC机制统一处理,也就无需用recycle了。
关于bitmap优化,不同版本方法也不相同,2.3.3版本及以前,就要做到及时调用recycle来回收不在使用的bitmap,而3.0开始可以使用BitmapFactory.Options.inBitmap这个选项,设置一个可复用的bitmap,这样以后新的bitmap且大小相同的就可以直接使用这块内存,而无需重复申请内存。4.4之后解决了对大小的限制,不同大小也可以复用该块空间。
注:若调用了Bitmap.recycle()后,再绘制Bitmap,则会出现Canvas: trying to use a recycled bitmap错误
2、根据分辨率适配 & 缩放图片
若 Bitmap 与 当前设备的分辨率不匹配,则会拉伸Bitmap,而Bitmap分辨率增加后,所占用的内存也会相应增加
因为Bitmap 的内存占用 根据 x、y的大小来增加的
3、按需 选择合适的解码方式
不同的图片解码方式 对应的 内存占用大小 相差很大,具体如下
使用参数:BitmapFactory.inPreferredConfig 设置
默认使用解码方式:ARGB_8888
4、设置 图片缓存
重复加载图片资源耗费太多资源(CPU、内存 & 流量)
内存泄露,即Memory Leak,指程序中不再使用到的对象因某种原因从而无法被GC正常回收。发生内存泄露,会导致一些不再使用到的对象没有及时释放,这些对象占用了宝贵的内存空间,很容易导致后续需要分配内存的时候,内存空间不足而出现OOM(内存溢出)。
1、静态变量导致的内存泄露
静态变量的生命周期与应用的生命周期一致,该对象会一直被引用直到应用结束。
例子1:
public class MainActivity extends Activity {
public static Context context;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
context=this;
}
}
上述代码在MainActivity中context为静态变量,并持有Context,当Activity退出后,由于Activity被context一直引用着,导致Activity无法被回收,因此造成了内存泄漏。上述代码比较明显,一般不会犯这种错误。
例子2:
public class MainActivity extends Activity {
public static Out mOut;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mOut = new Out(this);
}
}
//外部Out类
public class Out {
Out(Context context) {
}
}
上述代码与例子1类似,mOut为静态变量,生命周期与应用一致,传入的MainActivity也被一直引用,导致Activity无法被回收,造成内存泄漏。
解决方案:
1、在不使用静态变量时,置空。
2、可以使用Application的Context。
3、通过弱引用和软引用来引用Activity。
例子3:
单例模式在Android开发中会经常用到,但是如果使用不当就会导致内存泄露。因为单例的静态特性使得它的生命周期同应用的生命周期一样长,如果一个对象已经没有用处了,但是单例还持有它的引用,那么在整个应用程序的生命周期它都不能正常被回收,从而导致内存泄露。
public class Singleton {
private static Singleton singleton = null;
private Context mContext;
public Singleton(Context mContext) {
this.mContext = mContext;
}
public static Singleton getSingleton(Context context){
if (null == singleton){
singleton = new Singleton(context);
}
return singleton;
}
}
像上面代码中这样的单例,如果我们在调用getInstance(Context context)方法的时候传入的context参数是Activity、Service等上下文,就会导致内存泄露。
当我们退出Activity时,该Activity就没有用了,但是因为singleton作为静态单例(在应用程序的整个生命周期中存在)会继续持有这个Activity的引用,导致这个Activity对象无法被回收释放,这就造成了内存泄露。
2、非静态内部类导致内存泄露
非静态内部类(包括匿名内部类)默认就会持有外部类的引用,当非静态内部类对象的生命周期比外部类对象的生命周期长时,就会导致内存泄露。
非静态内部类导致的内存泄露在Android开发中有一种典型的场景就是使用Handler,很多开发者在使用Handler是这样写的:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
start();
}
private void start() {
Message msg = Message.obtain();
msg.what = 1;
mHandler.sendMessage(msg);
}
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == 1) {
// 做相应逻辑
}
}
};
}
当Activity退出后,msg可能仍然存在于消息对列MessageQueue中未处理或者正在处理,那么这样就会导致Activity无法被回收,以致发生Activity的内存泄露。
通常在Android开发中如果要使用内部类,但又要规避内存泄露,一般都会采用静态内部类+弱引用的方式。
public class MainActivity extends AppCompatActivity {
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler = new MyHandler(this);
start();
}
private void start() {
Message msg = Message.obtain();
msg.what = 1;
mHandler.sendMessage(msg);
}
private static class MyHandler extends Handler {
private WeakReference<MainActivity> activityWeakReference;
public MyHandler(MainActivity activity) {
activityWeakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = activityWeakReference.get();
if (activity != null) {
if (msg.what == 1) {
// 做相应逻辑
}
}
}
}
}
mHandler通过弱引用的方式持有Activity,当GC执行垃圾回收时,遇到Activity就会回收并释放所占据的内存单元。这样就不会发生内存泄露了。
上面的做法确实避免了Activity导致的内存泄露,发送的msg不再已经没有持有Activity的引用了,但是msg还是有可能存在消息队列MessageQueue中,所以更好的是在Activity销毁时就将mHandler的回调和发送的消息给移除掉。
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
非静态内部类造成内存泄露还有一种情况就是使用Thread或者AsyncTask。
比如在Activity中直接new一个子线程Thread:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(new Runnable() {
@Override
public void run() {
// 模拟相应耗时逻辑
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
或者直接新建AsyncTask异步任务:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
// 模拟相应耗时逻辑
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
}.execute();
}
}
这种方式新建的子线程Thread和AsyncTask都是匿名内部类对象,默认就隐式的持有外部Activity的引用,导致Activity内存泄露。要避免内存泄露的话还是需要像上面Handler一样使用静态内部类+弱应用的方式(代码就不列了,参考上面Hanlder的正确写法)。
3、集合类内存泄露
集合类添加元素后,将会持有元素对象的引用,导致该元素对象不能被垃圾回收,从而发生内存泄漏。
4、未关闭资源对象内存泄露
WebView扩展:
WebView 解析网页时会申请Native堆内存用于保存页面元素,当页面较复杂时会有很大的内存占用。如果页面包含图片,内存占用会更严重。并且打开新页面时,为了能快速回退,之前页面占用的内存也不会释放。有时浏览十几个网页,都会占用几百兆的内存。这样加载网页较多时,会导致系统不堪重负,最终强制关闭应用,也就是出现应用闪退或重启。
由于占用的都是Native 堆内存,所以实际占用的内存大小不会显示在常用的 DDMS Heap 工具中( DMS Heap 工具看到的只是Java虚拟机分配的内存,即使Native堆内存已经占用了几百兆,这里显示的还只是几兆或十几兆)。只有使用 adb shell 中的一些命令比如 dumpsys meminfo 包名,或者在程序中使用 Debug.getNativeHeapSize()才能看到 Native 堆内存信息。
5、使用系统服务引发的内存泄漏
为了方便我们使用一些常见的系统服务,Activity 做了一些封装。比如说,可以通过 getPackageManager在 Activtiy 中获取 PackageManagerService,但是,里面实际上调用了 Activity 对应的 ContextImpl 中的 getPackageManager 方法
ContextWrapper#getPackageManager
@Override
public PackageManager getPackageManager() {
return mBase.getPackageManager();
}
ContextImpl#getPackageManager
@Override
public PackageManager getPackageManager() {
if (mPackageManager != null) {
return mPackageManager;
}
IPackageManager pm = ActivityThread.getPackageManager();
if (pm != null) {
// Doesn't matter if we make more than one instance.
return (mPackageManager = new ApplicationPackageManager(this, pm));//创建 ApplicationPackageManager
}
return null;
}
ApplicationPackageManager#ApplicationPackageManager
ApplicationPackageManager(ContextImpl context,
IPackageManager pm) {
mContext = context;//保存 ContextImpl 的强引用
mPM = pm;
}
private UserManagerService(Context context, PackageManagerService pm,
Object packagesLock, File dataDir) {
mContext = context;//持有外部 Context 引用
mPm = pm;
//代码省略
}
PackageManagerService#PackageManagerService
public class PackageManagerService extends IPackageManager.Stub {
static UserManagerService sUserManager;//持有 UMS 静态引用
public PackageManagerService(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
sUserManager = new UserManagerService(context, this, mPackages);//初始化 UMS
}
}
遇到的内存泄漏问题是因为在 Activity 中调用了 getPackageManger 方法获取 PMS ,该方法调用的是 ContextImpl,此时如果ContextImpl 中 PackageManager 为 null,就会创建一个 PackageManger(ContextImpl 会将自己传递进去,而 ContextImpl 的 mOuterContext 为 Activity),创建 PackageManager 实际上会创建 PackageManagerService(简称 PMS),而 PMS 的构造方法中会创建一个 UserManger(UserManger 初始化之后会持有 ContextImpl 的强引用)。
只要 PMS 的 class 未被销毁,那么就会一直引用着 UserManger ,进而导致其关联到的资源无法正常释放。
解决办法:
将getPackageManager()改为 getApplication()#getPackageManager() 。这样引用的就是 Application Context,而非 Activity 了。
1、leakcanary
leakcanary是square开源的一个库,能够自动检测发现内存泄露,其使用也很简单:
在build.gradle中添加依赖:
dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.1'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'
//可选项,如果使用了support包中的fragments
debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.1'
}
根目录下的build.gradle添加mavenCentral()即可,如下:
allprojects {
repositories {
google()
jcenter()
mavenCentral()
}
}
然后在自定义的Application中调用以下代码就可以了。
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
return;
}
LeakCanary.install(this);
//正常初始化代码
}
}
如果检测到有内存泄漏,通知栏会有提示,如下图;如果没有内存泄漏,则没有提示。
2、Memory Profiler
Memory Profiler 是 Android Profiler 中的一个组件,可以帮助你分析应用卡顿,崩溃和内存泄露等等问题。
打开 Memory Profiler后即可看到一个类似下图的视图。
上面的红色数字含义如下:
1.用于强制执行垃圾回收事件的按钮。
2.用于捕获堆转储的按钮。
3.用于记录内存分配情况的按钮。 此按钮仅在连接至运行 Android 7.1 或更低版本的设备时才会显示。
4.用于放大/缩小/还原时间线的按钮。
5.用于跳转至实时内存数据的按钮。
6.Event 时间线,其显示 Activity 状态、用户输入 Event 和屏幕旋转 Event。
7.内存使用量时间线,其包含以下内容:
一个显示每个内存类别使用多少内存的堆叠图表,如左侧的 y 轴以及顶部的彩色键所示。
虚线表示分配的对象数,如右侧的 y 轴所示。
用于表示每个垃圾回收事件的图标。
如何Memory Profiler分析内存泄露,按以下步骤来即可:
1.使用Memory Profiler监听要分析的应用进程
2.旋转几次要分析的Activity。(这是因为旋转Activity后会重新创建)
3.点击捕获堆转储按钮去捕获堆转储
4.在捕获结果中搜索要分析的类。(这里是MainActivity)
5.点击要分析的类,右边会显示这个类创建对象的数量。
如下图:
内存抖动的原因:
内存抖动一般是瞬间创建了大量对象,会在短时间内触发多次GC,产生卡顿。
内存抖动的在分析工具上的表现:
解决方案:
最简单的做法就是把之前的主线程操作放到子线程去,虽然内存抖动依然存在,但是卡顿问题可以大大缓解。
对于内存抖动本身:
尽量避免在循环体内创建对象,应该把对象创建移到循环体外。
需要大量使用Bitmap和其他大型对象时,尽量尝试复用之前创建的对象。
客户端请求流程如下:
分析网络情况的方式可以通过Wireshark, Fiddler, Charlesr等抓包工具,也可以通过Android Studio的Network Profiler
窗口顶部显示的是 Event 时间线以及 1 无线装置功耗状态(低/高)与 WLAN 的对比。 在时间线上,您可以 2点击并拖动选择时间线的一部分来检查网络流量。
下方的3窗口会显示在时间线的选定片段内收发的文件,包括文件名称、大小、类型、状态和时间。 您可以点击任意列标题为此列表排序。
同时,您还可以查看时间线选定片段的明细数据,显示每个文件的发送或接收时间。
点击网络连接的名称即可查看 4 有关所发送或接收的选定文件的详细信息。 点击各个标签可查看响应数据、标题信息或调用堆栈。
注: 必须启用高级分析才能从时间线中选择要检查的片段,查看发送和接收的文件列表,或查看有关所发送或接收的选定文件的详细信息。 要启用高级分析,请参阅启用高级分析。
启用高级分析需要点击Run Configuration:
打开Run/Debug Configurations,左侧选择你的应用,右侧在Profiling中勾选Enable advanced profiling。
通过以上这些工具可以查看某个时间段内网络请求的具体情况,从而进行网络优化的相关工作。
1、后端API设计
后端设计API时需要考虑网络请求的频次、资源状态,在某些情况下可以合并多个接口以满足客户端业务需求。
2、Gzip压缩
使用Gzip来压缩request和response, 减少传输数据量, 从而减少流量消耗。同时可以考虑使用Protocol Buffer代替JSON,protobuf会比JSON数据量小很多.
3、图片大小优化
1、Batterystats & bugreport
Android 5.0及以上的设备, 允许我们通过adb命令dump出电量使用统计信息.
因为电量统计数据是持续的, 会非常大, 统计我们的待测试App之前先reset下, 连上设备,命令行执行:
$ adb shell dumpsys batterystats --reset
Battery stats reset.
断开测试设备, 操作我们的待测试App,重新连接设备, 使用adb命令导出相关统计数据:
// 此命令持续记录输出, 想要停止记录时按Ctrl+C退出.
$ adb bugreport > bugreport.txt
导出的统计数据存储到bugreport.txt, 此时我们可以借助如下工具来图形化展示电池的消耗情况。
2、Battery Historian
Google提供了一个开源的电池历史数据分析工具
Battery Historian链接
建议:
根据具体业务需求,严格限制应用位于后台时是否禁用某些数据传输,尽量能够避免无效的数据传输。
数据传输的频度问题,如网络请求可以压缩合并,如本地数据上传,可以选择恰当的时机上传。
通过不停的唤醒CPU(通过后天常驻的Service)来达到一些功能的使用,这样会造成电量资源的消耗,比如后台日志的上报,定期更新数据等等,在Android 5.0提供了一个JobScheduler组件,通过设置一系列的预置条件,当条件满足时,才执行对应的操作,这样既能省电,有保证了功能的完整性。
JobScheduler的适用场景:
JobScheduler的使用
private Context mContext;
private JobScheduler mJobScheduler;
public JobSchedulerManager(Context context){
this.mContext=context;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
this.mJobScheduler= (JobScheduler) mContext.getSystemService(Context.JOB_SCHEDULER_SERVICE);
}
}
通过getSystemService()方法获取一个JobSchedule的对象。
public boolean addTask(int taskId) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
JobInfo.Builder builder = new JobInfo.Builder(taskId,
new ComponentName("com.apk.administrator.loadapk",
JobScheduleService.class.getName()));
switch (taskId) {
case 1:
//每隔1秒执行一次
builder.setPeriodic(1000);
break;
case 2:
//设备重启后,不再执行该任务
builder.setPersisted(false);
break;
default:
break;
}
if (null != mJobScheduler) {
return mJobScheduler.schedule(builder.build()) > 0;
} else {
return false;
}
} else {
return true;
}
}
创建一个JobInfo对象时传入两个参数,第一个参数是任务ID,可以对不同的任务ID做不同的触发条件,执行任务时根据任务ID执行具体的任务;第二个参数是JobScheduler任务的服务,参数为进程名和服务类名。
JobInfo支持以下几种触发条件:
public class JobScheduleService extends JobService {
@Override
public boolean onStartJob(JobParameters params) {
return false;
}
@Override
public boolean onStopJob(JobParameters params) {
return false;
}
}
JobService运行在主线程,如果是耗时任务,使用ThreadHandler或者一个异步任务来运行耗时的任务,防止阻塞主线程。
JobScheduleService继承JobService,实现两个方法onStartJob和onStopJob。
任务开始时,执行onStartJob方法,当任务执行完毕后,需要调用jobFinished方法来通知系统;任务执行完成后,调用jobFinished方法通知JobScheduler;当系统接受到一个取消请求时,调用onStopJob方法取消正在等待执行的任务。如果系统在接受到一个取消请求时,实际任务队列中已经没有正在运行的任务,onStopJob不会被调用。
最后在AndroidManifest中配置下:
<service
android:name=".JobScheduleService"
android:permission="android.permission.BIND_JOB_SERVICE" />
1、从图片入手:.9图、压缩或采用Webp。
2、使用Lint删除无用资源
3、通过Gradle配置,过滤无用资源和.so文件
4、第三方库慎重使用,可以只提取使用到的代码
5、资源混淆:方案有:美团和微信,前者是通过修改AAPT在处理资源文件相关的源码达到资源名的替换,后者通过直接修改resources.arsc文件来达到资源文件名的混淆。
6、插件化
整合网上相关资料,不定期更新此文。