-
onCreate():正在被创建,常用来初始化工作,比如调用 setContentView 加载界面布局资源,初始化 Activity 所需数据等;
-
onRestart():正在重新启动,一般情况下,当前 Acitivty 从不可见重新变为可见时,OnRestart 就会被调用;
-
onStart():正在被启动,此时 Activity 可见但不在前台,还处于后台,无法与用户交互;
-
onResume():获得焦点,此时 Activity 可见且在前台并开始活动,这是 与onStart 的区别所在;
-
onPause():正在停止,此时可做一些存储数据、停止动画等工作,但不能太耗时,因为这会影响到新 Activity 的显示,onPause 必须先执行完,新 Activity 的 onResume 才会执行;
-
onStop():即将停止,可以做一些稍微重量级的回收工作,比如注销广播接收器、关闭网络连接等,同样不能太耗时;
-
onDestroy():即将被销毁,Activity 生命周期中的最后一个回调,常做回收工作、资源释放;
-
不设置 android:configChanges ,切屏会销毁当前 Activity,重新加载各个生命周期,切横屏时会执行一次,切竖屏时会执行两次。
-
设置 android:configChanges="orientation"
- 在Android5.1 即 API 23级别下,切屏会重新调用各个生命周期,切横、竖屏时只会执行一次。
- 在Android9 即API 28级别下,切屏不会重新调用各个生命周期,只会执行 onConfigurationChanged 方法
-
设置 android:configChanges="orientation|keyboardHidden|screenSize":切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法。
A -> B
A:onPause
B:onCreate -> onStart -> onResume
A:onStop
B -> A
B:onPause
A:onRestart -> onStart -> onResume
B:onStop -> onDestroy
//B是透明主题
//A:不会调用 onStop
- 前台进程:与用户正在交互的 Activity 或者 Activity 用到的 Service 等,如果系统内存不足时前台进程是最晚被杀死的。
- 可见进程:处于暂停状态(onPause)的 Activity 或者绑定在其上的 Service,即被用户可见,但由于失了焦点而不能与用户交互。
- 服务进程:其中运行着使用 startService 方法启动的 Service,虽然不被用户可见,但是却是用户关心的,例如用户正在非音乐界面听的音乐或者正在非下载页面下载的文件等;当系统要空间运行,前两者进程才会被终止。
- 后台进程:其中运行着执行 onStop 方法而停止的程序,但是却不是用户当前关心的,例如后台挂着的QQ,这时的进程系统一旦没了有内存就首先被杀死。
- 空进程:不包含任何应用程序的进程,这样的进程系统是一般不会让他存在的。
- Handler
- 广播
- 事件总线:EventBus、RxBus、Otto
- 接口回调
- Bundle 和 setArguments(bundle)
系统配置发生改变时导致 Activity 被杀死并重新创建、资源内存不足导致优先级低的 Activity 被杀死。
系统会调用 onSaveInstanceState 来保存当前 Activity 的状态,此方法调用在onStop之前,与onPause 没有既定的时序关系;
当Activity被重建后,系统会调用 onRestoreInstanceState,并且把 onSaveInstanceState 方法所保存的 Bundle 对象同时传参给 onRestoreInstanceState 和 onCreate(),因此可以通过这两个方法判断Activity 是否被重建 ,调用在 onStart 之后;
standard标准模式:每次启动一个 Activity 都会重新创建一个新的实例,不管这个实例是否已经存在,此模式的 Activity 默认会进入启动它的 Activity 所属的任务栈中;
singleTop栈顶复用模式:如果新 Activity 已经位于任务栈的栈顶,那么此 Activity 不会被重新创建,同时会回调 onNewIntent 方法,如果新 Activity 实例已经存在但不在栈顶,那么 Activity 依然会被重新创建;
singleTask栈内复用模式:只要 Activity 在一个任务栈中存在,那么多次启动此 Activity 都不会重新创建实例,并回调 onNewIntent 方法,此模式启动 Activity,系统首先会寻找是否 Activity 存在想要的任务栈,如果不存在,就会重新创建一个任务栈,然后把创建好 Activity 的实例放到栈中,具有 clearTop 功能;
singleInstance单实例模式:这是一种加强的 singleTask 模式,具有此种模式的 Activity 只能单独地位于一个任务栈中,且此任务栈中只有唯一一个实例;
FLAG_ACTIVITY_NEW_TASK : 对应singleTask启动模式;
FLAG_ACTIVITY_SINGLE_TOP : 对应singleTop启动模式;
**FLAG_ACTIVITY_CLEAR_TOP :**当它启动时,在同一个任务栈中所有位于它上面的 Activity 都要出栈。这个标记位一般会和 singleTask 模式一起出现,在这种情况下,被启动 Activity 的实例如果已经存在,那么系统就会回调 onNewIntent。如果被启动的 Activity 采用 standard 模式启动,那么它以及连同它之上的 Activity 都要出栈,系统会创建新的 Activity 实例并放入栈中;
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS : 具有这个标记的 Activity 不会出现在历史 Activity 列表中;
window (实现类是 PhoneWindow )相当于一个容器,里面盛放着很多 view,这些 view 是以树状结构组织起来的。
一个 Activity 对应一个 Window,Activity 本身是没办法处理显示什么控件(view)的,是通过 PhoneWindow 进行显示的。
在保证有权限访问的情况下,通过隐式 Intent 进行目标 Activity 的 IntentFilter 匹配,原则是:
- 一个 intent 只有同时匹配某个 Activity 的 intent-filter 中的 action、category、data 才算完全匹配,才能启动该Activity;
- 一个 Activity 可以有多个 intent-filter,一个 intent 只要成功匹配任意一组 intent-filter,就可以启动该Activity;
应用程序有一段时间响应不够灵敏,系统会向用户显示一个对话框,这个对话框称作应用程序无响应(ANR:Application Not Responding)对话框。用户可以选择让程序继续运行,但是,用户在使用应用程序时,并不希望每次都要处理这个对话框。因此,在程序里对响应性能的设计很重要,这样,系统不会显示 ANR 给用户。
不同的组件发生 ANR 的时间不同(均为前台):
- Activity:5s
- BroadcastReceiver:10s
- Service:20s
开发机器上出现问题,可以通过查看 /data/anr/traces.txt 。
出现原因:
- 主线程做了耗时操作(IO操作等)
- 主线程使用 Object.wait()、Thread.sleep() 等错误的操作
- 应用在规定时间内没有处理完相关事件(Activity、BroadcastReceiver 、Service )
处理:
- 使用 AsyncTask、Thread、HandlerThread 等处理耗时操作
- 使用 Handler 线程之间的通信,而不是使用 Object.wait() 或者 Thread.sleep() 来阻塞主线程
- onCreate 和 onResume 回调中尽量避免耗时的代码
AsyncTask 是一种轻量级的异步任务类,可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程并主线程中更新 UI,通过 AsyncTask 可以更加方便执行后台任务以及在主线程中访问 UI,但是 AsyncTask 并不适合进行特别耗时的后台任务,对于特别耗时的任务来说,建议使用线程池。
AsyncTask 是一个抽象的泛型类,提供了 Params(参数类型)、Progress(后台任务的执行进度和类型) 和 Result(后台任务的返回结果的类型)这三个泛型参数,如果 AsyncTask 不需要传递具体的参数,那么这三个泛型参数可以用 Void 来代替。
AsyncTask 里面线程池是一个核心线程数为 CPU + 1,最大线程数为 CPU * 2 + 1,工作队列长度为128 的线程池,线程等待队列的最大等待数为 28,但是可以自定义线程池。线程池是由 AsyncTask 来处理的,线程池允许tasks并行运行,需要注意的是并发情况下数据的一致性问题,新数据可能会被老数据覆盖掉。所以希望tasks能够串行运行的话,使用SERIAL_EXECUTOR。
- AsyncTask 中有两个线程池(SerialExecutor 和 THREAD_POOL_EXECUTOR)和一个Handler(InternalHandler),SerialExecutor 用于任务的排队,THREAD_POOL_EXECUTOR 用于真正地执行任务,InternalHandler 用于将执行环境从线程池切换到主线程。
- sHandler 是一个静态的 Handler 对象,为了能够将执行环境切换到主线程,这就要求 sHandler这个对象必须在主线程创建。由于静态成员会在加载类的时候进行初始化,因此就要求AsyncTask 的类必须在主线程中加载,否则同一个进程中的 AsyncTask 都将无法正常工作。
- 生命周期:Activity 销毁之前,没有取消 AsyncTask,有可能让应用崩溃(crash)。
- 内存泄漏:如果 AsyncTask 被声明 非静态内部类,会保留一个对 Activity 的引用,AsyncTask 的后台线程还在执行,它将继续在内存里保留这个引用,导致 Activity 无法被回收,引起内存泄漏。
- 结果丢失:屏幕旋转或 Activity 在后台被系统杀掉等情况会导致 Activity 的重新创建,之前运行的 AsyncTask 会持有一个之前 Activity 的引用,这个引用已经无效,这时调用 onPostExecute()再去更新界面将不再生效。
- 并行还是串行
- Android1.6 之前,串行
- Android 1.6 之后,采用线程池处理并行任务
- 从Android 3.0开始,一个线程来串行执行任务。可以使用 executeOnExecutor() 方法来并行地执行任务。
- View 动画:
- 作用对象是 View,可用 xml 定义,建议使用 xml 比较易读
- 四种类型:平移、旋转、缩放、透明度
- 帧动画:
- 通过 AnimationDrawable 实现,容易 OOM
- 属性动画:
- 可作用于任何对象,可用 xml 定义,Android 3 引入,建议代码实现比较灵活
- 包括 ObjectAnimator、ValuetAnimator、AnimatorSet
- 时间插值器:根据时间流逝的百分比计算当前属性改变的百分比,系统预置匀速、加速、减速等插值器
- 类型估值器:根据当前属性改变的百分比计算改变后的属性值,系统预置整型、浮点、色值等类型估值器
- 使用注意事项:避免使用帧动画,容易OOM;界面销毁时停止动画,避免内存泄漏;开启硬件加速,提高动画流畅性
- 硬件加速原理:将 cpu 一部分工作分担给 gpu ,使用 gpu 完成绘制工作;从工作分摊和绘制机制两个方面优化了绘制速度
属性动画真正的实现了 view 的移动,补间动画对 view 的移动只是在不同地方绘制了一个影子,实际对象还是处于原来的地方。 当动画的 repeatCount 设置为无限循环时,如果在 Activity 退出时没有及时将动画停止,属性动画会导致Activity无法释放而导致内存泄漏,而补间动画却没问题。 xml 文件实现的补间动画,复用率极高。在 Activity切换,窗口弹出时等情景中有着很好的效果。 使用帧动画时需要注意,不要使用过多特别大的图,容导致内存不足。
- 属性动画:其实就是利用插值器和估值器,来计算出各个时刻 View 的属性,然后通过改变View的属性来,实现View的动画效果。
- View 动画:只是影像变化,view的实际位置还在原来的地方。
- 帧动画:是在xml中定义好一系列图片之后,使用 AnimationDrawable 来播放的动画。
- 属性动画:
- 插值器:根据时间的流逝的百分比来计算属性改变的百分比
- 估值器:根据插值器的结果计算出属性变化了多少数值
为 bundle 传递数据时,只支持基本数据类型,所以在传递数据时,要将对象序列化转化成可以存储或者可以传输的本质状态,即字节流。序列化后的对象可以在网络,页面之间传递,也可以存储到本地。
- MaterialDesign设计风格
- 支持 64 位ART虚拟机(5.0推出的ART虚拟机,在5.0之前都是Dalvik。他们的区别是: Dalvik,每次运行,字节码都需要通过即时编译器转换成机器码(JIT)。 ART,第一次安装应用的时候,字节码就会预先编译成机器码(AOT))
- 通知详情可以用户自己设计
- 动态权限管理
- 支持快速充电的切换
- 支持文件夹拖拽应用
- 相机新增专业模式
- 多窗口支持
- V2签名
- 增强的Java8语言模式
- 夜间模式
-
优化通知
通知渠道 (Notification Channel) 通知标志 休眠 通知超时 通知设置 通知清除
-
画中画模式:清单中 Activity 设置 android:supportsPictureInPicture
-
后台限制
- 室内WIFI定位
- “刘海”屏幕支持
- 安全增强
- 夜间模式:包括手机上的所有应用都可以为其设置暗黑模式。
- 桌面模式:提供类似于PC的体验,但是远远不能代替PC。
- 屏幕录制:通过长按“电源”菜单中的"屏幕快照"来开启。
- 短信更新改进:聊天泡泡
- 隐私和权限:位置、麦克风和摄像头的一次性权限许可
- 内置屏幕录制
- 适配不同设备:折叠手机等
- 网络优化:5G
Serializable 是序列化的意思,表示将一个对象转换成存储或可传输的状态。序列化后的对象可以在网络上进传输,也可以存储到本地。
除了 Serializable 之外,使用 Parcelable 也可以实现相同的效果,不过不同于将对象进行序列化,Parcelable方式的实现原理是将一个完整的对象进行分解,而分解后的每一部分都是Intent所支持的数据类型,这也就实现传递对象的功能了。
作用:
-
永久性保存对象,保存对象的字节序列到本地文件中;
-
通过序列化对象在网络中传递对象;
-
通过序列化在进程间传递对象。
序列化方法的原则
-
在使用内存的时候,Parcelable 比 Serializable 性能高,所以推荐使用 Parcelable。
-
Serializable 在序列化的时候会产生大量的临时变量,从而引起频繁的GC。
-
Parcelable 不能使用在要将数据存储在磁盘上的情况,因为 Parcelable 不能很好的保证数据的持续性在外界有变化的情况下。尽管 Serializable 效率低点,但此时还是建议使用 Serializable 。
JSON 的全称是 JavaScript Object Notation,也就是 JavaScript 对象表示法 JSON是存储和交换文本信息的语法,类似XML,但是比XML更小、更快,更易解析 JSON是轻量级的文本数据交换格式,独立于语言,具有可描述性,更易理解,对象可以包含多个名称/值对,比如:
{"name":"test" , "age":25}
不同手机型号,分配的内存大小不同,可以设置 android:largeheap = "true"。
-
从 Activity 中启动新的 Activity 时,可以直接 mContext.startActivity(intent)
-
Context 中启动 Activity 须给 intent 设置Flag:
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//设置类型
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT)
//权限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINOW" />
可以,使用全局的 BroadCastRecevier 能进行跨进程通信,但是注意它只能被动接收广播,此外,LocalBroadCastRecevier 只限于本进程的广播间通信。
一般 20 为一页,滑动到底部,监听滑动事件,加载更多。
编译期注解:RetentionPolicy.CLASS,作用域 class 字节码上,生命周期只有在编译器间有效,常使用 APT(注解处理器)+ JavaPoet(Java源代码生成框架),EventBus、Dagger 等使用。
运行时注解:RetentionPolicy.RUNTIME,注解不仅被保存到 class 文件中,jvm 加载 class 文件之后,仍然存在;获取注解,需要通过反射来获取运行时注解。
SAX(Simple API for XML):是一种基于事件的解析器,事件驱动的流式解析方式,从文件的开始顺序解析到文档的结束,不可暂停或倒退。 **优点:**解析速度快,占用内存少。非常适合在 Android 移动设备中使用。 **缺点:**不会记录标签的关系,而要让你的应用程序自己处理,这样就增加了你程序的负担。 **工作原理:**对文档进行顺序扫描,当扫描到文档(document)开始与结束、元素(element)开始与结束、文档 (document)结束等地方时通知事件处理函数,由事件处理函数做相应动作,然后继续同样的扫描,直至文档结束。
PULL:运行方式和 SAX 类似,都是基于事件的模式。不同的是,在 PULL 解析过程中返回的是数字,且我们需要自己获取产生的事件然后做相应的操作,而不像 SAX 那样由处理器触发一种事件的方法,执行我们的代码。
优点: PULL解析器小巧轻便,解析速度快,简单易用,非常适合在Android移动设备中使用,Android系统内部在解析各种 XML 时也是用 PULL 解析器,Android官方推荐开发者们使用Pull解析技术。Pull解析技术是第三方开发的开源技术,它同样可以应用于 JavaSE 开发。
DOM:即对象文档模型,它是将整个XML文档载入内存(所以效率较低,不推荐使用),每一个节点当做一个对象,结合代码分析。DOM实现时首先为XML文档的解析定义一组接口,解析器读入整个文档,然后构造一个驻留内存的树结构,这样代码就可以使用DOM接口来操作整个树结构。 由于DOM在内存中以树形结构存放,因此检索和更新效率会更高。但是对于特别大的文档,解析和加载整个文档将会很耗资源。 当然,如果XML文件的内容比较小,采用DOM是可行的。 **工作原理:**使用DOM对XML文件进行操作时,首先要解析文件,将文件分为独立的元素、属性和注释等,然后以节点树的形式在内存中对XML文件进行表示,就可以通过节点树访问文档的内容,并根据需要修改文档。
- Activity.runOnUiThread(Runnable)
- Handler
- View.post(Runnable),View.postDelay(Runnable, long)(可以理解为在当前操作视图UI线程添加队列)
- AsyncTask
- RxJava
- LiveData
- jar 包里面只有代码
- aar 里面有代码、资源文件,比如 drawable 文件,xml资源文件,对于一些不常变动的 Android Library,我们可以直接引用 aar,加快编译速度。
//AndroidManifest.xml
android:installLocation="internalOnly":表示程序只能被安装在内存中,如果内存为空,则程序将不能成功安装,因为安装在 SD 卡中时会接收不到系统的广播消息(暂时未验证)
//添加权限
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
//注册广播
<!--注册接收系统开机广播消息的广播接收者-->
<receiver
android:name=".broadcastReceiver.MyBroadcastReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<category android:name="android.intent.category.HOME" />
</intent-filter>
</receiver>
//高版本基本没用,每个手机厂商都有一个手机管家,可以在里面设置自启动管理
BroadcastReceiver | LocalBroadcastReceiver | |
---|---|---|
应用场景 | 应用之间的传递消息 | 应用内部传递消息 |
安全 | 使用 Content API,本质上它是跨应用的,所以在使用它时必须要考虑到不要被别的应用滥用 | 不需要考虑安全问题,因为它只在应用内部有效。 |
原理 | Binder | Handler |
- commit 返回 boolean,表示修改是否提交成功,apply 没有返回值
- commit 将数据同步的提交到硬件磁盘,apply 先将修改数据原子提交到内存, 而后异步真正提交到硬件磁盘
private int getParents(View view){
if(view.getParents() == null)
return 0;
} else {
return (1 + getParents(view.getParents));
}
}
assets:不会在 R 文件中生成相应标记,存放到这里的资源在打包时会打包到程序安装包中。(通过 AssetManager 类访问这些文件)
res:会在 R 文件中生成 id 标记,资源在打包时如果使用到则打包到安装包中,未用到不会打入安装包中。
- Handler : 负责发送并处理消息
- Looper::负责关联线程以及消息的分发,在该线程下从 MessageQueue 获取 Message,分发给 Handler ;
- MessageQueue :消息队列,负责消息的存储与管理,负责管理由 Handler 发送过来的 Message;
- Message:消息,封装信息;
- Handler 通过 sendMessage() 发送 Message 到消息队列 MessageQueue。
- Looper 通过 loop() 不断提取触发条件的Message,并将 Message 交给对应的 target handler(发送该消息的 Handler )来处理。
- target handler调用自身的 handleMessage() 方法来处理 Message。
//基本使用
var handler = Handler {}
handler.sendMessage(msg)
handler.post(runnable)
//Handler 基本使用
class LooperThread : Thread() {
lateinit var mHandler: Handler
override fun run() {
Looper.prepare()//创建 Looper
mHandler = Handler {
}
Looper.loop()//不断尝试从 MessageQueue 中获取 Message , 并分发给对应的 Handler
}
}
//Handler 构造方法
public Handler(Callback callback, boolean async) {
...
mLooper = Looper.myLooper();
//检查当前线程的 Looper 是否存在
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
//Looper 持有一个 MessageQueue
mQueue = mLooper.mQueue;
...
}
Handler 跟线程的关联是靠 Looper 来实现的。
Handler post 等方法 最终都会调用 MessageQueue.enqueueMessage(Message,long)
,所以 Message 由 MessageQueue 存储和管理。
Message 存储和管理之后,就要进行分发与处理,调用 Looper.loop() 。
public static void loop() {
...
final Looper me = myLooper();
...
for (;;) {//死循环 不断从 MessageQueue 获取消息处理
Message msg = queue.next();
if (msg == null) {
return;
}
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
...
try {
//msg.target 就是发送该消息的 Handler
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
...
//回收 message
msg.recycleUnchecked();
}
}
queue.next():获取消息
Message next() {
...
for (;;) {
//本地方法
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
if (mQuitting) {
dispose();
return null;
}
}
}
msg.target 是发送该消息的 Handler,回调该 Handler :
public void dispatchMessage(Message msg) {
if (msg.callback != null) {//callback 优先级比较高
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
ActivityThread.main() 方法中调用了 Looper.prepareMainLooper() 方法创建了 主线程的 Looper ,并且调用了 loop() 方法,所以我们就可以直接使用 Handler 了。
Handler 允许我们发送延时消息,如果在延时期间用户关闭了 Activity,那么该 Activity 会泄露。 这个泄露是因为 Message 会持有 Handler,而又因为 Java 的特性,内部类会持有外部类,使得 Activity 会被 Handler 持有,这样最终就导致 Activity 泄露。
解决:将 Handler 定义成静态的内部类,在内部持有 Activity 的弱引用,并在Acitivity的onDestroy()中调用handler.removeCallbacksAndMessages(null) 及时移除所有消息。
优先处理消息的权利。
- 通过 Message 的静态方法 Message.obtain()
- 通过 Handler 的公有方法 handler.obtainMessage()
涉及到 Linux pipe/epoll 机制,简单说就是在主线程的 MessageQueue 没有消息时,便阻塞在loop的 queue.next() 中的 nativePollOnce() 方法里,此时主线程会释放 CPU 资源进入休眠状态,直到下个消息到达或者有事务发生,通过往 pipe 管道写端写入数据来唤醒主线程工作。这里采用的 epoll 机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质是同步I/O,即读写是阻塞的。所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。
//加上当前时间
SystemClock.uptimeMillis() + delayMillis
msg.when = when;
handler.postDelay 并不是先等待一定的时间再放入到MessageQueue中,而是直接进入MessageQueue,以 MessageQueue 的时间顺序排列和唤醒的方式结合实现的。
- 将 Runnable post 到主线程执行;
- 利用 Looper 判断当前线程是否是主线程。
主线程不允许退出,退出就意味 APP 要挂。
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
Toast.makeText(HandlerActivity.this, "test", Toast.LENGTH_SHORT).show();
Looper.loop();
}
}).start();
使用 GPU 实现图形绘制,通过底层软件代码,将 CPU 不擅长的图形计算转换成 GPU 专用指令,由GPU完成。
- CPU更擅长复杂逻辑控制,而GPU得益于大量ALU和并行结构设计,更擅长数学运算。
- 页面由各种基础元素(DisplayList)构成,渲染时需要进行大量浮点运算。
- 硬件加速条件下,CPU用于控制复杂绘制逻辑、构建或更新DisplayList;GPU用于完成图形计算、渲染DisplayList。
- 硬件加速条件下,刷新界面尤其是播放动画时,CPU只重建或更新必要的DisplayList,进一步提高渲染效率。
- 实现同样效果,应尽量使用更简单的DisplayList,从而达到更好的性能(Shape代替Bitmap等)。
- SpareArray
//避免了对key的自动装箱,两个数组来进行数据存储的,一个存储key,另外一个存储value,查找使用二分查找法
private int[] mKeys;
private Object[] mValues;
-
ArrayMap:
-
<key,value> 映射的数据结构,类似于 SpareArray
如果 key 类型为 int 建议使用 SpareArray,类似 LongSparseMap。
如果 key 类型为其他类型,则使用 ArrayMap。
注意:SparseArray、ArrayMap 在增加、查找、删除都需要使用 二分查找法,数据量大,效率会低。
-
布局适配
- 布局自适应屏幕尺寸:使用相对布局(RelativeLayout),禁用绝对布局(AbsoluteLayout)
- 根据屏幕的配置来加载相应的 UI 布局
- 尺寸限定符
- 例如有一个平板,可以使用:res/layout-large/main.xml ,只适合Android 3.2版本前
- 最小宽度限定符:例如 layout-sw600dp 的最小宽度限定符,只适合Android 3.2版本后
- 布局别名
- 屏幕方向限定符
- 尺寸限定符
- 使用 sp、dp
- 屏幕尺寸和屏幕密度之间适配(以某一分辨率为基准,生成所有分辨率对应像素数列表)
-
布局组件适配:使得布局组件自适应屏幕尺寸:wrap_content、match_parent、weight
-
图片资源适配:图片资源组件自适应屏幕尺寸 :.9.png 、只切一套图 xhdpi
-
用户界面流程适配:根据屏幕的配置来加载相应的用户界流程
- 确定当前布局
- 根据当前布局做出响应
- 重复使用其他活动中的片段
- 处理屏幕配置变化
-
推荐:今日头条的适配方案:动态更改 density
px:像素 px = dp * (dpi / 160)
dp:设备独立像素
dpi:像素密度 dpi = 根号(宽^2 + 高^2) / 屏幕尺寸(inch)
//基本公式
px = dp * density;
density = dpi / 160;
px = dp * (dpi / 160);
private static void adaptScreen(final Activity activity,
final int sizeInPx,
final boolean isVerticalSlide) {
final DisplayMetrics systemDm = Resources.getSystem().getDisplayMetrics();
final DisplayMetrics appDm = App.getAppContext().getResources().getDisplayMetrics();
final DisplayMetrics activityDm = activity.getResources().getDisplayMetrics();
if (isVerticalSlide) {
activityDm.density = activityDm.widthPixels / (float) sizeInPx;
} else {
activityDm.density = activityDm.heightPixels / (float) sizeInPx;
}
activityDm.scaledDensity = activityDm.density * (systemDm.scaledDensity / systemDm.density);
activityDm.densityDpi = (int) (160 * activityDm.density);
appDm.density = activityDm.density;
appDm.scaledDensity = activityDm.scaledDensity;
appDm.densityDpi = activityDm.densityDpi;
}
//假如要使用第三方的UI界面的时候,重新设置为系统的density即可
public static void cancelAdaptScreen(final Activity activity) {
final DisplayMetrics systemDm = Resources.getSystem().getDisplayMetrics();
final DisplayMetrics appDm = App.getAppContext().getResources().getDisplayMetrics();
final DisplayMetrics activityDm = activity.getResources().getDisplayMetrics();
activityDm.density = systemDm.density;
activityDm.scaledDensity = systemDm.scaledDensity;
activityDm.densityDpi = systemDm.densityDpi;
appDm.density = systemDm.density;
appDm.scaledDensity = systemDm.scaledDensity;
appDm.densityDpi = systemDm.densityDpi;
}
显式 Intent,明确指出目标组件的名称
bt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this,secondActivity.class);
startActivity(intent);
}
});
隐式 Intent,没有明确指出目标组件名称,intent-filter
-
动作(Action)
-
类别(Category ['kætɪg(ə)rɪ] )
-
数据(Data )
<activity android:name=".secondActivity">
<intent-filter>
<action android:name="com.example.aries.androidtest.ACTION_START"></action>
<category android:name="android.intent.category.DEFAULT"></category>
</intent-filter>
</activity>
广播传输的数据一般经过 IPC,Intent 在传递数据时是有大小限制的,大约限制在 1MB 之内,IPC 需要把数据从内核copy到进程中,每一个进程有一个接收内核数据的缓冲区,默认是1M;如果一次传递的数据超过限制,就会出现异常。
-
应用程序升级,验证 APP 的唯一性,包名和签名都一致才能升级
-
应用程序模块化,可以模块化部署多个应用到一个进程,只要他们的签名都一样
-
代码或者数据共享,同一个签名有相同的权限,可以共享数据和代码
merge: 删减多余的层级,优化 UI,多用于替换 FrameLayout 或者当一个布局包含另一个
ViewStub: 按需加载,不会影响 UI 初始化时的性能
include:重用布局文件
内容提供器,调用 Context 的 getContentResolver 方法。
Uri uri = Uri.parse("content:xxx/xxx");
Cursor cursor = getContentResolver().query(uri,new String[]{"author","price"},
"name=?",new String[]{"Java"},null);
if(cursor.moveToFirst()){
do{
Log.d("MainActivity",cursor.getString(cursor.getColumnIndex("name")));
Log.d("MainActivity",cursor.getString(cursor.getColumnIndex("price")));
}while(cursor.moveToNext());
}
cursor.close()
- 优化布局,不要嵌套太多
- 主线程不要做太多的耗时工作
- JSONArray、JSONObject
- Gson
- support 版本中利用 setUserVisibleHint 和 onHiddenChanged
public class LazyLoadFragment extends Fragment {
//判断是否已进行过加载,避免重复加载
private boolean isLoad=false;
//判断当前fragment是否可见
private boolean isVisibleToUser = false;
//判断当前fragment是否回调了resume
private boolean isResume = false;
private boolean isCallUserVisibleHint = false;
@Override
public void onResume() {
super.onResume();
isResume=true;
if (!isCallUserVisibleHint) isVisibleToUser=!isHidden();
lazyLoad();
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
Log.i(TAG, “setUserVisibleHint: “+isVisibleToUser+” “+FragmentD.this);
this.isVisibleToUser=isVisibleToUser;
isCallUserVisibleHint=true;
lazyLoad();
}
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
Log.i(TAG, "onHiddenChanged: "+hidden+" "+FragmentD.this);
isVisibleToUser=!hidden;
lazyLoad();
}
@Override
public void onDestroyView() {
super.onDestroyView();
isLoad=false;
isCallUserVisibleHint=false;
isVisibleToUser=false;
isResume=false;
}
private void lazyLoad() {
if (!isLoad&&isVisibleToUser&&isResume){
//懒加载。。。
isLoad=true;
}
}
- Androidx 版本。
在 Androidx 在 FragmentTransaction 中增加了 setMaxLifecycle 方法来控制 Fragment 所能调用的最大的生命周期函数,该方法可以设置活跃状态下 Fragment 最大的状态,如果该 Fragment 超过了设置的最大状态,那么会强制将 Fragment 降级到正确的状态。
ViewPager+Fragment: 在 FragmentPagerAdapter 与 FragmentStatePagerAdapter 新增了含有 behavior 字段的构造函数。
public class LazyLoadFragment extends Fragment {
//判断是否已进行过加载,避免重复加载
private boolean isLoad=false;
@Override
public void onResume() {
super.onResume();
if (!isLoad&& !isHidden()){
lazyLoad();
isLoad=true;
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
isLoad=false;
}
private void lazyLoad() {
//懒加载。。。
}
}
- 要选择合适的图片规格(bitmap类型):
ALPHA_8 每个像素占用1byte内存
ARGB_4444 每个像素占用2byte内存
ARGB_8888 每个像素占用4byte内存(默认)
RGB_565 每个像素占用2byte内存
-
降低采样率。BitmapFactory.Options 参数 inSampleSize 的使用,先把 options.inJustDecodeBounds 设为 true,只是去读取图片的大小,在拿到图片的大小之后和要显示的大小做比较通过 calculateInSampleSize() 函数计算 inSampleSize 的具体值,得到值之后。options.inJustDecodeBounds 设为 false 读图片资源。
-
复用内存。通过软引用,复用内存块,不需要再重新给这个 bitmap 申请一块新的内存,避免了一次内存的分配和回收,从而改善了运行效率。
-
使用 recycle() 方法及时回收内存。
-
压缩图片
- 音乐播放器
- 大图查看
- 多模块应用
Android 有自己的垃圾回收机制,如果只使用了少量的图片,回收与否关系不大。可是若有大量的 bitmap 需要垃圾回收,那么垃圾回收的次数过于频繁,会造成系统资源负荷,这时候还是调用 recycler() 比较好。
通过源码可以了解到,加载Bitmap到内存里以后,是包含两部分内存区域的。简单的说,一部分是Java部分的,一部分是C部分的。这个Bitmap对象是由Java部分分配的,不用的时候系统就会自动回收了
但是那个对应的C可用的内存区域,虚拟机是不能直接回收的,这个只能调用底层的功能释放。所以需要调用recycle()方法来释放C部分的内存
bitmap.recycle()方法用于回收该Bitmap所占用的内存,接着将bitmap置空,最后使用System.gc()调用一下系统的垃圾回收器进行回收,调用System.gc()并不能保证立即开始进行回收过程,而只是为了加快回收的到来。
Bitamp 所占内存大小 = 宽度像素 *(inTargetDensity / inDensity)* 高度像素 *(inTargetDensity / inDensity)* 一个像素所占的内存字节大小
注:这里 inDensity 表示目标图片的dpi(放在哪个资源文件夹下),inTargetDensity表示目标屏幕的dpi,所以你可以发现inDensity和inTargetDensity会对Bitmap的宽高进行拉伸,进而改变Bitmap占用内存的大小。
在Bitmap里有两个获取内存占用大小的方法。
- getByteCount():API12 加入,代表存储 Bitmap 的像素需要的最少内存。
- getAllocationByteCount():API19 加入,代表在内存中为 Bitmap 分配的内存大小,代替了 getByteCount() 方法。
- 在不复用 Bitmap 时,getByteCount() 和 getAllocationByteCount 返回的结果是一样的。在通过复用 Bitmap 来解码图片时,那么 getByteCount() 表示新解码图片占用内存的大 小,getAllocationByteCount() 表示被复用 Bitmap 真实占用的内存大小
- 将现有表命名为临时表
- 创建新表
- 将临时表的数据导入新表
- 删除临时表
- Canvas.save():来保存 Canvas 的状态。save 之后,可以调用 Canvas 的平移、放缩、旋转、错切、裁剪等操作。
- Canvas.restore():用来恢复 Canvas 之前保存的状态。防止 save 后对 Canvas 执行的操作对后续的绘制有影响。
save 和 restore 要配对使用,restore 次数 <= save 少
-
bindService 方法执行时,LoadedApk 会记录 ServiceConnection 信息。
-
Activity 执行 finish 方法时,会通过 LoadedApk 检查 Activity 是否存在未注销/解绑的 BroadcastReceiver 和 ServiceConnection,如果有,那么会通知 AMS 注销/解绑对应的 BroadcastReceiver 和 Service,并打印异常信息,告诉用户应该主动执行注销/解绑的操作。
不一定,如果自定义 view 计算复杂,可能效率不一定高,但是一般情况下,自定义 view 效率高于 xml 定义。
- 少了解析 xml
- 自定义 View 减少了 ViewGroup 与 View之间的测量,包括父量子,子量自身,子在父中位置摆放,当子 view变化时,父的某些属性都会跟着变化
我们使用友盟多渠道打包。
android {
productFlavors {
xiaomi {}
baidu {}
wandoujia {}
_360 {} // 或“"360"{}”,数字需下划线开头或加上双引号
}
}
或者使用 360 加固,配置多渠道打包。
-
Handler:在 Android 中负责发送和处理消息,线程之间的消息通信。
-
Thread:Java进程中执行运算的最小单位,亦即执行处理机调度的基本单位。某一进程中一路单独运行的程序。
-
HandlerThread:
- 继承自 Thread。
- 内部实现了 Looper ,便于消息的分发。
- 静态注册(常驻广播):在 AndroidManifest.xml 配置
- 常驻,不受任何组件的生命周期影响
- 如果程序被关闭,如果有信息广播来,程序依旧会被系统调用
- 需要时刻监听广播
- 动态注册(非常驻广播):Context.registerReceiver()
- 非常驻,灵活,跟随组建的生命周期变化
- 不需要特定时刻监听广播
- 当用来注册的 Activity 关掉后,广播也就失效了。
ddms:davik debug monitor service。简单的说 ddms 是一个程序执行查看器,在里面可以看见线程和堆栈等信息。
traceView: 程序性能分析器。traceview 是 ddms 中的一部分内容。
traceview 是 Android 平台特有的数据采集和分析工具,它主要用于分析 Android 中应用程序的 hotspot(瓶颈)。Traceview 本身只是一个数据分析工具,而数据的采集则需要使用 Android SDK 中的 Debug 类或者利用DDMS 工具。二者的用法如下:开发者在一些关键代码段开始前调用 Android SDK 中 Debug 类的 startMethodTracing 函数,并在关键代码段结束前调用 stopMethodTracing 函数。这两个函数运行过程中将采集运行时间内该应用所有线程(注意,只能是 Java线程) 的函数执行情况, 并将采集数据保存到/mnt/sdcard/下的一个文件中。 开发者然后需要利用 SDK 中的 Traceview工具来分析这些数据。
-
ContentProvider 内容提供者:对外提供数据,通过 ContentProvider 把应用中的数据共享给其他应用访问,其他应用可以通过ContentProvider 对你应用中的数据进行添删改查。
-
ContentResolver 内容解析者:按照一定规则访问内容提供者的数据(其实就是调用内容提供者自定义的接口来操作它的数据)。
-
ContentObserver 内容监听器:监听(捕捉)特定Uri引起的数据库的变化,继而做一些相应的处理,它类似于数据库技术中的触发器(Trigger),当ContentObserver所观察的Uri发生变化时,便会触发它。
- 竖向的 TextView
- 使用TextPaint绘制相同文字在TextView的底部,TextPaint的字显示要比原始的字大一些,这样看起来就像是有描边的文字。
默认情况,如果没有显示的指 service 所运行的进程, Service 和 activity 是运行在当前 app 所在进程的 main thread(UI 主线程)里面。
service 里面不能执行耗时的操作(网络请求,拷贝数据库,大文件)
四大组件之一,用于与用户交互,一个界面对应一个 Activity。
使用 onSaveInstanceState()
override fun onSaveInstanceState(outState: Bundle) {
//保存状态
super.onSaveInstanceState(outState)
}
- Activity 和 Application 都是Context的子类
- Context:上下文,管理上下文环境中各个参数和变量。
- Activity 维护一个 Acitivity 的生命周期,其对应的 Context 也只能访问该 Activity内的各种资源。
- Application 维护一个 Application 的生命周期。
-
描述的是一个应用程序环境的信息,即上下文。
-
抽象(abstract class)类,Android 提供了该抽象类的具体实现类。
-
通过它我们可以获取应用程序的资源和类,也包括一些应用级别操作,例如:启动一个Activity,发送广播,接受Intent信息
-
Context 实例个数 = Service 个数 + Activity 个数 + 1(Application对应的Context实例)
bindService(Intent service, ServiceConnection conn, int flags)。
Activity 中可以通过 startService 和 bindService 启动 Service。一般情况下如果想获取 Service 的服务对象那么通过 bindService(),比如音乐播放器,第三方支付等。如果仅仅只是为了开启一个后台任务那么可以使用 startService()方法。
- 非绑定模式
- 当第一次调用 startService :onCreate() -> onStartCommand()
- 多次调用 startService:onStartCommand()
- 关闭:onDestory()
- 绑定模式
- 第一次调用 bindService:onCreate() -> onBind()
- 解除绑定: onUnbind() -> onDestory()。
- IntentService 是 Service 的子类,创建独立的 worker 线程来处理所有的 Intent 请求;
- 请求处理完成后,IntentService 会自动停止,无需调用 stopSelf() 停止 Service;
Activity 和 Service 都是 Android 四大组件之一。都是 Context 类的子类 ContextWrapper 的子类。
-
Activity 和 Service 都是 Context 类的子类ContextWrapper 的子类
-
Activity 负责用户界面的显示和交互,Service 负责后台任务的处理
-
Intent 传递数据,因此可以把 Intent 看作是通信使者
对于同一 app 来说默认情况下在同一个线程中,也就是 MainThread (UI 线程)。
可以,Service 是 Context 的子类。
- 优化布局文件
- onCreate() 和 onReume() 减少复杂操作,可以利用多线程
- 减少主线程阻塞时间
- SparseArray:key 为 int 类型,value 为 Object。key 的查找使用了二分的查找方式。
- ArrayMap:存放了两个数组。一个存放 hash,另外一个存放的是真正的数值。二分法查找和实时扩容机制,实现了一个有序的HashMap.
- 断点续传和断点下载都是用的 RandomAccessFile, seek() 具有移动指定的文件大小的位置的功能。
- 在 Http 请求中,加入请求头 Range,下载指定区间的文件数。
- Bundle
- 通过系统文件
- ContentProvider
- Messenger
- AIDL
android:theme="@android:style/Theme.Dialog"
- HandlerThread 本质是一个线程类,继承了 Thread;
- HandlerThread 有自己的内部 Looper 对象,可以进行looper循环;
- 通过获取 HandlerThread 的 Looper 对象传递给Handler对象,可以在 handleMessage方法中执行异步任务;
- 创建HandlerThread后必须先调用HandlerThread.start()方法,Thread会先调用run方法,创建Looper对象。
其实就是 Service + HandlerThread。
- IntentService是继承自 Service 并处理异步请求的一个类,在 IntentService 内有一个工作线程来处理耗时操作。
- 当任务执行完后,IntentService 会自动停止,不需要我们去手动结束。
- 如果启动 IntentService 多次,那么每一个耗时操作会以工作队列的方式在 IntentService 的 onHandleIntent 回调方法中执行,依次去执行,使用串行的方式,执行完自动结束。
Android中的 Scheme 是一种页面跳转协议,和网站通过URL的形式访问一样,APP同样可以通过这种方式进行跳转,可以满足以下需求:
- 当应用接收到 Push,点击通知栏消息跳转到特定页面,比如商品详情等。
- 通过服务器下发的跳转路径,客户端可以根据路径跳转相应页面。
- 应用跳转到其他 APP 指定页面。
- H5页面点击锚点,APP端跳转具体页面。
- RelativeLayout 会对子View做两次 measure
- 如果 LinearLayout 中有weight属性,则也需要进行两次 measure,但即便如此,应该仍然会比RelativeLayout的情况好一点。
- RelativeLayout 的 子 View 如果高度和RelativeLayout不同,会引发效率问题,当子View很复杂时,这个问题会更加严重。如果可以,尽量使用 padding 代替 margin。
- 把原数据库放到 res/raw
- 把数据库复制到 /data/data/com.(package name)/ 目录下
- 利用onStartCommand方法中,返回START_STICKY
- 提高优先级:android:priority="1000"
- onDestroy 方法里重启 Service
- 使用 startForeground 将 service 放到前台状态,提升service进程优先级
-
在支付宝开放平台创建应用,添加功能并签约,配置密钥。
-
导入支付宝 SDK
- 是整个应用的主配置清单文件,用于记录应用的相关配置信息,包括应用的包名、组件、权限等。
- 申明组件(四大组件)
-
bindService 方法执行时,LoadedApk 会记录 ServiceConnection 信息。
-
Activity 执行 finish 方法时,会通过 LoadedApk 检查 Activity 是否存在未注销/解绑的 BroadcastReceiver 和 ServiceConnection,如果有,那么会通知 AMS 注销/解绑对应的 BroadcastReceiver 和 Service,并打印异常信息,告诉用户应该主动执行注销/解绑的操作。
可以,在try语句中声明了很大的对象,导致OOM,但是不合理,谨慎使用。
- 通过某种方式修改函数的执行流程 (inline hook)
- 以系统类的身份去反射
- 直接把我们自己变成系统类
- 借助系统类去调用反射(通过 「元反射」来反射调用
VMRuntime.setHiddenApiExemptions
将要使用的隐藏 API 全部都豁免掉了) - 参考:https://github.com/tiann/FreeReflection
- 单元测试(Junit4、Mockito、PowerMockito、Robolectric)
- UI测试(Espresso、Example)
- 压力测试(Monkey)
可以,viewRootImp 的创建是在 Activity中 onResume 方法中创建的,我们可以单独开启了线程在 onCreate 里,它逃过了viewRootImp的创建,所以不会抛异常,但是线程一旦阻塞两秒了,viewRootImp 已经创建好了,所以能检查到。
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
RemoteViews
- 运行时权限 Dalvik ( android授权)
- 文件系统 linux 内核授权
- SurfaceView:使用双缓冲机制,有自己的 surface,在一个独立的线程里绘制,Android7.0之前不能平移、缩放
- TextureView:持有 SurfaceTexture,将图像处理为 OpenGL 纹理更新到 HardwareLayer,必须开启硬件加速,Android5.0之前在主线程渲染,之后有独立的渲染线程,可以平移、旋转、缩放
- SurfaceTexture:将图像流转为 OpenGL 外部纹理,不直接显示
- GLSurfaceView:加入 EGL 管理,自带 GL 上下文和 GL 渲染线程
//1.创建一个Scroller对象,一般在View的构造器中创建
mScroller = new Scroller(context);
//2.重写 View 的 computeScroll()
@Override
public void computeScroll() {
super.computeScroll();
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
//3.调用startScroll()方法,startX和startY为开始滚动的坐标点,dx和dy为对应的偏移量
mScroller.startScroll (int startX, int startY, int dx, int dy);
invalidate();
-
在 mScroller.startScroll() 中为滑动做初始化准备,比如:起始坐标,滑动的距离和方向以及持续时间(有默认值),动画开始时间等。
-
mScroller.computeScrollOffset() 根据当前已经消逝的时间来计算当前的坐标点。因为在mScroller.startScroll()中设置了动画时间,那么在computeScrollOffset()方法中依据已经消逝的时间就很容易得到当前时刻应该所处的位置并将其保存在变量 mCurrX 和 mCurrY 中,除此之外该方法还可判断动画是否已经结束。
1、数据处理与视图绑定分离
RecyclerView 的 bindViewHolder 方法是在 UI 线程进行的,如果在该方法进行耗时操作,将会影响滑动的流畅性。
2、数据优化
分页加载数据;
对于新增或删除数据通过DiffUtil,来进行局部数据刷新;
3、布局优化
减少过度绘制。
减少布局层级,可以考虑使用自定义 View 来减少层级,或者更合理的设置布局来减少层级。
4、减少xml文件inflate时间
使用 即new View()的方式创建布局,因为 xml 文件包括:layout、drawable 的 xml,xml 文件 inflate 出 ItemView 是通过耗时的 IO 操作。
5、减少 View 对象的创建
一个稍微复杂的 Item 会包含大量的 View,而大量的 View 的创建也会消耗大量时间,所以要尽可能简化 ItemView;设计 ItemType 时,对多 ViewType 能够共用的部分尽量设计成自定义 View,减少 View 的构造和嵌套。
6、设置高度固定
如果item高度是固定的话,可以使用 RecyclerView.setHasFixedSize(true),来避免requestLayout浪费资源。
7、共用RecycledViewPool
在嵌套RecyclerView中,如果子RecyclerView具有相同的adapter,那么可以设置RecyclerView.setRecycledViewPool(pool)来共用一个RecycledViewPool。
Note: 如果LayoutManager是LinearLayoutManager或其子类,需要手动开启这个特性:layout.setRecycleChildrenOnDetach(true)。
8、RecyclerView数据预取
RecyclerView25.1.0及以上版本增加了Prefetch功能。
用于嵌套RecyclerView获取最佳性能。
详细分析:RecyclerView 数据预取。
9、加大RecyclerView的缓存
用空间换时间,来提高滚动的流畅性。
recyclerView.setItemViewCacheSize(20);
recyclerView.setDrawingCacheEnabled(true);
recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
10、增加RecyclerView预留的额外空间
额外空间:显示范围之外,应该额外缓存的空间
new LinearLayoutManager(this{
@Override
protected int getExtraLayoutSpace(RecyclerView.Statestate){
return size;
}
};
11、减少ItemView监听器的创建
对ItemView设置监听器,不要对每个item都创建一个监听器,而应该共用一个XxListener,然后根据ID来进行不同的操作,优化了对象的频繁创建带来的资源消耗。
12、优化滑动操作
设置 RecyclerView.addOnScrollListener() 来在滑动过程中停止加载的操作。
13、刷新闪烁
调用notifyDataSetChange时,适配器不知道整个数据集中的那些内容以及存在,再重新匹配ViewHolder时会发生闪烁。
设置adapter.setHasStableIds(true),并重写getItemId()来给每个Item一个唯一的ID
14、回收资源
通过重写RecyclerView.onViewRecycled(holder)来回收资源。
缓存区别:
- 层级不同:ListView有两级缓存,在屏幕与非屏幕内。RecyclerView比ListView多两级缓存,支持多个离屏ItemView缓存(匹配pos获取目标位置的缓存,如果匹配则无需再次bindView),支持开发者自定义缓存处理逻辑,支持所有RecyclerView共用同一个RecyclerViewPool(缓存池)。
- 缓存不同:ListView缓存View。RecyclerView缓存RecyclerView.ViewHolder,抽象可理解为: View + ViewHolder(避免每次createView时调用findViewById) + flag(标识状态);
优点 RecylerView提供了局部刷新的接口,通过局部刷新,就能避免调用许多无用的bindView。 RecyclerView的扩展性更强大(LayoutManager、ItemDecoration等)。
Android 中常用的有两种类加载器:
-
DexClassLoader
-
PathClassLoader
它们都继承于 BaseDexClassLoader。区别在于调用父类构造器时,DexClassLoader 多传了一个optimizedDirectory 参数,这个目录必须是内部存储路径,用来缓存系统创建的 Dex 文件。而PathClassLoader该参数为null,只能加载内部存储目录的 Dex 文件。所以我们可以用 DexClassLoader 去加载外部的apk。
onStart()
是Activity
界面被显示出来的时候执行的,但不能与它交互;onResume()
是 当该Activity
与用户能进行交互时被执行,用户可以获得它的焦点,能够与其交互。
Android是基于事件驱动的,即所有Activity的生命周期都是通过Handler事件驱动的。loop方法中会调用MessageQueue的next方法获取下一个message,当没有消息时,基于 Linux pipe/epoll 机制会阻塞在loop的queue.next() 中的 nativePollOnce() 方法里,并不会消耗CPU。
闲时机制,当消息队列空闲时会执行IdelHandler
的queueIdle()
方法,该方法返回一个boolean
值,如果为false
则执行完毕之后移除这条消息,如果为true
则保留,等到下次空闲时会再次执行,
IdleHandler 是一个回调接口,可以通过MessageQueue的addIdleHandler添加实现类。
Message 分为3种:
- 普通消息(同步消息)
- 屏障消息(同步屏障)
- 异步消息
我们通常使用的都是普通消息,而屏障消息就是在消息队列中插入一个屏障,在屏障之后的所有普通消息都会被挡着,不能被处理。不过异步消息却例外,屏障不会挡住异步消息,因此可以这样认为:屏障消息就是为了确保异步消息的优先级,设置了屏障后,只能处理其后的异步消息,同步消息会被挡住,除非撤销屏障。
同步屏障就是一个Message,一个target字段为空的Message。
同步屏障可以通过 MessageQueue.postSyncBarrier 函数来设置。该方法发送了一个没有 target 的 Message到Queue 中,在 next 方法中获取消息时,如果发现没有 target 的 Message,则在一定的时间内跳过同步消息,优先执行异步消息。再换句话说,同步屏障为 Handler 消息机制增加了一种简单的优先级机制,异步消息的优先级要高于同步消息。在创建 Handle r时有一个 async 参数,传 true 表示此 handler 发送的时异步消息。
ViewRootImpl.scheduleTraversals 方法就使用了同步屏障,保证UI绘制优先执行。
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//设置同步障碍,确保mTraversalRunnable优先被执行
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//内部通过Handler发送了一个异步消息
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
- getMeasureWidth():measure() 获取到,值通过setMeasuredDimension()方法来进行设置的
- getWidth():layout() 获取到,值通过视图右边的坐标减去左边的坐标计算出来的。
- requestLayout:会重新调用onMeasure、onLayout、onDraw 来刷新界面。
- invalidate:调用 onDraw() 来刷新界面。调用 invalidate(),会为该View添加一个标记位,同时不断向父容器请求刷新,父容器通过计算得出自身需要重绘的区域,直到传递到ViewRootImpl中,最终触发performTraversals方法,进行开始View树重绘流程(只绘制需要重绘的视图)。
- postInvalidate :调用onDraw() 来刷新界面,在非UI线程中调用。
Android View 深度分析requestLayout、invalidate与postInvalidate
- 复制 APK 到 /data/app 目录下,解压并扫描安装包。
- 资源管理器解析 APK 里的资源文件。
- 解析 AndroidManifest.xml,并在 /data/data/ 目录下创建对应的应用数据目录。
- 对 dex 文件进行优化,并保存在 dalvik-cache目录下。
- 将 AndroidManifest.xml 解析出的四大组件信息注册到 PackageManagerService 中。
- 安装完成后,发送广播。
- 打包资源文件,生成 R.java 文件
- aapt 工具(aapt.exe) -> AndroidManifest.xml 和 布局文件 XMl 都会编译 -> R.java -> AndroidManifest.xml 会被 aapt 编译成二进制
- res 目录下资源 -> 编译,变成二进制文件,生成 resource id -> 最后生成 resouce.arsc(文件索引表)
- 处理 aidl 文件,生成相应的 Java 文件
- aidl 工具(aidl.exe)
- 编译项目源代码,生成 class 文件
- 转换所有 class 文件,生成 classes.dex 文件
- dx.bat
- 打包生成 APK 文件
- apkbuilder 工具打包到最终的 .apk 文件中
- 对APK文件进行签名
- 对签名后的 APK 文件进行对齐处理(正式包)
- 对 APK 进行对齐处理,用到的工具是 zipalign
- 代码混淆
- 使用 lint 去除无用资源
- 去除无用国际化支持
- 切一套图 xxhdpi 或 xhdpi
- 图片压缩, png 压缩或者使用webP图片
- 使用矢量图形,简单的图标可以使用矢量图片
Android 应用 (APK) 文件包含 Dalvik Executable (DEX) 文件形式的可执行字节码文件,这些文件包含用来运行应用的已编译代码。Dalvik Executable 规范将可在单个 DEX 文件内引用的方法总数限制为 65536,其中包括 Android 框架方法、库方法以及您自己的代码中的方法。
- 检查应用的直接依赖项和传递依赖项
- 通过 R8 移除未使用的代码
- 应用层
- 应用框架层
- 系统运行库层
- HAL
- Linux 内核层
-
getActivity() 空指针:一般在异步任务中,Fragment 已经 onDetach(),可以使用全局变量 mActivity,onAttach() 方法里赋值。
-
视图重叠:重复加载了同一个 Fragment 导致重叠
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { // 在页面重启时,Fragment会被保存恢复,而此时再加载Fragment会重复加载,导致重叠 ; if(saveInstanceState == null){ // 或者 if(findFragmentByTag(mFragmentTag) == null) // 正常情况下去 加载根Fragment } }
- onMeasure:测量视图的大小,从顶层父View到子View递归调用measure()方法,measure()调用onMeasure()方法,onMeasure()方法完成测量工作。
- onLayout:确定视图的位置,从顶层父View到子View递归调用layout()方法,父View将上一步measure()方法得到的子View的布局大小和布局参数,将子View放在合适的位置上。
- onDraw:绘制最终的视图,首先ViewRoot创建一个Canvas对象,然后调用onDraw()方法进行绘制。onDraw()方法的绘制流程为:
- 绘制视图背景。
- 绘制画布的图层。
- 绘制View内容。
- 绘制子视图,如果有的话。
- 还原图层。
- 绘制滚动条。
传统的 Linux 进程通信。
- 管道:在创建时分配一个page大小的内存,缓存区大小比较有限;
- 消息队列:信息复制两次,额外的CPU消耗;不合适频繁或信息量大的通信;
- 共享内存:无须复制,共享缓冲区直接付附加到进程虚拟地址空间,速度快;但进程间的同步问题操作系统无法实现,必须各进程利用同步工具解决;
- 套接字:作为更通用的接口,传输效率低,主要用于不通机器或跨网络的通信;
- 信号量:常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
- 信号: 不适用于信息交换,更适用于进程中断控制,比如非法内存访问,杀死某个进程等;
Android 的 Binder 机制
Binder通信的四个角色:
- Client进程:使用服务的进程。
- Server进程:提供服务的进程。
- ServiceManager进程:ServiceManager的作用是将字符形式的Binder名字转化成Client中对该Binder的引用,使得Client能够通过Binder名字获得对Server中Binder实体的引用。
- Binder驱动:驱动负责进程之间Binder通信的建立,Binder在进程之间的传递,Binder引用计数管理,数据包在进程之间的传递和交互等一系列底层支持。
Linux 进程通信,需要先用 copy_from_user() 拷贝到内核空间,再用 copy_to_user()拷贝到另一个用户空间。
为了实现用户空间到用户空间的拷贝,mmap() 分配的内存除了映射进了接收方进程里,还映射进了内核空间。所以调用copy_from_user()将数据拷贝进内核空间也相当于拷贝进了接收方的用户空间,这就是Binder只需一次拷贝的‘秘密’。
最底层的是 Android的 ashmen(Anonymous shared memory) 机制,它负责辅助实现内存的分配,以及跨进程所需要的内存共享。AIDL(android interface definition language)对Binder的使用进行了封装,可以让开发者方便的进行方法的远程调用,后面会详细介绍。Intent是最高一层的抽象,方便开发者进行常用的跨进程调用。
从英文字面上意思看,Binder具有粘结剂的意思那么它是把什么东西粘接在一起呢?在Android系统的Binder机制中,由一系统组件组成,分别是Client、Server、Service Manager和Binder驱动,其中Client、Server、Service Manager运行在用户空间,Binder驱动程序运行内核空间。Binder就是一种把这四个组件粘合在一起的粘连剂了,其中,核心组件便是Binder驱动程序了,ServiceManager提供了辅助管理的功能,Client和Server正是Binder驱动和ServiceManager提供的基础设施上,进行Client-Server之间的通信。
参考阅读:
- 从 Java 层来看就像访问本地接口一样,客户端基于 BinderProxy ,服务端基于 IBinder 对象
- 从 native 层来看来看客户端基于 BpBinder 到 ICPThreadState 到 binder 驱动,服务端由 binder 驱动唤醒 IPCThreadSate 到 BbBinder 。
- 跨进程通信的原理最终是要基于内核的,所以最会会涉及到 binder_open 、binder_mmap 和 binder_ioctl这三种系统调用。
常见开发中事件冲突的有ScrollView与RecyclerView的滑动冲突、RecyclerView 内嵌同时滑动同一方向。
滑动冲突的处理规则:
- 对于由于外部滑动和内部滑动方向不一致导致的滑动冲突,可以根据滑动的方向判断谁来拦截事件。
- 对于由于外部滑动方向和内部滑动方向一致导致的滑动冲突,可以根据业务需求,规定何时让外部View拦截事件,何时由内部View拦截事件。
- 对于上面两种情况的嵌套,相对复杂,可同样根据需求在业务上找到突破点。
滑动冲突的实现方法:
- 外部拦截法:指点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,否则就不拦截。具体方法:需要重写父容器的 onInterceptTouchEvent 方法,在内部做出相应的拦截。
- 内部拦截法:指父容器不拦截任何事件,而将所有的事件都传递给子容器,如果子容器需要此事件就直接消耗,否则就交由父容器进行处理。具体方法:需要配合requestDisallowInterceptTouchEvent 方法。
SharePreferences是一个轻量级的存储类,特别适合用于保存软件配置参数。使用SharedPreferences保存数据,用 xml 文件存放数据,文件存放在/data/data/ < package name > /shared_prefs目录。
SharePreferences 在创建的时候会把整个文件全部加载进内存,如果SharedPreference文件比较大,会带来以下问题:
- 第一次从sp中获取值的时候,有可能阻塞主线程,使界面卡顿、掉帧。
- 解析sp的时候会产生大量的临时对象,导致频繁GC,引起界面卡顿。
- 这些 key 和 value 会永远存在于内存之中,占用大量内存。
优化建议
- 不要存放大的 key 和 value,会引起界面卡、频繁 GC、占用内存等等。
- 毫不相关的配置项就不要放在在一起,文件越大读取越慢。
- 读取频繁的 key 和不易变动的 key 尽量不要放在一起,影响速度,如果整个文件很小,那么忽略吧,为了这点性能添加维护成本得不偿失。
- 不要乱 edit 和 apply,尽量批量修改一次提交,多次 apply 会阻塞主线程。
- 尽量不要存放 JSON 和 HTML,这种场景请直接使用 JSON。
- sp 无法进行跨进程通信,MODE_MULTI_PROCESS 只是保证了在 API 11 以前的系统上,如果 sp 已经被读取进内存,再次获取这个 sp 的时候,如果有这个 flag,会重新读一遍文件,仅此而已。
数据库升级增加表和删除表都不涉及数据迁移,但是修改表涉及到对原有数据进行迁移。升级的方法如下所示:
- 将现有表命名为临时表。
- 创建新表。
- 将临时表的数据导入新表。
- 删除临时表。
重写
如果是跨版本数据库升级,可以由两种方式,如下所示:
- 逐级升级,确定相邻版本与现在版本的差别,V1升级到V2,V2升级到V3,依次类推。
- 跨级升级,确定每个版本与现在数据库的差别,为每个case编写专门升级大代码。
内存缓存基于 LruCache 实现,磁盘缓存基于 DiskLruCache 实现。这两个类都基于Lru算法和LinkedHashMap 来实现。
LRU 是Least Recently Used的缩写,最近最久未使用算法,它的核心原则是如果一个数据在最近一段时间没有使用到,那么它在将来被访问到的可能性也很小,则这类数据项会被优先淘汰掉。
为什么会选择LinkedHashMap 呢?
这跟 LinkedHashMap的特性有关,LinkedHashMap的构造函数里有个布尔参数 accessOrder,当它为 true 时,LinkedHashMap会以访问顺序为序排列元素,否则以插入顺序为序排序元素。
- PathClassLoader:只能加载已经安装到Android系统的APK文件,即/data/app目录,Android默认的类加载器。
- DexClassLoader:可以加载任意目录下的dex、jar、apk、zip文件。
出现原因:在客户端中,加载 H5 页面之前,需要先初始化WebView,在WebView完全初始化完成之前,后续的界面加载过程都是被阻塞的。
优化入手:
- 预加载WebView。
- 加载WebView的同时,请求H5页面数据。
常见方法:
- 全局 WebView。
- 客户端代理页面请求,WebView 初始化完成后向客户端请求数据。
- asset 存放离线包。
除此之外还有一些其他的优化手段:
- 脚本执行慢,可以让脚本最后运行,不阻塞页面解析。
- DNS与链接慢,可以让客户端复用使用的域名与链接。
- React框架代码执行慢,可以将这部分代码拆分出来,提前进行解析。
MeasureSpec 代表一个32位int值,高两位代表SpecMode(测量模式),低30位代表SpecSize(具体大小)。
SpecMode有三类:
- AT_MOST:父容器指定一个可用大小即SpecSize,view 的大小不能大于这个值,具体多大要看view的具体实现,相当于wrap_content。
- EXACTLY: 父容器已经检测出view所需的精确大小,这时候view的最终大小SpecSize所指定的值,相当于 match_parent 或指定具体数值。
- UNSPECIFIED:表示父容器不对View有任何限制,一般用于系统内部,表示一种测量状态;
Fragment 可见状态改变时会被调用setUserVisibleHint()方法,可以通过复写该方法实现Fragment的懒加载,但需要注意该方法可能在onVIewCreated之前调用,需要确保界面已经初始化完成的情况下再去加载数据,避免空指针。
//在java代码创建视图的时候被调用,如果是从xml填充的视图,就不会调用这个
public RoundProgressBar(Context context) {
this(context, null);
}
//在xml创建但是没有指定style的时候被调用
public RoundProgressBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RoundProgressBar(Context context, AttributeSet attrs, int defStyle) {}
SparseArray 优点:它要比 HashMap 节省内存,某些情况下比HashMap性能更好,按照官方问答的解释,主要是因为SparseArray 不需要对 key 和 value 进行auto-boxing(将原始类型封装为对象类型,比如把int类型封装成Integer类型),结构比 HashMap 简单(SparseArray 内部主要使用两个一维数组来保存数据,一个用来存 key,一个用来存 value)不需要额外的额外的数据结构(主要是针对HashMap中的HashMapEntry而言的)。
任意代码执行漏洞
-
WebView 中
addJavascriptInterface()
接口:当JS拿到Android这个对象后,就可以调用这个Android对象中所有的方法,包括系统类(java.lang.Runtime 类),从而进行任意代码执行。解决:4.2 版本之后:对被调用的函数以@JavascriptInterface
进行注解从而避免漏洞攻击。4.2 版本之前:采用**拦截prompt()**进行漏洞修复。 -
WebView 内置导出的
searchBoxJavaBridge_
对象:在Android 3.0以下,Android系统会默认通过searchBoxJavaBridge_
的Js接口给 WebView 添加一个JS映射对象:searchBoxJavaBridge_
对象该接口可能被利用,实现远程任意代码。解决:删除
searchBoxJavaBridge_
接口。 -
WebView 内置导出的
accessibility
和accessibilityTraversal
Object 对象:上同。
密码明文存储漏洞
WebSettings.setSavePassword(false) //关闭密码保存提醒
域控制不严格漏洞
当其他应用启动此 Activity 时, intent 中的 data 直接被当作 url 来加载(假定传进来的 url 为 file:///data/local/tmp/attack.html ),其他 APP 通过使用显式 ComponentName 或者其他类似方式就可以很轻松的启动该 WebViewActivity 并加载恶意url。
//对于不需要使用 file 协议的应用,禁用 file 协议;
// 禁用 file 协议;
setAllowFileAccess(false);
setAllowFileAccessFromFileURLs(false);
setAllowUniversalAccessFromFileURLs(false);
//对于需要使用 file 协议的应用,禁止 file 协议加载 JavaScript。
//需要使用 file 协议
setAllowFileAccess(true);
setAllowFileAccessFromFileURLs(false);
setAllowUniversalAccessFromFileURLs(false);
// 禁止 file 协议加载 JavaScript
if (url.startsWith("file://") {
setJavaScriptEnabled(false);
} else {
setJavaScriptEnabled(true);
}
一级缓存:屏幕内缓存(mAttachedScrap) 屏幕内缓存指在屏幕中显示的ViewHolder,这些ViewHolder会缓存在mAttachedScrap、mChangedScrap中 :
mChangedScrap 表示数据已经改变的ViewHolder列表,需要重新绑定数据(调用onBindViewHolder) mAttachedScrap 未与RecyclerView分离的ViewHolder列表
二级缓存:屏幕外缓存(mCachedViews) 用来缓存移除屏幕之外的 ViewHolder,默认情况下缓存容量是 2,可以通过 setViewCacheSize 方法来改变缓存的容量大小。如果 mCachedViews 的容量已满,则会优先移除旧 ViewHolder,把旧ViewHolder移入到缓存池RecycledViewPool 中。
三级缓存:自定义缓存(ViewCacheExtension) 给用户的自定义扩展缓存,需要用户自己管理 View 的创建和缓存,可通过Recyclerview.setViewCacheExtension()设置。
四级缓存:缓存池(RecycledViewPool ) ViewHolder 缓存池,在mCachedViews中如果缓存已满的时候(默认最大值为2个),先把mCachedViews中旧的ViewHolder 存入到RecyclerViewPool。如果RecyclerViewPool缓存池已满,就不会再缓存。从缓存池中取出的ViewHolder ,需要重新调用bindViewHolder绑定数据。
按照 ViewType 来查找 ViewHolder 每个 ViewType 默认最多缓存 5 个 可以多个 RecyclerView 共享 RecycledViewPool RecyclerViewPool底层是使用了SparseArray来分开存储不同ViewType的ViewHolder集合
6.0 :动态权限
7.0:FileProvider
8.0:通知权限、安装 APK
9.0:前台服务添加权限
10.0:后台运行时访问设备位置信息需要权限
11.0:用户对位置信息的控制,方法是添加了一次性权限