子线程调用Toast报Can't create handler inside thread that has not called Looper.prepare() 错误...

直接上原文,地址:http://hi.baidu.com/_guangren_/item/8e61f31cecad9a701009b5b7


今天用子线程调Toast报了一个Can'tcreatehandlerinsidethreadthathasnotcalledLooper.prepare()错误。在网上找了找答案,发现两篇文章:


【1】http://czhjchina.blog.163.com/blog/static/20027904720123163149186/

因为toast的现实需要在activity的主线程才能正常工作,所以传统的线程不能使toast显示在actvity上,通过Handler可以使自定义线程运行于Ui主线程
前几次碰到这个问题,确实郁闷了很久...log--java.lang.RuntimeException:Can'tcreatehandlerinsidethreadthathasnotcalledLooper.prepare()
解决办法很简单:


Looper.prepare();
Toast.makeText(getApplicationContext(),"test",Toast.LENGTH_LONG).show();
Looper.loop();

为什么要加这两句,看了源码就了解了
Toast

publicvoidshow(){

...

service.enqueueToast(pkg,tn,mDuration);//把这个toast插入到一个队列里面

...

}

Looper

publicstaticfinalvoidprepare(){

if(sThreadLocal.get()!=null){

thrownewRuntimeException("OnlyoneLoopermaybecreatedperthread");

}

sThreadLocal.set(newLooper());//在当前线程中创建一个Looper

}

privateLooper(){
mQueue=newMessageQueue();//关键在这,创建Looper都干了什么。其实是创建了消息队列
mRun=true;
mThread=Thread.currentThread();
}

一般如果不是在主线程中又开启了新线程的话,一般都会碰到这个问题。
原因是在创建新线程的时候默认情况下不会去创建新的MessageQueue。

总结下:Toast显示的必要条件:
1:Toast显示需要出现在一个线程的消息队列中....很隐蔽


【2】http://tech.cncms.com/shouji/android/96016.html


Android中HandlerThread类的解释

Android应用中的消息循环由Looper和Handler配合完成,Looper类用于封装消息循环,类中有个MessageQueue消息队列;Handler类封装了消息投递和消息处理等功能。

系统默认情况下只有主线程(即UI线程)绑定Looper对象,因此在主线程中可以直接创建Handler的实例,但是在子线程中就不能直接new出Handler的实例了,因为子线程默认并没有Looper对象,此时会抛出RuntimeException异常:
浏览下Handler的默认构造函数就一目了然了:

如果需要在子线程中使用Handler类,首先需要创建Looper类实例,这时可以通过Looper.prepare()和Looper.loop()函数来实现的。阅读Framework层源码发现,Android为我们提供了一个HandlerThread类,该类继承Thread类,并使用上面两个函数创建Looper对象,而且使用wait/notifyAll解决了多线程中子线程1获取子线程2的Looper对象为空的问题。


第一篇文章的处理方法是对的,但是解释应该是第二篇文章所说的,Toast创建时需要创建一个Handler,但是这个Handler需要获得Looper的实例,而在子线程中是没有这个实例的,需要手动创建。


附Toast部分源码:


publicToast(Contextcontext){
mContext=context;
mTN=newTN();
mTN.mY=context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.toast_y_offset);
}


privatestaticclassTNextendsITransientNotification.Stub{
……
finalHandlermHandler=newHandler();

……

}


Handler源码:


/**
*Defaultconstructorassociatesthishandlerwiththequeueforthe
*currentthread.
*
*Ifthereisn'tone,thishandlerwon'tbeabletoreceivemessages.
*/
publicHandler(){
if(FIND_POTENTIAL_LEAKS){
finalClass<?extendsHandler>klass=getClass();
if((klass.isAnonymousClass()||klass.isMemberClass()||klass.isLocalClass())&&
(klass.getModifiers()&Modifier.STATIC)==0){
Log.w(TAG,"ThefollowingHandlerclassshouldbestaticorleaksmightoccur:"+
klass.getCanonicalName());
}
}

mLooper=Looper.myLooper();
if(mLooper==null){
thrownewRuntimeException(
"Can'tcreatehandlerinsidethreadthathasnotcalledLooper.prepare()");
}
mQueue=mLooper.mQueue;
mCallback=null;
}


