8-《性能优化》

news2024/9/21 3:25:14

8-《性能优化》

    • 1 启动优化
      • 1.1.冷启动耗时统计?
      • 1.2.TraceView和System Trace
      • 1.3.优雅获取方法耗时
      • 1.4.启动速度优化小技巧
      • 1.5.启动优化之异步初始化
      • 1.6.启动优化之异步初始化最优解—启动器
      • 1.7.延迟初始化
      • 1.8.其它方案
      • 1.9.启动优化之模拟面试
    • 2 布局优化
    • 3 线程优化
      • 3.1.线程调度原理
      • 3.2.线程调度模型
      • 3.3.线程使用准则:
    • 4 网络优化
    • 5 Apk瘦身—包体积优化
    • 6 内存优化(非常重要)
      • 6.1 Android 内存管理机制
      • 6.2 Android的内存泄漏、内存溢出、内存抖动概念
      • 6.3 如何避免OOM(内存泄漏优化)。
      • 6.4 常用的内存检查工具。
    • 7 电量优化
    • 8.卡顿优化
    • 9.稳定性优化
    • 10.APP专项技术优化
  • 1mvc/mvp/mvvm
  • 2 常见设计模式
    • 2.1.设计模式的六大原则:
    • 2.2.单例模式
    • 2.3.建造者模式
    • 2.4.责任链模式
      • 1.View的事件分发
      • 2.Okhttp源码中的责任链模式
        • 2.1 Inteceptor
        • 2.2 Chain
    • 2.5.观察者模式
      • 1.各种控件的监听,如下:
      • 2.Adapter的notifyDataSetChanged()方法
      • 3. BroadcastReceiver
      • 4.RxJava、RxAndroid、EventBus、otto等等,也是使用了观察者模式。
    • 2.6.代理模式
    • 2.7.策略模式
    • 2.8.工厂模式
    • 2.9.适配器模式

1 启动优化

冷启动:耗时最多,是启动优化的衡量标准。启动应用时,后台没有该应用的进程,系统会重新创建一个新的进程分配给该应用,这个启动方式就是冷启动。

热启动:应用从后台到前台

优化方向:主要针对冷启动,Application和Activity生命周期

1.1.冷启动耗时统计?

1.系统提供的命令:(线下使用方面,不能带到线上;非严谨精确的时间)

1 | adb shell am start -S -W 包名/启动类的全限定名     //-S 表示重启当前应用 

执行

1 C:\Android\Demo>adb shell am start -S -W com.example.moneyqian.demo/com.example.moneyqian.demo.MainActivity
2 Stopping: com.example.moneyqian.demo
3 Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.example.moneyqian.demo/.MainActivity }
4 Status: ok
5 Activity: com.example.moneyqian.demo/.MainActivity
6 ThisTime: 2247
7 TotalTime: 2247
8 WaitTime: 2278
9 Complete
————————————————

ThisTime : 最后一个Activity的启动耗时(例如从 LaunchActivity - >MainActivity「adb命令输入的Activity」 , 只统计 MainActivity 的启动耗时)
TotalTime : 所有activity的启动耗时(有几个Activity 就统计几个)
WaitTime : 应用进程的创建过程 + TotalTime (AMS启动activity的总耗时)
————————————————
版权声明:本文为CSDN博主「刘忆初」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_34512207/article/details/123110031

2.手动打点:(精确,可带到线上,推荐)
注意截止时间的选取
误区:onWIndowFocusChanged 首帧时间
正解:真实数据展示,Feed第一条展示

1.2.TraceView和System Trace

1.TraceView:
特点:
运行时开销严重
可能会带偏优化方向

TraceView使用方式
代码中添加:

1 Debug.startMethodTracing();
2 被检测的方法;
3 Debug.stopMethodTracing()

需要使用adb pull将生成的.trace文件导出到电脑,然后使用Android Studio的Profiler进行加载;

打开 Profiler -> CPU -> 点击 Record -> 点击 Stop -> 查看Profiler下方Top Down/Bottom Up 区域,以找出耗时的热点方法。

结合CPU Profiler 分析

使用 Profile 的 CPU 模块可以帮我们快速找到耗时的热点方法。

TraceView作用:主要做热点分析,用来得到以下两种数据:
单次执行最耗时的方法。
执行次数最多的方法。

2.System Trace:
集合android内核的数据,生成html报告
API18以上使用,推荐TraceCompat
轻量级,开销小
直观反应cpu利用率
cputime与walltime的区别
-cputime是代码消耗cpu的时间(重点指标,优化方向)
-walltime是代码执行的时间(举例:锁冲突可能导致walltime时间长很多)

作用:
主要用于分析绘制性能方面的问题。
分析系统关键方法和应用方法耗时。

1.3.优雅获取方法耗时

1.常规实现:侵入性强,工作量大
在这里插入图片描述
2.AOP方式:面向切面编程

  • 针对同一类问题的统一处理,修改方便
  • 无侵入添加代码
  • 在这里插入图片描述
  • 详细使用请百度,封装一个打印耗时点日志的辅助类

1.4.启动速度优化小技巧

启动白屏优化—>theme切换:感觉上的快,对实际启动速度无帮助
解决启动白屏问题
再启动app时展示一张静态图 ,实现方法是给splash页面设置theme主题 ,主题设置background背景图代替白屏页,展示完成背景图和splash之后,,在Mainactivity onCreat()之前把系统appTheme设置回来。

1.5.启动优化之异步初始化

核心思想:子线程分担主线程的任务,并行减少时间

Application中加入异步线程:把application中各种初始化操作分散到子线程中执行。
在这里插入图片描述
把各种初始化的操作,通过线程池,放到子线程中执行,这里创建线程池的核心线程数,参考的是asyncTask中的核心线程数量的设置。如果自己随便设置,可能超过CPU的核数或者低于CPU核数太多。

这样处理后,application的onCreate将会快很多,但是会出现一些问题,比如有些初始化操作在子线程中还没执行完,其它地方如splashActivity已经要开始使用该方法中的某些内容。就会报错。那么要么放弃把这个初始化方法放到子线程,要么参考下面的方案

解决方案:通过CountDownLatch,在application中创建一个CountDownLatch

1 //相当于自己加一个锁
2 private final CountDownLatch countDownLatch=new CountDownLatch(1);
3 ...
4 @Override
5    public void onCreate() {
6    	...
7       service.submit(new Runnable() {
8           @Override
9           public void run() {
10               initGeTui();
11              countDownLatch.countDown(); //CountDown满足一次
12          }
13      });
14
15      try {
16          countDownLatch.await(); //会检测执行次数是否满足
17      } catch (InterruptedException e) {
18          e.printStackTrace();
19      }
20  }

1.6.启动优化之异步初始化最优解—启动器

上面方案的缺点:

  1. 代码不够优雅,
  2. 如果多个异步初始化之间存在依赖关系,不好处理
  3. 维护成本高

启动器的核心思想:充分利用CPU多核,自动梳理任务顺序

启动器设计流程:

  1. 任务Task化,启动逻辑抽象成Task。
  2. 根据所有任务依赖关系排序生成一个有向无环图。
  3. 多线程按照排序后的优先级依次执行。

1.7.延迟初始化

结合IdleHandler,在空闲时执行

1.8.其它方案

提前加载SharedPreferences:
Multidex之前加载,利用此阶段CPU

启动阶段不启动子进程:
子进程会共享CPU资源,导致主进程CPU紧张

类加载优化:
提前异步类加载(使用的时候不用再加载)

启动阶段抑制GC

CPU锁频

1.9.启动优化之模拟面试

1.你做启动优化怎么做的?
分析现状,确认问题。–目前的启动流程已经非常复杂,主线程执行了太多的代码。
异步优化。–异步初始化–>启动器
延迟初始化。–有些代码可能不需要application中执行,可以在页面初始化后空闲时

2.是怎么异步的,异步遇到什么问题没有?
new thread 或者线程池
代码变得不够优雅,依赖关系不好处理—>启动器

3.启动优化,有哪些容易忽略的注意点?
cpu time和wall time的区别
注意延迟初始化的优化
介绍下黑科技

4.版本迭代导致的启动变慢有什么好的解决办法吗?
启动器
结合CI
监控完善

