网上面试相关总结:
安卓事件分发机制
Android 的事件分发机制主要包括三个核心部分:Activity、ViewGroup 和 View,它们共同作用来处理触摸事件 (Touch Events)。事件分发机制可以理解为事件从上层控件传递到下层控件,以及在控件之间传递的过程。下面是对这一机制的详细解释:
1. 事件分发方法
主要有以下三个方法:
dispatchTouchEvent(MotionEvent event):分发事件。onInterceptTouchEvent(MotionEvent event):是否拦截事件(仅ViewGroup及其子类有此方法)。onTouchEvent(MotionEvent event):处理事件。
2. 事件传递流程
事件从 Activity 开始,通过 dispatchTouchEvent 进行分发:
(1) Activity
Activity 的 dispatchTouchEvent 方法会首先调用,然后将事件传递给 Window(通常是 PhoneWindow)。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
(2) ViewGroup
ViewGroup 的 dispatchTouchEvent 方法内部会调用 onInterceptTouchEvent 来决定是否拦截事件。如果返回 true,表示拦截事件,不再传递给子 View,否则传递给子 View。
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
if (onInterceptTouchEvent(ev)) {
handled = onTouchEvent(ev);
} else {
handled = child.dispatchTouchEvent(ev);
}
return handled;
}
onInterceptTouchEvent 方法:
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false; // 默认不拦截
}
(3) View
View 的 dispatchTouchEvent 方法会直接调用 onTouchEvent 来处理事件。
public boolean dispatchTouchEvent(MotionEvent event) {
return onTouchEvent(event);
}
onTouchEvent 方法:
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
return true;
case MotionEvent.ACTION_MOVE:
return true;
case MotionEvent.ACTION_UP:
return true;
default:
return false;
}
}
3. 事件处理流程总结
- 事件首先传递给
Activity的dispatchTouchEvent方法。 Activity将事件传递给Window,最终传递给当前界面的顶层ViewGroup。ViewGroup通过onInterceptTouchEvent判断是否拦截事件。- 若拦截,直接调用
ViewGroup的onTouchEvent处理。 - 若不拦截,将事件传递给子
View的dispatchTouchEvent。
- 若拦截,直接调用
- 子
View再依次进行类似的处理。
4. 事件传递的注意事项
ViewGroup可以通过onInterceptTouchEvent来决定是否拦截事件。- 如果某个
View或ViewGroup的onTouchEvent返回true,则表示事件被消耗,不会继续传递。 - 事件分发过程中,
ACTION_DOWN是起始事件,必须被处理,否则后续的ACTION_MOVE和ACTION_UP事件将不会再传递。
示例代码
以下是一个简单的示例,展示 ViewGroup 和 View 如何协同工作来处理触摸事件:
public class CustomViewGroup extends ViewGroup {
public CustomViewGroup(Context context) {
super(context);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 拦截所有事件
return true;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 处理按下事件
return true;
case MotionEvent.ACTION_MOVE:
// 处理移动事件
return true;
case MotionEvent.ACTION_UP:
// 处理抬起事件
return true;
default:
return false;
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// 布局子 View
}
}
通过上述代码,CustomViewGroup 将拦截所有触摸事件,并在 onTouchEvent 中处理它们。这样可以保证事件不会传递给子 View,并且所有事件都由 CustomViewGroup 处理。
Handler、Thread、HandlerThread
①Handler:在android中负责发送和处理消息,通过它可以实现其他支线线程与主线程之间的消息通讯。
②Thread:Java进程中执行运算的最小单位,亦即执行处理机调度的基本单位。某一进程中一路单独运行的程序。
③HandlerThread:一个继承自Thread的类HandlerThread,Android中没有对Java中的Thread进行任何封装,而是提供了一个继承自Thread的类HandlerThread类,这个类对Java的Thread做了很多便利的封装。
在Android开发中,Handler、Thread和HandlerThread都是用于管理和处理多线程操作的类,但它们各自有不同的用途和功能。以下是它们之间的区别:
1. Thread
Thread是Java中的一个类,用于创建和管理线程。每个Thread实例代表一个独立的执行线程,可以在后台执行耗时操作以避免阻塞主线程(UI线程)。使用Thread可以通过继承Thread类或实现Runnable接口来定义线程要执行的任务。
示例代码:
// 通过继承Thread类
class MyThread extends Thread {
@Override
public void run() {
// 在这里执行耗时操作
}
}
// 通过实现Runnable接口
class MyRunnable implements Runnable {
@Override
public void run() {
// 在这里执行耗时操作
}
}
// 使用Thread启动线程
Thread thread1 = new MyThread();
thread1.start();
Thread thread2 = new Thread(new MyRunnable());
thread2.start();
2. Handler
Handler是Android提供的一个用于在不同线程之间发送和处理消息的工具。通常用于更新UI,因为只能在主线程(UI线程)中更新UI,而Handler允许我们从后台线程发送消息到主线程。Handler通过消息队列(MessageQueue)和消息循环(Looper)机制来工作。
示例代码:
Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
// 处理消息
// 这里可以更新UI
}
};
// 在后台线程中发送消息到主线程
new Thread(new Runnable() {
@Override
public void run() {
Message message = handler.obtainMessage();
handler.sendMessage(message);
}
}).start();
3. HandlerThread
HandlerThread是一个专门用于处理后台任务的线程类,继承自Thread。它具有自己的消息循环(Looper),可以方便地在单独的线程中执行任务并处理消息。使用HandlerThread可以避免在每次需要处理后台任务时都创建新的线程。
示例代码:
HandlerThread handlerThread = new HandlerThread("MyHandlerThread");
handlerThread.start();
Handler handler = new Handler(handlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
// 处理消息
// 在HandlerThread线程中执行
}
};
// 向HandlerThread发送消息
Message message = handler.obtainMessage();
handler.sendMessage(message);
// 记得在适当的时候停止HandlerThread
handlerThread.quit();
区别总结
- Thread:基本的线程类,用于在后台执行任务,不具备消息处理机制。
- Handler:用于在不同线程间传递消息,通常用于从后台线程更新UI,必须与
Looper配合使用。 - HandlerThread:一个拥有自己消息循环的线程,适用于在单独线程中执行任务和处理消息,比普通
Thread更方便管理后台任务。
这三者各有用途,具体使用哪个取决于你的具体需求。例如,如果只是简单地执行后台任务,可以使用Thread;如果需要在后台任务完成后更新UI,可以使用Handler;如果需要长时间运行的后台任务且需要消息处理机制,则HandlerThread是一个很好的选择。
UI为什么在主线程中执行
在Android开发中,UI只能在主线程中执行的主要原因是为了确保线程安全和简化UI操作管理。具体来说,有以下几个关键原因:
1. 线程安全
UI组件(如View、TextView、Button等)并不是线程安全的。如果允许多个线程同时操作这些组件,就会引发并发问题,导致UI状态的不一致,甚至崩溃。例如,一个线程可能正在更新一个TextView的文本,而另一个线程正在测量或绘制这个TextView,这种竞争状态会导致不可预测的行为。
2. 简化UI操作管理
将所有的UI操作限制在主线程中,使得UI的状态管理变得简单和直观。开发者可以确保在主线程中操作UI时,不会有其他线程同时改变UI的状态,避免了复杂的同步问题。简化了开发过程,降低了错误率。
3. 事件分发机制
Android的事件分发机制(如触摸事件、点击事件等)也是基于主线程的。所有的用户交互事件都会首先被发送到主线程,如果允许在其他线程中处理这些事件,可能会导致事件处理顺序混乱,影响用户体验。
4. 性能和响应性
Android应用的主线程也称为UI线程,它是应用程序的“主干”。如果在主线程中执行耗时操作,会导致UI线程被阻塞,导致应用程序无响应,用户体验变差。因此,Android提供了多种机制(如AsyncTask、Handler、HandlerThread等)来处理耗时操作,并在操作完成后将结果传回主线程以更新UI。
如何在后台线程中更新UI
尽管UI操作必须在主线程中执行,但Android提供了多种机制来在后台线程执行耗时操作,并在操作完成后将结果传回主线程以更新UI。常用的方法包括:
1. 使用 Handler
Handler handler = new Handler(Looper.getMainLooper());
new Thread(new Runnable() {
@Override
public void run() {
// 执行耗时操作
String result = performLongRunningOperation();
// 在主线程更新UI
handler.post(new Runnable() {
@Override
public void run() {
textView.setText(result);
}
});
}
}).start();
private String performLongRunningOperation() {
// 模拟耗时操作
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "操作完成";
}
2. 使用 AsyncTask
private class MyAsyncTask extends AsyncTask<Void, Void, String> {
@Override
protected String doInBackground(Void... voids) {
return performLongRunningOperation();
}
@Override
protected void onPostExecute(String result) {
textView.setText(result);
}
}
// 启动 AsyncTask
new MyAsyncTask().execute();
private String performLongRunningOperation() {
// 模拟耗时操作
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "操作完成";
}
3. 使用 runOnUiThread 方法
new Thread(new Runnable() {
@Override
public void run() {
// 执行耗时操作
String result = performLongRunningOperation();
// 在主线程更新UI
runOnUiThread(new Runnable() {
@Override
public void run() {
textView.setText(result);
}
});
}
}).start();
private String performLongRunningOperation() {
// 模拟耗时操作
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "操作完成";
}
结论
限制UI操作只能在主线程中执行,是为了保证线程安全、简化开发、确保事件处理的正确性和提升应用性能及响应性。Android提供了多种机制来让开发者在后台线程执行耗时操作,并在操作完成后安全地更新UI。
协程是什么用过没有
进程是操作系统进行资源分配的最小单元,线程是操作系统进行运算调度的最小单元。
协程参考: juejin.cn/post/698772…
协程概念
1.协程通过将复杂性放入库来简化异步编程。程序的逻辑可以在协程中顺序地表达,而底层库会为我们解决其异步性。该库可以将用户代码的相关部分包装为回调、订阅相关事件、在不同线程(甚至不同机器)上调度执行,而代码则保持如同顺序执行一样简单。
2.协程是一种并发设计模式。
使用场景
kotlin协程基于Thread相关API的封装,让我们不用过多关心线程也可以方便地写出并发操作,这就是Kotlin的协程。协程的好处本质上和其他线程api一样,方便。
在 Android 平台上,协程有两个主要使用场景:
- 1、线程切换,保证线程安全。
- 2、处理耗时任务(比如网络请求、解析
JSON数据、从数据库中进行读写操作等)。
异步任务解决方案
RxJava
参考文档:
Handler
AsyncTask
Activity和Fragment通信的方式
参考: www.jianshu.com/p/9cd8bf18b…
Dagger框架
RecyclerView缓存机制
RecyclerView是Android开发中用于高效显示大量数据的视图组件,其核心优势之一在于其缓存机制。这种机制使得RecyclerView能够显著减少视图的创建和绑定操作,从而提高性能。RecyclerView的缓存机制主要包括以下几个方面:
-
ViewHolder的重用:
- RecyclerView使用ViewHolder模式来减少频繁创建和销毁视图对象的开销。每个ViewHolder代表一个列表项视图,并且在创建时会被缓存和重用。
-
缓存池(Recycler):
- RecyclerView维护一个缓存池,用于存储不可见的ViewHolder。当一个ViewHolder滚出屏幕时,它会被放入缓存池中,以便下次需要相同类型的ViewHolder时可以从缓存池中获取,而不是重新创建。
-
回收类型:
-
RecyclerView将缓存池分为三种类型:Scrap Heap、Recycled View Pool和View Cache Extension。
-
Scrap Heap:
- Scrap Heap用于存储当前被移出屏幕但仍然可能很快重新出现的ViewHolder。这些ViewHolder通常仍然附着在RecyclerView上。
-
Recycled View Pool:
- Recycled View Pool是一个全局缓存池,用于存储所有类型的ViewHolder。当一个ViewHolder被完全移出屏幕时,它会被放入这个池中。这个池在不同的RecyclerView实例之间是共享的,可以有效地复用ViewHolder。
-
View Cache Extension:
- 开发者可以通过实现
RecyclerView.ViewCacheExtension来自定义缓存逻辑,进一步优化缓存机制。
- 开发者可以通过实现
-
-
缓存大小控制:
- 缓存池的大小是有限的,默认情况下,RecyclerView会为每种视图类型缓存一定数量的ViewHolder。开发者可以通过
RecyclerView.setRecycledViewPool()和RecyclerView.getRecycledViewPool()方法来自定义缓存池的大小。
- 缓存池的大小是有限的,默认情况下,RecyclerView会为每种视图类型缓存一定数量的ViewHolder。开发者可以通过
具体实现流程
-
创建ViewHolder:
- 当RecyclerView需要显示新的数据项时,会通过Adapter的
onCreateViewHolder()方法创建一个新的ViewHolder。 - 如果缓存池中有可用的ViewHolder,则会从缓存池中获取,而不是重新创建。
- 当RecyclerView需要显示新的数据项时,会通过Adapter的
-
绑定数据:
- RecyclerView通过调用Adapter的
onBindViewHolder()方法将数据绑定到ViewHolder。 - 这个过程会重复进行,直到所有可见项都被填充。
- RecyclerView通过调用Adapter的
-
回收ViewHolder:
- 当一个ViewHolder滚出屏幕时,RecyclerView会将其移到缓存池中,准备复用。
示例代码
以下是一个简单的RecyclerView示例,展示了基本的缓存机制:
class MyAdapter(private val dataList: List<String>) : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val textView: TextView = itemView.findViewById(R.id.textView)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.item_layout, parent, false)
return MyViewHolder(itemView)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.textView.text = dataList[position]
}
override fun getItemCount(): Int {
return dataList.size
}
}
// 在Activity或Fragment中使用RecyclerView
val recyclerView: RecyclerView = findViewById(R.id.recyclerView)
val adapter = MyAdapter(dataList)
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(this)
这个示例展示了如何实现一个基本的RecyclerView.Adapter,RecyclerView会自动处理ViewHolder的缓存和复用,从而提高性能。开发者可以进一步优化缓存策略,根据应用的具体需求调整缓存池的大小和自定义缓存逻辑。
Context
在Android开发中,Context是一个非常重要的类,它提供了应用环境中的全局信息和功能。以下是Context在Android开发中的一些主要用途:
-
访问应用资源:
Context提供了访问应用资源的方法,如字符串、图片、颜色和布局等。例如,可以使用context.getResources().getString(R.string.app_name)来获取应用程序名称。
-
启动组件:
Context可以用来启动其他组件,如活动(Activity)、服务(Service)和广播接收器(BroadcastReceiver)。例如,可以使用context.startActivity(intent)来启动一个新活动。
-
访问文件和数据库:
Context提供了访问应用文件系统和数据库的方法。例如,可以使用context.openFileInput("filename")来读取应用私有文件,或者使用context.getDatabasePath("database_name")来获取数据库路径。
-
获取系统服务:
Context允许访问许多系统级别的服务,如LocationManager、ConnectivityManager、LayoutInflater等。例如,可以使用context.getSystemService(Context.LOCATION_SERVICE)来获取定位服务。
-
应用程序信息:
Context提供了关于应用程序包的详细信息,例如应用程序包名、版本号等。例如,可以使用context.getPackageName()来获取应用程序的包名。
-
主题和样式:
Context可以用来获取当前应用的主题和样式资源。例如,可以使用context.getTheme()来获取当前主题,并应用到视图中。
Context的类型
在Android中,Context有几种不同的类型,常见的有以下几种:
-
Application Context:
- 由应用程序对象提供(通过调用
getApplicationContext()),在整个应用程序的生命周期内保持有效。适用于需要在整个应用程序中共享的资源或服务。
- 由应用程序对象提供(通过调用
-
Activity Context:
- 由Activity提供(通过调用
this或getContext()),与Activity的生命周期相关。当Activity被销毁时,Activity Context也会失效。适用于与Activity相关的操作,例如显示对话框、启动新Activity等。
- 由Activity提供(通过调用
-
Service Context:
- 由Service提供(通过调用
this),与Service的生命周期相关。用于在Service中执行操作。
- 由Service提供(通过调用
Context的使用示例
以下是一些常见的Context使用示例:
-
启动新Activity:
Intent intent = new Intent(context, NewActivity.class); context.startActivity(intent); -
获取系统服务:
LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); -
访问资源:
String appName = context.getResources().getString(R.string.app_name); -
读取应用文件:
FileInputStream fis = context.openFileInput("filename");
注意事项
- 避免内存泄漏:尤其在持有Activity Context的情况下,确保不会导致内存泄漏。例如,不要在静态变量或长生命周期的对象中持有Activity Context,可以考虑使用Application Context。
- 使用正确的Context类型:根据实际需要选择合适的Context类型,避免在不恰当的地方使用Application Context或Activity Context。
Context在Android开发中是一个核心组件,理解和正确使用Context对于开发高效和健壮的应用程序至关重要。