2024年Android最全Android 面试之必问性能优化_android性能面试最简单三个步骤(2),面试必问面试官的问题

建议

当我们出去找工作,或者准备找工作的时候,我们一定要想,我面试的目标是什么,我自己的技术栈有哪些,近期能掌握的有哪些,我的哪些短板 ,列出来,有计划的去完成,别看前两天掘金一些大佬在驳来驳去 ,他们的观点是他们的,不要因为他们的观点,膨胀了自己,影响自己的学习节奏。基础很大程度决定你自己技术层次的厚度,你再熟练框架也好,也会比你便宜的,性价比高的替代,很现实的问题但也要有危机意识,当我们年级大了,有哪些亮点,与比我们经历更旺盛的年轻小工程师,竞争。

  • 无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,这四个字就是我的建议!!!!!!!!!

  • 准备想说怎么样写简历,想象算了,我觉得,技术就是你最好的简历

  • 我希望每一个努力生活的it工程师,都会得到自己想要的,因为我们很辛苦,我们应得的。

  • 有什么问题想交流,欢迎给我私信,欢迎评论

【附】相关架构及资料

Android高级技术大纲

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

内含往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术

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

需要这份系统化学习资料的朋友,可以戳这里获取

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

  • @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)


然后,我们在启动时使用的emulator命令如下。



$emulator -netdelay gprs -netspeed gsm -avd Nexus_5_API_22


**2,网络代理工具** 使用网络代理工具也可以模拟网络情况。以Charles为例,保持手机和PC处于同一个局域网, 在手机端wifi设置高级设置中设置代理方式为手动, 代理ip填写PC端ip地址, 端口号默认8888。


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


## 5,耗电优化


