92年的程序员终究没能逃脱掉"全员降薪",临近35岁,职业生涯比较担忧,是转行呢还是继续干个两三年再说呢???
先整理刷一些题目记录下来吧。。。
一.如何优化 Android应用的功耗?
优化Android应用的功耗可以从几个方向入手
1.尽量减少后台服务和优化后台服务,减少不必要的后台服务
后台服务对电量消耗比较大,尤其是在设备休眠的状态下。如果必须要使用后台服务,尽量使用JobScheduler或WorkManager来管理后台任务,另外还可以使用JobIntentServiced组件减少功耗
2.减少网络请求的频率和次数,合并小的网络请求
网络操作非常耗电,尤其是在弱网络的情况下,尽量批量的发起网络请求,减少网络的连接次数,这样合并处理可以减少电量的消耗,同时okhttp也是采用HTTP/2协议,再次优化网络性能,网络请求的缓存机制设计得当,可以减少重复请求
3.优化电量消耗高的组件:减少GPS,WIFI,蓝牙等高功耗硬件
当用户不需要定位是,务必关闭定位服务,可以使用Location API来优化定位请求,如使用FusedLocationProviderClient代替传统的位置管理器
4.避免主线程做太多的耗时操作,将耗时操作放在线程池中
主线程应负责UI渲染工作,应避免任何费时操作,可以考虑RxJava做耗时操作
二.Android中 Dalvik 虚拟机 和 ART 虚拟机是什么?两者有什么区别?
他们都是Android系统的运行环境
1.Dalvik 虚拟机 是Android 4.4以前的运行时环境, ART 虚拟机是Android 5.0开始推出的新的运行模式
2.Dalvik 虚拟机 基于JIT编译模式,只有运行时才会编译所需代码 ,每次启动时需要重新编译代码,所以启动速度相对比较慢,ART 虚拟机是AOT的编译模式,应用在安装时,将全部的字节码编译成机器码,启动速度快,减少了内存的使用
三.如何避免Android的内存泄漏
1.使用Application context代替activity context,因为activity context的生命周期与activity绑定,使得大量资源无法及时回收而引起的内存泄漏问题
2.避免持用生命周期长于组件本身的对象引用而造成的内存泄漏,在有可能长时间持用对象引用但又不希望垃圾回收时,可以使用弱引用和软引用来减少内存泄漏的风险
3.使用生命周期的感知组件如LiveData和ViewModel,他们可以在适当的情况下清理资源减少泄漏风险
4.避免非静态内部类持用外部类的引用,可以将内部类声明静态或者将外部类的弱引用来解决
5.Java垃圾回收机制只会回收没有被引用的对象,如果对象占用大量的内存却仍被引用,及时没有用也不会回收这些对象,这就是内存泄漏
6.在activity中的onDestory方法中及时清理被引用的对象,如 bitmap cursor 相机等
7.启用后台线程或异步任务时,如果没有正确处理生命周期管理,将会导致内存泄漏,可以使用handle或ThreadPoolExecutor等来处理这些问题,并结合声明周期进行管理
8.可以使用android studio自带的Memory Profiler或三方开源的LeakCanary检测内存泄漏问题,
四.什么是Hook?怎么使用?有什么风险?
1.Hook是一种技术手段,在运行时可以修改代码的行为,在不修改原始源码的情况下,拦截并替代方法的调用,修改返回结果等
2.常见的Hook就是 Xposed 框架, 如何使用Xposed 框架
- 安装 Xposed 框架,需要有root权限的设备
- 创建一个android工程,并在其编写hook代码,通常使用IXposedHookLoadPackage 接口来加载特性的应用或系统的类和方法
- 将编写的模块打包apk,安装并在Xposed Installer激活
- 重启设备激活hook模块,生效后就可以看到hook的运行效果
3.Hook技术非常强大,但也存在一定的风险
安全风险:不当的使用会导致敏感信息泄漏,产生安全漏洞风险
兼容性风险:导致应用或系统不稳定,甚至崩溃
法律风险:修改他人应用或系统有可能违反相关使用条款和服务协议
Hook扩展
除了Xposed框架,还有Frida跨平台工具,不仅支持android,还支持ios和windows等
也可以使用反射机制替代Hook的方案,虽然反射可以动态调用方法,但没有修改方法的实现能力
五.在 Android 中什么是 ANR?如何避免?
ANR 是应用在一段时间没有响应用户输入,会弹出应用无响应的提示框
怎么避免 ANR :
1.避免在主线程做耗时操作,将一些耗时操作放入异步中使用
2.Handler和Looper机制:了解 Handler 和 Looper 的机制是避免 ANR 的关键。主线程管理 UI 更新,合理使用 Handler 可以将耗时任务放到子线程执行,然后通过 Handler 将结果传回主线程更新 Ul。Looper 是消息循环,主线程通过它来持续处理消息队列中的消息。
3.ANR日志分析:每当应用发生 ANR 时,系统会生成一个 trace 文件。这个文件记录了与 ANR 相关的线程堆栈信息,可以帮助找到导致 ANR 的根本原因,从而进行针对性的优化。在 Android Studio 中调试时,也可以查看 Logcat 中的相关信息。
4.使用线程池管理线程,有效避免过多线程创建而导致性能问题
5.UI布局优化:复杂布局可能导致U绘制时间过长。优化 XML 布局文件,减少嵌套,使用 ConstraintLayout 之类的高效布局组件,可以减少布局绘制时间,提高 UI 响应速度
六.如何优化Android应用的启动速度
1.减少Application类中的执行量.非必要可以拖延在应用启动之后初始化
2.使用异步初始化:对于不需要在主线程上执行的初始化任务,使用线程异步
3.优化和管理你的启动 Activity 中的布局,减少布局的复杂性来提高启动速度
4.使用启动优化工具Android Profiler、Systrace 等工具找出并优化启动瓶颈
5.优化资源加载:提前加载静态资源,减少非必要资源的加载时间。
扩展
1.延迟初始化:一些服务组件可以延迟到真正需要的时候初始化,不是在启动的时候初始化
2.多进程优化:可以将一些操作放在一个单独的进程中,这样即便比较耗时也不影响主进程的启动速度,如service可以在单独的进程中运行
3.优化布局:使用约束布局代替过多的线性布局和相对布局,减少绘制速度,尽量减少和优化view的数量,减少ui布局的复杂性
4.减少应用的体积:使用ProGuard 或 R8 工具优化代码结构,删除无用的代码和资源,减少apk的体积,有助于减少启动时间
5.使用Android Profiler分析工具,检测cpu的使用率,内存占用等找到需要优化的地方
七.如何优化Android布局
1.减少嵌套层级:使用ConstraintLayout 代替RelativeLayout和LinearLayout ,减少 View 的嵌套层级,有助于提高布局的绘制速度,
2.ViewStub 和 include 标签:通过 include 标签复用布局,使用 Viewstub 延迟加载不频繁出现的 U 元素,提升应用初始加载速度。
3.RecyclerView:对于大数据列表,尽量使用 RecyclerView 代替 Listview 或Gridview,避免常见的性能问题。
4.对布局进行 Profile:开发工具 Android Profiler 提供了性能监视和分析的功能,可以使用 Layout Inspector 检查布局层级,找出冗余或低效的部分。
八.如何避免webview内存泄漏
1.在activity或fragment的onDestroy方法中调用webview.destroy()释放webview资源
2.调用webview.destroy()前现在父容器移除webview,调用removeView(webview)
3.使用全局的applicationcontext作为上下文,减少与activity生命周期的耦合
4.activity避免使用匿名内部类和非静态内部类的引用,减少内存泄漏
九.在Android中实现生产者-消费者模式?
可以使用java的BlockingQueue类,BlockingQueue是一个线程安全的队列,他的put和take方法分别用于生产者和消费者
实现的步骤
1.定义一个BlockingQueue类
2.创建生产者线程,将数据放入队列中
3.创建消费者线程,从队列中取出数据并处理、
扩展
1.BlockingQueue不同实现:
- ArrayBlockingQueue:这是一个有界队列,内部实现是一个数组
- LinkedBlockingQueue:可选界限的队列,内部实现是一个链表
- PriorityBlockingQueue:优先级排序的无界队列
2.线程池
可以使用ExecutorService来更高效地管理创建和销毁线程,
3.线程管理
可以使用HandlerThread管理后台线程,是专门处理后台任务的线程,可以结合Handler和Looper处理任务后并在主线程更新UI
4.Synchronization
BlockingQueue尽量内部处理了同步问题,但是更复杂的情况下我们需要自己来处理同步问题,可以使用Synchronization
十.Handler的工作原理及使用场景
Handler机制是Android中实现线程间通信的框架,
message:消息对象,需要传递数据
Handler:处理消息的工具类,负责将消息放入MessageQueued队列中,
MessageQueue:消息队列,handler负责将放在其中,Looper从中取出消息信息,然后分发
Looper:Looper从消息队列中取出消息信息,然后分发,每个线程只有一个并且只有一个
使用长场景
1.子线程做耗时操作在通过Handler更新UI
2.通过handler实现延迟操作或周期性任务
handler.postDelayed(new Runnable() {
@Override
public void run() {
// 一段时间后执行的任务
}
}, 1000); // 延迟时间为 1000 毫秒(1秒)
3.线程间的通信
// 主线程的Handler
Handler mainHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
// 处理UI更新等操作
}
};
// 工作线程的Handler
Handler workerHandler = new Handler(Looper.getMainLooper());
// 工作线程
Thread workerThread = new Thread(new Runnable() {
@Override
public void run() {
// 执行耗时任务
// 发送消息到主线程
Message message = Message.obtain();
// 可以携带额外数据
message.obj = "Hello from worker thread";
mainHandler.sendMessage(message);
}
});
workerThread.start();
十一.线程中的通讯有哪些
1.handler
依托于Looper的消息循环机制,子线程和主线程传递消息
2. AsyncTask
用于后台线程完成任务后将结果传递给UI线程
3. 使用第三方库
如 RxJava、EventBus 或 LiveData 也是非常流行的线程间通信方式。-例如,RxJava 提供了丰富的操作符和调度器,可以灵活处理异步任务和UI更新。
十二.进程间的通信有哪些
1.Binder
Binder 是 Android 中最常用的 IPC 机制,几乎所有主要的系统服务都是通过 Binder 实现的。
优点: 高效、稳定、安全,支持同步和异步调用。
缺点: 实现起来相对复杂,需要处理线程同步、错误处理等问题
2.AIDL
当需要在不同应用程序之间进行复杂的数据交换时,AIDL可以生成接口来提供进程间的通信
优点: 强类型接口设计,适用于复杂的数据通信。
缺点: 开发复杂,维护成本高。
3.ContentProvider内容提供者
专门用于管理和共享应用间的数据,例如访问联系人、媒体文件等。
优点:易于数据封装和维护,支持 SQL-like 查询。
缺点:主要用于数据共享,实时性和同步能力不如 Binder
4.Socket:
应用场景:适用于需要基于网络协议(如 TCP/IP)进行通信的场景
优点: 可以跨进程、跨设备进行通信,适合大规模数据传输
缺点: 实现复杂,需要处理网络通信的各种异常情况
广播可以实现线程或进程的通信
补充:进程的生命周期:前台进程>可见进程>服务进程>后台进程>空进程
进程保活的方式:
1.双进程守护
2.广播
3.利用系统提供的调度器,定时唤醒进程
十三.线程池的实现和优势
可以通过 Java 提供的 ExecutorService来实现
具体步骤如下:
1.创建一个 ExecutorService 实例。可以使用 Executors工具类的静态工厂方法,例如 newFixedThreadPool、newsingleThreadExecutor 等2.提交任务给线程池执行。可以使用 Executorservice 的submit 或 execute 方法。
3.关闭线程池,使用 shutdown 方法。
优势
减少创建和销毁的开销,提高系统的响应速度,更好的管理线程
shutdown:关闭线程池,等待提交的任务完成,不在接受新的任务
shutdownNow:强制关闭线程池,并试图停止所有需要等待的任务
十四.实现 Parcelable 接口有什么用处?
主要作用于在不同的Android组件(Activity、Service、 BroadcastReceiver)进行快速且高效的数据传输,
Parceable和Serializable的区别?
两者最大的区别在于存储媒介的不同,Serializable使用IO读写存储在硬盘上,而Parcelable是直接在内存中读写,很明显内存的读写速度通常大于IO读写,所以在Android中通常优先选择Parcelable。
1.Serializable是Java提供的序列化接口,Parcelable是Android提供的序列化接口。Android中设计Parcelable的初衷是因为Serializable太慢。为了在程序内不同组件间高效的传输数据而设计,这些数据仅在内存中存在,Parcelable是通过IBinder通信的消息的载体。
2.如果想实现数据持久化保存使用Serializable。Serializable是序列化到硬盘上。Serializable序列化实际上是用到了反射技术,反射会产生大量的临时对象,进而引起频繁的GC。
3.Parcelable方式的本质是将一个完整的对象进行分解,而分解后的每一部分都是Intent所支持的类型,这样就实现了传递对象的功能了。Serializable使用IO读写存储在硬盘上,而Parcelable是直接在内存中读写,很明显内存的读写速度通常大于IO读写,所以在Android中通常优先选择Parcelable。
十五.事件总线机制为什么可以代替广播
性能更优
- 广播机制利用的是系统的 Binder 机制,会涉及到序列化与反序列化操作,消耗较大。
- 事件总线通常是基于内存中的函数调用,速度更快,避免了广播机制带来的系统资源消耗。
灵活性高
- 广播的处理比较被动,容易受到系统组件(如 Activity、Service 等)的生命周期影响,使用不当时容易导致内存泄漏
- 事件总线允许在任意地方订阅和发布事件,且能够为事件订阅者指定线程模型,如在主线程、后台线程等执行。
解耦更加彻底
- 广播机制要求广播发送者与接收者有一定的联系,比如需要在 AndroidManifest.xml或者代码中声明 intent-filter 。
- 使用事件总线,发布者和订阅者之间不存在直接联系,通过总线(Bus)将其解耦,增强了代码的可维护性和扩展性。
代码更简洁
- 广播注册和反注册的操作较为繁杂,特别是在复杂应用中,处理起来比较麻烦。
- 事件总线可以通过简单的注解方式来实现注册和订阅,使得代码量大大减少,逻辑更加清晰。
常用事件总线库
- EventBus: 由 greenrobot 提供,最常用的事件总线库之一,功能丰富,性能强大。
- RxJava:虽然不是单纯的事件总线库,但是利用其发布-订阅模式的特性,同样可以实现事件总线的功能。
- LiveData: Android 官方的架构组件之一,同样可以作为事件总线来使用,特别在MVVM 架构中非常有用。
十六.Android中 JobScheduler 机制
JobScheduler是 Android 5.0 引入的一种任务调度系统。它专门用于在特定条件满足时执行任务,比如在设备插入充电器、连接到 Wi-Fi、设备空闲时等。这种机制非常适合处理后台任务,帮助开发者节约电池和数据流量
通过 JobScheduler,你可以创建一个 Job,并将其提交给系统。系统会根据设定的条件和设备的当前状态来判断何时执行这个 Job
此外,值得一提的是,随着 Android 的不断发展,相关的后台任务调度 API 也在不断改进,比如 WorkManager。它不仅兼容 JobScheduler,还能兼容其他后台执行方案,可以说是目前更推荐的选择。
十七.Android的缓存机制
常见的缓存方式有:内存缓存,磁盘缓存,数据库缓存
内存缓存
内存缓存是在RAM中存储数据,访问速度极快,适用于频繁访问,但手机内存有限,不适合存储大量数据,使用LRU缓存策略,Android提供的LruCache类
优点:读取快
缺点:内存有限,数据容易丢失
磁盘缓存
存储缓存是在设备上村蔟的数据,存储时间长,适用于大量数据或不经常访问的场景,常见的实现库okhttp的cache类,用于缓存http的响应数据
优点:数据存储久,容量大
缺点:读取速度相对内存较慢
数据库缓存
缓存到本地的数据库中,适合复杂结构数据的存储,支持高级查询操作,
访问速度介于内存和磁盘之间
网络缓存
将数据存储云端服务器,数据可以同步多台设备
图片缓存
对于大量数据且频繁访问的资源,通常会使用专门的缓存库,如Glide,Picasso,这些库会对图片进行内存和磁盘双重缓存
十八.Okhttp的原理及使用方法
核心原理
1.线程模型及连接池:OkHttp 是通过连接池来管理和复用http的连接,同时,通过使用调度器与线程池管理异步请求,提高了并发能力和响应速度
2.拦截器链:okhttp会通过一系列拦截器。拦截器可以做日志、修改请求和响应、处理重定向、缓存控制等操作。
3.缓存机制:通过实现 http 的缓存标准,可以减少不必要的网络请求,提升应用的性能。它会根据缓存请求头和存储的响应,在必要时直接从缓存中返回响应
使用方法
// 1)添加依赖:在项目的 build.gradle 文件中添加 OkHttp 的依赖,
implementationcom.squareup.okhttp3:okhttp:4.9.1
// 2)创建 OkHttpClient 实例:OkHttpClient 是发送和接收请求的主要类
OkHttpClient client=new OkHttpClient();
// 3)创建请求对象:通过 Request.Builder 构建请求对象。
Request request =new Request.Builder()
.url("https://api.example.com/data")
.build();
// 4)同步请求:通过 OkHttpClient 的newcall 方法创建 Call 对象,并调用法执行同步请求。
Response response= client.newCall(request).execute()
if(response.isSuccessful()){
System.out.println(response.body().string());
} else {
System.err.println("Request failed");
}
// 5)异步请求:如果需要在非主线程中进行网络请求,可以使用enqueue 方法执行异步请求。
client.newCall(request).enqueue(new Callback(){
@Override
public void onFailure(Call call, I0Exception e){
e.printStackTrace();
}
@Override
public void onResponse(Call call,Response response) throws IOExceptio{
if(response.isSuccessful()){
System.out.println(response.body().string());} else {
System.err.println("Request failed");
}
});
扩展区
自定义拦截器
构建自定义拦截器可以实现鉴权等高级功能
HTTP/2 和 WebSocket
OkHttp 原生支持 HTTP/2 和 WebSocket,使其能够更好地处理实时通信和高效的并发连接,
超时设置
OkHttp 允许配置各种类型的超时参数,如连接超时、读超时、写超时等,用于更细粒度地控制请求。
缓存控制
通过 OkHttp 的强大缓存机制,可以减少网络开销,提高响应速度
Cache cache = new Cache(new File(context.getCacheDir(), "http_cache"), 10 * 1024 * 1024); // 10 MB cache
OkHttpClient client = new OkHttpClient.Builder()
.cache(cache)
.build();
十九.Okhttp的设计模式
Builder模式
Okhttp 中的 OkHttpClient 和 Request 使用了 Builder 模式。这种模
式使得配置对象变得更加灵活,可以链式调用,非常适合用于创建复杂对象
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();
Request request = new Request.Builder()
.url("https://www.example.com")
.build();
责任链模式
Okhttp 提供了多个层次的拦截器,如应用拦截器和网络拦截器。开发者可以自定义拦截器来实现日志、重试等功能。
责任链模式使得不同部分的责任分离,增强了代码的可扩展性和可维护性。
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new LoggingInterceptor())
.build();
单例模式
OkHttpClient 可以作为单例使用,统一管理网络请求并复用连接池和线程池,这样可以节省资源,提升性能
public class OkHttpSingleton {
private static OkHttpClient instance;
private OkHttpSingleton() {}
public static OkHttpClient getInstance() {
if (instance == null) {
synchronized (OkHttpSingleton.class) {
if (instance == null) {
instance = new OkHttpClient();
}
}
}
return instance;
}
}
二十.Okhttp拦截器及原理
OkHttp 的拦截器通过责任链模式来实现,每个拦截器相当于责任链上的一个节点,它处理请求,并决定是否将其传递给下一个节点。
使用 OkHttp 的拦截器步骤
1.创建一个拦截器。这里有两种类型的拦截器:应用拦截器和网络拦截器
2.然后,实例化 OkHttpClient 时,添加这个拦截器
3.最后,使用这个 OkHttpClient 进行网络请求
public class Example {
public static void main(String[] args) {
// 创建拦截器
Interceptor interceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// 在这里对原始请求进行修改、重试或者其它处理
return chain.proceed(request);
}
};
// 创建 OkHttpClient 并添加拦截器
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(interceptor) // 添加拦截器
.build();
// 使用这个 OkHttpClient 进行请求
Request request = new Request.Builder()
.url("https://www.example.com")
.build();
try (Response response = client.newCall(request).execute()) {
System.out.println(response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
}
okhttp 内置了五大拦截器,通过责任链模式将请求逐层分发下去,每层完成自己这层该做的事,最后拿到相应结果逐层往上返回结果:
1.RetryAndFollowUpInterceptor(重试重定向拦截器)
负责失败重试以及重定向。请求失败自动重试,如果 DNS 设置了多个ip地址会自动重试其余ip地址。
2.BridgeInterceptor:桥接拦截器
负责把用户构造的请求转换为发送给服务器的请求,把服务器返回的响应转换为对用户友好的响应。会补全我们请求中的请求头,例如Host,Cookie,Accept-Encoding等。
3.CacheInterceptor:缓存拦截器
负责读取缓存以及更新缓存。会选择性的将响应结果进行保存,以便下次直接读取,不再需要再向服务器索要数据。
4.ConnectInterceptor:连接拦截器
负责与服务器建立连接。建立连接并得到对应的socket;管理连接池,从中存取连接,以便达到连接复用的目的。
5.CallServerInterceptor:服务请求拦截器
负责从服务器读取响应的数据。服务请求与服务器建立连接,具体进行网络请求,并将结果逐层返回的地方。
这五个是系统内置的拦截器,我们也可以通过 addInterceptor() 加入我们自己写的拦截器.下面我们详细看下拦截器的分发流程。
了解了系统内置的五大拦截器后,就可以自己写一个拦截器了
1.通过责任链分发下来的网络请求,拿到 Request 对象后根据需求做一些自己的事情。
2.然后将网络请求分发到下一个责任链上,并保存相应结果。
3.拿到返回结果后,再根据自己的业务需求,对 response 进行加工。
4.将 response 返回给上一个拦截器。
二十一.Retrofit库的使用和原理
使用方法方面,首先你需要在 build.gradle 中添加 Retrofit 依赖。然后定义网络接口并添加合适的注解(例如 @GET、@POST 等)。创建一个 Retrofit 实例配置必要的信息(如Base URL、Gson转换器等),并通过该实例生成具体的服务接
// 1.添加依赖:首先需要在 build.gradle 文件中添加 Retrofit 和 Gson 转换器的依赖:
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
// 2.定义接口:定义一个接口来描述网络请求,例如:
public interface ApiService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user")string user);
}
// 3.创建 Retrofit 实例:配置 Retrofit 实例并生成服务接口的实现:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create()).build();
ApiService apiService = retrofit.create(ApiService.class);
// 4.发起请求:通过生成的服务接口发起网络请求:
Call<List<Repo>> call = apiService.listRepos("octocat");
call.enqueue(new Callback<List<Repo>>() {
@Override
public void onResponse(Call<List<Repo>> call, Response<List<Repo>> response) {
if(response.isSuccessful()) {
List<Repo> repos = response.body();
// 处理成功响应
}
}
@Override
public void onFailure(Call<List<Repo>> call, Throwable t) {
// 处理失败响应
}
});
原理上,Retrofit 的核心是通过动态代理来创建请求接口的实现,并结合 OkHttp 来处理实际的网络请求。它将接口方法的定义映射为 HTTP 请求,并基于注解和方法参数来生成相应的请求 URL、请求体等。
核心机制解析
- 动态代理:Retrofit 使用 Java 动态代理机制实现接口方法的调用。通过retrofit.create(ApiService.class)自动生成接口实现类,所有请求都被转发到动态代理的 InvocationHandler 中。
- 注解处理:Retrofit 利用反射解析接口方法上的注解信息,将其转换为具体的HTTP 请求参数,例如 URL 路径、请求方法(GET、POST等)、查询参数、请求体等。
- 请求和响应处理:Retrofit 使用 OkHttp 发起网络请求,并通过转换器(例如GsonConverter)将响应数据转换为所需的Java对象。
Retrofit 支持同步(execute )和异步(enqueue)请求方式
二十二.Retrofit的设计模式
单例模式
在 Retrofit 中的应用:GsonConverterFactory 和其他一些全局性配置管理类经常采用单例模式。单例模式的使用避免了资源的重复加载,节省了系统资源,并确保类的实例化一致性。
工厂模式
Retrofit 通过 CallAdapter.Factory以及Converter.Factory 来创建适配器和转换器,比如 JSON 转换器可以是 GSON 或Jackson 的实现,这使得 Retrofit 更加灵活和可扩展
构建者模式
Retrofit 使用 Retrofit,Builder 来一步步构建 Retrofit 实例,比如使用 baseUrl、添加转换器、设置回调执行器等。这大大简化了配置过程,让代码更加清晰可读。
代理模式
Retrofit 使用动态代理生成 API接口的具体实现。在调用接口方法时代理会拦截请求,并将其转化为网络请求。这里的ServiceMethod 类起到了核心作用。
适配器模式
Retrofit 提供了自定义 CallAdapter和 Converter,它们实际上就是适配器的具体实现。比如,Retrofit 默认的call 可以被替换成 RxJava
的 Observable.
二十三.EventBus的原理及使用
其主要原理是通过发布/订阅模式来实现组件之间的通讯,EventBus可以提供代码的可维护性
使用
// 1.引入依赖:在项目的 build.gradle 中添加 EventBus 的依赖
implementation 'org.greenrobot:eventbus:3.2.0'
// 2.定义事件:创建一个普通的 Java 类来表示事件
public class MessageEvent {
public final String message;
public MessageEvent(String message) {
this.message = message;
}
}
// 3.注册和解注册:在需要接收事件的 Activity或Fragment的 onstart 方法中注册,
// 在 onstop 方法中解注册。
@Override
protected void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
@Override
protected void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
}
// 4.定义事件处理方法:在类中添加一个用 @Subscribe 注释的方法来接收事件。
@Subscribe
public void onMessageEvent(MessageEvent event) {
Toast.makeText(this, event.message, Toast.LENGTH_SHORT).show();
}
// 5.发布事件:在需要发布事件的地方调用 post 方法。
EventBus.getDefault().post(new MessageEvent("Hello EventBus!"));
EventBus 实际上是通过反射机制来进行事件处理方法的查找和调用的,这也是它能够保持代码简洁和解耦合的关键。
反射机制:EventBus通过这种机制在运行时查找带有@Subscribe注解的方法
线程模式:EventBus提供了多种线程模式
- `ThreadMode.MAIN`:事件处理方法在 Android 的主线程(UI 线程)中调用。
- `ThreadMode.BACKGROUND`:事件处理方法在后台线程中调用,如果事件发布在主线程,EventBus 会自动切换到后台线程来调用。
- `ThreadMode.ASYNC`:事件处理方法在新建的子线程中异步调用。
sticky事件:EventBus 允许发送 Sticky 类型的事件,这种事件即使订阅者在事件发布之后才注册,也能立即接收之前发布的事件。
EventBus.getDefault().postSticky(new MessageEvent("Sticky Event"));
注册方式与普通事件相同,只不过需要使用
`EventBus.getDefault().removeStickyEvent(MessageEvent.class);
` 来手动移除这些事件。
代码优化:在使用 EventBus 时,还要注意潜在的内存泄漏问题(例如忘记解注
册),可以使用 weakReference(弱引用)来避免内存泄漏
二十四.Rxjava的原理及使用
RxJava 是一个用于实现响应式编程的库,它基于 Java 实现,其核心思想是通过 Observable 类似流的方式异步处理数据流和事件。RxJava 的原理可以归纳为"观察者模式、操作符、线程调度等"。
核心原理:
1)观察者模式:RxJava 将数据流看作 Observable,对数据进行观察和操作的对象称为 Observer。
2)操作符:用于转换、过滤和组合 Observable 流中的数据。
3)线程调度:通过 Schedulers 控制代码在指定的线程中执行,从而实现异步与并发操作。
使用
// 1.创建 Observable
Observable<String> observable = Observable.just("Hello, RxJava!");
// 2.创建 Observer
Observer<String> observer = new Observer<String>() {
@Override
public void onSubscribe(Disposable d) {
// 当订阅发生时调用
}
@Override
public void onNext(String s) {
// 处理收到的数据流
System.out.println(s);
}
@Override
public void onError(Throwable e) {
// 错误处理
}
@Override
public void onComplete() {
// 完成时调用
}
};
// 3.订阅操作
observable.subscribe(observer);
// 线程调度
observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(observer);
RxJava 是一个强大且灵活的库,适用于从简单的事件处理到复杂的数据流管理。其优势主要在于提供了强大的链式操作方式,让代码逻辑更加清晰和简洁。
1.操作符:
- Transforming Observables: map(), flatMap( )。
- Filtering Observables: filter(),debounce()。
- Combining Observables: merge(),concat()
2.Schedulers:
- Schedulers.io():1/0 操作(文件读写、网络请求等)
- Schedulers.computation():计算密集型任务
- AndroidSchedulers.mainThread():Android 主线程,用于更新U
3.内存管理:
- 使用 CompositeDisposable 管理多个异步任务,防止内存泄漏
CompositeDisposable compositeDisposable = new CompositeDisposable();
Disposable disposable = observable.subscribe(observer);
compositeDisposable.add(disposable);
// 清理
compositeDisposable.clear();
二十五.GreenDao的原理及使用
GreenDao 是一个 ORM(对象关系映射)框架,用于 Android 平台上将 Java 对象映射到SQLite 数据库的表,并提供简洁高效的 CRUD(创建、读取、更新、删除)操作。
原理
1.GreenDao 生成与数据库表对应的实体类(JavaBean),这些实体类与数据库中的表记录--对应。
2.通过 DAO(Data Access Object)模式,GreenDao 自动生成用于数据库操作的代码,实现高效的数据持久化操作。
3.GreenDao 使用领域特定语言(DSL)来定义实体和关系结构,从而自动生成必要的数据库代码。
使用方法:
// 1.添加 GreenDao 到你的项目中:在 build.gradle 文件中添加:
dependencies {
implementation 'org.greenrobot:greendao:3.3.0' // 注意版本号可能会更新
}
// 2.创建实体类: 使用 GreenDao 注解来定义实体类,如:
@Entity
public class User {
@Id
private Long id;
@Property(nameInDb = "USERNAME")
private String username;
@Transient
private int tempUsageCount; // not persisted
// getters and setters
}
// 3.生成 DAO 类:在 build.gradle 中增加 GreenDao 代码生成任务配置
greendao {
schemaVersion 1
}
//然后进行代码生成操作。
// 4.初始化 GreenDao:
DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(context, "notes-db");
Database db = helper.getWritableDb();
DaoSession daoSession = new DaoMaster(db).newSession();
// 5.CRUD 操作:创建一个 DAO 实例,并进行数据操作,如:
UserDao userDao = daoSession.getUserDao();
User newUser = new User();
newUser.setUsername("John Doe");
userDao.insert(newUser); // 插入一条新记录
二十六.Glide 的原理及其使用方法
Glide的核心原理是利用内存缓存和磁盘缓存来提高图片的加载速度,内部实现包含图片加载,图片缓存,图片的变换和显示等。Glide通过高效的内存管理和线程池管理来实现这些功能
使用方法
1.添加依赖:在项目的 build.gradle 文件中添加 Glide 的依赖
2.加载图片:使用简单的链式调用 Glide 的 API来加载图片并显示到ImageView次
// 添加依赖
implementation 'com.github.bumptech.glide:glide:4.x.x'
// 在 ImageView 中加载一个图片
Glide.with(context)
.load(imageUrl)
.into(imageView);
缓存机制
内存缓存:Glide会将最近使用的图片缓存到内存中,提高图片的加载速度
磁盘缓存:已有的图片保存到磁盘中,便于下次直接读取,从而避免再次加载
图片转换
可以通过使用 Glide.with()和transform()方法来对图片进行变换操作。例如,将图片转换为圆形。
Glide.with(context)
.load(imageUrl)
.transform(new CircleCrop())
.into(imageView);
高级用法
加载占位图和错误占位图
Glide.with(context)
.load(imageUrl)
.placeholder(R.drawable.loading)
.error(R.drawable.error)
.into(imageView);
加载缩略图:当需要加载高清大图时,先显示一个低分辨率的缩略图以提高用户体验。
Glide.with(context)
.load(imageUrl)
.thumbnail(0.1f)
.into(imageView);
与 RecyclerView 合作
在 RecyclerView 中优化图片加载时,结合 Glide 的缓存机制,能有效地减少内存占用和提高加载速度。
二十七.ARouter的原理及使用
ARouter是阿里巴巴的开源路由框架,主要用于组件间的解耦和页面跳转,他的原理主要基于java注解处理器和反射机制,在编译期收齐路由信息,并在运行时进行路由跳转
使用
// 1.添加依赖: 在项目的Jbuild.gradle 文件中添加 ARouter 的依赖
dependencies {
implementation 'com.alibaba:arouter-api:x.y.z' // 替换为最新版本
annotationProcessor 'com.alibaba:arouter-compiler:x.y.z' // 替换为最新版本
}
// 2.初始化:在 Application 类中进行初始化
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
if (BuildConfig.DEBUG) {
ARouter.openLog(); // 打印日志
ARouter.openDebug(); // 开启调试模式(必须在初始化之前)
}
ARouter.init(this); // 初始化 ARouter
}
}
// 3.路由注册: 在目标 Activity 上添加@Route 注解,声明路由路径
@Route(path = "/test/activity")
public class TestActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
}
}
// 4.跳转页面: 使用 ARouter 进行页面跳转:
ARouter.getInstance().build("/test/activity").navigation();
二十八.协程
1.协程是什么
协程是轻量级的并发设计,处理异步编程,如网络请求和数据库查询等
2.协程使用
1.引入依赖。
2.使用Globalscope 或 lifecyclescope 。
3.定义和调用协程函数,比如 suspend 函数。
4.使用 launch 和 async 构建协程。
// 1. 在 build.gradle 中添加协程依赖
dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.x.x"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.x.x"
}
// 2. 在 Activity 或 Fragment 中使用协程
import kotlinx.coroutines.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 使用 lifecycleScope 启动协程
lifecycleScope.launch {
val result = fetchDataFromNetwork()
updateUI(result)
}
}
// 定义一个挂起函数
suspend fun fetchDataFromNetwork(): String {
return withContext(Dispatchers.IO) {
// 模拟网络请求
delay(2000)
"Data from network"
}
}
// 更新 UI
fun updateUI(data: String) {
// 更新UI操作
textView.text = data
}
}
挂起函数
这些函数可以被挂起和恢复,适合执行耗时任务避免阻塞线程。挂起函数只能在协程内或另一个挂起函数内调用。
CoroutineScope
它定义了协程的范围,在该范围启动的协程都会遵循其生命周期,例如在 Activity 或 Fragment 中我们通常使用 Globalscope 或lifecyclescope
Job 和 Deferred
Job:独立单元,可以用于取消协程。
Deferred:可带回值的协程,通常与 async 结合使用
Context Switch(上下文切换)
使用 Dispatcher,协程可以在不同的线程之间切换,这些是协程上下文的一部分。常见的 Dispatcher 有:
Dispatchers.Main:用于在主线程中执行代码,适合更新 UI
Dispatchers.IO :用于执行 I/O 操作,如网络请求和数据库读写。
3.协程的创建
创建有三种:launch,async 和runBlocking
launch
用于启动一个协程,没有返回值,通常在主线程执行后台任务
GlobalScope.launch {
// 在后台启动协程
delay(1000L) // 模拟耗时操作
println("Hello from Kotlin Coroutine!")
}
async
用于启动一个协程,执行的任务返回Deferred对象,用await方法获取结果,适用于并行计算并返回结果的场景
val deferred: Deferred<Int> = GlobalScope.async {
delay(1000L)
5 + 5
}
runBlocking {
println("Result: ${deferred.await()}")
}
runBlocking
会堵塞当前线程,知道协程内部执行完,一般用户测试及main方法使用
runBlocking {
launch {
delay(1000L)
println("Hello from runBlocking!")
}
println("Hello from main!")
}
4.协程的挂起和恢复
协程的挂起是通过suspend关键字修饰,挂起函数可以在不堵塞线程的情况下暂停线程的执行,并能在suspend函数执行完恢复线程的执行
函数挂起和恢复的主要核心在于withContext函数来实现函数的挂起和恢复,用的是协程的调度器(Dispatcher)和协程的上下文(Context),没有回调接口
GlobalScope.launch(Dispatchers.Main){
println("launch start in Thread:${Thread.currentThread().name}")
// 挂起点,主要是withContext函数实现了挂起和恢复
val withResult = withContext(Dispatchers.IO){
println("withContext run in Thread:${Thread.currentThread().name}")
"withContext success"
}
// 恢复点
println("launch end in Thread:${Thread.currentThread().name}, withResult = $withResult")
}
执行结果:
System.out: launch start in Thread:main
System.out: withContext run in Thread:DefaultDispatcher-worker-1
System.out: launch end in Thread:main, withResult = withContext success
5.协程与线程的区别
1.协程利用轻量级线程来管理并发工作,相比java线程,占用资源更少,性能更好
2.协程是基于挂起函数(suspend)和协程作用域,java线程是基于Thread类和实现Runnable接口
3.协程上下文切换开销比线程低
5.协程作用域
lifecycleScope :适用于生命周期感知组件(如 Activity、Fragment),确保在组件销毁时取消协程。
viewModelscope :适用于 ViewModel,确保在 ViewModel 清理时取消协程。
GlobalScope :全局应用生命周期管理的协程作用域,使用时需要谨慎,容易导致内存泄漏。
二十九.事件分发机制
事件从屏幕传递到各个 View,由 View 来使用这一事件或者忽略这一事件(不消费事件),这整个过程的控制就称之为事件分发机制
事件的传递层级
Activity -> Window -> DecorView -> ViewGroup -> View
1.Activity 持有 PhoneWindow 对象的引用(Activity.mWindow)
2.PhoneWindow 持有 DecorView 对象的引(PhoneWindow.mDecor)
3.DecorView 继承自 FrameLayout (extends ViewGroup)。
4.DecorView 就是 Activity 界面上的顶层 ViewGroup 容器。
5.于是,事件 MotionEvent 进入到 ViewGroup.dispatchTouchEvent 方法。
6.然后执行ViewGroup.onInterceptTouchEvent事件拦截,返回true表示被拦截,
由ViewGroup.OnTouchEvent 来处理事件,不在向下分发,返回false则分发给子view
7.调用子view的dispatchTouchEvent方法,返回false则调用ViewGroup.onTouchEvent,否则执行view.onTouchEvent方法
8.onTouch()的执行高于onClick(),onTouch()返回true就认为该事件被onTouch()消费掉,因而不会再继续向下传递,即不会执行OnClick()。
事件分发机制主要基于三个方法:dispatchTouchEvent(),
onInterceptTouchEvent()和 onTouchEvent()。这三个方法分别对应着事件的分发、拦截、以及处理。
1.dispatchTouchEvent(MotionEvent ev):
ViewGroup 首先调用这个方法来分发触摸事件。如果返回 true,意味着事件已经被消耗,不再向下分发;如果返回 false,则会继续向下分发给子 View。
2.onInterceptTouchEvent(MotionEvent ev):
这个方法决定了 ViewGroup 是否拦截事件。如果返回 true,意味着拦截事件,此时事件会交给该ViewGroup 的onTouchEvent 处理;如果返回 false,事件将继续传递到子View.
3.onTouchEvent(MotionEvent ev):
这个方法用于处理实际的触摸事件,比如点击、拖动等。如果 View 消耗了事件(返回 true),事件将不会继续向上传递,否则会向上传递给父级 View。
三十.Android启动的四个阶段
Android 应用的启动流程基本可以分为四个主要阶段:
1.Zygote 进程启动
Zygote 是 Android 系统中的一个非常特殊的进程,它的主要作用是负责孵化(fork)出新的应用进程。-Zygote 进程使用预加载的类和资源来缩短应用启动时间。这减少了反复加载相同资源的开销,提升了启动性能。-当设备启动时init 进程会启动 Zygote 进程,这个过程可以在 init.rc 脚本中找到相应配置。
2.SystemServer启动
System Server是 Android 系统的核心服务进程,它运行一系列系统服务(如 Activity Manager、Window Manager 等),提供基本的系统功能。System Server 是由 Zygote 进程通过 fork 出来的,因此 zygote 需要首先完成初始化。System Server 的启动方式是在 Zygote 初始化完成后,通过 startsystemServer 方法启动。
3.Launcher启动
当系统服务器启动后,接下来会通过活动管理服务(AMS)启动启动程序应用,也就是我们常看到的安卓桌面。一启动程序作为标准的应用程序,其启动过程也遵循常规的Android应用启动流程。-它展示了主屏幕,并允许用户启动其它应用程序。
4.Application 启动
用户在 Launcher 上选择启动某个应用,这时会请求 ActivityManager Service (AMS) 来启动相应的应用。-AMS 会首先检查应用是否已经在运行,如果没有则会请求 Zygote 进程 fork 出一个新的应用进程,然后通过反射调用应用的ActivityThread.main 方法,最终启动应用的主 Activity。-这个过程包含了 Context的创建、资源的加载、Activity 的启动等多个步骤,
三十一.Android的设计模式
常用的模式有哪些
1.单例模式
确保一个类只有一个实例,并提供一个全局访问点
1、饿汉式
先初始化好变量,需要时直接返回
//Kotlin
object Singleton{
}
//Java
public class Singleton{
private Singleton(){}
private static Singleton instace = new Singleton();
public static Singleton getInstance(){
return instance;
}
}
2、线程安全的懒汉式
使用同步锁解决懒汉式线程安全问题
//Kotlin
Class Singleton private constructor(){
companion object{
private var instance:Singleton? = null
get(){
if(field == null) {
field = Singleton()
}
return field
}
@Synchronized
fun getInstance() = instance!!
}
//Java
public class Singleton{
private Singleton(){}
private static Singelton instance;
public static sychronized Singleton getInstance(){
if(instance==null){
instance = new Singleton();
}
return instance;
}
}
3、双重校验锁
考虑线程安全,基于同步锁来创建并返回唯一实例对象
//Kotlin
companion object{
val instance:Singleton by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
Singleton()
}
}
//Java
public class Singleton{
private volatile static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instace == null){
synchronized(Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
4、静态内部类
**静态提供单个实例,实例通过内部类创建**
//Kotlin
class Singleton private constructor(){
companion object{
val instance = SingletonHolder.holder
}
private object SingletonHolder{
val holder = Singleton()
}
}
//Java
public class Singleton{
private static class SingletonHolder{
private static Singleton instance = new Singleton();
}
private Singleton(){}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
}
使用经验:
线程安全和性能要求高, 使用饿汉式、双重检查锁
需要保证创建单例是第一次访问时,使用了懒汉式、静态内部类
2.工厂模式
定义一个创建对象的接口,由子类决定实例化哪个类
3.观察者模式
事件总线,LiveData,ViewModel和UI组件之间的数据绑定。
public class UserViewModel extends ViewModel {
private MutableLiveData<User> user = new MutableLiveData<>();
public LiveData<User> getUser() {
return user;
}
public void setUser(User user) {
this.user.setValue(user);
}
}
3.适配器模式
RecyclerView适配器、ListView适配器等Adapter
4.代理模式
应用场景:Retrofit的动态代理,用于创建API接口实例。
public interface ApiService {
@GET("users/{user}")
Call<User> getUser(@Path("user") String user);
}
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.build();
ApiService service = retrofit.create(ApiService.class);
5.责任链模式
Okhttp 提供了多个层次的拦截器,如应用拦截器和网络拦截器。开发者可以自定义拦截器来实现日志、重试等功能。
6.迭代器模式
处理集合类,如遍历数据列表。
7.建造者模式(Builder)
将对象的构建与展示分离,允许用户更精细地控制对象的构造流程
Request request =new Request.Builder()
.url("https://api.example.com/data")
.build();
三十二.四大组件之Service
1.Service和Thread区别
Thread 是程序执行的最小单元,我们可以用它执行一些异步操作,相对独立而Service依托于他所在的主线程上,并不独立
2.开启Service的两种方式
startService
1.定义一个类继承Service
2.在Mainfest.xml 配置文件中配置该Service
3.使用Context的startService(Intent) 方法启动Service,传进去的参数是Intent
4.不使用时调用stopService(Intent)
当Service 被启动之后将无限的运行下去,即使Activity被销毁也不会停止,除非手动停止 Service
onCreate() -> onStartCommand() -> onDestroy()
第一次调用会触发 onCreate() 和 onStartCommand,
之后每次启动服务只触发 onStartCommand()
无论 startService 多少次,stopService 一次就会停止服务
bindService
Activity 和 Service 进行绑定,如果绑定全部取消之后,这个Service自动被销毁
onCreate() -> onBind() -> onUnbind() -> onDestroy()
第一次 bindService 会触发 onCreate() 和 onBind(),
若不unBind() 则以后每次 bindService 都不会调用任何生命周期方法。
bindService 启动的生命周期依附于启动它的 Context
三十三.Fragment和Activity的生命周期有何不同?
1.Activity的生命周期
onCreate()、onStart()、onResume()、onPause().
onStop()、onDestroy()。
2.Fragment 的生命周期
onAttach()、onCreate()、onCreateView()、onActivityCreated()、onStart()、onResume()、onPause()、onStop(),onDestroyView()、onDestroy()、onDetach()
3.两者的不同之处
视图管理
Fragment 具有一系列专门处理视图的回调,例如 onCreateView() 和onDestroyView(),这些在 Activity 中是不存在的。
关联与分离
Fragment有onAttach()和 onDetach()方法,分别在 Fragment 被附加到 Activity 或 从 Activity 中分离时调用,而 Activity 没有这样的生命周期。
嵌套
可以在一个 Activity 中嵌套多个 Fragment,而 Fragment 也可以嵌套其他Fragment.
三十四.组件化和插件化
在 Android 工程中,实现组件化和插件化是为了提高代码的模块复用性、扩展性和独立开发测试能力。实现的方法主要有以下几种:
1.组件化
模块分离:将项目中的不同功能拆分为多个独立的 module,。
·引入路由框架:例如 ARouter,可以在不同 module 之间进行页面跳转。
。公共库模块:把公用的代码放在一个单独的 module 中方便共享和复用。
2.插件化
动态加载:通过动态加载 ClassLoader 和反射机制实现插件代码的加载和调用,例如 AndFix、 Dexposed.
接入现成插件框架:如 RePlugin、VirtualAPK 这些已经成熟的解决方案。
3.组件间如何通信
- 通过接口
- eventbus
- Router
- 广播
三十五.SurfaceView是什么
SurfaceView 的典型使用场景涉及到游戏开发、视频播放和需要频繁重绘的自定义 UI元素。它通过在独立的绘制线程中绘制图像,避免了阻塞主线程,提高了绘制效率和流畅度
具体来说,SurfaceView 由两部分组成:
1.Surface:这是一个通过硬件加速的绘制表面
2.SurfaceHolder:它监听和控制 Surface 的生命周期
SurfaceView 的详细用法和优势:
1.独立线程绘制:可以在独立线程中绘制,以避免主线程的阻塞,提高绘制效率
2.硬件加速:利用硬件加速进行高效、快速的图像绘制
3.灵活控制生命周期:能够对 Surface 进行更细粒度的生命周期控制
三十六.自定义View
刷题更新中。。。