2 布局优化

工具:system trace
关注frames
正常:绿色原点,丢帧:黄色或红色
Alerts栏

工具:Layout Insepector
AndroidStudio自带工具,可查看视图层次结构

减少布局嵌套:层级嵌套过深的话,深度遍历各个节点会非常消耗时间,这也是布局优化余地最大的一个点了。使用约束布局 Constraintlayout减少嵌套 。

使用合适的布局
简单布局 FrameLayout>LinearLayout>RelativeLayout;
复杂布局 RelativeLayout>LinearLayout>FrameLayout
如果用 RelativeLayout 可以避免布局嵌套的话是值得的。(LinearLayout在设置weight时,也会调用子View 2次 onMeasure)

使用 include 标签: 在布局文件中,标签可以指定插入一段布局文件到当前布局。这样的话既提高了布局复用,也减少了我们的代码书写。另外,merge标签可以和include的标签一起使用从而减少布局层级。

ViewStub 延时加载: 有些布局,比如网络出错的布局,没必要在所有时候都加载出来。使用 ViewStub 可以实现按需加载。ViewStub 本身没有宽高,加载起来几乎不消耗什么资源。当对他setVisibility(View.VISIBLE)的时候会调用它引用的真实布局填充到当前位置,从而实现了延时加载,节省了正常加载的时间。

避免过渡绘制:去掉多余的背景色,减少复杂shape的使用

ondraw中避免:创建大对象,耗时操作

3 线程优化

3.1.线程调度原理

  1. 任意时刻,只有一个线程占用CPU,处于运行状态
  2. 多线程并发,轮流获取CPU使用权
  3. JVM 负责线程的调度:按照特定的机制分配CPU使用权

3.2.线程调度模型

分时调度模型:让所有线程轮流获取 CPU 的使用权,而且均分每个线程占用 CPU 的时间片,这种方式非常公平

抢占式调度模型让优先级高的线程优先获取到 CPU 的使用权,如果在可运行池当中的线程优先级都一样,那就随机选取一个(JVM使用这种)

3.3.线程使用准则:

  1. 严禁直接使用new Thread
  2. 提供基础线程池供各个业务线使用;避免各个业务线各自维护一套线程池,导致线程数过多。
  3. 根据任务类型选择合适的异步方式
    优先级低,长时间执行,HandlerThread
    定时任务,线程池
  4. 创建线程必须命名;方便定位线程归属,运行时通过Thread.currentThread().setName修改名字
  5. 关键异步任务监控;AOP方式做监控
  6. 重视优先级设置;Process.setThreadPriority(),可以多次设置

如何锁定线程创建者
项目变大之后收敛线程;项目源码、三方库、arr中都有线程的创建

分析:
创建线程的位置获取堆栈,
所有的异步方式,都会走到new thread

怎么在项目中对线程进行优化
线程收敛
统一线程池:任务区分
监控重要异步任务

线程的创建和销毁会带来比较大的性能开销。因此线程优化也很有必要。查看项目中是否存在随意 new thread,线程缺乏管理的情况。使用 AsyncTask 或者线程池对线程进行管理,可以提升 APP 的性能。另外,我比较推荐使用 Rxjava 来实现异步操作,既方便又优雅。

4 网络优化

  1. 连接复用:节省连接建立时间,如开启 keep-alive。于Android来说默认情况下HttpURLConnection和HttpClient都开启了keep-alive。

  2. 请求合并:即将多个请求合并为一个进行请求,比较常见的就是网页中的CSS Image Sprites。同一个页面数据尽量放到一个接口中去处理。

  3. 减少请求数据的大小:对于post请求,body可以做gzip压缩的,header也可以做数据压缩(不过只支持http 2.0)。 返回数据的body也可以做gzip压缩,body数据体积可以缩小到原来的30%左右(也可以考虑压缩返回的json数据的key数据的体积,尤其是针对返回数据格式变化不大的情况,支付宝聊天返回的数据用到了)。

  4. 根据用户的当前的网络质量来判断下载什么质量的图片(电商用的比较多)

  5. 使用HttpDNS优化DNS:DNS存在解析慢和DNS劫持等问题,DNS 不仅支持 UDP,它还支持 TCP,但是大部分标准的 DNS 都是基于 UDP 与 DNS 服务器的 53 端口进行交互。HTTPDNS 则不同,顾名思义它是利用 HTTP 协议与 DNS 服务器的 80 端口进行交互。不走传统的 DNS 解析,从而绕过运营商的 LocalDNS 服务器,有效的防止了域名劫持,提高域名解析的效率。

  6. 大量数据的加载采用分页的方式;上传图片时,在必要的时候压缩图片

网络优化工具:Network Profiler
1.显示实时网络波动:发送、接收数据及连接数
2.需要启动高级分析
3.只支持HttpURLConnection和OkHttp库

抓包工具:Charles(使用java开发的)
断点功能
Map Local
弱网环境模拟

NetWorkStatsManager
获取线上使用的流量情况

前后台流量获取方案

在网络方面你做了哪些监控,建立了哪些指标?
初期没有做,开发都是连着wifi,没有注意到这个问题。用户量增多后,收到用户的反馈。
补上网络方面的监控:
质量:请求成功率,每步耗时,状态码
流量:精确统计、前后台流量消耗

如何有效的降低用户流量消耗?
数据:缓存、增量更新
上传:压缩
图片:缩略图、webp

用户反馈消耗流量多这种问题怎么查?
精准流量获取能力
所有请求大小及次数的监控
主动预警能力

5 Apk瘦身—包体积优化

Apk 组成结构:
在这里插入图片描述
1.三方库处理:

  • 统一三方库,不要重复
  • 根据需求选择更小的库
  • 仅引入所需的部分代码:Fresco的webp支持;smartrefreshlayout也有类似的功能剥离。

2.资源优化
图片只保留一份,如xhdpi
android自带的功能:移除无用资源
jpg/png转webp
优先考虑能否用 shape 代码、.9 、svg、VectorDrawable 类来替换传统的图片
避免使用帧动画,可使用 Lottie 动画库

3.代码优化

