4.进程与线程管理

  • 进程管理
    • Android应用默认运行在独立进程中,ActivityManagerService负责进程的创建、销毁和优先级管理。
  • 线程管理
    • 主线程(UI线程)负责UI更新,子线程用于执行耗时操作。
    • HandlerLooperMessageQueue是线程间通信的核心机制。
  • Binder机制
    • Android中跨进程通信(IPC)的核心实现,Binder驱动负责数据传输。

Handler消息机制

Handler解决线程间通信

Handler异步通信系统

Handler机制主要的几个角色:HardlerMessageLooperMessageQueue

主线程在一开始就建立了这么一套系统


Hardler:子线程和主线程之间交互 (发送和处理消息)

Message

Looper:承担了主线程定时查看自己MessageQueue邮箱的一个任务。(取消息的角色)是个死循环,是一直不会退出去的(“心跳机制”)

消息循环 管理消息
不断去消息队列里判断有没有相应的消息
一旦发现有消息就将消息拿出来,给到 Handler

MessageQueue :(保存消息的一个队列)相当于是一个中转站,子线程要是想给主线程发送消息,就将消息发送到MessageQueue中,主线程会定期检查MessageQueue中有没有消息

Handler 调用 handleMessage 方法,处理任务

Hardler.png Handle工作流程.png

Handler多线程通信机制

子线程要想给主线程发送消息,首先要拿到主线程里的Handler,用主线程的Handler给主线程发消息,调用SendMessage,经过上图中的流转。主线程从消息队列中拿到消息,交给自己,调用handleMessage方法,就可以收到消息了

Handler多线程通信机制.png

Handler源码分析

1
2
3
4
5
6
7
8
//一旦初始化之后就不可修改
final Looper mLooper;
final MessageQueue mQueue;


//Handle的一个构造函数中
mLooper = Looper.myLooper();
mQueue = mLooper.mQueue;

MessageQueue的数据结构

main函数执行完之后就会退出,但是在安卓中,如果用户不主动退出,Activity将一直保持运行状态。这就是因为,在Looper内部,维护着一个无限循环,它保持app进程能够在这个死循环里面持续进行。(“心跳机制”)

线程如何跨越内存管理那些事儿

使用

Handler 在 Android 开发中主要用于在不同线程之间传递消息和执行任务,特别是从子线程向主线程更新 UI。下面是 Handler 的基本使用方法和示例。

基本步骤

  1. 创建 Handler:在主线程中创建一个 Handler 实例。
  2. 发送消息或任务:使用 Handler 的 sendMessagepostpostDelayed 方法安排消息或任务。
  3. 处理消息:重写 Handler 的 handleMessage 方法来处理接收到的消息。

示例代码

下面是一个简单的示例,展示如何使用 Handler 在主线程中更新 UI:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import androidx.appcompat.app.AppCompatActivity;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

private TextView textView;
private Handler handler;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

textView = findViewById(R.id.textView);

// 创建 Handler
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
// 更新 UI
textView.setText("更新的文本: " + msg.what);
}
};

