Search

Android - Multi-thread 多執行緒與 UI 更新操作

2015-07-23 2:13 PM

在 Android 的開發規則裡有一個規定

就是所有與網路連線的功能都必須在另一個執行緒內執行

而另一個相關的重要規定是

所有與 UI 相關的功能只能在 UI Thread 內執行(也就是主執行緒)

這項規定是由於程式執行流暢度的考量而產生的

但這麼一來

若在其他執行緒的做完某件事情時 該如何使用 UI 告知使用者呢?

這時就需要用到 Thread 搭配 Handler 的 Design Pattern 了

當然還有其他的選項 例如 AsyncTask

但為了最大的開發自由度

我建議還是使用 Thread 搭配 Handler 會較一勞永逸

當然還是要看情況而定

廢話不多說 我們來看看該如何使用這項功能

這裡同時會用到 WeakReference 的 Design Pattern

會在另一個教學文章 WeakReference 與多執行緒的 Handler 提到

程式碼範例
// Handler in main activity
private SSHThreadHandler sshHandler = new SSHThreadHandler(this);

private static class SSHThreadHandler extends Handler{

    private WeakReference<MainActivity> refActivity;

    public SSHThreadHandler(MainActivity activity) {
        refActivity = new WeakReference<>(activity);
    }

    @Override
    public void handleMessage(Message message){
        MainActivity    activity = refActivity.get();
        SSHConnection  sshConnection = activity.getSshConnection();
        TextView        redirectPageStatus = activity.getRedirectPageStatus();

        switch(message.what){
            case SSHConnection.waitingUsername:
                sshConnection.inputUsername();
                redirectPageStatus.setText( "連線中... 快好囉~" );
                break;
            case SSHConnection.waitingPassword:
                sshConnection.inputPassword();
                break;
            case ISSHConnection.waitingDuplicateConfirm:
                activity.showDuplicateLoginAlert();
                break;
            case SSHConnection.waitingContinueConfirm:
                sshConnection.doContinue();
                redirectPageStatus.setText( "歡迎回來 " + sshConnection.getUsername() );
                break;
            case SSHConnection.menuPage:
                activity.hideLoginPageFrame();
                activity.hideLoadingFrame();
                activity.showMenuPageHolder();
                break;
        }
    }
}

// 其他執行緒與 Handler 互動的方式

public static final int connected = 0;
public static final int waitingUsername = 1;
public static final int waitingPassword = 2;
public static final int waitingDuplicateConfirm = 3;
public static final int waitingContinueConfirm = 4;
public static final int menuPage = 5;

@Override
 public void connect(String username, String password, Handler handler) throws IOException {
  if( somthingHappened ){
  sendMessage( doSomething );
 }
 else{
  sendMessage( doAnother );
 }
}

/**
 * Send message to the UI thread.
 */
private void sendMessage(int msg){
        Message message = new Message();
        message.what = msg;
        handler.sendMessage(message);
}
詳細解說

首先我們必須要在 MainActivity.class 中建立一個實作 android.os.Handler 的 class

這個 Handler 可以根據另一個執行緒回傳的事件而執行與 UI 相關的動作

private static class SSHThreadHandler extends Handler{
 
}

並在這個 class 內使用 WeakReference 的設計方式以取得 MainActivity 的成員

private WeakReference<MainActivity> refActivity;

public SSHThreadHandler(MainActivity activity) {
    refActivity = new WeakReference<>(activity);
}

最後 Override handleMessage 方法以針對各種回傳的訊息做相對應的處理

 @Override
    public void handleMessage(Message message){
        MainActivity    activity = refActivity.get();
        SSHConnection  sshConnection = activity.getSshConnection();
        TextView        redirectPageStatus = activity.getRedirectPageStatus();

        switch(message.what){
            case SSHConnection.waitingUsername:
                sshConnection.inputUsername();
                redirectPageStatus.setText( "連線中... 快好囉~" );
                break;
            case SSHConnection.waitingPassword:
                sshConnection.inputPassword();
                break;
            case ISSHConnection.waitingDuplicateConfirm:
                activity.showDuplicateLoginAlert();
                break;
            case SSHConnection.waitingContinueConfirm:
                sshConnection.doContinue();
                redirectPageStatus.setText( "歡迎回來 " + sshConnection.getUsername() );
                break;
            case SSHConnection.menuPage:
                activity.hideLoginPageFrame();
                activity.hideLoadingFrame();
                activity.showMenuPageHolder();
                break;
        }
    }

在其他執行緒中我們需要經由 message.what 屬性傳遞訊息

而這個屬性是 int 類型

所以我們需要建立一個對應的訊息變數以便了解此訊息代表的意義

public static final int connected = 0;
public static final int waitingUsername = 1;
public static final int waitingPassword = 2;
public static final int waitingDuplicateConfirm = 3;
public static final int waitingContinueConfirm = 4;
public static final int menuPage = 5;

為了方便使用 我另外新增了一個 sendMessage 的方法

而傳入的參數就是上面建立的訊息變數

handler 則是傳入執行緒的參數,也就是我們剛剛在 MainActivity 建立的 Handler

/**
 * Send message to the UI thread.
 */
private void sendMessage(int msg){
        Message message = new Message();
        message.what = msg;
        handler.sendMessage(message);
}

最後我們只要在需要的時候傳遞訊息即可

@Override
public void connect(String username, String password, Handler handler) throws IOException {
  if( somthingHappened ){
  sendMessage( doSomething );
 }
 else{
  sendMessage( doAnother );
 }
}
各項資料連結
Android - WeakReference 與多執行緒的 Handler

No comments:

Post a Comment