启用混淆以移除无用代码(minifyEnabled true )
开启代码压缩( minifyEnabled true //打开代码压缩)
剔除 R 文件
用注解替代枚举

4.arsc文件优化
移除未使用的备用资源来优化 .arsc 文件

1 android {
2     defaultConfig {
3         ...
4         resConfigs "zh", "zh_CN", "zh_HK", "en"
5     }
6 }

5.so库打包优化(此种优化效果非常明显)

so是Android上的动态链接库,七种不同类型的CPU架构:armeabi、armeabi-v7a、arm64-v8a、x86
、x86_64、MIPS、MIPS64。arm 系列是绝大多数手机上使用的,x86 主要是运用在平板上。

优化方式一:移出非主流的so库,只提供对主流架构的支持,一般选择armeabi-v7a。
android {

1    defaultConfig {
2        ...
3        ndk {
4            abiFilters  "armeabi-v7a"
5        }
6    }
7}

这种方案相对来说比较暴力。

优化方案二(更优方案)
完美支持所有设备类型代价太大。都放在armeabi目录,根据CPU类型加载对应架构so。

其它方案:

  • so动态下载
  • 插件化

6 内存优化(非常重要)

首先需要了解ava 内存回收机制——GC机制,Java 对象引用方式 —— 强引用、软引用、弱引用和虚引用。
基础知识:

6.1 Android 内存管理机制

1.针对进程的内存策略

进程的内存分配策略为:由 ActivityManagerService 集中管理所有进程的内存分配

进程的内存回收策略为:首先Application Framework 决定回收的类型,当进程的内存空间紧张时会按照进程优先级由低到高的顺序自动回收进程及内存。

Android将进程分为5个优先级,具体如下:
在这里插入图片描述
真正执行回收进程的操作的是 Linux 内核。

梳理一下整体流程:

  1. ActivityManagerService 对所有进程进行评分。

  2. 更新评分到 Linux 内核。

  3. 由 Linux 内核完成真正的内存回收。

2.针对对象、变量的内存策略

Android的对于对象、变量的内存策略同 Java

内存管理 = 对象 / 变量的内存分配 + 内存释放

  1. 内存分配策略
    对象,变量的内存分配有系统负责,共有三种:静态分配、栈式分配、堆式分配,分别面向静态变量,动态变量和对象实例。

  2. 内存释放策略
    对象,变量的内存释放由Java的垃圾回收器GC负责。

内存分配注意:(非常重要)

  1. 成员变量全部存储在堆中(包括基本数据类型,引用及引用的对象实体)—因为他们属于类,类对象最终还是要被new出来的

  2. 局部变量的基本数据类型和引用存储于栈当中,引用的对象实体存储在堆中。—–因为他们属于方法当中的变量,生命周期会随着方法一起结束

1 public class Sample {    
2     // 该类的实例对象的成员变量s1、mSample1及指向的对象都存放在堆内存中
3     int s1 = 0;
4     Sample mSample1 = new Sample();   
5  	
6  	// 方法中的局部变量s2、mSample2存放在 栈内存
7  	// 变量mSample2所指向的对象实例存放在 堆内存
8     public void method() {        
9         int s2 = 0;
10        Sample mSample2 = new Sample();
11    }
12}
13	// 变量mSample3的引用存放在栈内存中
14    // 变量mSample3所指向的对象实例存放在堆内存
15	// 该实例的成员变量s1、mSample1也存放在堆内存中
16    Sample mSample3 = new Sample();
17

6.2 Android的内存泄漏、内存溢出、内存抖动概念

1.内存泄露
即 ML (Memory Leak),指 程序在申请内存后,当该内存不需再使用但却无法被释放,归还给 程序的现象。对应用程序的影响:容易使得应用程序发生内存溢出,即OOM(out of Memory)
在这里插入图片描述
发生内存泄露的本质原因
本质原因:持有引用者的生命周期>被引用者的生命周期
解释:本该回收的对象(该对象已经不再被使用),由于某些原因(如被另一个正在使用的对象引用)不能被回收。

2.内存抖动
在这里插入图片描述

优化方案
尽量避免频繁创建大量、临时的小对象

6.3 如何避免OOM(内存泄漏优化)。

1.减小对象的内存占用
1)使用更加轻量的数据结构
例如,我们可以考虑使用ArrayMap/SparseArray而不是HashMap等传统数据结构,下图演示了HashMap的简要工作原理,相比起Android系统专门为移动操作系统编写的ArrayMap容器,在大多数情况下,都显示效率低下,更占内存。通常的HashMap的实现方式更加消耗内存,因为它需要一个额外的实例对象来记录Mapping操作。另外,SparseArray更加高效在于他们避免了对key与value的autobox自动装箱,并且避免了装箱后的解箱。
在这里插入图片描述

可以参考 Android性能优化典范 - 第3季

2)避免在Android里面使用Enum
3)减小Bitmap对象的内存占用
Bitmap是一个极容易消耗内存的大胖子,减小创建出来的Bitmap的内存占用是很重要的,通常来说有下面2个措施:
inSampleSize:缩放比例,在把图片载入内存之前,我们需要先计算出一个合适的缩放比例,避免不必要的大图载入。
decode format:解码格式,选择ARGB_8888/RBG_565/ARGB_4444/ALPHA_8,存在很大差异。
4)使用更小的图片
在设计给到资源图片的时候,我们需要特别留意这张图片是否存在可以压缩的空间,是否可以使用一张更小的图片。尽量使用更小的图片不仅仅可以减少内存的使用,还可以避免出现大量的InflationException。

2.内存对象的重复利用
1)复用系统自带的资源
Android系统本身内置了很多的资源,例如字符串/颜色/图片/动画/样式以及简单布局等等,这些资源都可以在应用程序中直接引用。这样做不仅仅可以减少应用程序的自身负重,减小APK的大小,另外还可以一定程度上减少内存的开销,复用性更好。但是也有必要留意Android系统的版本差异性,对那些不同系统版本上表现存在很大差异,不符合需求的情况,还是需要应用程序自身内置进去。

2)注意在ListView/GridView等出现大量重复子组件的视图里面对ConvertView的复用
3)Bitmap对象的复用
在ListView与GridView等显示大量图片的控件里面需要使用LRU的机制来缓存处理好的Bitmap。
4)避免在onDraw方法里面执行对象的创建
类似onDraw等频繁调用的方法,一定需要注意避免在这里做创建对象的操作,因为他会迅速增加内存的使用,而且很容易引起频繁的gc,甚至是内存抖动。
5)StringBuilder
在有些时候,代码中会需要使用到大量的字符串拼接的操作,这种时候有必要考虑使用StringBuilder来替代频繁的“+”。

3.避免对象的内存泄露

内存对象的泄漏,会导致一些不再使用的对象无法及时释放,这样一方面占用了宝贵的内存空间,很容易导致后续需要分配内存的时候,空闲空间不足而出现OOM。显然,这还使得每级Generation的内存区域可用空间变小,gc就会更容易被触发,容易出现内存抖动,从而引起性能问题。(LeakCanary开源控件,可以很好的帮助我们发现内存泄露的情况)
1)注意Activity的泄漏
通常来说,Activity的泄漏是内存泄漏里面最严重的问题,它占用的内存多,影响面广,我们需要特别注意以下两种情况导致的Activity泄漏:

内部类引用导致Activity的泄漏:非静态(匿名)内部类会默认持有外部类引用。

  1. Handler导致的Activity泄漏(最经典的场景),如果Handler中有延迟的任务或者是等待执行的任务队列过长,都有可能因为Handler继续执行而导致Activity发生泄漏。此时的引用关系链是Looper -> MessageQueue -> Message -> Handler -> Activity。为了解决这个问题,可以在UI退出之前,执行remove Handler消息队列中的消息与runnable对象(removeCallbacksAndMessages(null)–同时清空消息队列 ,结束Handler生命周期)。或者是使用Static + WeakReference的方式来达到断开Handler与Activity之间存在引用关系的目的。为了保证Handler中消息队列中的所有消息都能被执行,此处推荐使用 静态内部类 + 弱引用的方式。

  2. 线程造成的内存泄漏
    在 Activity 内定义了一个匿名的 AsyncTask 对象,就有可能发生内存泄漏。如果 Activity 被销毁之后 AsyncTask 仍然在执行,那就会阻止垃圾回收器回收Activity 对象,进而导致内存泄漏,直到执行结束才能回收 Activity。
    同样的,使用 ThreadTimerTask 也可能导致 Activity 泄漏。只要它们是通过匿名类创建的,尽管它们在单独的线程被执行,它们也会持有对 Activity 的强引用,进而导致内存泄漏

总结:
内部类引起的泄漏不仅仅会发生在Activity上,其他任何内部类出现的地方,都需要特别留意!我们可以考虑尽量使用static类型的内部类,同时使用WeakReference的机制来避免因为互相引用而出现的泄露。

2)考虑使用Application Context而不是Activity Context

对于大部分非必须使用Activity Context的情况(Dialog的Context就必须是Activity Context),我们都可以考虑使用Application Context而不是Activity的Context,这样可以避免不经意的Activity泄露。(Activity Context被传递到其他实例中,这可能导致Activity自身被引用而发生泄漏)

3)注意临时Bitmap对象的及时回收

4)注意监听器的注销
在Android程序里面存在很多需要register与unregister的监听器,我们需要确保在合适的时候及时unregister那些监听器。自己手动add的listener,需要记得及时remove这个listener。

5)注意WebView的泄漏
Android中的WebView存在很大的兼容性问题,不仅仅是Android系统版本的不同对WebView产生很大的差异,另外不同的厂商出货的ROM里面WebView也存在着很大的差异。更严重的是标准的WebView存在内存泄露的问题,看这里WebView causes memory leak - leaks the parent Activity。所以通常根治这个问题的办法是为WebView开启另外一个进程,通过AIDL与主进程进行通信,WebView所在的进程可以根据业务的需要选择合适的时机进行销毁,从而达到内存的完整释放

6)注意Cursor对象是否及时关闭
在程序中我们经常会进行查询数据库的操作,但时常会存在不小心使用Cursor之后没有及时关闭的情况。这些Cursor的泄露,反复多次出现的话会对内存管理产生很大的负面影响,我们需要谨记对Cursor对象的及时关闭。

6.4 常用的内存检查工具。

