UI與執行緒

      在〈UI與執行緒〉中尚無留言

UI主執行緒

Android在更新畫面上, 全交由Main Thread來執行. 此時有二種狀況發生
1. 在Main Thread 執行耗時的計算時, 整個UI的操作將會卡死
2. 新增新的Thread, 則新的Thread無法控制UI的元件(TextView除外)

新Thread在運算完一陣子了, 若要將計算的結果顯示在UI 元件上, 礙於它本身無法控制UI, 因此就必需把結果丟給Main Thread, 再由Main Thread來執行. 所以如何將結果丟給Main Thread即是本章要討論的地方.

Activity runOnUiThread

使用Activity的 runOnUiThread是比較簡單的方法.

public class MainActivity extends AppCompatActivity {
    TextView txt;
    int i;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        txt=(TextView)findViewById(R.id.txt);
    }
    public void btnClick(View v){
        new Thread(()->{
            for (i=0;i<100;i++){
                MainActivity.this.runOnUiThread(()->{
                    txt.setText(String.valueOf(i));
                });
                try {Thread.sleep(1000);}
                catch (InterruptedException e) {}
            }
        }).start();
    }
}

上面使用Lambda語法, 所以在build.gradle中必需加入如下編譯條件

buildTypes {
    release {
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
}
compileOptions {
    sourceCompatibility = '1.8'
    targetCompatibility = '1.8'
}

在其他類別使用runOnUiThread

在其他類別若有接收MainActivity的context, 則可以將context轉成MainActivity, 再執行runOnUiThread

((MainActivity)context).runOnUiThread(()->{
    MahalImage.this.setImageBitmap(bitmap);
});

創建Main Thread Handler

如下程式碼, 利用Activity的Context, 創造一個新的Handler, 然後調用handler.post(Runnable), 將要控制UI元件的工作放在Runnable物件中.

public class MainActivity extends AppCompatActivity {
    TextView txt;
    Context context;
    int i;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        context=this;
        txt=(TextView)findViewById(R.id.txt);
    }
    public void btnClick(View v){
        new Thread(()->{
            for (i=0;i<100;i++){
                new Handler(context.getMainLooper()).post(()->{
                    txt.setText(String.valueOf(i));
                });
                try {Thread.sleep(1000);}
                catch (InterruptedException e) {}
            }
        }).start();
    }
}

亦可使用 handler.postDelayed(runnable, 1000), 此方法會延遲一秒鐘再執行. 不過懂Java的人應該知道,一定會超過一秒鐘的。

Main Thread Handler 接收

如下程式碼, 先在Activity中建立一個 Handler, 接收來自其他Thread的message. 利用msg.what可以判斷是那一個Thread.

在新的執行緒中, 把要傳遞的資訊用Bundle包起來,再放入Message物件, 並設定message.what. 然後由handler.sendMessage(msg) 將訊息送出

public class MainActivity extends AppCompatActivity {
    TextView txt;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        txt=(TextView)findViewById(R.id.txt);
        new Thread(new MyTask(handler)).start();
    }
    Handler handler=new Handler(){
        @Override
        public void handleMessage(Message msg) {
            Bundle bundle=null;
            switch(msg.what){
                case 100:
                    bundle= msg.getData();
                    int t=bundle.getInt("TIME");
                    txt.setText(String.valueOf(t));
                    break;
            }
        }
    };
}
class MyTask implements Runnable{
    final int ID=100;
    Handler handler;
    public MyTask(Handler handler){
        this.handler=handler;
    }
    @Override
    public void run() {
        for (int i=0;i<100;i++){
            Message msg=new Message();
            msg.what=ID;
            Bundle bundle=new Bundle();
            bundle.putInt("TIME", i);
            msg.setData(bundle);
            handler.sendMessage(msg);
            try {Thread.sleep(1000);} catch (InterruptedException e) {}
        }
    }
}

todo

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *