个人总结

136 阅读13分钟

网上面试相关总结:

github.com/JsonChao/Aw…

安卓事件分发机制

Android 的事件分发机制主要包括三个核心部分:ActivityViewGroupView,它们共同作用来处理触摸事件 (Touch Events)。事件分发机制可以理解为事件从上层控件传递到下层控件,以及在控件之间传递的过程。下面是对这一机制的详细解释:

1. 事件分发方法

主要有以下三个方法:

  • dispatchTouchEvent(MotionEvent event):分发事件。
  • onInterceptTouchEvent(MotionEvent event):是否拦截事件(仅 ViewGroup 及其子类有此方法)。
  • onTouchEvent(MotionEvent event):处理事件。

2. 事件传递流程

事件从 Activity 开始,通过 dispatchTouchEvent 进行分发:

(1) Activity

ActivitydispatchTouchEvent 方法会首先调用,然后将事件传递给 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

ViewGroupdispatchTouchEvent 方法内部会调用 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

ViewdispatchTouchEvent 方法会直接调用 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. 事件处理流程总结

  • 事件首先传递给 ActivitydispatchTouchEvent 方法。
  • Activity 将事件传递给 Window,最终传递给当前界面的顶层 ViewGroup
  • ViewGroup 通过 onInterceptTouchEvent 判断是否拦截事件。
    • 若拦截,直接调用 ViewGrouponTouchEvent 处理。
    • 若不拦截,将事件传递给子 ViewdispatchTouchEvent
  • View 再依次进行类似的处理。

4. 事件传递的注意事项

  • ViewGroup 可以通过 onInterceptTouchEvent 来决定是否拦截事件。
  • 如果某个 ViewViewGrouponTouchEvent 返回 true,则表示事件被消耗,不会继续传递。
  • 事件分发过程中,ACTION_DOWN 是起始事件,必须被处理,否则后续的 ACTION_MOVEACTION_UP 事件将不会再传递。

示例代码

以下是一个简单的示例,展示 ViewGroupView 如何协同工作来处理触摸事件:

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开发中,HandlerThreadHandlerThread都是用于管理和处理多线程操作的类,但它们各自有不同的用途和功能。以下是它们之间的区别:

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提供了多种机制(如AsyncTaskHandlerHandlerThread等)来处理耗时操作,并在操作完成后将结果传回主线程以更新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

参考文档:

blog.csdn.net/qq_45649553…

Handler

AsyncTask

Activity和Fragment通信的方式

参考: www.jianshu.com/p/9cd8bf18b…

Dagger框架

RecyclerView缓存机制

RecyclerView是Android开发中用于高效显示大量数据的视图组件,其核心优势之一在于其缓存机制。这种机制使得RecyclerView能够显著减少视图的创建和绑定操作,从而提高性能。RecyclerView的缓存机制主要包括以下几个方面:

  1. ViewHolder的重用

    • RecyclerView使用ViewHolder模式来减少频繁创建和销毁视图对象的开销。每个ViewHolder代表一个列表项视图,并且在创建时会被缓存和重用。
  2. 缓存池(Recycler):

    • RecyclerView维护一个缓存池,用于存储不可见的ViewHolder。当一个ViewHolder滚出屏幕时,它会被放入缓存池中,以便下次需要相同类型的ViewHolder时可以从缓存池中获取,而不是重新创建。
  3. 回收类型

    • 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来自定义缓存逻辑,进一步优化缓存机制。
  4. 缓存大小控制

    • 缓存池的大小是有限的,默认情况下,RecyclerView会为每种视图类型缓存一定数量的ViewHolder。开发者可以通过RecyclerView.setRecycledViewPool()RecyclerView.getRecycledViewPool()方法来自定义缓存池的大小。

具体实现流程

  1. 创建ViewHolder

    • 当RecyclerView需要显示新的数据项时,会通过Adapter的onCreateViewHolder()方法创建一个新的ViewHolder。
    • 如果缓存池中有可用的ViewHolder,则会从缓存池中获取,而不是重新创建。
  2. 绑定数据

    • RecyclerView通过调用Adapter的onBindViewHolder()方法将数据绑定到ViewHolder。
    • 这个过程会重复进行,直到所有可见项都被填充。
  3. 回收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开发中的一些主要用途:

  1. 访问应用资源

    • Context提供了访问应用资源的方法,如字符串、图片、颜色和布局等。例如,可以使用context.getResources().getString(R.string.app_name)来获取应用程序名称。
  2. 启动组件

    • Context可以用来启动其他组件,如活动(Activity)、服务(Service)和广播接收器(BroadcastReceiver)。例如,可以使用context.startActivity(intent)来启动一个新活动。
  3. 访问文件和数据库

    • Context提供了访问应用文件系统和数据库的方法。例如,可以使用context.openFileInput("filename")来读取应用私有文件,或者使用context.getDatabasePath("database_name")来获取数据库路径。
  4. 获取系统服务

    • Context允许访问许多系统级别的服务,如LocationManagerConnectivityManagerLayoutInflater等。例如,可以使用context.getSystemService(Context.LOCATION_SERVICE)来获取定位服务。
  5. 应用程序信息

    • Context提供了关于应用程序包的详细信息,例如应用程序包名、版本号等。例如,可以使用context.getPackageName()来获取应用程序的包名。
  6. 主题和样式

    • Context可以用来获取当前应用的主题和样式资源。例如,可以使用context.getTheme()来获取当前主题,并应用到视图中。

Context的类型

在Android中,Context有几种不同的类型,常见的有以下几种:

  1. Application Context

    • 由应用程序对象提供(通过调用getApplicationContext()),在整个应用程序的生命周期内保持有效。适用于需要在整个应用程序中共享的资源或服务。
  2. Activity Context

    • 由Activity提供(通过调用thisgetContext()),与Activity的生命周期相关。当Activity被销毁时,Activity Context也会失效。适用于与Activity相关的操作,例如显示对话框、启动新Activity等。
  3. Service Context

    • 由Service提供(通过调用this),与Service的生命周期相关。用于在Service中执行操作。

Context的使用示例

以下是一些常见的Context使用示例:

  1. 启动新Activity

    Intent intent = new Intent(context, NewActivity.class);
    context.startActivity(intent);
    
  2. 获取系统服务

    LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
    
  3. 访问资源

    String appName = context.getResources().getString(R.string.app_name);
    
  4. 读取应用文件

    FileInputStream fis = context.openFileInput("filename");
    

注意事项

  • 避免内存泄漏:尤其在持有Activity Context的情况下,确保不会导致内存泄漏。例如,不要在静态变量或长生命周期的对象中持有Activity Context,可以考虑使用Application Context。
  • 使用正确的Context类型:根据实际需要选择合适的Context类型,避免在不恰当的地方使用Application Context或Activity Context。

Context在Android开发中是一个核心组件,理解和正确使用Context对于开发高效和健壮的应用程序至关重要。