(1)Memory Monitor 工具:

它是Android Studio自带的一个内存监视工具,它可以很好地帮助我们进行内存实时分析。通过点击Android Studio右下角的Memory Monitor标签,打开工具可以看见较浅蓝色代表free的内存,而深色的部分代表使用的内存从内存变换的走势图变换,可以判断关于内存的使用状态,例如当内存持续增高时,可能发生内存泄漏;当内存突然减少时,可能发生GC等,如下图所示。

(2)LeakCanary工具:

LeakCanary是Square公司基于MAT开发的一款监控Android内存泄漏的开源框架。其工作的原理是: 监测机制利用了Java的WeakReference和ReferenceQueue,通过将Activity包装到WeakReference中,被WeakReference包装过的Activity对象如果被回收,该WeakReference引用会被放到ReferenceQueue中,通过监测ReferenceQueue里面的内容就能检查到Activity是否能够被回收(在ReferenceQueue中说明可以被回收,不存在泄漏;否则,可能存在泄漏,LeakCanary是执行一遍GC,若还未在ReferenceQueue中,就会认定为泄漏)。

如果Activity被认定为泄露了,就抓取内存dump文件(Debug.dumpHprofData);之后通过HeapAnalyzerService.runAnalysis进行分析内存文件分析;接着通过HeapAnalyzer (checkForLeak—findLeakingReference—findLeakTrace)来进行内存泄漏分析。最后通过DisplayLeakService进行内存泄漏的展示。

(3)Android Lint 工具:

Android Lint Tool 是Android Sutido种集成的一个Android代码提示工具,它可以给你布局、代码提供非常强大的帮助。硬编码会提示以级别警告,例如:在布局文件中写了三层冗余的LinearLayout布局、直接在TextView中写要显示的文字、字体大小使用dp而不是sp为单位,就会在编辑器右边看到提示。

7 电量优化

电量重视度不够:开发中长时间连着手机,难以感知到电量变化
电量消耗线上难以量化

如何解决问题:
1、找特定场景专项测试:比如进入详情页操作一段时间,再看电量变化。
2、注册电量相关的广播:ACTION_BATTERY_CHANGED

1 IntentFilter intentFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
2 mBatteryLowReceiver = new BatteryLowReceiver();
3 registerReceiver(mBatteryLowReceiver,intentFilter);

  • 价值不大 针对手机整体的耗电量,而非特定的APP
  • 实时性差,精度较低,被动通知。

Battery Historian(电量使用记录分析工具)

Battery Historian是Android 5.0开始引入的新API。通过下面的指令,可以得到设备上的电量消耗信息。
特点:

  • 功能强大,推荐使用;
  • 可视化展示指标:耗电比例、执行时间、次数;
  • 只适合线下使用

使用步骤:

  1. 安卓Docker
  2. 执行命令 docker run -p :9999 gcr.io/android-battery-historian/stable:3.0 --port 9999,完成Battery Historian的安装
  3. 电量重置:adb shell dumpsys batterystats --reset
  4. 到出电量信息:adb bugreport bugreport.zip(过程可能会有点慢)
  5. 打开http://localhost:9999,上传bugreport文件即可进行分析

面试问题模拟
1.怎么做电量测试?
电量相关的测试相对难做一些,因为app在具体用户的耗电量无法统计,每个设备的硬件不一样,相关的功耗也不一样,且功耗值只能在线下拿到。只能尽可能监控来判断

分场景逐个击破:按照app功能进行针对性的专项测试,利用手机设置里面的电量消耗功能作为判断的依据,操作某个功能一段时间之后,看耗电量,直观,但精确性不行

Battery Historian : google推出的Android系统电量分析工具,5.0以后可用。拿到的电量信息精确也丰富很多。可以获取到详细的耗电组件:GPS、weakLock、蓝牙等的工作时间及耗电量。 可以比对优化前及优化后的电量信息。

但是这个工具只能在线下使用,因此需要线下测试后增加一些线上监控,比如耗电组件的使用次数,调用堆栈以及访问时间, 如果有用户在线上反馈,就可以通过这些信息来判断用户是否有耗电的操作。

2.有哪些有效的电量优化手段?
因为不能精确的统计线上电量消耗,因此需要尽可能线下优化好电量。

界面相关
离开界面后停止相关活动
耗电操作判断前台后台,如果后台就不操作(如动画,onresume中start onpause中cancel)

网络相关:
控制网络请求时机和次数;将可以延迟的网络请求批量发送
网络数据传输前进行数据压缩,减少时间,还能节约流量
禁止使用轮训方式做业务操作
不需要实时性的任务在连接wifi后在执行(wifi网络传输的电量消耗要比移动网络少很多)

定位相关(传感器相关)
根据场景谨慎选择定位模式
考虑网络定位代替GPS
使用后务必及时关闭,减少更新频率

WakeLock 相关
注意成对出现:acquire和release
使用带参数的acquire来设置超时时间,避免异常情况导致WakeLock无法释放
finally确保一定会被释放

JobScheduler

8.卡顿优化

9.稳定性优化

10.APP专项技术优化

7-《设计模式》

    • 1 启动优化
      • 1.1.冷启动耗时统计?
      • 1.2.TraceView和System Trace
      • 1.3.优雅获取方法耗时
      • 1.4.启动速度优化小技巧
      • 1.5.启动优化之异步初始化
      • 1.6.启动优化之异步初始化最优解—启动器
      • 1.7.延迟初始化
      • 1.8.其它方案
      • 1.9.启动优化之模拟面试
    • 2 布局优化
    • 3 线程优化
      • 3.1.线程调度原理
      • 3.2.线程调度模型
      • 3.3.线程使用准则:
    • 4 网络优化
    • 5 Apk瘦身—包体积优化
    • 6 内存优化(非常重要)
      • 6.1 Android 内存管理机制
      • 6.2 Android的内存泄漏、内存溢出、内存抖动概念
      • 6.3 如何避免OOM(内存泄漏优化)。
      • 6.4 常用的内存检查工具。
    • 7 电量优化
    • 8.卡顿优化
    • 9.稳定性优化
    • 10.APP专项技术优化
  • 1mvc/mvp/mvvm
  • 2 常见设计模式
    • 2.1.设计模式的六大原则:
    • 2.2.单例模式
    • 2.3.建造者模式
    • 2.4.责任链模式
      • 1.View的事件分发
      • 2.Okhttp源码中的责任链模式
        • 2.1 Inteceptor
        • 2.2 Chain
    • 2.5.观察者模式
      • 1.各种控件的监听,如下:
      • 2.Adapter的notifyDataSetChanged()方法
      • 3. BroadcastReceiver
      • 4.RxJava、RxAndroid、EventBus、otto等等,也是使用了观察者模式。
    • 2.6.代理模式
    • 2.7.策略模式
    • 2.8.工厂模式
    • 2.9.适配器模式

1mvc/mvp/mvvm

MVC:Model-View-Controller,是一种分层解偶的框架,Model层提供本地数据和网络请求,View层处理视图,Controller处理逻辑,存在问题是Controller层和View层的划分不明显,Model层和View层的存在耦合。

MVP:Model-View-Presenter,是对MVC的升级,Model层和View层与MVC的意思一致,但Model层和View层不再存在耦合,而是通过Presenter层这个桥梁进行交流。

MVVM:Model-View-ViewModel,不同于上面的两个框架,ViewModel持有数据状态,当数据状态改变的时候,会自动通知View层进行更新。

MVC和MVP的区别是什么?

MVP是MVC的进一步解耦,简单来讲,在MVC中,View层既可以和Controller层交互,又可以和Model层交互;而在MVP中,View层只能和Presenter层交互,Model层也只能和Presenter层交互,减少了View层和Model层的耦合,更容易定位错误的来源。

MVVM和MVP的最大区别在哪?

MVP中的每个方法都需要你去主动调用,它其实是被动的,而MVVM中有数据驱动这个概念,当你的持有的数据状态发生变更的时候,你的View你可以监听到这个变化,从而主动去更新,这其实是主动的。

严格来说这三种都不是设计模式,只能算是框架,或者一种思想。每种模式也没有严格定义,不同的人有不同的理解

2 常见设计模式

