Android 面试之必问性能优化_android性能面试最简单三个步骤(2)

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip204888 (备注Android)
img

正文

    fun endRecord(postion: String) {
        val cost = System.currentTimeMillis() - sStart
        println("===$postion===$cost")
    }
}

}


启动时埋点我们直接在Application的attachBaseContext中进行打点。那么启动结束应该在哪里打点呢?结束埋点建议是在页面数据展示出来进行埋点。可以使用如下方法:



class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    mTextView.viewTreeObserver.addOnDrawListener {
        LaunchRecord.endRecord("onDraw")
    }

}

override fun onWindowFocusChanged(hasFocus: Boolean) {
    super.onWindowFocusChanged(hasFocus)
    LaunchRecord.endRecord("onWindowFocusChanged")
}

}


#### 1.2.2 优化检测工具


在做启动优化的时候,可以借助三方工具来帮助我们理清各个阶段的方法或者线程、CPU的执行耗时等情况。这里主要介绍以下TraceView和SysTrace两款工具。


**TraceView**


TraceView是以图形的形式展示执行时间、调用栈等信息,信息比较全面,包含所有线程,如下图所示。 ![](https://img-blog.csdnimg.cn/img_convert/2a83e431a71bf04b747487e973c9c2a0.webp?x-oss-process=image/format,png)


使用TraceView检测生成生成的结果会放在Andrid/data/packagename/files路径下。因为Traceview收集的信息比较全面,所以会导致运行开销严重,整体APP的运行会变慢,因此我们无法区分是不是Traceview影响了我们的启动时间。


**SysTrace** Systrace是结合Android内核数据,生成HTML报告,从报告中我们可以看到各个线程的执行时间以及方法耗时和CPU执行时间等。


![](https://img-blog.csdnimg.cn/img_convert/f6a31f4905b9fe5ffb5e63088a5b6577.webp?x-oss-process=image/format,png)


再API 18以上版本,可以直接使用TraceCompat来抓取数据,因为这是兼容的API。



开始:TraceCompat.beginSection("tag ")
结束:TraceCompat.endSection()


然后,执行如下脚本。



python systrace.py -b 32768 -t 10 -a packagename -o outputfile.html sched gfx view wm am app


这里可以大家普及下各个字端的含义:


* b: 收集数据的大小
* t:时间
* a:监听的应用包名
* o: 生成文件的名称


Systrace开销较小,属于轻量级的工具,并且可以直观反映CPU的利用率。


## 2,UI渲染优化


Android系统每隔16ms就会重新绘制一次Activity,因此,我们的应用必须在16ms内完成屏幕刷新的全部逻辑操作,每一帧只能停留16ms,否则就会出现掉帧现象。Android应用卡顿与否与UI渲染有直接的关系。


### 2.1CPU、GPU


对于大多数手机的屏幕刷新频率是60hz,也就是如果在1000/60=16.67ms内没有把这一帧的任务执行完毕,就会发生丢帧的现象,丢帧是造成界面卡顿的直接原因,渲染操作通常依赖于两个核心组件:CPU与GPU。CPU负责包括Measure,Layout等计算操作,GPU负责Rasterization(栅格化)操作。


所谓栅格化,就是将矢量图形转换为位图的过程,手机上显示是按照一个个像素来显示的,比如将一个Button、TextView等组件拆分成一个个像素显示到手机屏幕上。而UI渲染优化的目的就是减轻CPU、GPU的压力,除去不必要的操作,保证每帧16ms以内处理完所有的CPU与GPU的计算、绘制、渲染等等操作,使UI顺滑、流畅的显示出来。


### 2.2 过度绘制


UI渲染优化的第一步就是找到Overdraw(过度绘制),即描述的是屏幕上的某个像素在同一帧的时间内被绘制了多次。在重叠的UI布局中,如果不可见的UI也在做绘制的操作或者后一个控件将前一个控件遮挡,会导致某些像素区域被绘制了多次,从而增加了CPU、GPU的压力。


那么如何找出布局中Overdraw的地方呢?很简单,就是打开手机里开发者选项,然后将调试GPU过度绘制的开关打开即可,然后就可以看到应用的布局是否被Overdraw,如下图所示。 ![](https://img-blog.csdnimg.cn/img_convert/f0af74c280ced08d4c05d58b5f65641e.webp?x-oss-process=image/format,png)


蓝色、淡绿、淡红、深红代表了4种不同程度的Overdraw情况,1x、2x、3x和4x分别表示同一像素上同一帧的时间内被绘制了多次,1x就表示一次(最理想情况),4x表示4次(最差的情况),而我们需要消除的就是3x和4x。


### 2.3 解决自定义View的OverDraw


我们知道,自定义View的时候有时会重写onDraw方法,但是Android系统是无法检测onDraw里面具体会执行什么操作,从而系统无法为我们做一些优化。这样对编程人员要求就高了,如果View有大量重叠的地方就会造成CPU、GPU资源的浪费,此时我们可以使用canvas.clipRect()来帮助系统识别那些可见的区域。


这个方法可以指定一块矩形区域,只有在这个区域内才会被绘制,其他的区域会被忽视。下面我们通过谷歌提供的一个小的Demo进一步说明OverDraw的使用。 ![](https://img-blog.csdnimg.cn/img_convert/9158791a4a4420e33a61292b60c96900.webp?x-oss-process=image/format,png)


在下面的代码中,DroidCard类封装的是卡片的信息,代码如下。



public class DroidCard {

public int x;//左侧绘制起点
public int width;
public int height;
public Bitmap bitmap;

public DroidCard(Resources res,int resId,int x){
this.bitmap = BitmapFactory.decodeResource(res,resId);
this.x = x;
this.width = this.bitmap.getWidth();
this.height = this.bitmap.getHeight();
}
}


自定义View的代码如下:



public class DroidCardsView extends View {
//图片与图片之间的间距
private int mCardSpacing = 150;
//图片与左侧距离的记录
private int mCardLeft = 10;

private List mDroidCards = new ArrayList();

private Paint paint = new Paint();

public DroidCardsView(Context context) {
super(context);
initCards();
}

public DroidCardsView(Context context, AttributeSet attrs) {
super(context, attrs);
initCards();
}
/**

  • 初始化卡片集合
    */
    protected void initCards(){
    Resources res = getResources();
    mDroidCards.add(new DroidCard(res,R.drawable.alex,mCardLeft));

mCardLeft+=mCardSpacing;
mDroidCards.add(new DroidCard(res,R.drawable.claire,mCardLeft));

mCardLeft+=mCardSpacing;
mDroidCards.add(new DroidCard(res,R.drawable.kathryn,mCardLeft));
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (DroidCard c : mDroidCards){
drawDroidCard(canvas, c);
}
invalidate();
}

/**

  • 绘制DroidCard
    */
    private void drawDroidCard(Canvas canvas, DroidCard c) {
    canvas.drawBitmap(c.bitmap,c.x,0f,paint);
    }
    }

然后,我们运行代码,打开手机的overdraw开关,效果如下: ![](https://img-blog.csdnimg.cn/img_convert/565678353f058f2dc78994ece84291ec.webp?x-oss-process=image/format,png)


可以看到,淡红色区域明显被绘制了三次,是因为图片的重叠造成的。那怎么解决这种问题呢?其实,分析可以发现,最下面的图片只需要绘制三分之一即可,保证最下面两张图片只需要回执其三分之一最上面图片完全绘制出来就可。优化后的代码如下:



public class DroidCardsView extends View {

//图片与图片之间的间距
private int mCardSpacing = 150;
//图片与左侧距离的记录
private int mCardLeft = 10;

private List mDroidCards = new ArrayList();

private Paint paint = new Paint();

public DroidCardsView(Context context) {
super(context);
initCards();
}

public DroidCardsView(Context context, AttributeSet attrs) {
super(context, attrs);
initCards();
}
/**

  • 初始化卡片集合
    */
    protected void initCards(){
    Resources res = getResources();
    mDroidCards.add(new DroidCard(res, R.drawable.alex,mCardLeft));

mCardLeft+=mCardSpacing;
mDroidCards.add(new DroidCard(res, R.drawable.claire,mCardLeft));

mCardLeft+=mCardSpacing;
mDroidCards.add(new DroidCard(res, R.drawable.kathryn,mCardLeft));
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < mDroidCards.size() - 1; i++){
drawDroidCard(canvas, mDroidCards,i);
}
drawLastDroidCard(canvas,mDroidCards.get(mDroidCards.size()-1));
invalidate();
}

/**

  • 绘制最后一个DroidCard
  • @param canvas
  • @param c
    */
    private void drawLastDroidCard(Canvas canvas,DroidCard c) {
    canvas.drawBitmap(c.bitmap,c.x,0f,paint);
    }

/**

  • 绘制DroidCard
  • @param canvas
  • @param mDroidCards
  • @param i
    */
    private void drawDroidCard(Canvas canvas,List mDroidCards,int i) {
    DroidCard c = mDroidCards.get(i);
    canvas.save();
    canvas.clipRect((float)c.x,0f,(float)(mDroidCards.get(i+1).x),(float)c.height);
    canvas.drawBitmap(c.bitmap,c.x,0f,paint);
    canvas.restore();
    }
    }

在上面的代码中,我们使用Canvas的clipRect方法,绘制之前裁剪出一个区域,这样绘制的时候只在这区域内绘制,超出部分不会绘制出来。重新运行上面的代码,效果如下图所示。 ![](https://img-blog.csdnimg.cn/img_convert/762fce38384a85e36bcf1aa6bce4b6ca.webp?x-oss-process=image/format,png)


### 2.4 Hierarchy Viewer


Hierarchy Viewer 是 Android Device Monitor 中内置的一种工具,可让开发者测量布局层次结构中每个视图的布局速度,以及帮助开发者查找视图层次结构导致的性能瓶颈。Hierarchy Viewer可以通过红、黄、绿三种不同的颜色来区分布局的Measure、Layout、Executive的相对性能表现情况。


打开


1. 将设备连接到计算机。如果设备上显示对话框提示您允许 USB 调试吗?,请点按确定。
2. 在 Android Studio 中打开您的项目,在您的设备上构建并运行项目。
3. 启动 Android Device Monitor。Android Studio 可能会显示 Disable adb integration 对话框,因为一次只能有一个进程可以通过 adb 连接到设备,并且 Android Device Monitor 正在请求连接。因此,请点击 Yes。
4. 在菜单栏中,依次选择 Window > Open Perspective,然后点击 Hierarchy View。
5. 在左侧的 Windows 标签中双击应用的软件包名称。这会使用应用的视图层次结构填充相关窗格。


![](https://img-blog.csdnimg.cn/img_convert/57173b057e7d99508034cbeea5302065.webp?x-oss-process=image/format,png)


提升布局性能的关键点是尽量保持布局层级的扁平化,避免出现重复的嵌套布局。如果我们写的布局层级比较深会严重增加CPU的负担,造成性能的严重卡顿,关于Hierarchy Viewer的使用可以参考:[使用 Hierarchy Viewer 分析布局]( )。


### 2.5 内存抖动


在我们优化过view的树形结构和overdraw之后,可能还是感觉自己的app有卡顿和丢帧,或者滑动慢等问题,我们就要查看一下是否存在内存抖动情况了。所谓内存抖动,指的是内存频繁创建和GC造成的UI线程被频繁阻塞的现象。


Android有自动管理内存的机制,但是对内存的不恰当使用仍然容易引起严重的性能问题。在同一帧里面创建过多的对象是件需要特别引起注意的事情,在同一帧里创建大量对象可能引起GC的不停操作,执行GC操作的时候,所有线程的任何操作都会需要暂停,直到GC操作完成。大量不停的GC操作则会显著占用帧间隔时间。如果在帧间隔时间里面做了过多的GC操作,那么就会造成页面卡顿。 ![](https://img-blog.csdnimg.cn/img_convert/d15147adffb4ca792fc3af633bd2419c.webp?x-oss-process=image/format,png)


在Android开发中,导致GC频繁操作有两个主要原因:


* 内存抖动,所谓内存抖动就是短时间产生大量对象又在短时间内马上释放。
* 短时间产生大量对象超出阈值,内存不够,同样会触发GC操作。


Android的内存抖动可以使用Android Studio的Profiler进行检测。 ![](https://img-blog.csdnimg.cn/img_convert/4b95d89e9cd8c4afd866a83166ec5af7.webp?x-oss-process=image/format,png)


然后,点击record记录内存信息,查找发生内存抖动位置,当然也可直接通过Jump to Source定位到代码位置。 ![](https://img-blog.csdnimg.cn/img_convert/3f10ed060ea9e1623b8835437c1472c2.webp?x-oss-process=image/format,png)


为了避免发生内存抖动,我们需要避免在for循环里面分配对象占用内存,需要尝试把对象的创建移到循环体之外,自定义View中的onDraw方法也需要引起注意,每次屏幕发生绘制以及动画执行过程中,onDraw方法都会被调用到,避免在onDraw方法里面执行复杂的操作,避免创建对象。对于那些无法避免需要创建对象的情况,我们可以考虑对象池模型,通过对象池来解决频繁创建与销毁的问题,但是这里需要注意结束使用之后,需要手动释放对象池中的对象。


## 3,内存优化


### 3.1 内存管理


在前面Java基础环节,我们对Java的内存管理模型也做了基本的介绍,参考链接:[Android 高频面试必问之Java基础]( )


#### 3.1.1 内存区域


在Java的内存模型中,将内存区域划分为方法区、堆、程序计数器、本地方法栈、虚拟机栈五个区域,如下图。 ![](https://img-blog.csdnimg.cn/img_convert/a00506db9abffd636b6336b0f3e8bb53.webp?x-oss-process=image/format,png)


**方法区**


* 线程共享区域,用于存储类信息、静态变量、常量、即时编译器编译出来的代码数据。
* 无法满足内存分配需求时会发生OOM。


**堆**


* 线程共享区域,是JAVA虚拟机管理的内存中最大的一块,在虚拟机启动时创建。
* 存放对象实例,几乎所有的对象实例都在堆上分配,GC管理的主要区域。


**虚拟机栈**


* 线程私有区域,每个java方法在执行的时候会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。方法从执行开始到结束过程就是栈帧在虚拟机栈中入栈出栈过程。
* 局部变量表存放编译期可知的基本数据类型、对象引用、returnAddress类型。所需的内存空间会在编译期间完成分配,进入一个方法时在帧中局部变量表的空间是完全确定的,不需要运行时改变。
* 若线程申请的栈深度大于虚拟机允许的最大深度,会抛出SatckOverFlowError错误。
* 虚拟机动态扩展时,若无法申请到足够内存,会抛出OutOfMemoryError错误。


**本地方法栈**


* 为虚拟机中Native方法服务,对本地方法栈中使用的语言、数据结构、使用方式没有强制规定,虚拟机可自有实现。
* 占用的内存区大小是不固定的,可根据需要动态扩展。


**程序计数器**


* 一块较小的内存空间,线程私有,存储当前线程执行的字节码行号指示器。
* 字节码解释器通过改变这个计数器的值来选取下一条需要执行的字节码指令:分支、循环、跳转等。
* 每个线程都有一个独立的程序计数器
* 唯一一个在java虚拟机中不会OOM的区域


#### 3.1.2 垃圾回收


**标记清除算法** 标记清除算法主要分为有两个阶段,首先标记出需要回收的对象,然后咋标记完成后统一回收所有标记的对象; 缺点:


* 效率问题:标记和清除两个过程效率都不高。
* 空间问题:标记清除之后会导致很多不连续的内存碎片,会导致需要分配大对象时无法找到足够的连续空间而不得不触发GC的问题。


**复制算法** 将可用内存按空间分为大小相同的两小块,每次只使用其中的一块,等这块内存使用完了将还存活的对象复制到另一块内存上,然后将这块内存区域对象整体清除掉。每次对整个半区进行内存回收,不会导致碎片问题,实现简单且效率高效。 缺点: 需要将内存缩小为原来的一半,空间代价太高。


**标记整理算法** 标记整理算法标记过程和标记清除算法一样,但清除过程并不是对可回收对象直接清理,而是将所有存活对象像一端移动,然后集中清理到端边界以外的内存。


**分代回收算法** 当代虚拟机垃圾回收算法都采用分代收集算法来收集,根据对象存活周期不同将内存划分为新生代和老年代,再根据每个年代的特点采用最合适的回收算法。


* 新生代存活对象较少,每次垃圾回收都有大量对象死去,一般采用复制算法,只需要付出复制少量存活对象的成本就可以实现垃圾回收;
* 老年代存活对象较多,没有额外空间进行分配担保,就必须采用标记清除算法和标记整理算法进行回收;


### 3.2 内存泄漏


所谓内存泄露,指的是内存中存在的没有用的确无法回收的对象。表现的现象是会导致内存抖动,可用内存减少,进而导致GC频繁、卡顿、OOM。


下面是一段模拟内存泄漏的代码:



/**

  • 模拟内存泄露的Activity
    */
    public class MemoryLeakActivity extends AppCompatActivity implements CallBack{
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_memoryleak);
    ImageView imageView = findViewById(R.id.iv_memoryleak);
    Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.splash);
    imageView.setImageBitmap(bitmap);

     // 添加静态类引用
     CallBackManager.addCallBack(this);
    

    }
    @Override
    protected void onDestroy() {
    super.onDestroy();
    // CallBackManager.removeCallBack(this);
    }
    @Override
    public void dpOperate() {
    // do sth
    }


当我们使用Memory Profiler工具查看内存曲线,发现内存在不断的上升,如下图所示。 ![](https://img-blog.csdnimg.cn/img_convert/488d89164795535c826547d23b751474.webp?x-oss-process=image/format,png)


如果想分析定位具体发生内存泄露位置,我们可以借助MAT工具。首先,使用MAT工具生成hprof文件,点击dump将当前内存信息转成hprof文件,需要对生成的文件转换成MAT可读取文件。执行一下转换命令即可完成转换,生成的文件位于Android/sdk/platorm-tools路径下。



hprof-conv 刚刚生成的hprof文件 memory-mat.hprof


使用mat打开刚刚转换的hprof文件,然后使用Android Studio打开hprof文件,如下图所示。 ![](https://img-blog.csdnimg.cn/img_convert/6793ed9663ac73d65c6b01ba296e1659.webp?x-oss-process=image/format,png)


然后点击面板的【Historygram】,搜索MemoryLeakActivity,即可查看对应的泄漏文件的相关信息。 ![](https://img-blog.csdnimg.cn/img_convert/ee7f631ce82ce4f5fe6340d23087f075.webp?x-oss-process=image/format,png)


然后,查看所有引用对象,并得到相关的引用链,如下图。 ![](https://img-blog.csdnimg.cn/img_convert/a9a74d28f3f017286f8cb1f841747022.webp?x-oss-process=image/format,png)


![](https://img-blog.csdnimg.cn/img_convert/4822e1f0204e5914c219a9ed643fd7b8.webp?x-oss-process=image/format,png)


可以看到GC Roots是CallBackManager ![](https://img-blog.csdnimg.cn/img_convert/ab198ea8cfe7923331fc354d96a967e3.webp?x-oss-process=image/format,png)


所以,我们在Activity销毁时将CallBackManager引用移除即可。



@Override
protected void onDestroy() {
super.onDestroy();
CallBackManager.removeCallBack(this);
}


当然,上面只是一个MAT分析工具使用的示例,其他的内存泄露都可以借助MAT分析工具解决。


### 3.3 大图内存优化


在Android开发中,经常会遇到加载大图导致内存泄露的问题,对于这种场景,有一个通用的解决方案,即使用ARTHook对不合理图片进行检测。我们知道,获取Bitmap占用的内存主要有两种方式:


* 通过getByteCount方法,但是需要在运行时获取
* width `*` height `*` 一个像素所占内存 `*` 图片所在资源目录压缩比


通过ARTHook方法可以优雅的获取不合理图片,侵入性低,但是因为兼容性问题一般在线下使用。使用ARTHook需要安装以下依赖:



implementation ‘me.weishu:epic:0.3.6’


然后自定义实现Hook方法,如下所示。



public class CheckBitmapHook extends XC_MethodHook {
@Override protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
ImageView imageView = (ImageView)param.thisObject;
checkBitmap(imageView,imageView.getDrawable());
}
private static void checkBitmap(Object o,Drawable drawable) {
if(drawable instanceof BitmapDrawable && o instanceof View) {
final Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
if(bitmap != null) {
final View view = (View)o;
int width = view.getWidth();
int height = view.getHeight();
if(width > 0 && height > 0) {
if(bitmap.getWidth() > (width <<1) && bitmap.getHeight() > (height << 1)) {
warn(bitmap.getWidth(),bitmap.getHeight(),width,height,
new RuntimeException(“Bitmap size is too large”));
}
} else {
final Throwable stacktrace = new RuntimeException();
view.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override public boolean onPreDraw() {
int w = view.getWidth();
int h = view.getHeight();
if(w > 0 && h > 0) {
if (bitmap.getWidth() >= (w << 1)
&& bitmap.getHeight() >= (h << 1)) {
warn(bitmap.getWidth(), bitmap.getHeight(), w, h, stacktrace);
}
view.getViewTreeObserver().removeOnPreDrawListener(this);
}
return true;
}
});
}
}
}
}
private static void warn(int bitmapWidth, int bitmapHeight, int viewWidth, int viewHeight, Throwable t) {
String warnInfo = new StringBuilder(“Bitmap size too large: “)
.append(”\n real size: (”).append(bitmapWidth).append(‘,’).append(bitmapHeight).append(‘)’)
.append(“\n desired size: (”).append(viewWidth).append(‘,’).append(viewHeight).append(‘)’)
.append(“\n call stack trace: \n”).append(Log.getStackTraceString(t)).append(‘\n’)
.toString();
LogUtils.i(warnInfo);


最后,在Application初始化时注入Hook。



DexposedBridge.hookAllConstructors(ImageView.class, new XC_MethodHook() {
@Override protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
DexposedBridge.findAndHookMethod(ImageView.class,“setImageBitmap”, Bitmap.class,
new CheckBitmapHook());
}
});


### 3.4 线上监控


##### 3.4.1 常规方案


**方案一** 在特定场景中获取当前占用内存大小,如果当前内存大小超过系统最大内存80%,对当前内存进行一次Dump(Debug.dumpHprofData()),选择合适时间将hprof文件进行上传,然后通过MAT工具手动分析该文件。


缺点:


* Dump文件比较大,和用户使用时间、对象树正相关。
* 文件较大导致上传失败率较高,分析困难。


**方案二** 将LeakCannary带到线上,添加预设怀疑点,对怀疑点进行内存泄露监控,发现内存泄露回传到服务端。


缺点:


* 通用性较低,需要预设怀疑点,对没有预设怀疑点的地方监控不到。
* LeakCanary分析比较耗时、耗内存,有可能会发生OOM。


##### 3.4.2 LeakCannary改造


改造主要涉及以下几点:


* 将需要预设怀疑点改为自动寻找怀疑点,自动将前内存中所占内存较大的对象类中设置怀疑点。
* LeakCanary分析泄露链路比较慢,改造为只分析Retain size大的对象。
* 分析过程会OOM,是因为LeakCannary分析时会将分析对象全部加载到内存当中,我们可以记录下分析对象的个数和占用大小,对分析对象进行裁剪,不全部加载到内存当中。


完成的改造步骤如下:


1. 监控常规指标:待机内存、重点模块占用内存、OOM率
2. 监控APP一个生命周期内和重点模块界面的生命周期内的GC次数、GC时间等
3. 将定制的LeakCanary带到线上,自动化分析线上的内存泄露


## 4,网络优化


### 4.1 网络优化的影响


App的网络连接对于用户来说, 影响很多, 且多数情况下都很直观, 直接影响用户对这个App的使用体验. 其中较为重要的几点: **流量** :App的流量消耗对用户来说是比较敏感的, 毕竟流量是花钱的嘛. 现在大部分人的手机上都有安装流量监控的工具App, 用来监控App的流量使用. 如果我们的App这方面没有控制好, 会给用户不好的使用体验。 **电量** :电量相对于用户来说, 没有那么明显. 一般用户可能不会太注意. 但是如电量优化中的那样, 网络连接(radio)是对电量影响很大的一个因素. 所以我们也要加以注意。 **用户等待** :也就是用户体验, 良好的用户体验, 才是我们留住用户的第一步. 如果App请求等待时间长, 会给用户网络卡, 应用反应慢的感觉, 如果有对比, 有替代品, 我们的App很可能就会被用户无情抛弃。


### 4.2 网络分析工具


网络分析可以借助的工具有Monitor、代理工具等。


#### 4.2.1 Network Monitor


Android Studio内置的Monitor工具提供了一个Network Monitor,可以帮助开发者进行网络分析,下面是一个典型的Network Monitor示意图。 ![](https://img-blog.csdnimg.cn/img_convert/322cf5b442abaedad519200a3849a387.webp?x-oss-process=image/format,png)


* Rx — R(ecive) 表示下行流量,即下载接收。
* Tx — T(ransmit) 表示上行流量,即上传发送。


Network Monitor实时跟踪选定应用的数据请求情况。 我们可以连上手机,选定调试应用进程, 然后在App上操作我们需要分析的页面请求。


#### 4.2.2 代理工具


网络代理工具有两个作用,一个是截获网络请求响应包, 分析网络请求;另一个设置代理网络, 移动App开发中一般用来做不同网络环境的测试, 例如Wifi/4G/3G/弱网等。


现在,可以使用的代理工具有很多, 诸如Wireshark, Fiddler, Charles等。


### 4.3 网络优化方案


对于网络优化来说,主要从两个方面进行着手进行优化:


1. **减少活跃时间**:减少网络数据获取的频次,从而就减少了radio的电量消耗以及控制电量使用。
2. **压缩数据包的大小**:压缩数据包可以减少流量消耗,也可以让每次请求更快, 。


基于上面的方案,可以得到以下一些常见的解决方案:


#### 4.3.1 接口设计


**1,API设计** App与服务器之间的API设计要考虑网络请求的频次,资源的状态等。以便App可以以较少的请求来完成业务需求和界面的展示。


例如, 注册登录. 正常会有两个API, 注册和登录, 但是设计API时我们应该给注册接口包含一个隐式的登录. 来避免App在注册后还得请求一次登录接口。


**2,使用Gzip压缩**


使用Gzip来压缩request和response, 减少传输数据量, 从而减少流量消耗。使用Retrofit等网络请求框架进行网络请求时,默认进行了Gzip的压缩。


**3,使用Protocol Buffer** 以前,我们传输数据使用的是XML, 后来使用JSON代替了XML, 很大程度上也是为了可读性和减少数据量。而在游戏开发中,为了保证数据的准确和及时性,Google推出了Protocol Buffer数据交换格式。


**4,依据网络情况获取不同分辨率的图片** 我们使用淘宝或者京东的时候,会看到应用会根据网络情况,获取不同分辨率的图片,避免流量的浪费以及提升用户的体验。


#### 4.3.2 合理使用网络缓存


适当的使用缓存, 不仅可以让我们的应用看起来更快, 也能避免一些不必要的流量消耗,带来更好的用户体验。


**1,打包网络请求**


当接口设计不能满足我们的业务需求时。例如,可能一个界面需要请求多个接口,或是网络良好,处于Wifi状态下时我们想获取更多的数据等。这时就可以打包一些网络请求, 例如请求列表的同时, 获取Header点击率较高的的item项的详情数据。


**2,监听设备状态** 为了提升用户体验,我们可以对设备的使用状态进行监听,然后再结合JobScheduler来执行网络请求.。比方说Splash闪屏广告图片, 我们可以在连接到Wifi时下载缓存到本地; 新闻类的App可以在充电,Wifi状态下做离线缓存。


#### 4.3.3 弱网测试&优化


**1,弱网测试** 有几种方式来模拟弱网进行测试:


**Android Emulator** 通常,我们创建和启动Android模拟器可以设置网络速度和延迟,如下图所示。 ![](https://img-blog.csdnimg.cn/img_convert/8c391e903fb538903111388f16b0d055.webp?x-oss-process=image/format,png)


### 尾声

评论里面有些同学有疑问关于如何学习material design控件,我的建议是**去GitHub搜**,有很多同行给的例子,这些栗子足够入门。

有朋友说要是动真格的话,需要NDK以及JVM等的知识,首现**NDK并不是神秘的东西,**你跟着官方的步骤走一遍就知道什么回事了,**无非就是一些代码格式以及原生/JAVA内存交互,进阶一点的有原生/JAVA线程交互,线程交互确实有点蛋疼,但平常避免用就好了,再说对于初学者来说关心NDK干嘛,据鄙人以前的经历,只在音视频通信和一个嵌入式信号处理(离线)的两个项目中用过,嵌入式信号处理是JAVA->NDK->.SO->MATLAB这样调用的我原来MATLAB的代码,其他的大多就用在游戏上了吧,一般的互联网公司会有人给你公司的SO包的。**
**至于JVM,该掌握的那部分,相信我,你会掌握的,不该你掌握的,有那些专门研究JVM的人来做,不如省省心有空看看计算机系统,编译原理。**

**一句话,平常多写多练,这是最基本的程序员的素质,尽量挤时间,读理论基础书籍,JVM不是未来30年唯一的虚拟机,JAVA也不一定再风靡未来30年工业界,其他的系统和语言也会雨后春笋冒出来,但你理论扎实会让你很快理解学会一个语言或者框架,你平常写的多会让你很快熟练的将新学的东西应用到实际中。**
**初学者,一句话,多练。**



**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注Android)**
![img](https://img-blog.csdnimg.cn/img_convert/8c401647615f3411f4d9da326cecbc8d.png)

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

知识,首现**NDK并不是神秘的东西,**你跟着官方的步骤走一遍就知道什么回事了,**无非就是一些代码格式以及原生/JAVA内存交互,进阶一点的有原生/JAVA线程交互,线程交互确实有点蛋疼,但平常避免用就好了,再说对于初学者来说关心NDK干嘛,据鄙人以前的经历,只在音视频通信和一个嵌入式信号处理(离线)的两个项目中用过,嵌入式信号处理是JAVA->NDK->.SO->MATLAB这样调用的我原来MATLAB的代码,其他的大多就用在游戏上了吧,一般的互联网公司会有人给你公司的SO包的。**
**至于JVM,该掌握的那部分,相信我,你会掌握的,不该你掌握的,有那些专门研究JVM的人来做,不如省省心有空看看计算机系统,编译原理。**

**一句话,平常多写多练,这是最基本的程序员的素质,尽量挤时间,读理论基础书籍,JVM不是未来30年唯一的虚拟机,JAVA也不一定再风靡未来30年工业界,其他的系统和语言也会雨后春笋冒出来,但你理论扎实会让你很快理解学会一个语言或者框架,你平常写的多会让你很快熟练的将新学的东西应用到实际中。**
**初学者,一句话,多练。**



**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注Android)**
[外链图片转存中...(img-sM1auHaP-1713440355538)]

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 7
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值