Android 主线程和线程之间相互发送消息

本文介绍了一个简单的示例,展示了Android应用中主线程如何与子线程通过Handler和Message进行通信。主线程定期发送消息给子线程,子线程处理后返回消息给主线程并展示。

      通过分析Activity源码,我们知道每个Activity都有一个Looper,所以主线程在接收Message是不需要调用Looper.prepare()和Looper.loop(),但是线程是不带Looper的,当线程要接收来自主线程的消息是就需要调用Looper.prepare()和Looper.loop()。以下我们将通过一个示例,讲述主线程和线程之间如何发送Message。

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.Toast;

/**
 * 这个类是主线程,它每隔两秒就发送一个Message给线程,线程收到Message后将发送一个Message给主线程,主线程收到Message后会将Message内容Toast出来。
 * org.fneg.HandlerTestActivity.java
 * Create at: 2012-6-4 下午9:39:19
 * @author:feng
 * Email:fengcunhan@gmail.com
 *
 */
public class HandlerTestActivity extends Activity {
    private Handler uiHandler;
    private ThreadWithLooper thread;
    private Runnable showRunable;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        uiHandler=new Handler(){
        	@Override
        	public void handleMessage(Message msg) {
        		switch(msg.what){
				case Messages.MSG_HELLO:
					 Toast.makeText(HandlerTestActivity.this, (String)msg.obj, Toast.LENGTH_SHORT).show();
					break;
					
				}
        	}
        };
        thread=new ThreadWithLooper(uiHandler);
		
		thread.start();
        showRunable=new Runnable() {
			
			@Override
			public void run() {
				//給线程发送一个Message
				thread.getHandler().sendEmptyMessage(Messages.MSG_HELLO);
				uiHandler.postDelayed(this, 2*1000);
			}
		};
        uiHandler.post(showRunable);
        
    }
    
    @Override
    protected void onStop() {
    	super.onStop();
    	uiHandler.removeCallbacks(showRunable);
    }
}

import android.os.Handler;
import android.os.Looper;
import android.os.Message;

/**
 * 从线程发送消息到UI线程(主线程)
 * org.fneg.ThreadWithLooper.java
 * Create at: 2012-6-4 下午4:58:11
 * @author:feng<br/>
 * Email:fengcunhan@gmail.com
 *
 */
public class ThreadWithLooper extends Thread {
	private Handler handler;
	
	private Handler uiHandler;
	
	public ThreadWithLooper(Handler mHandler){
		this.uiHandler=mHandler;
		//初始化Handler,接收到主线程发送过来的Message就回复一个Message给主线程,消息内容是 一个字符串和当前时间
		handler =new Handler(){

			@Override
			public void handleMessage(Message msg) {
				switch(msg.what){
				case Messages.MSG_HELLO:
					 Message message=new Message();
					 message.what=Messages.MSG_HELLO;
					 message.obj="Yes!I get a hello"+System.currentTimeMillis();
					 uiHandler.sendMessage(message);
					break;
					
				}
			}
			
		};
	}
	
	public Handler getHandler() {
		return handler;
	}

	public void setHandler(Handler handler) {
		this.handler = handler;
	}
	
	@Override
	public void run() {
		Looper.prepare();
		
		Looper.loop();
	}
	
	
}

/**
 *
 * org.fneg.Messages.java
 * Create at: 2012-6-4 下午4:51:20
 * @author:feng
 * Email:fengcunhan@gmail.com
 *
 */
public class Messages {
   public static final int MSG_HELLO=0X1;
}

 