2.1.设计模式的六大原则:

  • 单一原则:一个类或者一个方法只负责一项职责,尽量做到类的只有一个行为原因引起变化;
  • 里氏替换原则:子类可以扩展父类的功能,但不能改变原有父类的功能;
  • 依赖倒置原则:上层模块不应该依赖下层模块,两者应依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象;
  • 接口隔离原则:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上面
  • 迪米特原则:最少知道原则,尽量降低类与类之间的耦合;每个类尽量减少对其他类的依赖,减少对外暴露方法,使得功能模块独立,低耦合
  • 开闭原则:对于扩展是开放的,对于修改是封闭的

总结:
单一职责原则告诉我们实现类要职责单一;
里氏替换原则告诉我们不要破坏继承体系;
依赖倒置原则告诉我们要面向接口编程;
接口隔离原则告诉我们在设计接口的时候要精简单一;
迪米特法则告诉我们要降低耦合;
而开闭原则是总纲,他告诉我们要对扩展开放,对修改关闭。

2.2.单例模式

关键点:某个类只能有一个实例,提供一个全局的访问点。

恶汉式:

public class SingleInstance {
	//有一个静态属性instance,在JVM虚拟机装载类信息的时候,会new SingleInstance()对其进行初始化。
	//线程安全,但是没有延迟加载,浪费资源
    private SingleInstance() {}//构造函数私有化
    private final static SingleInstance instance=new SingleInstance();
    public static  SingleInstance getInstance() {
        return instance;
    }
}

懒汉式

public class SingleInstance {
    private SingleInstance() {}
    private  static SingleInstance instance;
    public static SingleInstance getInstance() {
    	//假如2个线程同时进入if 语句,会创建两个对象,线程不安全.
        if (instance==null){
            instance=new SingleInstance();
        }
        return instance;
    }
}

改进:

public class SingleInstance {
    private SingleInstance() {}
    private  static SingleInstance instance;
    public static synchronized SingleInstance getInstance() {
        if (instance==null){
            instance=new SingleInstance();
        }
        return instance;
    }
}
//或者
public class SingleInstance {
    private SingleInstance() {}
    private  static SingleInstance instance;
    public static  SingleInstance getInstance() {
        synchronized(SingleInstance.class){
            if (instance==null){
                instance=new SingleInstance();
            }
        }
        return instance;
    }
}

synchronized 是比较耗费性能的,我们每次调用这个 getInstance()方法的时候,都会进入 synchronized 包裹的代码块内,即使这个时候单例对象已经生成,不再需要创建对象也会进入 synchronized 内部,造成不必要的同步开销。

双重锁定(DCL模式)(重要)

public class SingleInstance {
    private SingleInstance() {}
    private  static SingleInstance instance;
    public static  SingleInstance getInstance() {
        if (instance==null){
            synchronized(SingleInstance.class){
                if (instance==null){
                    instance=new SingleInstance();
                }
            }
        }
        return instance;
    }
}

看起来已经能够实现懒加载和线程安全了,但是还存在一个问题,那就是没有考虑到 JVM 编译器的指令重排序.

DCL模式会有什么问题?

对象生成实例的过程中,大概会经过以下过程:

  • 1.为对象分配内存空间。
  • 2.初始化对象中的成员变量。
  • 3.将对象指向分配的内存空间(此时对象就不为null)。

由于Jvm会优化指令顺序,也就是说2和3的顺序是不能保证的。在多线程高并发的情况下,当一个线程完成了1、3过程后,当前线程的时间片已用完,这个时候会切换到另一个线程,另一个线程调用这个单例,会使用这个还没初始化完成的实例。

解决方法是使用volatile关键字:

优化后的DCL模式(非常重要)

public class SingleInstance {
	private static volatile SingleInstance instance;//volatile 可以禁止指令重排序
	private SingleInstance() {}
	public static SingleInstance getInstance() {
		if(instance == null) {
			synchronized (SingleInstance.class) {
				if(instance == null) {
					instance = new SingleInstance();
				}
			}
		}
		return instance;
	}
}

上面的分析,会发现懒加载和线程安全是我们自己通过加锁和 volatile 关键字实现的,那么有没有让 JVM 帮我们实现线程安全和懒加载呢?

静态内部类单例(非常重要)

public class SingleInstance {
	private SingleInstance() {}
	public static SingleInstance getInstance() {
		return SingleHolder.instance;
	}
	
	private static class SingleHolder{
		//静态初始化器,由JVM来保证线程安全
		private static final SingleInstance instance = new SingleInstance();
	}
}

首先在 JVM 进行类加载的时候,只是加载了 SingleInstance 类,并不会去执行其中的静态方法,也不会去加载 SingleInstance 内的静态内部类 SingleHolder。所以也就是并不会在初次类加载的时候创建单例对象。
在我们使用getInstance()的时候,我们使用 SingleHolder的静态属性,这个时候会对 SingleHolder 这个静态内部类进行加载,这个时候,就回到了第一种写法 饿汉式中的原理,在类加载的初始化阶段,会对创建单例对象,并且赋值给 INSTANCE 属性。同样,这些操作是发生在类加载阶段的,由 JVM 保证了线程安全,并且是在使用的时候进行加载的,也实现了懒加载。

缺点就是:初始化的时候没法传值给单例类。这个时候就可以使用上面优化后的DCL模式。

枚举单例

public enum SingletonEnum {
	INSTANCE
}

// 获取单例对象

SingletonEnum .INSTANCE// 假如枚举类中有一个方法 getString(),就可以这样调用
SingletonEnum .INSTANCE.getString()

缺点:

不能懒加载
运行时占用内存比非枚举的大很多

2.3.建造者模式

关键点:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示,适用于初始化的对象比较复杂且参数较多的情况。

当一个类的构造函数参数个数超过4个,而且这些参数有些是可选的参数,考虑使用构造者模式。

Retrofit 和 OkHttp 等开源库,大多都是采用的构造者模式去实现的。

如何实现:假设要创建一个Computer对象:

  • 1.在Computer中创建一个静态内部类Builder,然后将Computer中的参数都复制到Builder类中。
  • 2.在Computer中创建一个private的构造函数,参数为Builder类型
  • 3.在Builder中创建一个public的构造方法,参数为Computer中必填的那些参数,cpu和ram。
  • 4在Builder中创建设置方法,对Computer中那些可选参数进行赋值,返回值为Builder类型的实例
  • 5在Builder中创建一个build()方法,在其中构建Computer的实例并返回
public class Computer {
    private final String cpu;//必须
    private final String ram;//必须
    private final int usbCount;//可选
    private final String keyboard;//可选
    private final String display;//可选

    private Computer(Builder builder){
        this.cpu=builder.cpu;
        this.ram=builder.ram;
        this.usbCount=builder.usbCount;
        this.keyboard=builder.keyboard;
        this.display=builder.display;
    }
    public static class Builder{
        private String cpu;//必须
        private String ram;//必须
        private int usbCount;//可选
        private String keyboard;//可选
        private String display;//可选

        public Builder(String cup,String ram){
            this.cpu=cup;
            this.ram=ram;
        }

        public Builder setUsbCount(int usbCount) {
            this.usbCount = usbCount;
            return this;
        }
        public Builder setKeyboard(String keyboard) {
            this.keyboard = keyboard;
            return this;
        }
        public Builder setDisplay(String display) {
            this.display = display;
            return this;
        }        
        public Computer build(){
            return new Computer(this);
        }
    }
  //省略getter方法
}

使用:

Computer computer=new Computer.Builder("因特尔","三星")
                .setDisplay("三星24寸")
                .setKeyboard("罗技")
                .setUsbCount(2)
                .build();

2.4.责任链模式

关键点:将请求的发送者和接收者解耦,使的多个对象都有处理这个请求的机会。
举例:

1.View的事件分发

  • a). 事件收集之后最先传递给 Activity, 然后依次向下传递,大致如下:
Activity> PhoneWindow> DecorView> ViewGroup> ...> View
  • b). 如果没有任何View消费掉事件,那么这个事件会按照反方向回传,最终传回给Activity,如果最后 Activity 也没有处理,本次事件才会被抛弃:
Activity <PhoneWindow <DecorView <ViewGroup <... <View

