androidtoast子线程
A. toast.maketext可以在子线程吗
:对组件操作只能在 UI 线程中进行,你放到子线程里去执行当然出错。 要用 Handler。
B. 安卓 多线程 toast 为什么报错
对组件操作只能在 UI 线程中进行,你放到子线程里去执行当然出错。
要用 Handler。
C. android 子线程真的不能操作ui么
在开发应用中,如果子线程中更新UI会抛出异常,但并不是因为只有UI线程才能更新UI,
而是因为ViewRootImpl会进行检查,如果 mThread!=当前线程 时会抛出异常异常
ViewRootImple.java
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new (
"Only the original thread that created a view hierarchy can touch its views.");
}
}
那mThread是什么呢?
通过ViewRootImpl的构造函数我们可以发现mThread会被赋值为创建ViewRootImp的那个线程
public ViewRootImpl(Context context, Display display) {
mContext = context;
mWindowSession = WindowManagerGlobal.getWindowSession();
//此处进行mThread的赋值
mThread = Thread.currentThread();
//代码省略...
}
而ViewRootImpl是在主线程中创建的,所以,才需要在主线程中更新UI
不过,设定为在主线程更新UI也是为了安全和简化起见吧
那么,能否在子线程中更新UI呢
如果ViewRootImpl是由子线程创造的,那么自然可以在该子线程中更新UI
但是如果我们直接创建ViewRootImpl实例的话,会发现找不到该类。
可以通过WindowManager.addView来间接创建一个ViewRootImpl
比如
class TestThread1 extends Thread{
@Override
public void run() {
Looper.prepare();
TextView tx = new TextView(MainActivity.this);
tx.setText("test11111111111111111");
WindowManager wm = MainActivity.this.getWindowManager();
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
250, 250, 200, 200, WindowManager.LayoutParams.FIRST_SUB_WINDOW,
WindowManager.LayoutParams.TYPE_TOAST,PixelFormat.OPAQUE);
wm.addView(tx, params);
Looper.loop();
}
}
MainActivity是建立android工程时生成的入口类,TestThread1是MainActivity的内部类。感兴趣的话,试试吧!看看是不是在屏幕上看到了”test11111111111111111”?
具体创建ViewRoot的地方在wm.addView(tx, params)
具体流程:
WindowManagerImpl.addView(View view, ViewGroup.LayoutParams params)
->
WindowManagerImpl.addView(View view, ViewGroup.LayoutParams params, boolean nest)
代码(精简):
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
//新建ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
root.setView(view, wparams, panelParentView);
}
D. Android 的Thread编程,我在Thread的run()方法中用Toast输出信息时出错!
不能在子线程中更新UI,这是我之前写的一个例子,你看看,使用Handler消息机制
public class HandlerDemoActivity extends Activity implements OnClickListener {
Button btn1,btn2;
ProgressBar progressBar;
UpdateDataHandler updateDataHandler;
HandlerThread handlerThread;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
btn1 = (Button)findViewById(R.id.btn1);
btn2 = (Button)findViewById(R.id.btn2);
progressBar = (ProgressBar)findViewById(R.id.progressBar1);
btn1.setOnClickListener(this);
btn2.setOnClickListener(this);
}
/**
* 自定义一个类来继承Handler类,并重写handleMessage函数,
* 并且需要一个带有Looper对象的构造函数
* 循环检测是否有消息,如果有消息,将调用handleMessage取出数据,
* 如果没有消息,进入等待状态
* @author Administrator
*
*/
class UpdateDataHandler extends Handler{
public UpdateDataHandler(Looper looper){
super(looper);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
System.out.println("----------" + Thread.currentThread().getId());
progressBar.setProgress(msg.arg1); //从消息队列中取出数据,并更新控件
updateDataHandler.post(printRunnable);
}
}
Runnable printRunnable = new Runnable() {//实现一个线程类
int i = 0;
@Override
public void run() { //重写线程类中的run函数
i += 20;
Message msg = updateDataHandler.obtainMessage(); //获得handler的message对象
msg.arg1=i; //向message对象中放置数据
//msg.obj = obj;//还可以发送一些对象
//msg.setData();//可以发送Bundle对象
try {
Thread.sleep(1000); //暂停1秒钟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
if(i > 100) //判断当变量到达100时,操作结束
{
updateDataHandler.removeCallbacks(printRunnable);//从handler中释放线程类
return;
}
//msg.sendToTarget(); //使用此函数也可以实现发送消息
updateDataHandler.sendMessage(msg); //将message对象放到消息队列中
}
};
@Override
public void onClick(View v) {
if(v == btn1)
{
handlerThread = new HandlerThread("");// HandlerThread是一个Thread,继承自Thread。它保留着对Looper实例的引用
handlerThread.start(); //一定要调用线程的start函数
updateDataHandler = new UpdateDataHandler(handlerThread.getLooper());
updateDataHandler.post(printRunnable);
System.out.println("主线程" + Thread.currentThread().getId());
}
}
}
E. android创建子线程
创建后台线程的方法有多种,这里说三种,可以回去试试
1、使用Android系统工具类 AsyncTask(Params,Progress,Result)
AsyncTask是一个轻量级线程,三个泛型参数分别是 Params传入参数,int型Progress为进度条进度,Result为返回值
要使用AsyncTask,必须继承之并复写其中的几个重要的函数。
onPreExecute(), 该方法将在执行实际的后台操作前被UI thread调用。可以在该方法中做一些准备工作,如在界面上显示一个进度条。
doInBackground(Params...), 将在onPreExecute 方法执行后马上执行,该方法运行在后台线程中。这里将主要负责执行那些很耗时的后台计算工作。可以调用 publishProgress方法来更新实时的任务进度。该方法是抽象方法,子类必须实现。
onProgressUpdate(Progress...),在publishProgress方法被调用后,UI thread将调用这个方法从而在界面上展示任务的进展情况,例如通过一个进度条进行展示。
onPostExecute(Result), 在doInBackground 执行完成后,onPostExecute 方法将被UI thread调用,后台的计算结果将通过该方法传递到UI thread.
注:Task必须在UI线程中创建,并调用并且只能调用一次execute方法,该方法的参数为传入的泛型Params。
其余函数最好不要手动调用,容易造成线程崩溃。多次调用Task,容易造成线程池溢出。
2、使用Handler和HandlerThread
误区: Handler handler = new Handler ();
handler.post(r);
这种做法看似创建了一个线程,但实际上handler只是直接调用Runnable中的run() 方法,而不执行线程的start()函数,所以这两句代码执行后,程序仍然在UI线程中执行。所以我们引入HandlerThread,因为HandlerThread中有一个Looper对象,用以循环消息队列。
为了使用Looper,必须子类化Handler,并复写它的构造函数。
class MyHandler extends Handler{
public MyHandler() {}
public MyHandler(Looper looper){
super (looper);
}
public void handleMessage(Message msg){
//....这里运行耗时的过程
}
}
}
handleMessage(Message msg)函数用以接收消息,msg则是从UI线程中发出的消息,可以传递各种对象,根据这些对象和数值进行操作。
有了Handler子类,则可以在UI线程中进行创建和初始化
HandlerThread handlerThread = new HandlerThread( "backgroundThread" );
handlerThread.start();
MyHandler myHandler = new MyHandler(handlerThread.getLooper());
Message msg = myHandler.obtainMessage();
//....此处对msg进行赋值,可以创建一个Bundle进行承载
msg.sendToTarget();
之后如果需要调用线程,只要对handler传入msg,就可以执行相应的过程了
最后,很重要的一点,HandlerThread 不随Activity的生命周期结束而消亡,所以必须复写Ondestory(),调用HandlerThread .stop()
3、使用线程同步 synchronized、 wait()、 notify()
使用线程同步机制synchronized实现多线程操作,相对来说比较复杂,但也是灵活性最佳、效率最高的一种做法,在产品开发当中也使用得最广。本人水平相当有限,也只学得一点皮毛。
synchronized相当于一把锁,当线程运行的时候,会同时有几个线程访问一个对象,对其进行操作或者修改。这可能引起很不良的后果(例如改变判定条件,或者在别的线程还没使用完的时候对象已经被析构等等),所以必须对一些对象进行加锁,保证它在同一时间只允许被一个线程访问。
synchronized使用方法有两种:
<1> 同步方法在方法名前加入synchronized关键字,则该方法在同一时间内只允许一个线程访问它,其代码逻辑较为简单,但使用起来不太灵活,并且大大降低效率,耗时太长的同步方法甚至会使得多线程失去原本的意义成为单线程
<2>同步参数 对某一个代码块加锁,并且以synchronized(obj)的方式,表明该代码块内的obj对象是线程同步的。这个做法使得代码灵活性大大加强,缩小同步代码块的范围,并且可以在不同的函数体和类内进行同步,唯一遗憾的是实现起来比较复杂,容易产生一些问题
而wait()和notify(),必须在synchronized锁的代码块内执行,否则系统将会报错。
有了以上基础,就可以很容易的创建后台线程了
Private Runnable backgroundRunnable = new Runnable () {
@Override
public void run() {
if(isFinish){
//.....
break;
}
for(;;){
synchronized(this){
//....写耗时过程
wait();
}
}
}
}
UI线程中,就是一般的创建线程过程了
Thread backgroundThread = new Thread (backgroundRunnable);
backgroundThread.start();
这样,在后台线程中会不断的循环,当执行完一次过程以后就会wait()休眠。然后在OnTouchListener或者OnClickListener内相应的位置执行
synchronized(backgroundRunnable){
backgroundRunnable.notify();
}
当用户触摸屏幕产生一个event的时候,线程就会被唤醒,执行下一次循环。
最后,还是内存安全问题,必须复写Activity中的OnDestory()方法,将标志量isFinish设为false,并且backgroundThread .stop()
F. 奇特:子线程的Toast怎么显示不出来
因为Toast在创建的时候会依赖于一个Handler,并且一个Handler是需要有一个Looper才能够创建,而普通的线程是不会自动去创建一个Looper对象,比如说在某个Activity中能new一个Handler是因为Android系统在启动一个Activity的时候会默认的创建一个Looper对象,因此不能够在子线程中显示Toast,你可以在开启的子线程中执行Looper.prepare()来构建一个Looper,然后在显示Toast,但是不要忘记执行Looper.loop()来加载这个Looper,当然,也可以使用主线程的Looper,获取主线程的Looper的方法是Looper.getMainLooper();同时需要注意的是,同样的不能在子线程中去更新UI界面,因为Toast是相对独立于UI界面的,就好比应用虽然crash掉了,并且已经返回到home界面,但是Toast依然会在hone界面显示出来。
G. android怎么样在子线程实例化的handler去更新UI
之前用过android-async-http,虽然没认真看过源码,但也有简单的浏览过,心里一直有个疑问,因为android-async-http也是采用hanlder机制来执行回调的,也就是说handler是它实例化的,可我们知道handler的一个重要作用是将一个任务切换到handler所在的线程去执行的,我的疑问就是:如果我们在子线程调用android-async-http的网络请求,这时候如果handler是在子线程被实例化的呢(当然我没认真研究过源码,也不知道它是怎么实现),还能更新UI吗?
我们都正常以为handler在哪个线程实例化的,我们通过handler就可以把任务切换到该任务去执行,我们看如下代码:
private void initd() {
new Thread(new Runnable() {
@Override
public
void run() {
Looper.prepare();
Handler handler = new
Handler() {
@Override
public void handleMessage(Message msg) {
Toast.makeText(MainActivity.this, "测试子线程弹出toast",
Toast.LENGTH_LONG).show();
((TextView)findViewById(R.id.main_tv_text)).setText("测试子线程");
}
};
Looper.loop();
handler.sendEmptyMessage(0);
}
}).start();
}
经过我的测试上面这段方法是无法更新UI的,因为handler是在子线程实例化的,并非在UI线程,也证实了我们的想法。
如果我的疑问存在(我没尝试过在子线程使用android-async-http,不知道什么情况,这里只是做个假设),那么它使怎么实现的呢。
最近看了android开发艺术探索,特意去研究了一下android的消息机制,才弄明白了Handler的工作原理,其实起关键作用的是Looper并不是handler,我们先来看下Looper的构造方法:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Looper会把所在的当前的线程保存起来,而handler的工作需要Looper,于是我尝试了一下如下代码:
private void init() {
new Thread(new Runnable() {
@Override
public
void run() {
Handler handler = new
Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
Toast.makeText(MainActivity.this, "测试子线程弹出toast",
Toast.LENGTH_LONG).show();
((TextView)findViewById(R.id.main_tv_text)).setText("测试子线程");
}
};
handler.sendEmptyMessage(0);
}
}).start();
}
handler实例化的时候,我传入的是UI线程的Looper,确实是可以更新UI。
总结:
1、handler执行任务不是在它实例化所在的线程决定的,而是关键在于Looper。
2、我们可以在子线程实例化handler并且可以用它来更新UI了。
H. android如何通知用户使用 Toast OkHttp 请求返回 200 事情吗
不能直接在子线程中调用Toast,否则程序会闪退,可以通过Handler来发送消息给主线程;privateHandlerhandler=newHandler(context);handler.post(newRunnable(){publicvoidrun(){//Toast200});
I. Android Toast在子线程中为什么无法正常使用
Toast的show和hide方法实现是基于Handler机制。我们可以把Toast理解为创建了一个handler,这样一来发消息的对象在这就是Toast了。
而且在TN类中并没有发现任何Looper.perpare()和Looper.loop()方法。所以这里的mhandler调用的就是当前线程的loop对象。
在对looper类说明的时候,知道线程本身默认是没有looper对象的,所以Toast在线程中使用的时候,必须创建一个looper对象。