<think>我们正在讨论的是Android ContentProvider的call方法执行在哪个线程主线程还是子线程)。 根据提供的引用Android系统的设计原理,我们可以分析如下: 引用[1]提到ContentProvider的call方法是在API 11中加入的,用于跨进程的方法调用,它比广播延时低,比AIDL轻量。 引用[2]指出,不管访问者是同一个进程的不同线程还是不同进程,Provider方是同一个对象实例,当并发访问同一个方法时,该方法运行在不同的线程中,不会相互影响。 引用[3]描述了Binder机制中onTransact方法运行在服务端Binder线程池中,而call方法作为ContentProvider提供的一种跨进程通信方式,其底层也是基于Binder实现的。 引用[4]则说明了ContentProvider的线程安全设计,特别是数据库操作通过同步锁保证线程安全。 结合以上引用Android系统的知识,我们可以推断: 1. ContentProvider的call方法用于跨进程调用,其底层通过Binder机制实现。 2. Binder机制中,服务端(即ContentProvider所在进程)的跨进程请求是由Binder线程池中的线程处理的(如引用[3]所述,onTransact运行在Binder线程池中)。 3. 因此,当客户端调用ContentProvider的call方法时,该请求会被发送到ContentProvider所在进程,并由Binder线程池中的一个线程执行。 所以,ContentProvider的call方法不会运行在主线程(UI线程),而是运行在Binder线程池中的一个子线程中。 但是,需要注意的是,如果ContentProvider调用者在同一个进程(即非跨进程),那么call方法将直接调用,此时它运行在调用者所在的线程。不过,根据Android的设计,即使在同一进程,ContentProvider的CRUD方法(包括call)也是被设计为线程安全的,并且系统可能会进行一些线程调度。 然而,根据引用[2]的内容,不管是否跨进程,Provider实例是单例的,并且当并发访问同一个方法时,该方法会运行在不同的线程中(即不会在主线程)。因此,我们可以确认: - 跨进程调用:call方法在Binder线程池中的线程执行(子线程)。 - 同一进程内调用:如果调用者是在主线程调用,那么call方法会直接在主线程执行吗?这里需要谨慎。 实际上,根据Android官方文档设计,ContentProvider的方法(包括call)在同一个进程内调用时,并不会自动切换到后台线程。也就是说,如果调用者在主线程调用call方法,那么call方法就会在主线程执行。但是,在跨进程的情况下,call方法是在Binder线程池中执行的。 然而,我们引用的内容并没有直接说明同一进程内调用call方法的线程情况。但是引用[2]提到“当并发访问CP的同一个方法的时候,这个方法运行在不同的线程中”,这暗示了即使是同一个进程内,多个线程同时调用同一个方法,这些方法也会在不同的线程中执行(即调用者线程)。所以,在同一进程内,call方法运行在调用者线程。 因此,我们可以总结: - 跨进程:call方法在服务端进程的Binder线程池中的线程执行(子线程)。 - 同一进程:call方法在调用者线程中执行。如果调用者线程主线程,那么call方法就在主线程执行;如果调用者线程是子线程,那么call方法就在子线程执行。 但是,为了验证同一进程内的情况,我们可以参考Android官方文档或源码。实际上,在ContentProvider的源码中,call方法的实现最终会调用到ContentProvider的call方法,而这个过程并没有切换线程。因此,在同一进程内,call方法直接运行在调用者线程。 然而,我们还需要注意:ContentProvider的设计初衷是提供数据共享,其方法(包括call)可能会执行耗时操作。因此,无论是否跨进程,我们都应该避免在主线程中调用call方法(尤其是可能耗时的call操作),以防止ANR。 所以,回答用户的问题: **Android ContentProvider的call方法执行线程取决于调用场景:** 1. **跨进程调用**:运行在**子线程**(Binder线程池)。 2. **同一进程内调用**:运行在**调用者线程**(如果调用者线程主线程,则call方法在主线程执行;如果是子线程,则在子线程执行)。 建议:为了避免ANR,无论是否跨进程,都不应在主线程调用可能耗时的call方法。 引用验证: - 引用[3]支持跨进程调用在Binder线程池(子线程)执行。 - 引用[2]支持并发访问时方法运行在不同线程,这暗示了同一进程内调用时,方法运行在调用者线程。 因此,我们可以这样回答:</think>### Android ContentProvider `call()` 方法的线程机制分析 #### 1. **跨进程调用场景(最常见)** - **执行线程**:**子线程(Binder线程池)** 当通过`call()`进行跨进程通信(IPC)时,系统底层使用Binder机制处理请求。如引用[3]所述: > `onTransact` 方法运行在服务端Binder线程池中,客户端跨进程请求会通过系统底层封装后交由此方法处理[^3]。 这意味着: - 请求由Binder线程池中的**工作线程**处理 - 不会阻塞ContentProvider所在进程的主线程 - 典型调用流程: ```mermaid graph LR A[客户端进程] -- IPC请求 --> B[Binder驱动] B -- 分配线程 --> C[Binder线程池] C --> D[执行Provider的call方法] ``` #### 2. **同进程调用场景** - **执行线程**:**调用者线程** 如引用[2]强调: > 不管访问者是同一进程的不同线程还是不同进程,Provider方是同一个对象实例。当并发访问时,方法运行在不同线程中[^2]。 这意味着: - 若调用者在主线程发起`call()` → 则`call()`在主线程执行 - 若调用者在子线程发起`call()` → 则`call()`在该子线程执行 - **重要风险**:在主线程调用耗时`call()`方法可能导致ANR #### 3. **线程安全并发处理** - **单例模型**:ContentProvider实例是进程内单例(引用[2]) - **并发控制**: - 系统自动为每次请求分配独立执行线程 - 开发者需自行处理共享资源竞争(如数据库加锁) - 如引用[4]所示,可通过`synchronized`或`SQLiteOpenHelper`内置锁保证线程安全[^4] #### 4. **性能优化建议** 1. **避免主线程调用** 无论是否跨进程,都应在子线程调用`call()`: ```kotlin viewModelScope.launch(Dispatchers.IO) { contentResolver.call(uri, "encrypt", null, bundle) } ``` 2. **耗时操作异步化** 若`call()`包含复杂计算/IO操作,内部应切子线程: ```java public Bundle call(String method, String arg, Bundle extras) { return Executors.newSingleThreadExecutor().submit(() -> { // 实际耗时操作 }).get(); } ``` 3. **权限验证轻量化** 权限检查应在`call()`开头快速完成(引用[3]的权限验证建议)[^3] #### 总结 | 调用场景 | 执行线程 | 线程切换机制 | |----------------|-------------------|-----------------------| | **跨进程通信** | Binder线程池 | 系统自动分配工作线程 | | **同进程调用** | 调用者所在线程 | 无自动线程切换 | **核心结论**: ContentProvider的`call()`方法**默认不保证在子线程执行**——跨进程时在Binder线程池(子线程),同进程时调用者线程相同。开发者必须主动管理线程以避免ANR[^2][^3]。 --- ### 相关问题 1. 如何验证`ContentProvider.call()`在实际运行中的线程状态? 2. 跨进程`call()`方法出现ANR的可能原因及排查方法? 3. `ContentProvider.call()``Binder`通信的线程模型有何关联? 4. 同进程内调用`call()`时如何避免阻塞主线程? 5. `call()`方法CRUD方法的线程机制有何异同?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值