这是一个非常经典的责任链模式,如果我能处理就拦截下来自己干,如果自己不能处理或者不确定就交给责任链中下一个对象。

2.Okhttp源码中的责任链模式

OkHttp 的拦截器就是基于责任链模式,每个节点有自己的职责,同时可以选择是否把任务传递给下一个环节

2.1 Inteceptor

主要方法 Intercept。会传递一个 Chain 对象过来,可以在 Chain 在执行 proceed 的前后添加代码。

2.2 Chain

主要方法 proceed。OkHttp 的唯一实现类是 RealInterceptorChain。内部维护了所有要执行的拦截器列表,在 proceed 内部会唤醒下一个 Interceptor ,调用 intercept 来进行下一步:

public Response proceed(Request request, StreamAllocation streamAllocation, HttpStream httpStream,
    Connection connection) throws IOException {
  ...
  RealInterceptorChain next = new RealInterceptorChain(
      interceptors, streamAllocation, httpStream, connection, index + 1, request);
  Interceptor interceptor = interceptors.get(index);
  Response response = interceptor.intercept(next);
  ...
  return response;
}

可以看到,RealInterceptorChain的process方法中,会生成一个RealInterceptorChain对象,且注意到index+1,即生成下一个Chain对象,并且同时获取拦截器集合里的下一个拦截器,调用它的intercept,将下一个Chain(next)作为参数传给他去处理,回顾到刚才上面说的,拦截器的interpect里面调用了chain的process,也就是说,每一个拦截器都会持有下一个拦截器的chain对象,并通过chain的process方法,触发RealInterceptorChain里的index下标再+1,从而串联起整个拦截链。

2.5.观察者模式

关键点:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。(被观察者可以添加一堆观察者,被观察着发生变化,观察者做出响应)

Android中我们遇到的最常用的观察者模式

1.各种控件的监听,如下:

 //注册观察者
 button.setOnClickListener(new View.OnClickListener() {
        //观察者实现
        @Override
        public void onClick(View arg0) {
            Log.d("test", "Click button ");
        }
    });

button就是被观察者;new出来的View.OnClickListenerd对象就是具体的观察者。在这里OnClickListener是个接口,也就是抽象观察者;通过setOnClickListener把观察者注册到被观察者中。

一旦button捕获的点击事件就会通过回调注册的OnClickListener观察者的onClick方法会来通知观察者。

2.Adapter的notifyDataSetChanged()方法

当我们使用ListView时,需要更新数据时我们就会调用Adapter的notifyDataSetChanged()方法,那么我们来看看notifyDataSetChanged()的实现原理,这个方法是定义在BaseAdaper中,具体代码如下:

public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
     //数据集被观察者
    private final DataSetObservable mDataSetObservable = new DataSetObservable();

    //注册观察者
    public void registerDataSetObserver(DataSetObserver observer) {
        mDataSetObservable.registerObserver(observer);
    }
    //注销观察者
    public void unregisterDataSetObserver(DataSetObserver observer) {
        mDataSetObservable.unregisterObserver(observer);
    }
    //数据集改变时,通知所有观察者
    public void notifyDataSetChanged() {
        mDataSetObservable.notifyChanged();
    }
}
    //其他代码略

上面的代码可以看出BaseAdapter实际上就是使用了观察者模式,BaseAdapter就是具体的被观察者。接下来看看 mDataSetObservable.notifyChanged()的实现:
//数据集被观察者

public class DataSetObservable extends Observable<DataSetObserver> {
   
    public void notifyChanged() {
        synchronized(mObservers) {
            //遍历所有观察者,并调用他们的onChanged()方法
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onChanged();
            }
        }
    }
    //其他代码略
}

AdapterDataSetObserver类中的onChanged()方法没看出啥,继续看他父类的onChanged()方法:

class AdapterDataSetObserver extends DataSetObserver {
        private Parcelable mInstanceState = null;
        //观察者的核心实现
        @Override
        public void onChanged() {
            mDataChanged = true;
            mOldItemCount = mItemCount;
            mItemCount = getAdapter().getCount();//获取Adapter中的数据的数量
            if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
                    && mOldItemCount == 0 && mItemCount > 0) {
                AdapterView.this.onRestoreInstanceState(mInstanceState);
                mInstanceState = null;
            } else {
                rememberSyncState();
            }
            checkFocus();
            //重新布局
            requestLayout();
        }

       //其他代码略
    }

最终就是在AdapterDataSetObserver这个类里面的**onChanged()**方法中实现了布局的更新。

简单总结:

当ListView的数据发生变化时,我们调用Adapter的notifyDataSetChanged()方法,这个方法又会调用所有观察者(AdapterDataSetObserver)的onChanged()方法,onChanged()方法又会调requestLayout()方法来重新进行布局。

3. BroadcastReceiver

4.RxJava、RxAndroid、EventBus、otto等等,也是使用了观察者模式。

2.6.代理模式

为其他的对象提供一种代理以控制对这个对象的访问。适用于当无法或不想直接访问某个对象时通过一个代理对象来间接访问,为了保证客户端使用的透明性,委托对象与代理对象需要实现相同的接口。

静态代理:
静态代理很好理解就是我们需要编写一个代理类。实现我们需要代理的所有方法。所以称之为静态代理。

动态代理:
在java的动态代理机制中,有两个重要的类或接口

一个是 InvocationHandler(Interface),另一个则是Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的

Proxy这个类的 newProxyInstance 这个方法:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

oader:一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
interfaces:一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上。

InvocationHandler:

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
proxy:指代生成的代理对象;
method:指代的是我们所要调用真实对象的某个方法的Method对象;
args:指代的是调用真实对象某个方法时接受的参数;
每一个代理实例类的InvocationHandler 都要实现InvocationHandler这个接口。并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的invoke 方法来进行调用

示例:

// 定义相关接口
public interface BaseInterface {
    void doSomething();
}

// 接口的相关实现类
public class BaseImpl implements BaseInterface {
    @Override
    public void doSomething() {
        System.out.println("doSomething");
    }
}

public static void main(String args[]) {
    BaseImpl base = new BaseImpl();
    // Proxy 动态代理实现
    BaseInterface proxyInstance = (BaseInterface) Proxy.newProxyInstance(base.getClass().getClassLoader(), base.getClass().getInterfaces(), new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (method.getName().equals("doSomething")) {
                method.invoke(base, args);
                System.out.println("do more");
            }
            return null;
        }
    });

    proxyInstance.doSomething();
}

2.7.策略模式

关键点:定义了一系列的算法,并封装起来,提供针对同一类型问题的多种处理方式。
应用场景

同一个问题具有不同算法时,即仅仅是具体的实现细节不同时,如各种排序算法等等。
对客户隐藏具体策略(算法)的实现细节,彼此完全独立;提高算法的保密性与安全性。
一个类拥有很多行为,而又需要使用if-else或者switch语句来选择具体行为时。使用策略模式把这些行为独立到具体的策略类中,可以避免多重选择的结构。

Android中的源码分析:用的ListView时都需要设置一个Adapter,而这个Adapter根据我们实际的需求可以用ArrayAdapter、SimpleAdapter等等,这里就运用到策略模式。

   listView = (ListView)findViewById(R.id.list_view);
    
    //使用ArrayAdapter
    listView.setAdapter(new ArrayAdapter<String>(this,R.id.item,
    					new String[] {"one","two"}));
    
     //使用BaseAdapter
    listView.setAdapter(new BaseAdapter() {
        @Override
        public int getCount() {
            return 0;
        }

        @Override
        public Object getItem(int position) {
            return null;
        }
        .....
    });

相关源码:

 public class ListView extends AbsListView {//相当于环境类
        @Override
        public void setAdapter(ListAdapter adapter) {//设置策略,即adapter
            //其他代码略
        }
    }

    public interface ListAdapter extends Adapter {//抽象策略接口
        
    }
    //具体策略类BaseAdapter,实现ListAdapter接口
    public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
        
    }
    //具体策略类ArrayAdapter,继承BaseAdapter,即实现ListAdapter接口
    public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSpinnerAdapter {
        
    }

通过设置不同的Adapter(即不同的策略),我们就可以写出符合我们需求的ListView布局。
另外,动画中的插值器(ValueAnimator 的 setInterpolator 方法)也是有运用到策略模式。

