一、Android基础知识
1、四大组件、六大布局、五大存储
四大组件:
activity、service、content provider、broadcast
六大布局(现在是 7 大了):
- 线性布局(LinearLayout)
- 相对布局(RelativeLayout)
- 帧布局(FrameLayout)
- 表格布局(TableLayout)
- 网格布局(GridLayout)
- 协调布局(ConstraintLayout)
- 绝对布局(AbsoluteLayout)
五大存储
SharedPreferences、文件存储、SQLite数据库方式、内容提供器(Content provider)、网络
2、Activity生命周期
onCreate() -> onStart() -> onResume() -> onPause() -> onStop() -> onDetroy()
Fragment生命周期
onAttach() -> onCreate() -> onCreateView() -> onActivityCreate() -> onStart() -> onResume() -> onPause() -> onStop() -> onDestoryView() -> onDestory() -> onDetach()
3、启动模式
standard
:默认启动模式
singleTop
栈顶复用模式:当activity为此种模式时,开启这个activity会对当前栈顶activity进行检查,如果栈顶activity与待开启的activity一致(或者说为同一个activity),那么将复用栈顶acticity,而不会启动新的
singletask
栈内复用模式
singleInstance
模式:当activity设定为此种模式时,那么系统将会为这个activity单独开启一个栈,在这个栈中只能存在这一个activity,并且对于一个activity这种栈只能开启一个,重复开启页面时会复用之前的activity。
singleInstancePerTask
:singleInstancePerTas是 android12 新增的,与singleTask几乎一样,不同的是它允许应用程序在一个新的任务中启动一个新的实例,而不是在现有任务中启动实例。
4、Service生命周期?
通过startService()这种方式启动的service,生命周期是这样:
调用startService() --> onCreate()–> onStartConmond()–> onDestroy()。
startService()
方法用于启动一个服务并在后台运行,它的生命周期如下:
onCreate()
:当服务第一次被创建时调用,这是初始化代码的好地方。onStartCommand()
:每次调用startService()
方法时都会调用此方法。在此方法中,您可以处理来自客户端的请求并执行任何必要的操作,例如启动新线程或播放音乐。onDestroy()
:当服务不再需要时,将调用此方法。在此方法中,您可以执行任何必要的清理操作,并释放所有资源。
通过bindService()方式进行绑定,这种方式绑定service,
生命周期走法:bindService–>onCreate()–>onBind()–>unBind()–>onDestroy()
bindService()
方法用于将客户端绑定到服务,并与服务进行通信。它的生命周期如下:
onCreate()
:当服务第一次被创建时调用,这是初始化代码的好地方。onBind()
:每次调用bindService()
方法时都会调用此方法。在此方法中,您可以返回一个IBinder
接口的实现,以便客户端可以与服务进行通信。onUnbind()
:当客户端与服务解除绑定时,将调用此方法。在此方法中,您可以执行任何必要的清理操作。onRebind()
:当客户端重新绑定到服务时,将调用此方法。在此方法中,您可以执行任何必要的操作,例如重新初始化数据或恢复状态。onDestroy()
:当服务不再需要时,将调用此方法。在此方法中,您可以执行任何必要的清理操作,并释放所有资源。
5、Broadcast注册方式与区别
此处延伸:什么情况下用动态注册
Broadcast广播,注册方式主要有两种.
第一种是静态注册,也可成为常驻型广播,这种广播需要在Androidmanifest.xml中进行注册,这中方式注册的广播,不受页面生命周期的影响,即使退出了页面,也可以收到广播这种广播一般用于想开机自启动啊等等,由于这种注册的方式的广播是常驻型广播,所以会占用CPU的资源。
第二种是动态注册,而动态注册的话,是在代码中注册的,这种注册方式也叫非常驻型广播,收到生命周期的影响,退出页面后,就不会收到广播,我们通常运用在更新UI方面。这种注册方式优先级较高。最后需要解绑,否会会内存泄露
广播是分为有序广播和无序广播。
6、android各版本差异
Android 5.x(Lollipop)
- 开始支持64位的处理器
- ART(Android Runtime)取代了Dalvik虚拟机,提升应用性能(即时编译 ->提前编译)
Android 6.x(Marshmallow)
- 动态权限管理系统
- 取消支持 Apache HTTP 客户端,转而推荐使用HttpUrlConnection或者OkHttp库来进行网络请求。
Android 7.x(Nougat)
- 分屏模式,支持同时运行两个应用程序
- 系统权限的更改,分享私有文件内容的推荐方法是使用 FileProvider,禁止向你的应用外公开 file:// URI
- 更便捷的通知栏,自动将多条通知合并。
- 支持app应用签名v2的打包方式(在AS2.2后,在打包签名应用时,可勾选jar打包(v1)和全应用打包(v2)
Android 8.x(Oreo)
- 通知渠道 — Notification Channels
- 画中画模式,支持应用窗口悬浮播放视频
- 安装未知来源的第三方开关被移出,变成了每次安装未知的第三方都要手动授权。
Android 9.x(Pie)
-
异型屏适配
-
使用前台服务,必须请求 FOREGROUND_SERVICE 权限
-
不能直接非 Activity 环境中(比如Service,Application)启动 Activity,否则会崩溃报错。保活的说法从此之后越来越少
Android 10.x(Q)
Android 11.x(R)
- 用户存储权限
Android 12.x
-
启动模式新增singleInstancePerTask(与singleTask几乎一样,不同的是它允许应用程序在一个新的任务中启动一个新的实例,而不是在现有任务中启动实例。)
-
JDK版本强制JDK11
7、[重要]activity a 启动 activity b 在ams层上是怎么流转的
当 Activity A 启动 Activity B 时,在 AMS(Activity Manager Service)层面上的流转如下:
- Activity A 发起启动请求,并将该请求传递给 AMS。
- AMS 接收到启动请求后,会进行一系列的处理和判断,包括权限检查、任务栈管理等。
- 如果 Activity B 尚未创建,则 AMS 会创建一个新的实例,并将其添加到任务栈中。
- 如果 Activity B 已经存在于任务栈中,AMS 会将其调至前台,并执行相应的生命周期方法(如 onCreate()、onStart()、onResume())。
- 同时,AMS 会将 Activity A 转入后台,并执行相应的生命周期方法(如 onPause())。
- 当 Activity B 完成其生命周期后,AMS 可能会执行一些后续操作,如销毁 Activity B、返回到上一个 Activity 等。
总之,在 AMS 层面上,Activity A 启动 Activity B 的流转是通过任务栈的管理和生命周期方法的调用来实现的。
8、activity a跳转到activity b 都经过了哪些生命周期
1、B页面完全遮住A页面
a页面 -> b页面
A页面:onPause
B页面:onCreate -> onStart -> onResume
A页面:onStop
B页面 -> a页面
B页面:onPause
A页面:onRestart -> onStart -> onResume
B页面:onStop -> onDestroy
2、B是透明的或者完全没有遮挡A
a页面 -> b页面
A页面:onPause
B页面:onCreate -> onStart -> onResume
B页面 -> a页面
B页面:onPause
A页面:onResume //因为是透明的,所以A执行 onResume(不会执行onRestart->onStart)
B页面:onStop -> onDestroy
9、Android中的几种动画
- 属性动画(Property Animation):可以对任意对象的属性进行动画操作,如平移、缩放、旋转等。示例代码如下:
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "translationX", 0f, 200f);
animator.setDuration(1000);
animator.start();
- 补间动画(Tween Animation):通过定义起始状态和结束状态来实现动画效果,常用的补间动画包括透明度渐变、缩放、旋转等。示例代码如下:
Animation animation = AnimationUtils.loadAnimation(context, R.anim.fade_in);
view.startAnimation(animation);
- 帧动画(Frame Animation):通过连续播放一系列预先定义好的图片帧来实现动画效果。示例代码如下:
ImageView imageView = findViewById(R.id.image_view);
imageView.setBackgroundResource(R.drawable.frame_animation);
AnimationDrawable animationDrawable = (AnimationDrawable) imageView.getBackground();
animationDrawable.start();
- 转场动画(Transition Animation):用于在Activity或Fragment之间实现平滑的过渡效果,如淡入淡出、滑动等。示例代码如下:
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.setCustomAnimations(R.anim.slide_in, R.anim.slide_out);
transaction.replace(R.id.container, newFragment);
transaction.commit();
二、UI设计
1、mipmap、drawable的区别
mipmap文件夹主要用于应用程序图标,而drawable文件夹则用于其他类型的图像资源
mipmap、drawable都是安卓存放图片资源的文件夹目录
2、自定义控件
1、组合控件。这种自定义控件不需要我们自己绘制,而是使用原生控件组合成的新控件。如标题栏。
2、继承原有的控件。这种自定义控件在原生控件提供的方法外,可以自己添加一些方法。如制作圆角,圆形图片。
3、完全自定义控件:这个View上所展现的内容全部都是我们自己绘制出来的。比如说制作水波纹进度条。
3、View的绘制原理(绘制流程)
Android绘制原理涉及到Android的View体系和绘图流程。当一个View需要绘制时,Android会调用View的onDraw()
方法,该方法中可以通过Canvas对象进行绘制操作。
绘制过程大致分为以下几个步骤:
- 测量(Measure):根据View的测量规则,确定View的尺寸大小。
- 布局(Layout):确定View在父容器中的位置和大小。
- 绘制(Draw):通过调用View的
onDraw()
方法,使用Canvas对象进行绘制操作,如绘制背景、文本、图形等。 - 重绘(Invalidate):如果View的内容发生改变或需要更新,可以调用
invalidate()
方法来触发重绘流程。
在绘制过程中,Android使用了双缓冲技术来提高绘制效率和避免闪烁。它使用了两个屏幕缓冲区,一个用于显示当前内容,另一个用于绘制下一帧的内容。当绘制完成后,交换两个缓冲区的内容,实现平滑的显示效果。
4、View、ViewGroup事件分发(绘制原理、UI事件传递)
区分:dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent
具体的触摸事件分发流程如下:
- 当触摸事件发生时,首先会调用顶层视图的
dispatchTouchEvent
方法。 - 在
dispatchTouchEvent
方法中,会按照一定的规则将触摸事件传递给目标视图,并在传递过程中调用每个视图的onTouchEvent
方法。 - 如果目标视图是一个 ViewGroup,并且该 ViewGroup 重写了
onInterceptTouchEvent
方法,并且在该方法中返回了true
,表示拦截事件,那么接下来触摸事件会直接传递给该 ViewGroup 的onTouchEvent
方法处理,而不会再继续传递给子视图。 - 如果目标视图是一个普通的 View 或者 ViewGroup,并且没有重写
onInterceptTouchEvent
方法,或者在方法中返回了false
,那么触摸事件会继续传递给子视图进行处理。
先走的是 dispatchTouchEvent
方法,然后根据 onInterceptTouchEvent
的返回值来决定是否拦截事件。
事件分发L型链传递
L型链的特点是最终会有一个组件对事件进行消费(执行点击事件逻辑)。如果没有组件消费,最终事件会重回Activity,变成U型链,也就是第二种情况
事件分发U型链传递
三、通信机制
1、Handler机制
Handler机制由四个组件组成:
1、Message:消息体
2、MessageQueue:消息队列(链表实现)
3、Handler:消息处理者
4、Looper:消息循环器
工作原理:Android中主线程是不能进行耗时操作的,子线程是不能进行更新UI的。所以就有了handler,它的作用就是实现线程之间的通信。当应用创建的时候,就会在主线程中创建handler对象,我们通过要传送的消息保存到Message中,handler通过调用sendMessage方法将Message发送到MessageQueue中,Looper对象就会不断的调用loop()方法,不断的从MessageQueue中取出Message交给handler进行处理。从而实现线程之间的通信
。
理解:Message、Handler、Looper、MessageQueue 这四者就构成了一个生产者和消费者模式。Message 相当于产品,MessageQueue 相当于传输管道,Handler 相当于生产者,Looper 相当于消费者
备注:Handler 能用于更新 UI 包含了一个隐性的前提条件:Handler 与主线程 Looper 关联在了一起。在主线程中初始化的 Handler 会默认与主线程 Looper 关联在一起,所以其 handleMessage(Message msg) 方法就会由主线程来调用。在子线程初始化的 Handler 如果也想执行 UI 更新操作的话,则需要主动获取 mainLooper 来初始化 Handler
2、Android中跨进程通讯的几种方式
IPC全称为Inter-Process Communication,含义为进程间通信,指的是两个进程之间进行数据交换的过程。
Android 跨进程通信,像intent,contentProvider,广播,service都可以跨进程通信。
1、intent:这种跨进程方式并不是访问内存的形式,它需要传递一个uri,比如说打电话。
2、contentProvider:这种形式,是使用数据共享的形式进行数据共享。
3、service:远程服务,aidl
4、AIDL:功能强大,支持进程间一对多的实时并发通信,并可实现RPC(远程过程调用)
5、Broadcast Receiver:广播,但只能单向通信,接收者只能被动接受信息。
6、文件共享:在非高并发的情况下共享简单的数据。
7、Socket:通过网络传输数据。
3、AIDL理解
AIDL(Android Interface Definition Language):Android中用于定义进程间通信接口的一种语言(基于 Binder 的)。
饿了么HermesEventBus跨进程通信,本质也是AIDL通信
AIDL: 每一个进程都有自己的Dalvik VM实例,都有自己的一块独立的内存,都在自己的内存上存储自己的数据,执行着自己的操作,都在自己的那片狭小的空间里过完自己的一生。而aidl就类似与两个进程之间的桥梁,使得两个进程之间可以进行数据的传输,跨进程通信有多种选择,比如 BroadcastReceiver , Messenger 等,但是 BroadcastReceiver 占用的系统资源比较多,如果是频繁的跨进程通信的话显然是不可取的;Messenger 进行跨进程通信时请求队列是同步进行的,无法并发执行。
4、Binder机制原理
Binder是Android中进程间通信的核心机制,它通过驱动层和框架层的配合实现进程间通信。Binder的工作原理如下:
- 服务端注册Binder对象:服务端通过Binder机制创建一个Binder对象,并将其注册到ServiceManager中,以便客户端可以通过Binder对象的引用来访问服务端。
- 客户端获取Binder对象:客户端通过ServiceManager获取服务端注册的Binder对象的引用。
- 客户端与服务端通信:客户端通过Binder对象的引用调用服务端提供的方法,实现进程间通信。
- 跨进程通信:Binder在内核层通过驱动进行跨进程通信,具体实现是通过进程间共享内存和进程间信号量来完成的。当客户端调用服务端的方法时,Binder会将客户端的请求封装成一个数据结构,然后通过驱动将数据发送给服务端。服务端接收到请求后,再将处理结果返回给客户端。
- Binder对象的引用计数:Binder对象的引用计数机制可以确保在没有客户端使用时,服务端可以被回收。当没有客户端使用时,Binder对象会调用其onLastStrongRef()方法,然后被销毁。
IPC 机制:进程间通信机制
Binder机制是Android系统中的一个重要的进程间通信(IPC)机制。以下是一些与Binder机制相关的面试知识点:
-
Binder的基本原理:Binder机制基于C/S架构,使用了驱动层、服务管理器、Binder驱动和Binder对象等组件来实现进程间通信。
-
Binder的角色:在Binder机制中,有三种主要角色:客户端、服务端和Binder驱动。客户端通过代理对象与服务端进行通信,而Binder驱动负责处理进程间的数据传输和通信。
-
Binder对象:在Binder机制中,进程间通信是通过Binder对象进行的。每个Binder对象都有一个唯一的标识符(Binder引用),用于在不同进程之间进行通信。
-
进程间通信的方式:Binder机制提供了多种进程间通信的方式,包括跨进程方法调用、跨进程数据传输和跨进程事件通知等。
-
AIDL(Android Interface Definition Language):AIDL是一种用于定义客户端和服务端之间接口的语言,它可以帮助开发者生成跨进程通信所需的代码。
-
进程间通信的安全性:Binder机制通过权限验证和用户ID(UID)来确保进程间通信的安全性,防止未授权的访问和恶意行为。
-
Binder的性能优化:在使用Binder机制时,可以通过减少跨进程调用次数、使用轻量级数据传输方式和优化服务端代码等方式来提高性能。
5、linux的共享内存有几次拷贝,为啥android不使用linux的IPC机制
Linux 中的共享内存在使用时通常涉及两次拷贝。首先,数据从用户空间被拷贝到内核空间,然后再从内核空间拷贝到另一个进程的用户空间。
Android 之所以不直接使用 Linux 的共享内存,是因为 Android 使用了基于 Binder 的进程间通信机制。Binder 通过将数据序列化为跨进程的消息进行传递,避免了额外的内核空间拷贝。这种设计可以提供更高效的进程间通信,并且具有更好的安全性和稳定性。
此外,Android 还提供了其他的共享内存机制,如 Ashmem(匿名共享内存),用于实现特定的共享内存需求,但它并不是 Android 应用程序常用的进程间通信方式。
四、架构设计
1、MVP,MVC,MVVM
参考:https://blog.csdn.net/zg0601/article/details/123587933
1、MVC(Model-View-Controller)
可以理解为:1、页面万能activity + xml +model 或者 2、adapter 中 adapter +xml +model
MVC是模型-视图-控制器,它是MVC、MVP、MVVM这三者中最早产生的框架,其他两个框架是以它为基础发展而来的。
优点:耦合性低:视图层和业务层分离
缺点:Controller层与View层之间是一一对应的,断绝了View层复用的可能,产生了很多冗余代码
2、MVP(Model-View-Presenter)
MVP是模型-视图-表示器,Model层提供数据,View层负责视图显示,Controller/Presenter层负责逻辑的处理。将Controller改名为Presenter的同时改变了通信方向。
Model:模型层,用于数据存储以及业务逻辑。View:视图层,用于展示与用户实现交互的页面,通常实现数据的输入和输出功能。Presenter:表示器,用于连接M层、V层,完成Model层与View层的交互,还可以进行业务逻辑的处理。
- 数据模型 Model 是 M 层,负责处理数据的获取、存储和处理。
- Activity 和 XML 是 V 层,负责展示界面和与用户交互。
- 抽象类 HomePresenter 是 P 层,负责处理业务逻辑和协调 Model 和 View 的交互。
在 Android MVP 中,Contract 是用于定义 Model、View 和 Presenter 之间的接口规范的一个契约类。它通常包含了以下几个接口:
- View 接口:定义了 View 层的操作方法,供 Presenter 层调用。
- Presenter 接口:定义了 Presenter 层的操作方法,供 View 层调用。
- Model 接口:定义了 Model 层的操作方法,供 Presenter 层调用。
Contract 的存在可以帮助实现解耦和模块化,使得各个层之间的交互更加清晰和可维护。
优点:所以View层可以抽离出来做成组件,view复用性强
缺点:
1、由于View层和Model层都需要经过Presenter层,导致Presenter层比较复杂,维护起来也会有一定的问题
2、没有绑定数据,所有数据都需要Presenter层进行“手动同步”
3、MVVM(Model-View-ViewModel)
MVVM是模型-视图-视图模型。MVVM与MVP框架区别在于:MVVM采用双向绑定:View的变动,自动反映在ViewModel,反之亦然。
Model:数据模型(数据处理业务),指的是后端传递的数据。View:视图(包含 xml),将Model的数据以某种方式展示出来。ViewModel:视图模型,数据的双向绑定(当Model中的数据发生改变时View就感知到,当View中的数据发生变化时Model也能感知到),是MVVM模式的核心。ViewModel 层把 Model 层和 View 层的数据同步自动化了,解决了 MVP 框架中数据同步比较麻烦的问题,不仅减轻了 ViewModel 层的压力,同时使得数据处理更加方便——只需告诉 View 层展示的数据是 Model 层中的哪一部分即可。
优点:
1.低耦合:视图(View)可以独立于Model变化和修改
2.可重用性:你可以把一些视图逻辑放在一个 ViewModel 里面,让很多 view 重用这段视图逻辑。
缺点:
1.对于简单的图形界面不实用
2.如果多个页面共用一个model(个人也不建议这么干),会导致model数据变多,内存开销变大,影响性能;
3.view和model的绑定,使页面异常追踪变得不方便。
2、热修复的原理
Android热修复(Hotfix)是一种在运行时修复应用程序的机制,它可以在不重新安装或升级应用的情况下,修复应用中的bug或添加新功能。Android热修复的原理通常涉及以下几个步骤:
1、补丁生成:开发者根据需要修复的问题或添加的功能,生成一个代码补丁(Patch),通常是一个包含修复或新增代码的.dex文件。
2、下载补丁:应用在启动时,通过网络或其他方式下载最新的补丁文件。
3、补丁加载:应用在下载完补丁后,将补丁文件加载到内存中。
4、类加载器替换:通过替换应用的类加载器,将新加载的补丁类加入到应用的类加载器中。
需要注意的是,Android热修复虽然可以在运行时修复问题,但也有一些限制。例如,无法修复资源文件、Manifest文件等静态资源的问题;同时,由于涉及到动态加载和替换代码,需要保证补丁的正确性和安全性,以避免潜在的风险。因此,在使用热修复技术时,开发者需要谨慎处理,并进行充分的测试和验证。我们知道Java虚拟机 —— JVM 是加载类的class文件的,而Android虚拟机——Dalvik/ART VM 是加载类的dex文件,而他们加载类的时候都需要ClassLoader,ClassLoader有一个子类BaseDexClassLoader,而BaseDexClassLoader下有一个数组——DexPathList,是用来存放dex文件,当BaseDexClassLoader通过调用findClass方法时,实际上就是遍历数组,找到相应的dex文件,找到,则直接将它return。而热修复的解决方法就是将新的dex添加到该集合中,并且是在旧的dex的前面,所以就会优先被取出来并且return返回。
5、修复生效:应用在执行时,会优先使用补丁中的修复或新增代码逻辑,从而达到修复bug或添加新功能的目的。
3、热修复单classloader和双classloader的区别
热修复单ClassLoader和双ClassLoader的区别在于:
单ClassLoader方案中,所有的补丁包和原始包都会被加载到同一个ClassLoader中,因此可能会出现类冲突的问题。而双ClassLoader方案中,补丁包和原始包会被加载到不同的ClassLoader中,从而避免了类冲突的问题。同时,双ClassLoader方案中,补丁包的生命周期也可以更好地控制,可以更加灵活地管理和升级补丁。但是双ClassLoader方案也会带来一些额外的开销和复杂度。
4、设计一个 日志系统模块 你都能想到哪些点
1.需要保证日志系统收集到的数据的有效性和完整性,在app崩溃时日志不丢失.
2.保证收集日志的过程不影响app的性能,不能占用过高cpu资源,频繁IO操作造成卡顿现象。
使用 MMAP就非常适用于解决这个问题,MMAP是一种内存映射文件的方法,将文件或者一些对象映射到进程的地址空间。
因为在app崩溃的时候,MMAP写入的映射内存数据也会被操作系统自动回写到文件磁盘空间。
Android中使用mmap,可以通过RandomAccessFile与MappedByteBuffer来配合
五、性能优化
1、内存优化
1、android 内存分类
Android每个应用程序可以分配的内存包括以下几部分,按照从大到小的顺序:
-
Java堆内存(Java Heap Memory):用于存储Java对象的内存空间。这是应用程序最大的内存区域,其中包括了各种对象实例、数组等。Java堆内存的大小可以通过虚拟机参数进行调整,默认情况下通常在16MB到256MB之间。
-
Native堆内存(Native Heap Memory):用于存储C/C++代码和本地库的内存空间。Native堆内存的大小一般比Java堆内存小得多,具体大小取决于设备和系统配置。
-
栈内存(Stack Memory):用于存储方法调用、局部变量、方法参数等数据。每个线程都会有一个独立的栈内存,用于维护方法调用的上下文。栈内存的大小通常较小,一般在几KB到几MB之间。
-
运行时常量池(Runtime Constant Pool):用于存储常量和符号引用。运行时常量池包含了类、方法、字段的符号引用以及字符串等常量。它位于方法区(Method Area)中,占用一部分堆内存空间。
-
方法区(Method Area):用于存储类的元数据信息、静态变量、常量池等数据。方法区也是堆内存的一部分,但与Java堆内存和Native堆内存有所区别。
备注:
1、其中 OOM 指的是 java 堆内存
2、图片库Fracsco 、ImageLoader图片加载库的缓存在Native 堆内存,Glide、Picago 缓存在 Java 堆内存。这是前者使用了自己的缓存机制,将缓存存储在Native堆内存中,以提高性能和效率,而后者使用了Android系统提供的LRU缓存机制。
2、OOM 优化
1、Manifest文件中设置android:largeHeap="true"来增加应用程序的堆内存大小
2、优化代码:通过优化应用程序的代码和算法,可以减少内存的使用,从而提高应用程序的性能和稳定性。
3、使用图片压缩:对于需要大量使用图片资源的应用程序,可以使用图片压缩技术来减小图片的尺寸和大小,从而降低内存的使用。
4、使用分页加载:对于需要加载大量数据的应用程序,可以使用分页加载技术来分批加载数据,减少一次性加载数据所需的内存空间。
5、使用内存缓存:对于需要频繁读写数据的应用程序,可以使用内存缓存技术来缓存数据,避免重复读写和占用过多的内存空间。
3、Handler 引起的内存泄漏。
原因:非静态内部类,或者匿名内部类。使得Handler默认持有外部类的引用
解决:将Handler声明为静态内部类,通过弱引用的方式调用,就不会持有Activity的引用,其生命周期就和外部类无关,
2、网络优化
减少网络请求次数:通过合并请求、缓存数据
使用压缩技术:gzip
使用CDN加速:通过使用CDN(内容分发网络)来加速数据传输
3、绘制优化
- 减少绘制操作:用
invalidate()
方法的局部刷新、判断onDraw()
方法中的绘制条件来减少绘制操作。 - 使用硬件加速:设置
android:hardwareAccelerated="true"
来开启硬件加速,将部分绘制操作转移到GPU进行处理,提高绘制效率。 - 布局优化:减少布局层次的复杂性,避免过深的嵌套。
- 视图复用:根使用
RecyclerView
进行列表项的复用等。
4、App启动优化
延迟初始化和懒加载:将一些不必要立即初始化的组件或资源延迟到真正需要时再初始化,避免不必要的启动时间消耗。
减少主线程阻塞:在应用程序启动过程中,主线程的阻塞会导致界面卡顿,影响用户体验。可以将耗时的操作(如网络请求、数据库查询等)放在子线程中执行,避免阻塞主线程。
5、Leakcanery中referencequeue底层实现是什么
LeakCanary中ReferenceQueue底层实现是使用Java中的ReferenceQueue类。ReferenceQueue是一个队列,用于保存被垃圾回收器标记为可回收的对象的引用。当这些对象被垃圾回收器回收时,它们的引用会被添加到ReferenceQueue中。LeakCanary会检查ReferenceQueue中是否有引用,如果有,就说明该对象已经被回收,因此可以判断出是否存在内存泄漏。
在LeakCanary中,ReferenceQueue
底层的实现是使用Java中的java.lang.ref.ReferenceQueue
类。这个类是Java提供的用于处理弱引用、软引用和虚引用的队列。它的主要作用是收集被垃圾回收器回收的对象,并通知相应的引用对象。通过使用ReferenceQueue
,LeakCanary能够跟踪并监测应用中的潜在内存泄漏问题。
六、第三方框架
1、讲一讲Glide的原理
内部是使用 LruCache、弱引用和硬盘缓存实现的。 先从缓存中读取,然后磁盘 最后网络
图片加载策略: 首先从ActivateResource获取,是个值为弱引用的Map
LruCache 最近最少使用算法。核心思想是当缓存满时,会优先淘汰那些近期最少使用的缓存对象。其内部采用的数据结构是LinkedHashMap,当缓存满时,把最近最少使用的对象从内存中移除,并提供了get和put方法来完成缓存的获取和添加操作。