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對象。