2.8.工厂模式

工厂模式分为三种 :简单工厂模式 、工厂方法模式 、抽象工厂模式 。

简单工厂模式:

	//抽象产品类 
    public abstract class Product {
        public abstract void show();
    }
  //具体产品类A 
    public class ProductA extends Product {
        @Override
        public void show() {
            System.out.println("product A");
        }
    }
    //具体产品类B
    public class ProductB extends Product {
        @Override
        public void show() {
            System.out.println("product B");
        }
    }
  //具体产品类A 
    public class ProductA extends Product {
        @Override
        public void show() {
            System.out.println("product A");
        }
    }
    //具体产品类B
    public class ProductB extends Product {
        @Override
        public void show() {
            System.out.println("product B");
        }
    }
//创建工厂类,创建具体的产品:
public class Factory {
    public static Product create(String productName) {
        Product product = null;
        //通过switch语句控制生产哪种商品
        switch (productName) {
            case "A":
                product = new ProductA();
                break;
            case "B":
                product = new ProductB();
                break;
        }
        return product;
    }
}
 public void test() {
        Factory.create("A").show();//生产ProductA
        Factory.create("B").show();//生产ProductB
        try {
            Factory.create("C").show();//生产ProductC
        } catch (NullPointerException e) {
            System.out.println("没有ProductC");//没有ProductC,会报错
        }
    }

缺点

  • 1.违背开放封闭原则,若需添加新产品则必须修改工厂类逻辑,会造成工厂逻辑过于复杂。

  • 2.简单工厂模式使用了静态工厂方法,因此静态方法不能被继承和重写。

  • 3.工厂类包含了所有实例(产品)的创建逻辑,若工厂类出错,则会造成整个系统都会会受到影响。
    工厂方法模式与简单工厂模式比较

  • 1.工厂方法模式有抽象工厂类,简单工厂模式没有抽象工厂类且其工厂类的工厂方法是静态的。

  • 2.工厂方法模式新增产品时只需新建一个工厂类即可,符合开放封闭原则;而简单工厂模式需要直接修改工厂类,违反了开放封闭原则。
    工厂方法模式

创建抽象产品类,定义公共接口:

  //抽象产品类
    public abstract class Product {
        public abstract void show();
    }

创建具体产品类,继承Product类:

//具体产品类A 
public class ProductA extends Product {
    @Override
    public void show() {
        System.out.println("product A");
    }
}
//具体产品类B
public class ProductB extends Product {
    @Override
    public void show() {
        System.out.println("product B");
    }
}

创建抽象工厂类,定义公共接口:

//抽象工厂类
public abstract class Factory {
    public abstract Product create();
}

创建具体工厂类,继承抽象工厂类,实现创建具体的产品:

  //具体工厂类A
    public class FactoryA extends Factory {
        @Override
        public Product create() {
            return new ProductA();//创建ProductA
        }
    }
    //具体工厂类B
    public class FactoryB extends Factory {
        @Override
        public Product create() {
            return new ProductB();//创建ProductB
        }
    }
 public void test() {
        //产品A
        Factory factoryA = new FactoryA();
        Product productA = factoryA.create();
        productA.show();
        //产品B
        Factory factoryB = new FactoryB();
        Product productB = factoryB.create();
        productB.show();
    }

应用场景
生成复杂对象时,无需知道具体类名,只需知道相应的工厂方法即可。
优点

符合开放封闭原则。新增产品时,只需增加相应的具体产品类和相应的工厂子类即可。
符合单一职责原则。每个具体工厂类只负责创建对应的产品。

Android中的ThreadFactory就是使用了工厂方法模式来生成线程的,线程就是ThreadFactory的产品。

ThreadFactory相关源码分析

   //抽象产品:Runnable
    public interface Runnable {
        public abstract void run();
    }
    
    //具体产品:Thread
    public class Thread implements Runnable {
        //构造方法
        public Thread(Runnable target, String name) {
            init(null, target, name, 0);
        }
        
        @Override
        //实现抽象产品的抽象方法
        public void run() {
            if (target != null) {
                target.run();
            }
        }
        
        //其他代码略
    }
    
    
    //抽象工厂:ThreadFactory
    public interface ThreadFactory {
        Thread newThread(Runnable r);
    }
    
    //具体工厂:AsyncTask中的实现
    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);
        
        //实现抽象工厂的抽象方法
        public Thread newThread(Runnable r) {
            return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());//返回Thread这个产品
        }
    };
通过ThreadFactory,我们可以创建出不同的Thread来。
同样,我们可以创建另外类似的工厂,生产某种专门的线程,非常容易扩展。

抽象工厂模式

创建抽象产品类

//抽象产品类-- CPU
public abstract class CPU {
    public abstract void showCPU();
}
//抽象产品类-- 内存
public abstract class Memory {
    public abstract void showMemory();
}
//抽象产品类-- 硬盘
public abstract class HD {
    public abstract void showHD();
}

创建具体产品类

    //具体产品类-- Intet CPU
    public class IntelCPU extends CPU {

        @Override
        public void showCPU() {
            System.out.println("Intet CPU");
        }
    }
    
    //具体产品类-- AMD CPU
    public class AmdCPU extends CPU {

        @Override
        public void showCPU() {
            System.out.println("AMD CPU");
        }
    }

    //具体产品类-- 三星 内存
    public class SamsungMemory extends Memory {

        @Override
        public void showMemory() {
            System.out.println("三星 内存");
        }
    }
    
    //具体产品类-- 金士顿 内存
    public class KingstonMemory extends Memory {

        @Override
        public void showMemory() {
            System.out.println("金士顿 内存");
        }
    }

    //具体产品类-- 希捷 硬盘
    public class SeagateHD extends HD {

        @Override
        public void showHD() {
            System.out.println("希捷 硬盘");
        }
    }

    //具体产品类-- 西部数据 硬盘
    public class WdHD extends HD {

        @Override
        public void showHD() {
            System.out.println("西部数据 硬盘");
        }
    }

创建抽象工厂类:定义工厂中用来创建不同产品的方法:

//抽象工厂类,电脑工厂类
public abstract class ComputerFactory {

    public abstract CPU createCPU();

    public abstract Memory createMemory();

    public abstract HD createHD();
}
 public void test() {
        System.out.println("--------------------生产联想电脑-----------------------");
        ComputerFactory lenovoComputerFactory = new LenovoComputerFactory();
        lenovoComputerFactory.createCPU().showCPU();
        lenovoComputerFactory.createMemory().showMemory();
        lenovoComputerFactory.createHD().showHD();

        System.out.println("--------------------生产华硕电脑-----------------------");
        ComputerFactory asusComputerFactory = new AsusComputerFactory();
        asusComputerFactory.createCPU().showCPU();
        asusComputerFactory.createMemory().showMemory();
        asusComputerFactory.createHD().showHD();
        
        System.out.println("--------------------生产惠普电脑-----------------------");
        ComputerFactory hpComputerFactory = new HpComputerFactory();
        hpComputerFactory.createCPU().showCPU();
        hpComputerFactory.createMemory().showMemory();
        hpComputerFactory.createHD().showHD();
    }
--------------------生产联想电脑-----------------------
Intet CPU
三星 内存
希捷 硬盘
--------------------生产华硕电脑-----------------------
AMD CPU
金士顿 内存
西部数据 硬盘
--------------------生产惠普电脑-----------------------
Intet CPU
金士顿 内存
西部数据 硬盘

应用场景

生产多个产品组合的对象时。

优点

代码解耦,创建实例的工作与使用实例的工作分开,使用者不必关心类对象如何创建。

缺点

如果增加新的产品,则修改抽象工厂和所有的具体工厂,违反了开放封闭原则

工厂方法模式与抽象工厂模式比较

在工厂方法模式中具体工厂负责生产具体的产品,每一个具体工厂对应一种具体产品,工厂方法具有唯一性。
抽象工厂模式则可以提供多个产品对象,而不是单一的产品对象。

2.9.适配器模式

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/547963.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于Zynq的雷达10Gbps高速PCIE数据采集卡方案(一)总体设计