// 启动新线程
new Thread(new Runnable() {
@Override
public void run() {
try {
// 模拟耗时操作
Thread.sleep(2000);

// 创建并发送消息
Message msg = handler.obtainMessage(1); // 1 是消息的标识
handler.sendMessage(msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}

说明

  1. 创建 Handler:在 onCreate 方法中创建了一个 Handler 实例,并重写 handleMessage 方法以处理消息。
  2. 新线程:启动了一个新线程以模拟耗时操作(如网络请求),在该线程完成后,通过 Handler 发送消息回主线程。
  3. 更新 UI:在 handleMessage 中接收到消息后,更新 TextView 的文本。

常用方法

  • **sendMessage(Message msg)**:发送消息到 Handler 的队列。
  • **post(Runnable runnable)**:将一个 Runnable 添加到消息队列中,Runnable 会在主线程中执行。
  • **postDelayed(Runnable runnable, long delayMillis)**:将一个 Runnable 添加到消息队列中,延迟指定的时间后执行。

注意事项

  • 避免内存泄露:在使用 Handler 时,如果它引用了 Activity 或其他可能导致内存泄露的对象,请在适当的生命周期回调中移除未处理的消息或回调。
  • 使用弱引用:可以考虑使用 WeakReference 来持有对 Activity 的引用,减少内存泄露的风险。

如果你有更具体的使用场景或问题,可以告诉我,我可以提供更详细的解答或示例!

Coroutine协程

协程是一种并发设计模式,可以使用它来简化异步执行的代码
协程有助于管理长时间运行的任务,如果管理不当,这些任务可能会阻塞主线程并导致应用无响应。

功能:
特点包括:

  1. 轻量 :您可以在单个线程上运行多个协程,因为协程支持挂起 suspend ,不会使正在运行协程的线程阻塞。挂起比阻塞节省内存,且支持多个并行操作。
  2. 内存泄漏更少 :使用结构化并发机制在一个作用域内执行多项操作。
  3. 内置取消支持 :取消操作会自动在运行中的协程层次结构内传播。
  4. Jetpack 集成 :许多 Jetpack 库都包含提供全面协程支持的扩展。某些库还提供自己的协程作用域,可供您用于结构化并发。

  1. 协程域 CoroutineScope()

    • 挂起的函数代码块必须在 CoroutineScope 里执行

    • suspend 修饰的函数/方法

    • 挂起函数 这个函数耗时比较长

    • 不希望阻塞主线程挂起函数的执行必须在 CoroutineScope 内执行

  2. 协程上下文 CoroutineContext()
    这个协程在哪个线程上执行

    • Dispatchers.IO 子线程

    • Dispatchers.Main UI主线程

    • Dispatchers.Default 默认(当前线程)

  3. 任务开启
    使用 launch 开启一个同步任务 return: Job

    • a -> b-> c

    • 返回 Job 任务本身

    • 不需要返回值
      使用 async 开启一个异步任务return Deferred<>

    • a b c 同时做

    • 执行完一段代码之后它需要给我一个结果

    • 需要给我返回值

    • await() 获取对应的值,必须在一个协程内调用

  4. 使用 withContext() 切换线程
    Dispatchers.IO 子线程
    Dispatchers.Main Ul主线程
    最后一行是返回值

在特定的 CoroutineScop e中创建一个新的 CoroutineContext
它允许您在特定的作用域内设置 CoroutineContext,以便在该作用域内执行协程时使用该上下文。
这可以帮助您在协程中使用不同的上下文,例如切换线程或设置协程的调度器。

通常,withContext函数用于在协程中执行需要特定上下文的操作,例如在 IO线程执行网络请求 或 在主线程更新UI。

总之,withContext函数可以帮助您在协程中更改上下文,以便更好地控制协程的执行环境和调度方式。

  1. Flow 数据流

    • 当调用一个方法,在不同的时间/情况可能需要返回多个值

    • emit() 发射新的数据

    • collect() 订阅接受数据 必须在一个协程域内调用

    • flow{ } 创建一个Flow对象

  2. 协程域
    Activity / Fragment

  • lifecycleScope :
    为我们默认提供了一个CoroutineScope 直接使用就可以了
    如果是自己创建,自己需要管理这个协程的生命周期(创建->销毁)

ViewModel

  • ViewModelScope

除此以外就需要自已创建 CoroutineScope


使用步骤

导入依赖:
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9")

用 suspend 标识耗时的操作,这个耗时操作只能在协程域里面执行

1
2
3
suspend fun count(){

}

(1)创建一个协程域

1
val scope = CoroutineScope(Dispatchers.lO)

(2)在这个协程域上开启一个任务

  • 使用 launch 开启一个同步任务 a-> b-> c 任务默认就在 scope 所在的区域执行,会返回 Job
1
2
3
4
5
6
7
8
scope.launch{ //默认不指定就在 scope 规定的线程
count()
}

//↓可以切换线程
scope.launch(Dispatchers.lO){ //另外指定线程
count()
}
  • async 开启一个异步任务
1
2
3
4
5
6
7
val sum2 = scope.async{
var sum = sum1.await()
for (i in 1 < .. < 100){
sum += i
}
sum
}

withContext(Dispatchers.Main) 切换上下文 切换代码执行的scope 切换线程

1
2
3
4
5
6
7
8
9
suspend fun count(){
for (i in 1..100){
withContext(Dispatchers.Main){
mTextView.text = "$i"
}
delay( timeMillis: 200)
}
}

Flow

在Kotlin中,Flow 的 emit 方法用于发射(emit)一个新的元素到 Flow 中,而 collect 方法用于从Flow中收集(collect)元素并进行处理。

  1. emit方法:emit方法是Flow的一个扩展函数,用于向Flow中发射一个新的元素。当调用emit方法时,会将指定的元素发送到Flow中,并通知Flow的观察者(collect方法)有新的元素可用。emit方法通常用于在Flow中生成新的元素,例如在网络请求或数据库查询返回结果后,将结果发射到Flow中。

示例代码:

1
2
3
4
5
6
7
fun generateNumbers(): Flow<Int> = flow {
for (i in 1..5) {
emit(i)
}
}

val numbersFlow = generateNumbers()
  1. collect 方法:collect 方法是Flow的一个挂起函数,用于从 Flow 中收集元素并进行处理。当调用 collect 方法时,会阻塞当前协程直到 Flow 中有新的元素可用,然后处理这个元素。 collect 方法通常用于订阅 Flow 并处理其中的元素,例如打印元素、更新 UI 等操作。

示例代码:

1
2
3
4
5
6
val numbersFlow = generateNumbers()
viewModelScope.launch {
numbersFlow.collect { number ->
Log.d("FlowExample", "Received number: $number")
}
}

通过 emit 方法向 Flow 中发射新的元素,然后通过 collect 方法从 Flow 中收集并处理这些元素,可以实现在 Flow 中生成和处理数据的功能。


cancel() 取消该任务
join() 等待任务做完
cancelAneJoin() 将 CoroutineScope 的属性 isActive 转换为 false

面试

请回答一下Android进程间的通信方式?

在 Android 中,进程间通信(IPC,Inter-Process Communication)是实现不同应用或同一应用内不同进程间数据交换的关键机制。Android 提供了多种 IPC 方式,以下是常见的几种:


1. Intent

  • 用途:用于在 Activity、Service、BroadcastReceiver 等组件之间传递数据。

  • 特点

    • 简单易用,适合传递少量数据。
    • 支持显式和隐式调用。
  • 限制

    • 只能传递基本数据类型或实现了 Parcelable/Serializable 的对象。
    • 不适合频繁或大数据量的通信。
  • 示例

    1
    2
    3
    Intent intent = new Intent(this, TargetActivity.class);
    intent.putExtra("key", "value");
    startActivity(intent);

2. Bundle

  • 用途:通常与 Intent 配合使用,用于封装多个数据。
  • 特点
    • 支持传递基本数据类型、字符串、ParcelableSerializable 对象。
    • 适合在组件间传递复杂数据。
  • 示例
    1
    2
    3
    4
    5
    Bundle bundle = new Bundle();
    bundle.putString("key", "value");
    Intent intent = new Intent(this, TargetActivity.class);
    intent.putExtras(bundle);
    startActivity(intent);

3. Messenger

  • 用途:基于 Handler 的轻量级 IPC 方式,适合单向通信。
  • 特点
    • 使用 Message 对象传递数据。
    • 底层基于 Binder 机制。
    • 适合简单的跨进程通信。
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 服务端
    Messenger messenger = new Messenger(new Handler() {
    @Override
    public void handleMessage(Message msg) {
    // 处理消息
    }
    });
    // 客户端
    Messenger clientMessenger = new Messenger(serviceConnection);
    Message message = Message.obtain();
    message.replyTo = clientMessenger;
    clientMessenger.send(message);

4. AIDL(Android Interface Definition Language)

  • 用途:用于定义跨进程通信的接口,支持双向通信。
  • 特点
    • 功能强大,适合复杂的跨进程通信。
    • 支持多线程并发处理。
    • 需要定义 .aidl 文件并生成接口。
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // AIDL 文件
    interface IMyService {
    void doSomething();
    }
    // 服务端实现
    public class MyService extends Service {
    private final IMyService.Stub binder = new IMyService.Stub() {
    @Override
    public void doSomething() {
    // 实现逻辑
    }
    };
    @Override
    public IBinder onBind(Intent intent) {
    return binder;
    }
    }

5. ContentProvider

  • 用途:用于跨进程共享数据,通常用于访问数据库或文件。
  • 特点
    • 提供统一的接口访问数据。
    • 支持权限控制。
    • 适合大量数据的共享。
  • 示例
    1
    2
    3
    // 查询 ContentProvider
    Uri uri = Uri.parse("content://com.example.provider/data");
    Cursor cursor = getContentResolver().query(uri, null, null, null, null);

6. Binder

  • 用途:Android 的核心 IPC 机制,AIDL 和 Messenger 都是基于 Binder 实现的。
  • 特点
    • 高效、安全。
    • 支持跨进程调用方法。
    • 需要手动实现 Binder 接口。
  • 示例
    1
    2
    3
    4
    5
    6
    // 自定义 Binder
    public class MyBinder extends Binder {
    public void doSomething() {
    // 实现逻辑
    }
    }

7. Socket

  • 用途:基于网络的 IPC 方式,适合跨设备或复杂场景。
  • 特点
    • 支持 TCP 或 UDP 协议。
    • 适合远程通信。
    • 实现复杂,性能开销较大。
  • 示例
    1
    2
    3
    4
    5
    // 服务端
    ServerSocket serverSocket = new ServerSocket(8080);
    Socket socket = serverSocket.accept();
    // 客户端
    Socket socket = new Socket("127.0.0.1", 8080);

8. SharedPreferences(跨进程)

  • 用途:通过 MODE_MULTI_PROCESS 模式实现跨进程共享数据。
  • 特点
    • 简单易用。
    • 适合小规模数据共享。
    • 已过时,不推荐使用。
  • 示例
    1
    SharedPreferences sharedPreferences = getSharedPreferences("data", Context.MODE_MULTI_PROCESS);

9. 文件共享

  • 用途:通过读写文件实现数据共享。
  • 特点
    • 简单直接。
    • 适合大规模数据共享。
    • 需要处理并发和同步问题。
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    // 写入文件
    FileOutputStream fos = openFileOutput("data.txt", MODE_PRIVATE);
    fos.write("data".getBytes());
    fos.close();
    // 读取文件
    FileInputStream fis = openFileInput("data.txt");
    BufferedReader reader = new BufferedReader(new InputStreamReader(fis));
    String data = reader.readLine();

总结

通信方式 适用场景 特点
Intent 简单数据传递 简单易用,适合少量数据
Bundle 复杂数据传递 支持多种数据类型
Messenger 单向通信 轻量级,基于 Binder
AIDL 复杂双向通信 功能强大,支持多线程
ContentProvider 数据共享 适合大规模数据,支持权限控制
Binder 底层 IPC 高效、安全,AIDL 和 Messenger 的基础
Socket 跨设备通信 适合远程通信,实现复杂
SharedPreferences 小规模数据共享(已过时) 简单易用,不推荐使用
文件共享 大规模数据共享 简单直接,需处理并发问题

根据具体需求选择合适的 IPC 方式,可以高效地实现进程间通信。

谈谈 Handler 机制和原理?

首先在UI线程我们创建了一个Handler实例对象,无论是匿名内部类还是自定义类生成的Handler实例对象,我们都需要对 handleMessage方法进行重写,在handleMessage方法中我们可以通过参数msg来写接受消息过后UIi线程的逻辑处理,接着我们创建子线程,在子线程中需要更新UI的时候,新建一个 Message对象,并且将消息的数据记录在这个消息对象 Message的内部,比如arg1,arg2,obj等,然后通过前面的 Handler实例对象调用sendMessge方法把这个Message实例 对象发送出去,之后这个消息会被存放于 MessageQueue中等待被处理,此时MessageQueue的管家 Looper正在不停的把MessageQueue存在的消息取出来,通过回调dispatchMessage方法将消息传递给Handler

请说一说Handler消息机制

Activity中使用coroutine开启一个线程,Activity杀掉后线程还会执行么

通常,Coroutine 是与某个 CoroutineScope 绑定的,最常见的是 lifecycleScope(绑定到 Activity 或 Fragment 的生命周期)。当 Activity 被销毁时,如果你使用的是 lifecycleScope 或者一个与 Activity 生命周期相对应的 CoroutineScope,那么 Coroutine 也会被取消。

主线程切换到子线程执行逻辑,子线程会立即执行么

  1. 线程创建与启动:在Android中,创建子线程通常是通过继承Thread类或实现Runnable接口后,将其传递给Thread对象。调用start()方法后,子线程会进入就绪状态,但并不会立即执行。
  2. 线程调度:子线程的执行受到CPU调度的影响。即使你调用了start(),子线程仍然需要等待系统分配CPU时间片才能开始执行。这个过程可能会有延迟。
  3. 主线程的任务:如果主线程或其他线程正在执行高优先级的任务,可能需要等待这些任务完成后,才会有CPU时间片分配给子线程。
Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.

扫一扫,分享到微信

微信分享二维码
  • Copyrights © 2023-2025 Annie
  • Visitors: | Views:

嘿嘿 请我吃小蛋糕吧~

支付宝
微信