数据库
Android中,在子线程使用Toast会报错?
一、Android中,在子线程使用Toast会报错?
看了下上面的回答,竟然都认为这个异常是子线程不能执行UI操作导致的,实际上这样的的说法没有错,但并不是这里抛出异常的原因,因为根本还没有执行到更新UI那一步。
其实问题没那么复杂,直接从代码分析原因即可。
先看Toast.makeText(Context,CharSequence,int)的源码:
public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
Toast result = new Toast(context);
LayoutInflater inflate = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
tv.setText(text);
result.mNextView = v;
result.mDuration = duration;
return result;
}
这里就是初始化View并给Toast赋值,但是这里并没有涉及Handler,为什么会出现“Can't create handler inside thread that has not called Looper.prepare()”这样的错误呢?
其实是在Toast的构造方法中:
public Toast(Context context) {
mContext = context;
mTN = new TN();
mTN.mY = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.toast_y_offset);
mTN.mGravity = context.getResources().getInteger(
com.android.internal.R.integer.config_toastDefaultGravity);
}
注意其中的TN这个类(这个类名也是没sei了,叫ToastNative也更好呀)的部分代码如下:
private static class TN extends ITransientNotification.Stub {
final Runnable mShow = new Runnable() {
@Override
public void run() {
handleShow();
}
};
final Runnable mHide = new Runnable() {
@Override
public void run() {
handleHide();
// Don't do this in handleHide() because it is also invoked by handleShow()
mNextView = null;
}
};
private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
final Handler mHandler = new Handler();
int mGravity;
int mX, mY;
float mHorizontalMargin;
float mVerticalMargin;
View mView;
View mNextView;
WindowManager mWM;
TN() {
...
}
/**
* schedule handleShow into the right thread
*/
@Override
public void show() {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
mHandler.post(mShow);
}
/**
* schedule handleHide into the right thread
*/
@Override
public void hide() {
if (localLOGV) Log.v(TAG, "HIDE: " + this);
mHandler.post(mHide);
}
...
}
注意其中的mHandler这个成员,final Handler mHandler=new Handler(); 会在TN的构造方法之前执行,从而导致在Toast()中抛出异常。
所以上面那些“子线程更新 UI ,需要利用 Handler 切换回到主线程进行操作”的说法没有错,但并不是这里抛出异常的原因,因为根本还没有执行到更新UI那一步。实际上这部分代码也是可以在子线程中执行的,后面会给出我的示例。
为了找出解决方法,就看一下Android中的Toast显示的完整过程吧。Toast#show()的代码如下:
public void show() {
if (mNextView == null) {
throw new RuntimeException("setView must have been called");
}
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
try {
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Empty
}
}
而getService()的代码如下:
static private INotificationManager getService() {
if (sService != null) {
return sService;
}
sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));
return sService;
}
非常典型的Binder通信,不啰嗦了,对应的NotificationManagerService代码如下:
public void enqueueToast(String pkg, ITransientNotification callback, int duration)
{
if (pkg == null || callback == null) {
return ;
}
synchronized (mToastQueue) {
int callingPid = Binder.getCallingPid();
long callingId = Binder.clearCallingIdentity();
try {
ToastRecord record;
int index = indexOfToastLocked(pkg, callback);
// If it's already in the queue, we update it in place, we don't
// move it to the end of the queue.
if (index >= 0) {
record = mToastQueue.get(index);
record.update(duration);
} else {
record = new ToastRecord(callingPid, pkg, callback, duration);
mToastQueue.add(record);
index = mToastQueue.size() - 1;
keepProcessAliveLocked(callingPid);
}
// If it's at index 0, it's the current toast. It doesn't matter if it's
// new or just been updated. Call back and tell it to show itself.
// If the callback fails, this will remove it from the list, so don't
// assume that it's valid after this.
if (index == 0) {
showNextToastLocked();
}
} finally {
Binder.restoreCallingIdentity(callingId);
}
}
}
显然,就是让Toast请求进入队列统一管理,而显示下一条Toast的代码如下:
private void showNextToastLocked() {
ToastRecord record = mToastQueue.get(0);
while (record != null) {
try {
record.callback.show();
scheduleTimeoutLocked(record, false);
return;
} catch (RemoteException e) {
// remove it from the list and let the process die
int index = mToastQueue.indexOf(record);
if (index >= 0) {
mToastQueue.remove(index);
}
keepProcessAliveLocked(record.pid);
if (mToastQueue.size() > 0) {
record = mToastQueue.get(0);
} else {
record = null;
}
}
}
}
注意其中的record.callback.show()其实对应的就是TN中的show(),其代码如下:
public void show() {
mHandler.post(mShow);
}
显然会调用handleShow()方法:
public void handleShow() {
if (mView != mNextView) {
// remove the old view if necessary
handleHide();
mView = mNextView;
Context context = mView.getContext().getApplicationContext();
String packageName = mView.getContext().getOpPackageName();
if (context == null) {
context = mView.getContext();
}
mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
// We can resolve the Gravity here by using the Locale for getting
// the layout direction
final Configuration config = mView.getContext().getResources().getConfiguration();
final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());
mParams.gravity = gravity;
if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
mParams.horizontalWeight = 1.0f;
}
if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
mParams.verticalWeight = 1.0f;
}
mParams.x = mX;
mParams.y = mY;
mParams.verticalMargin = mVerticalMargin;
mParams.horizontalMargin = mHorizontalMargin;
mParams.packageName = packageName;
if (mView.getParent() != null) {
mWM.removeView(mView);
}
mWM.addView(mView, mParams);
trySendAccessibilityEvent();
}
}
显然,这里才是真正显示Toast的地方,这里才真正涉及到了更新UI的操作。
那如果我们要在子线程中进行显示Toast的操作要怎么办,很简单的方案就是利用主线程的handler.post(...)来执行Toast.makeText(...).show()的操作。那有没有其他的方法呢?
其实从刚刚的分析中,我们发现只要在创建Toast()时不让它抛出异常,并且保证TN中的mHandler是基于主线程消息队列的Handler对象即可。
由于ITransientNotification和INotificationManager对应用开发者不可见,故没有办法构造一个可以完成TN功能的类,那就只能从反射入手了。
如下是我的一种解决方案:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(new Runnable() {
@Override
public void run() {
showToast();
}
}).start();
}
private void showToast(){
Looper.prepare();
Toast toast = Toast.makeText(MainActivity.this, "Enjoy your national day", Toast.LENGTH_LONG);
try {
Field field = toast.getClass().getDeclaredField("mTN");
field.setAccessible(true);
Object obj = field.get(toast);
setNextView(toast,obj);
changeHandlerValue(obj);
enqueueToast(toast,obj);
} catch (Exception e) {
}
Looper.loop();
}
private void setNextView(Toast toast,Object tn){
try{
Field toastNextView=toast.getClass().getDeclaredField("mNextView");
toastNextView.setAccessible(true);
Field nextViewField=tn.getClass().getDeclaredField("mNextView");
nextViewField.setAccessible(true);
nextViewField.set(tn,toastNextView.get(toast));
}catch (Exception ex){
ex.printStackTrace();
}
}
private void changeHandlerValue(Object tn){
try{
Field mHandlerField=tn.getClass().getDeclaredField("mHandler");
mHandlerField.setAccessible(true);
mHandlerField.set(tn,new Handler(Looper.getMainLooper()));
}catch (Exception ex){
ex.printStackTrace();
}
}
private void enqueueToast(Toast toast,Object tn){
try{
Method getServiceMethod=toast.getClass().getDeclaredMethod("getService",null);
getServiceMethod.setAccessible(true);
Object obj=getServiceMethod.invoke(null);
Method[]methods=obj.getClass().getDeclaredMethods();
Method enqueueMethod=null;
for(Method method:methods){
if("enqueueToast".equals(method.getName())){
enqueueMethod=method;
break;
}
}
if(enqueueMethod==null){
return;
}
enqueueMethod.setAccessible(true);
enqueueMethod.invoke(obj,"wang.imallen.toastsample",tn,Toast.LENGTH_LONG);
}catch (Exception ex){
ex.printStackTrace();
}
}
}
就三个要点:
1) 为了防止在创建TN时抛出异常,需要在子线程中使用Looper.prepare();和Looper.loop();
2)为了使TN中最终调用的Handler对象是基于主线程的,需要使用反射将其替换掉,changeHandlerValue()的作用就是这个;
3)由于ITransientNotification不可见,所以不能通过obj.getClass().getDeclaredMethod("enqueueToast",...)的方法来直接获取到这个Method;
不过,虽然这种方法用另一种思路实现了在子线程中显示Toast的操作,但是非常不推荐这样做,因为对于非public成员的反射是有风险的,万一在某个版本中这个成员的名称换了,这种方法就会出错。
二、android支持多线程吗?
android支持多线程。
因为android搭载了最先进的多线程系统和功能芯片系统,并且是可以进行无限距离操作的,所以是支持的。
三、Android在子线程用handler发送的消息,主线程是怎么loop到的?
是可以访问的!
但是,你不可以在非UI线程(子线程)创建handler。所以Handler的创建应该放在UI线程(主线程),然后在非UI线程(子线程)中使用它。
例如,你可以在UI线程(主线程)中创建:Handlerhandler=newHandler();
然后在非UI线程(子线程)中使用:handler.sendEmptyMessage(0);
这样你就可以通过多线程来处理android的UI,这也是几种异步处理UI方式中的一种。
希望对你有帮助哦!
四、android什么场景需要线程池?
android在要创建多个线程做同一件事情时场景需要线程池,线程池减少创建线程的开发
五、Android连接SQLServer数据库?
android好像没办法连接sqlserver吧。呵呵,如果你真想获取sqlserver中的数据,只能通过访问一个网页,以读取xml文件的方式来读取。
六、多线程查询数据库?
Excel文件中的记录比较多的话,要考虑使用多线程。可以考虑使用多线程设计模式中的Producer-Consumer模式。首先,专门开辟一个线程(一个够用,多了无益,以下称之为Reader线程),该线程负责读取Excel文件中的记录。比如使用第三方工具POI,此时读取到的Excel记录是一个Java对象。该线程每次读取到记录都将其存入队列(如ArrayBlockingQueue)。它仅负责读取记录并将其存入队列,其它的事情它不做。其次,再设置若干个线程(如果一个够用,就一个。
数量最好不要超过系统的CPU个数
,以下称为Processor线程),这些线程负责从上述队列中取出记录(对象),然后对记录中的数据进行校验,写入数据库(这里我假设导入的目标是数据库,你的问题中并没有说明导入目标是什么)。最后,Reader线程读取完所以记录之后,要“通知”Processor线程:等你处理完所有记录后,你可以停止了。这点,可以借助多线程设计模式中的Two-phase Termination模式来实现。其主要思想是为要停止的线程(目标线程,这里就是Processor线程)设置一个停止标志,并设置一个表示目标线程的工作任务数(这里值有多少条记录需要它处理)的变量。当目标线程侦测到其待处理的任务数为0,且线程停止标志已置为true的情况下,该线程就可以停止了。Two-phase Termination模式参考这里:Java多线程编程模式实战指南(三):Two-phase Termination模式
。更详细的,可以参考我的新书。最后,相应注意“产品”的粒度。即Reader线程往队列(传输通道)中存入的“产品”是个什么对象,是一条Excel记录,还是多条Excel记录?一般为了减少“产品”在队列中的移动次数(以减少相应开销)要适当将“产品”的粒度设置粗一些。例如,创建一个“容器型”对象用来存储多条记录。七、Android Studio数据库教程?
1、第一步我们需要建一个类继承SQLiteOpenHelper帮助类来创建数据库,使用public static final String CREATE_BOOK = "create table Book()方法,如下图所示:
2、第二步我们activity_main.xml布局文件中添加一个按钮,实现在界面点击按钮创建数据库的功能,如下图所示:
3、第三步修改MainActivity的部分代码,通过dbHelper.getWritableDatabase();来创建数据库,如下图所示:
4、第四步我们运行项目,在界面上点击创建数据库按钮,可以看到已经创建成功了,可以到adb中去看数据库具体内容,如下图所示:
八、Java多线程编程:如何实现主线程等待子线程结束
引言
在Java多线程编程中,有时候我们需要主线程等待所有子线程都执行完毕后再继续执行。本文将介绍几种实现主线程等待子线程结束的方法,帮助您更好地掌握Java多线程编程知识。
方法一:使用Thread的join方法
Thread类中的join方法可以让一个线程等待另一个线程执行完成。主线程可以调用子线程的join方法来等待子线程执行完毕。
示例代码:
Thread thread = new Thread(() -> {
// 子线程执行的代码
});
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
方法二:使用CountDownLatch类
CountDownLatch是Java并发包中的一个工具类,可以用来控制线程的执行顺序。通过将CountDownLatch的计数器初始化为子线程的数量,主线程可以调用await方法等待子线程执行完毕。
示例代码:
CountDownLatch latch = new CountDownLatch(2); // 假设有两个子线程
new Thread(() -> {
// 子线程1执行的代码
latch.countDown();
}).start();
new Thread(() -> {
// 子线程2执行的代码
latch.countDown();
}).start();
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
方法三:使用线程池
如果使用线程池来管理子线程,可以通过主线程等待线程池中的任务执行完毕来实现主线程等待子线程结束。
示例代码:
ExecutorService executorService = Executors.newFixedThreadPool(2); // 假设线程池大小为2
executorService.submit(() -> {
// 子线程1执行的代码
});
executorService.submit(() -> {
// 子线程2执行的代码
});
executorService.shutdown();
try {
executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
结语
以上介绍了三种实现主线程等待子线程结束的方法:使用Thread的join方法、使用CountDownLatch类、使用线程池。根据实际需求选择合适的方法可以帮助我们更好地管理线程,提高程序的效率和稳定性。
谢谢您阅读本文,希望对您在Java多线程编程中有所帮助!
九、Android web用什么数据库?
Web端使用的是MySql数据库,Apache服务器和PHP语言编写的。数据交互的简单理解就是Android能向服务端进行数据获取,同时也能进行数据提交。
十、MYSQL数据库如何多线程?
1。通过线程的互斥来同步操作数据库
2。数据库采用事务处理表中的数据
3。采用共享方式打开数据库,不是以独占方式打开数据库
建立一个mysql连接表加上一个临界区,表结点是这样的(mysqlcon,bool),根据实际情况定大小。我用的是10个连接。
当要进行mysql操作时,就从表中取出一个闲置的mysql连接,并把bool量改为true,使用完后改成false,临界区的做用是保障一个mysql连接一次只能被一个线程使用。
热点信息
-
在Python中,要查看函数的用法,可以使用以下方法: 1. 使用内置函数help():在Python交互式环境中,可以直接输入help(函数名)来获取函数的帮助文档。例如,...
-
一、java 连接数据库 在当今信息时代,Java 是一种广泛应用的编程语言,尤其在与数据库进行交互的过程中发挥着重要作用。无论是在企业级应用开发还是...
-
一、idea连接mysql数据库 php connect_error) { die("连接失败: " . $conn->connect_error);}echo "成功连接到MySQL数据库!";// 关闭连接$conn->close();?> 二、idea连接mysql数据库连...
-
要在Python中安装modbus-tk库,您可以按照以下步骤进行操作: 1. 确保您已经安装了Python解释器。您可以从Python官方网站(https://www.python.org)下载和安装最新版本...