事实上,如果我们的应用需要播放视频、需要获取 GPS 信息,亦或者是游戏应用,耗电都是比较严重的。如何判断哪些耗电是可以避免,或者是需要去优化的呢?我们可以打开手机自带的耗电排行榜,发现“王者荣耀”使用了 7 个多小时,这时用户对“王者荣耀”的耗电是有预期的。 ![](https://img-blog.csdnimg.cn/img_convert/e01bb30ea754b6483eeff12ea291b8bb.webp?x-oss-process=image/format,png)


### 5.1 优化方向


假设这个时候发现某个应用他根本没怎么使用,但是耗电却非常多,那么就会被系统无情的杀掉。**所以耗电优化的第一个方向是优化应用的后台耗电。**


知道了系统是如何计算耗电的,我们也就可以知道应用在后台不应该做什么,例如长时间获取 WakeLock、WiFi 和蓝牙的扫描等,以及后台服务。为什么说耗电优化第一个方向就是优化应用后台耗电,因为大部分厂商预装项目要求最严格的正是应用后台待机耗电。


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


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


当然前台耗电我们不会完全不管,但是标准会放松很多。再来看看下面这张图,如果系统对你的应用弹出这个对话框,可能对于微信来说,用户还可以忍受,但是对其他大多数的应用来说,可能很多用户就直接把你加入到后台限制的名单中了。


**耗电优化的第二个方向是符合系统的规则,让系统认为你耗电是正常的。**


而 Android P 及以上版本是通过 Android Vitals 监控后台耗电,所以我们需要符合 Android Vitals 的规则,目前它的具体规则如下。 ![](https://img-blog.csdnimg.cn/img_convert/78fa3a419c730b085b67c92956d43ff0.webp?x-oss-process=image/format,png)


可以看到,Android系统目前比较关心是后台 Alarm 唤醒、后台网络、后台 WiFi 扫描以及部分长时间 WakeLock 阻止系统后台休眠,因为这些都有可能导致耗电问题。


### 5.2 耗电监控


#### 5.2.1 Android Vitals


Android Vitals 的几个关于电量的监控方案与规则,可以帮助我们进行耗电监测。


* [Alarm Manager wakeup 唤醒过多]( )
* [频繁使用局部唤醒锁]( )
* [后台网络使用量过高]( )
* [后台 WiFi Scans 过多]( )


在使用了一段时间之后,我发现它并不是那么好用。以 Alarm wakeup 为例,Vitals 以每小时超过 10 次作为规则。由于这个规则无法做修改,很多时候我们可能希望针对不同的系统版本做更加细致的区分。其次跟 Battery Historian 一样,我们只能拿到 wakeup 的标记的组件,拿不到申请的堆栈,也拿不到当时手机是否在充电、剩余电量等信息。 下图是wakeup拿到的信息。


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


对于网络、WiFi scans 以及 WakeLock 也是如此。虽然 Vitals 帮助我们缩小了排查的范围,但是依然没办法确认问题的具体原因。


### 5.3 如何监控耗电


前面说过,Android Vitals并不是那么好用,而且对于国内的应用来说其实也根本无法使用。那我们的耗电监控系统应该监控哪些内容,又应该如何做呢?首先,我们看一下耗电监控具体应该怎么做呢?


* **监控信息**:简单来说系统关心什么,我们就监控什么,而且应该以后台耗电监控为主。类似 Alarm wakeup、WakeLock、WiFi scans、Network 都是必须的,其他的可以根据应用的实际情况。如果是地图应用,后台获取 GPS 是被允许的;如果是计步器应用,后台获取 Sensor 也没有太大问题。
* **现场信息**:监控系统希望可以获得完整的堆栈信息,比如哪一行代码发起了 WiFi scans、哪一行代码申请了 WakeLock 等。还有当时手机是否在充电、手机的电量水平、应用前台和后台时间、CPU 状态等一些信息也可以帮助我们排查某些问题。
* **提炼规则**:最后我们需要将监控的内容抽象成规则,当然不同应用监控的事项或者参数都不太一样。 由于每个应用的具体情况都不太一样,可以用来参考的简单规则。


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


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


#### 5.3.2 Hook方案


明确了我们需要监控什么以及具体的规则之后,接下来我们来看一下电量监控的技术方案。这里首先来看一下Hook 方案。Hook 方案的好处在于使用者接入非常简单,不需要去修改代码,接入的成本比较低。下面我以几个比较常用的规则为例,看看如何使用 Java Hook 达到监控的目的。


**1,WakeLock** WakeLock 用来阻止 CPU、屏幕甚至是键盘的休眠。类似 Alarm、JobService 也会申请 WakeLock 来完成后台 CPU 操作。WakeLock 的核心控制代码都在PowerManagerService中,实现的方法非常简单,如下所示。



// 代理 PowerManagerService
ProxyHook().proxyHook(context.getSystemService(Context.POWER_SERVICE), “mService”, this);

@Override
public void beforeInvoke(Method method, Object[] args) {
// 申请 Wakelock
if (method.getName().equals(“acquireWakeLock”)) {
if (isAppBackground()) {
// 应用后台逻辑,获取应用堆栈等等
} else {
// 应用前台逻辑,获取应用堆栈等等
}
// 释放 Wakelock
} else if (method.getName().equals(“releaseWakeLock”)) {
// 释放的逻辑
}
}


**2,Alarm** Alarm 用来做一些定时的重复任务,它一共有四个类型,其中ELAPSED\_REALTIME\_WAKEUP和RTC\_WAKEUP类型都会唤醒设备。同样,Alarm 的核心控制逻辑都在AlarmManagerService中,实现如下。



// 代理 AlarmManagerService
new ProxyHook().proxyHook(context.getSystemService
(Context.ALARM_SERVICE), “mService”, this);

public void beforeInvoke(Method method, Object[] args) {
// 设置 Alarm
if (method.getName().equals(“set”)) {
// 不同版本参数类型的适配,获取应用堆栈等等
// 清除 Alarm
} else if (method.getName().equals(“remove”)) {
// 清除的逻辑
}
}


除了WakeLock和Alarm外,对于后台 CPU,我们可以使用卡顿监控相关的方法;对于后台网络,同样我们可以通过网络监控相关的方法;对于 GPS 监控,我们可以通过 Hook 代理LOCATION\_SERVICE;对于 Sensor,我们通过 Hook SENSOR\_SERVICE中的“mSensorListeners”,可以拿到部分信息。


最后,我们将申请资源到的堆栈信息保存起来。当我们触发某个规则上报问题的时候,可以将收集到的堆栈信息、电池是否充电、CPU 信息、应用前后台时间等辅助信息上传到后台即可。


#### 5.3.3 插桩法


使用 Hook 方式虽然简单,但是某些规则可能不太容易找到合适的 Hook 点,而且在 Android P 之后,很多的 Hook 点都不支持了。出于兼容性考虑,我首先想到的是插桩法。以 WakeLock 为例:



public class WakelockMetrics {
// Wakelock 申请
public void acquire(PowerManager.WakeLock wakelock) {
wakeLock.acquire();
// 在这里增加 Wakelock 申请监控逻辑
}
// Wakelock 释放
public void release(PowerManager.WakeLock wakelock, int flags) {
wakelock.release();
// 在这里增加 Wakelock 释放监控逻辑
}
}


如果你对电量消耗又研究,那么肯定知道Facebook 的耗电监控的开源库Battery-Metrics,它监控的数据非常全,包括 Alarm、WakeLock、Camera、CPU、Network 等,而且也有收集电量充电状态、电量水平等信息。不过,遗憾的是Battery-Metrics 只是提供了一系列的基础类,在实际使用时开发者仍然需要修改大量的源码。


## 6,安装包优化


现在市面上的App,小则几十M,大则上百M。安装包越小,下载时省流量,用户好的体验,下载更快,安装更快。那么对于安装包,我们可以从哪些方面着手进行优化呢?


### 6,1 常用的优化策略


**1,清理无用资源** 在android打包过程中,如果代码有涉及资源和代码的引用,那么就会打包到App中,为了防止将这些废弃的代码和资源打包到App中,我们需要及时地清理这些无用的代码和资源来减小App的体积。清理的方法是,依次点击android Studio的【Refactor】->【Remove unused Resource】,如下图所示。 ![](https://img-blog.csdnimg.cn/img_convert/7dcb8b17d5860b6a7137b9319af03b31.webp?x-oss-process=image/format,png)


**2,使用Lint工具**


Lint工具还是很有用的,它给我们需要优化的点:


* 检测没有用的布局并且删除
* 把未使用到的资源删除
* 建议String.xml有一些没有用到的字符也删除掉


**3,开启shrinkResources去除无用资源** 在build.gradle 里面配置shrinkResources true,在打包的时候会自动清除掉无用的资源,但经过实验发现打出的包并不会,而是会把部分无用资源用更小的东西代替掉。注意,这里的“无用”是指调用图片的所有父级函数最终是废弃代码,而shrinkResources true 只能去除没有任何父函数调用的情况。



android {
    buildTypes {
        release {
            shrinkResources true
        }
    }
}

除此之外,大部分应用其实并不需要支持几十种语言的国际化支持,还可以删除语言支持文件。


### 6.2 资源压缩


在android开发中,内置的图片是很多的,这些图片占用了大量的体积,因此为了缩小包的体积,我们可以对资源进行压缩。常用的方法有:


1. 使用压缩过的图片:使用压缩过的图片,可以有效降低App的体积。
2. 只用一套图片:对于绝大对数APP来说,只需要取一套设计图就足够了。
3. 使用不带alpha值的jpg图片:对于非透明的大图,jpg将会比png的大小有显著的优势,虽然不是绝对的,但是通常会减小到一半都不止。
4. 使用tinypng有损压缩:支持上传PNG图片到官网上压缩,然后下载保存,在保持alpha通道的情况下对PNG的压缩可以达到1/3之内,而且用肉眼基本上分辨不出压缩的损失。
5. 使用webp格式:webp支持透明度,压缩比比,占用的体积比JPG图片更小。从Android 4.0+开始原生支持,但是不支持包含透明度,直到Android 4.2.1+才支持显示含透明度的webp,使用的时候要特别注意。
6. 使用svg:矢量图是由点与线组成,和位图不一样,它再放大也能保持清晰度,而且使用矢量图比位图设计方案能节约30~40%的空间。
7. 对打包后的图片进行压缩:使用7zip压缩方式对图片进行压缩,可以直接使用微信开源的AndResGuard压缩方案。



apply plugin: 'AndResGuard'
buildscript {
    dependencies {
        classpath 'com.tencent.mm:AndResGuard-gradle-plugin:1.1.7'
    }
}
andResGuard {
    mappingFile = null
    use7zip = true
    useSign = true
    keepRoot = false
    // add <your_application_id>.R.drawable.icon into whitelist.
    // because the launcher will get thgge icon with his name
    def packageName = <your_application_id>
            whiteList = [
    //for your icon
    packageName + ".R.drawable.icon",
            //for fabric
            packageName + ".R.string.com.crashlytics.*",
            //for umeng update
            packageName + ".R.string.umeng*",
            packageName + ".R.string.UM*",
            packageName + ".R.string.tb_*",
            packageName + ".R.layout.umeng*",
            packageName + ".R.layout.tb_*",
            packageName + ".R.drawable.umeng*",
            packageName + ".R.drawable.tb_*",
            packageName + ".R.anim.umeng*",
            packageName + ".R.color.umeng*",
            packageName + ".R.color.tb_*",
            packageName + ".R.style.*UM*",
            packageName + ".R.style.umeng*",

作者2013年从java开发,转做Android开发,在小厂待过,也去过华为,OPPO等大厂待过,18年四月份进了阿里一直到现在。

参与过不少面试,也当面试官 面试过很多人。深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长,而且极易碰到天花板技术停滞不前!

我整理了一份阿里P7级别的最系统的Android开发主流技术,特别适合有3-5年以上经验的小伙伴深入学习提升。

主要包括阿里,以及字节跳动,腾讯,华为,小米,等一线互联网公司主流架构技术。如果你想深入系统学习Android开发,成为一名合格的高级工程师,可以收藏一下这些Android进阶技术选型

我搜集整理过这几年阿里,以及腾讯,字节跳动,华为,小米等公司的面试题,把面试的要求和技术点梳理成一份大而全的“ Android架构师”面试 Xmind(实际上比预期多花了不少精力),包含知识脉络 + 分支细节。

Java语言与原理;
大厂,小厂。Android面试先看你熟不熟悉Java语言

高级UI与自定义view;
自定义view,Android开发的基本功。

性能调优;
数据结构算法,设计模式。都是这里面的关键基础和重点需要熟练的。

NDK开发;
未来的方向,高薪必会。

前沿技术;
组件化,热升级,热修复,框架设计

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

我在搭建这些技术框架的时候,还整理了系统的高级进阶教程,会比自己碎片化学习效果强太多,CodeChina上可见;

当然,想要深入学习并掌握这些能力,并不简单。关于如何学习,做程序员这一行什么工作强度大家都懂,但是不管工作多忙,每周也要雷打不动的抽出 2 小时用来学习。

不出半年,你就能看出变化!

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

需要这份系统化学习资料的朋友,可以戳这里获取

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

发的基本功。

[外链图片转存中…(img-HvPU20cx-1715705556557)]

性能调优;
数据结构算法,设计模式。都是这里面的关键基础和重点需要熟练的。

[外链图片转存中…(img-AehkCXWh-1715705556557)]

NDK开发;
未来的方向,高薪必会。

[外链图片转存中…(img-wvJicZyf-1715705556558)]

前沿技术;
组件化,热升级,热修复,框架设计

[外链图片转存中…(img-rc0KzIyf-1715705556558)]

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

我在搭建这些技术框架的时候,还整理了系统的高级进阶教程,会比自己碎片化学习效果强太多,CodeChina上可见;

当然,想要深入学习并掌握这些能力,并不简单。关于如何学习,做程序员这一行什么工作强度大家都懂,但是不管工作多忙,每周也要雷打不动的抽出 2 小时用来学习。

不出半年,你就能看出变化!

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

需要这份系统化学习资料的朋友,可以戳这里获取

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值