package com.example.juble_app; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; import androidx.activity.EdgeToEdge; import androidx.appcompat.app.AppCompatActivity; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; import com.example.juble_app.callback.BleCallback; import com.example.juble_app.utils.BleHelper; import com.example.juble_app.viewmodle.MyViewModel; import com.google.android.material.bottomnavigation.BottomNavigationView; public class ModbusConnectActivity extends AppCompatActivity { /** Gatt / private static BluetoothGatt bluetoothGatt; /* 设备是否连接 / private boolean isConnected = false; /* Gatt回调 / private BleCallback bleCallback; /* MODBUS数据 */ private MyViewModel viewModel; private BottomNavigationView bottomNavigationView; private Button modbus_get; private TextView address; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); EdgeToEdge.enable(this); setContentView(R.layout.activity_modbus_connect); // 创建Handler用于更新UI Handler handler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { super.handleMessage(msg); // 更新TextView显示收到的内容 String receivedContent = (String) msg.obj; viewModel.setData(receivedContent); } }; bleCallback = new BleCallback(handler); //获取上个页面传递过来的设备 BluetoothDevice device = getIntent().getParcelableExtra("device"); //连接gatt 设置Gatt回调 bluetoothGatt = device.connectGatt(this, false, bleCallback); bottomNavigationView = findViewById(R.id.bottomNavigationView); modbus_get = findViewById(R.id.actionButton); modbus_get.setOnClickListener(v -> { String command = "00420017000109D0"; // 发送指令到子线程 new Thread(() -> { BleHelper.sendCommand(bluetoothGatt, command, true); }).start(); }); address = findViewById(R.id.displayTextView); ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); return insets; }); } /** Toast提示 @param msg 内容 */ private void showMsg(String msg) { Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); } }分析此段代码,并代码实现将modbus_get.setOnClickListener(v -> { String command = "00420017000109D0"; // 发送指令到子线程 new Thread(() -> { BleHelper.sendCommand(bluetoothGatt, command, true); }).start(); });返回数据显示在address控件上,不在handler中做修改,
最新发布
03-12
<think>好的,我现在需要帮用户分析他们提供的Android代码,并实现将Modbus指令返回的数据显示在address控件上,同时不在原有的Handler中修改。首先,我得仔细看看用户提供的代码结构和现有的逻辑。 首先,用户有一个ModbusConnectActivity,里面有一个按钮modbus_get,点击后会发送一个Modbus命令。当前的代码中,发送命令是在子线程中通过BleHelper.sendCommand执行的。然后,通过BleCallback和一个Handler来处理接收到的数据,更新ViewModel中的数据。但用户现在希望将返回的数据直接显示在address这个TextView上,而不在原来的Handler里修改。 我需要理解现有的数据流。BleCallback可能是在接收到蓝牙数据后,通过Handler发送消息到主线程,然后更新ViewModel。现在用户不想改动这个Handler,所以可能需要另外的方式获取到返回的数据并显示。 可能的解决方案是,在BleCallback中添加一个接口或者回调方法,当数据接收到时,除了原有的Handler处理外,还能触发另一个回调,从而在ModbusConnectActivity中处理数据显示。或者,查看BleHelper.sendCommand是否有返回数据的方法,或者是否有地方可以监听返回的数据。 但根据代码,BleHelper.sendCommand可能负责发送命令,而BleCallback负责接收数据。现有的BleCallback已经使用Handler将数据传递到主线程,并更新ViewModel。如果用户不想修改原有的Handler,那么可能需要扩展BleCallback,使其能够支持额外的回调,比如在接收到数据时,除了发送消息给Handler,还能触发一个自定义的回调函数,这样在Activity中可以注册这个回调,并更新address控件。 例如,在BleCallback类中添加一个回调接口,当数据到达时,除了发送Message外,还调用这个接口。然后在ModbusConnectActivity中设置这个回调,并在回调方法里更新UI。 不过,需要确认BleCallback是否允许添加这样的回调。查看用户提供的代码,BleCallback的构造函数接收一个Handler,所以可能在接收到数据时,通过Handler发送消息。如果BleCallback是用户自己编写的,那么可以修改它,添加一个监听器或者回调方法。假设BleCallback是可控的,那么可以在其中增加一个接口,比如OnDataReceivedListener,当数据到达时触发。 然后,在ModbusConnectActivity中,可以注册这个监听器,并在回调中更新address的文本。这样,不需要修改原有的Handler处理逻辑,只需要在接收到数据时同时触发新的回调。 另外,需要考虑线程问题,因为蓝牙操作通常在子线程UI更新必须在主线程。所以,在回调中需要使用runOnUiThread或者Handler来切换到主线程更新TextView。 现在,假设BleCallback可以修改,那么具体步骤如下: 1. 在BleCallback类中定义一个接口OnDataReceivedListener,包含一个方法onDataReceived(String data)。 2. 在BleCallback中添加设置监听器的方法,比如setOnDataReceivedListener。 3. 在BleCallback处理数据的地方,当收到数据时,除了发送Message给Handler,还检查是否有注册的监听器,如果有,就调用onDataReceived方法,并传递数据。 4. 在ModbusConnectActivity中,创建这个监听器的实例,在onCreate中设置到bleCallback对象上。 5. 在监听器的onDataReceived方法中,使用runOnUiThread来更新address的文本。 但用户提到不需要修改原有的Handler,所以可能原来的Handler还在处理其他数据,而这里只需要在发送命令后,接收特定响应并显示到address上。这种情况下,可能需要区分不同的数据来源或命令响应,但可能比较复杂。 另一个可能性是,BleHelper.sendCommand发送命令后,返回的数据通过某个途径传递回来。或者,BleCallback在接收到数据后,通过Handler发送消息,而消息中包含的数据被viewModel.setData(receivedContent)处理。如果viewModel是观察者模式,比如LiveData,那么可能在Activity中观察这个LiveData的变化,并更新address控件。但用户当前的代码中,viewModel.setData(receivedContent)可能已经更新了某个LiveData,但address并没有观察它。所以,可能只需要在Activity中观察viewModel的数据变化,并更新address。 不过,用户希望的是直接显示返回的数据,而不是通过原来的Handler处理,所以可能需要另一种途径。例如,在发送命令的时候,注册一个一次性的回调,当接收到对应的响应数据时,处理并显示。 但如何将发送的命令和接收的数据对应起来呢?可能需要为每个命令设置一个标识,或者在回调中根据数据内容判断属于哪个命令的响应。这可能比较复杂。 或者,假设每次发送命令后,下一次接收的数据就是该命令的响应,那么可以在发送命令后,设置一个标志,当接收到数据时,检查这个标志,然后处理数据并清除标志。但需要处理并发的情况,可能会有多个命令发送,导致数据混乱。 不过,根据用户提供的代码,当前发送的是一个固定的命令字符串"00420017000109D0",可能每次点击按钮都会发送这个命令,那么对应的响应数据应该会返回,此时只需要在接收到数据时,将其显示在address上。 所以,可能的解决方法是,在BleCallback中添加一个监听器,当数据到达时,触发该监听器,然后在Activity中设置这个监听器,并在回调中更新address控件。 现在,假设可以修改BleCallback类,以下是具体步骤: 1. 修改BleCallback类,添加一个OnDataReceivedListener接口: ```java public class BleCallback extends BluetoothGattCallback { public interface OnDataReceivedListener { void onDataReceived(String data); } private OnDataReceivedListener dataReceivedListener; public void setOnDataReceivedListener(OnDataReceivedListener listener) { this.dataReceivedListener = listener; } // 在接收到数据的地方,比如onCharacteristicChanged @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { super.onCharacteristicChanged(gatt, characteristic); byte[] data = characteristic.getValue(); String receivedData = new String(data); // 发送消息给Handler Message msg = handler.obtainMessage(); msg.obj = receivedData; handler.sendMessage(msg); // 触发监听器 if (dataReceivedListener != null) { dataReceivedListener.onDataReceived(receivedData); } } } ``` 2. 在ModbusConnectActivity的onCreate方法中,设置这个监听器: ```java bleCallback.setOnDataReceivedListener(data -> { runOnUiThread(() -> { address.setText(data); }); }); ``` 这样,每次接收到数据时,除了原来的Handler处理外,还会将数据显示在address控件上。 但需要注意,原来的Handler可能还在处理数据并更新ViewModel,所以这里可能会有重复的数据处理。不过用户的需求是额外显示在address上,所以可能没有问题。 如果无法修改BleCallback类,那么可能需要通过其他方式获取数据,例如观察ViewModel中的数据变化,如果viewModel中的data是LiveData,可以在Activity中观察它: ```java viewModel.getData().observe(this, data -> { address.setText(data); }); ``` 但用户当前的代码中,viewModel.setData(receivedContent)可能是在Handler调用的,所以如果viewModel使用LiveData,这种方法可能可行,但需要确认viewModel的实现。 不过用户提到“不在handler中做修改”,所以可能希望绕过原来的Handler处理,直接获取数据。因此,修改BleCallback添加监听器可能更直接。 总结,如果BleCallback可以修改,添加监听器是可行的;否则,可能需要通过观察ViewModel的数据。根据用户提供的代码,viewModel的类型是MyViewModel,可能包含LiveData,所以第二种方法可能更合适,但需要MyViewModel的实现支持。 假设用户无法修改BleCallback,那么可能需要观察viewModel的数据。例如,在Activity的onCreate中: ```java viewModel.getData().observe(this, newData -> { address.setText(newData); }); ``` 这样,当Handler调用viewModel.setData(receivedContent)时,LiveData会通知观察者,从而更新address的文本。这种方法不需要修改原有的Handler,只需要添加一个观察者。 因此,最终的解决方案可能是在Activity中观察viewModel的数据变化,并更新UI。但需要确认MyViewModel是否使用LiveData,如果是,那么这样可行。 根据用户提供的代码,viewModel的声明是: private MyViewModel viewModel; 但未在onCreate中初始化,可能需要先获取ViewModel实例: viewModel = new ViewModelProvider(this).get(MyViewModel.class); 然后观察其数据: viewModel.getData().observe(this, data -> { address.setText(data); }); 这样,当Handler调用viewModel.setData(receivedContent)时,address会自动更新,而不需要修改Handler中的代码。 因此,用户只需要在ModbusConnectActivity中添加对viewModel的数据观察即可实现需求,而不需要修改原有的Handler处理逻辑。 所以,最终的步骤可能是在onCreate方法中初始化viewModel,并设置观察者: 1. 在onCreate中获取ViewModel实例: viewModel = new ViewModelProvider(this).get(MyViewModel.class); 2. 观察数据变化: viewModel.getData().observe(this, data -> { address.setText(data); }); 这样,当Handler调用viewModel.setData(receivedContent)时,address会显示最新的数据。 但是,用户当前的代码中,viewModel可能没有被正确初始化。在提供的代码中,viewModel被声明为: private MyViewModel viewModel; 但在onCreate中并没有初始化,这可能是一个遗漏。因此,需要先在onCreate中初始化viewModel: viewModel = new ViewModelProvider(this).get(MyViewModel.class); 然后,在Handler中设置数据,viewModel.setData(receivedContent),这可能更新LiveData,进而触发观察者。 因此,综合以上分析,用户需要: 1. 在onCreate中初始化viewModel。 2. 设置观察者,将数据绑定到address控件。 这样,无需修改原有的Handler,只需利用ViewModel的LiveData特性。 所以,最终的代码修改是在ModbusConnectActivity的onCreate方法中添加: viewModel = new ViewModelProvider(this).get(MyViewModel.class); viewModel.getData().observe(this, data -> { address.setText(data); }); 这样,当Handler中的viewModel.setData被调用时,观察者会被触发,更新address的文本。 总结,用户需要检查MyViewModel是否使用LiveData,如果是,则添加上述观察代码即可;如果不是,可能需要修改MyViewModel,使其使用LiveData来保存数据,从而支持观察。</think>以下分步骤解释如何实现返回数据在`address`控件显示,同时不修改原有`Handler`: ### 步骤分析 1. **现有数据流向**:`BleCallback`通过`Handler`将数据传递给`ViewModel` 2. **需求**:绕过`Handler`直接显示到`address`,需另建独立回调通道 3. **解决方案**:在`BleCallback`中添加新回调接口,通过接口直接将数据传回Activity ### 具体代码实现 #### 第一步:修改BleCallback类 ```java // 在BleCallback类中添加 public interface OnRawDataListener { void onRawDataReceived(String data); } private OnRawDataListener rawDataListener; public void setRawDataListener(OnRawDataListener listener) { this.rawDataListener = listener; } // 在数据接收方法中(如onCharacteristicChanged) @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { byte[] data = characteristic.getValue(); String result = new String(data); if (rawDataListener != null) { rawDataListener.onRawDataReceived(result); // 新回调 } // 原有Handler逻辑保持不变 Message msg = handler.obtainMessage(); msg.obj = result; handler.sendMessage(msg); } ``` #### 第二步:在Activity中设置监听 ```java // 在ModbusConnectActivity的onCreate中 bleCallback.setRawDataListener(data -> runOnUiThread(() -> address.setText("接收数据: " + data) )); // 补充初始化ViewModel(若未初始化) viewModel = new ViewModelProvider(this).get(MyViewModel.class); ``` ### 关键点说明 1. **双通道机制**:保留原有`Handler→ViewModel`通道,新增`rawDataListener→address`直连通道 2. **线程安全**:通过`runOnUiThread`确保UI操作在主线程 3. **解耦设计**:新增监听器实现功能扩展,不破坏原有结构 ### 最终效果 点击按钮发送指令后: 1. 原有业务逻辑通过`Handler`更新`ViewModel` 2. 新增逻辑通过监听器直接更新`address`控件 3. 两套系统并行工作,互不干扰 > 此方案最大程度保持代码稳定性,新增功能通过扩展而非修改实现,符合开闭原则。建议在真实设备测试时添加超时机制和异常处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值