2.1 引言 本课题是来源于雷达辐射源识别项目&#xff0c;需要对雷达辐射源中频信号进行采集传输 和存储。本章基于项目需求&#xff0c;介绍采集卡的总体设计方案。采集卡设计包括硬件设计 和软件设计。首先对采集卡的性能和指标进行分析&#xff0c;接着提出硬件的总体设计…

OPCUA 聚合服务器和历史数据服务器

前言 开放自动化是一个热门话题&#xff0c;自动化XML&#xff08;AutomationML&#xff09;&#xff0c;基于信息模型的通信协议&#xff08;OPC UA&#xff09;和工业4.0 管理壳&#xff08;ASS&#xff09; 可谓是开放自动化的三套件。三者相互交叉&#xff0c;相互引用&…

教你精通Java语法之第十五章、Lambda表达式

目录 一、背景 1.1Lambda表达式的语法 1.2函数式接口 二、Lambda表达式的基本使用 2.1语法精简 三、变量捕获 3.1匿名内部类 3.2匿名内部类的变量捕获 3.3Lambda的变量捕获 四、Lambda在集合当中的使用 4.1Collection接口 4.2 List接口 4.3Map接口 五、总结 一、背…

2023国赛tomcat题

环境: 10.10.120.128 安装 tomcaA 10.10.120.129 安装tomcatB 10.10.120.130 安装 nginx 配置dns: 正向解析 反向解析 Tomcat ssl配置 [root@localhost ~]# tar -zxvf jdk-11.0.8_linux-x64_bin.tar.gz [root@localhost ~]# mv jdk-11.0.8 /usr/local/ Vim /etc/profile …

如何在代码开发中便捷使用 ChatGPT 协助开发

在五一节前后&#xff0c;抽空测试了下网上推荐的开发环境和开发插件&#xff0c;在这里推荐给前后端代码开发的同胞们。 方法一&#xff1a;IDEA 安装 插件 bito 习惯使用 IDEA 开发代码的同胞&#xff0c;可以尝试直接在 IDEA 中安装插件 bito&#xff0c;注意操作过程中需要…

jarvisoj_fm

小白做题垃圾笔记&#xff0c;不建议阅读。 1.32位操作系统&#xff0c;有canary&#xff0c;和NX &#xff0c;本来以为是绕过canary呢。 2.第12行&#xff0c;如果x4就执行/bin/sh ,第10行存在格式化字符串漏洞。 是格式化字符串漏洞&#xff0c;对任意地址内容更改。 3.确定…

实操Linux 部署单机版nacos

目录 Nacos部署环境要求JDK安装下载和构建Nocas控制面板&#xff0c;新建配置Nacos数据库配置 总结 Nacos部署环境要求 64bit OS: Linux/Unix/Mac/Windows 支持, Linux/Unix/Mac 推荐 64bit JDK 1.8 Maven 3.2.x JDK安装 1、准备好jdk1.8 链接&#xff1a;https://pan.baidu.…

【SpringCloud组件——Eureka】

前置准备&#xff1a; 分别提供订单系统&#xff08;OrderService&#xff09;和用户系统&#xff08;UserService&#xff09;。订单系统主要负责订单相关信息的处理&#xff0c;用户系统主要负责用户相关信息的处理。 一、微服务当中的提供者和消费者 1.1、概念 服务提供…

第二章 逻辑分类模型

目录 一、逻辑回归基本模型二、处理多维特征输入三、加载数据集四、多分类问题 一、逻辑回归基本模型 基本模型&#xff1a; y ^ σ ( x ∗ ω b ) \hat{y} \sigma (x * \omega b) y^​σ(x∗ωb)&#xff0c;其中 σ ( ) \sigma() σ() 表示 sigmod 函数 σ ( x ) 1 1…

LMS,RGB,XYZ色彩空间转换

前言 首先需要指明本文中描述的R,G,B并非通常的sRGB中的三个分量R,G,B&#xff0c;而是波长分别为700nm&#xff0c;546.1nm&#xff0c;435.8nm的单色红光&#xff0c;单色绿光&#xff0c;单色蓝光。sRGB中的RGB中的红色、绿色、蓝色已经不是单色光了。虽然习惯上大家都叫RGB…

文法的FIRST集

求文法的FIRST集和FOLLOW集的步骤如下&#xff1a; 求FIRST集 如果符号是终结符&#xff0c;则FIRST集合为该终结符本身。如果符号是非终结符&#xff0c;则将该非终结符可以推导出的所有串的FIRST集合合并&#xff0c;得到该非终结符的FIRST集合。如果一个非终结符可以推导出…

Linux篇5

Shell常用命令 1. 磁盘管理类1.1 du&#xff1a;查看文件和目录占用的磁盘空间1.2 df&#xff1a;查看磁盘空间使用情况1.3 free&#xff1a;查看内存使用情况1.4 lsblk&#xff1a;查看设备挂载情况1.5 mount/umount&#xff1a;挂载/卸载1.6 fdisk&#xff1a;分区 2. 进程管…

双模齐下,提质增效:CODING 携手知微共创 BizDevOps 体系新篇章

为了提升工作和管理效率&#xff0c;工具建设是许多企业不得不面对的现实&#xff0c;然而在工具建设落地过程中&#xff0c;往往存在一系列的问题。如不同组织、部门之间互不相通&#xff0c;各自为政&#xff0c;工具流程与实际工作所需不符&#xff0c;导致工具建设的结果是…

(1分钟速览)g2o入门指南--笔记版

在slam后端中&#xff0c;优化的框架很多&#xff0c;有ceres&#xff0c;g2o&#xff0c;gtsam这些。要想真正掌握slam后端的优化内容&#xff0c;这些框架是必不可少的上手练习的内容。本文则介绍有关g2o的相关内容&#xff0c;作为一个入门指南&#xff0c;目标&#xff1a;…

使用教程 | 基于TSMaster如何实现LIN RBS 剩余总线仿真

本文导读 RBS 全称是&#xff1a;residual bus simulation&#xff0c;也就是所谓的剩余总线仿真。主要是基于车载网络数据库&#xff0c;如 CAN/LIN/FlexRay/以太网数据库&#xff0c;仿真该网络内部各个节点的通讯行为。 本文主要讲解 TSMaster 中 LIN RBS 的操作流程。 “ …

aosp11/12/13 壁纸高斯模糊,毛玻璃SurfaceFlinger层面原理-第二节千里马framework实战

hi&#xff0c;粉丝朋友们&#xff01; 上一个blog已经详细讲解了系统中自带相关接口实现窗口的高斯模糊相关效果&#xff0c;具体点击这里 https://blog.csdn.net/learnframework/article/details/130767893 1、补充app层面实现方式 更多framework干货知识手把手教学 Log.i…

Restful API

Restful API 一、RESTful API是什么意思二、对比三、小知识四、用 apifox 进行测试1. GET2. POST3. PUT4. DELETE 一、RESTful API是什么意思 REST 与技术无关&#xff0c;代表的是一种软件架构风格&#xff0c;REST是Representational State Transfer的简称&#xff0c;中文翻…

操作系统实验三 内存分配及回收研究

前言 本次实验跟前两次相比简单许多&#xff0c;主要是体会底层的一些运行机制。其实&#xff0c;要说简单&#xff0c;也不是真的很简单&#xff0c;毕竟还是存在一些更底层的东西需要我们去探讨。接下来就让我们通过实验来感受一下。 1.实验目的 掌握Linux操作系统的内存分…

地表水灌区取水计量设施包含哪些设备?

方案背景 根据《关于强化取水口取水监测计量的意见》、《十四五”节水型社会建设规划》以及《2022年水资源管理工作要点》等政策要求&#xff0c;为强化水资源管理&#xff0c;做好水资源税改革&#xff0c;构建节水型社会&#xff0c;要全面加强取水计量监测设施建设&#xff…

【计算机系统基础3】数据的存储与运算

3.程序调试与实践&#xff1a;数据存储与运算 3.1真值与机器数 真值&#xff1a; 数据在现实世界中的表示 机器数&#xff1a; 数据在计算机内部的二进制编码表示 温度&#xff1a;零下3.5度 习惯写法&#xff1a;-3.5 (数据的真值/数据的实际值) 3.1.1整数的编码 带符号整数&…