技術學習記錄

[Android]多執行緒

在寫開發的過程中,難免會進行一些像下載檔案、讀取檔案之類的耗時操作。

如果將這些操作放在主執行緒執行的話,很容易造成UI介面卡住。直到操作執行完之後,UI畫面才會更新。

Android底層有個機制,只要主執行緒被耗時的操作卡住超過5秒以上,系統就會跳出ANR(應用程式沒有回應)提示。

且使用者也會因為這個狀況,憤而刪除掉APP、或是在Google Play商店給一顆星的評價。

因此,通常很耗時間的操作,都會額外再開一個執行緒來做處理。處理完成後再透過主執行緒呈現結果。


使用方式1

這裡直接使用new Thread的方式開啟執行緒,並透過Thread.sleep(1000)來模擬耗時操作,如下所示:

public class MainActivity extends AppCompatActivity {

    private Button btnThread;

    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView = (TextView) findViewById(R.id.textView);
        btnThread = (Button) findViewById(R.id.btnThread);

        btnThread.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 按下按鈕模擬執行耗時操作
                threadRun();
            }
        });
    }

    private void threadRun() {
        // 開啟執行緒,並延遲一秒
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.e(TAG, "Thread" + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // UI更新時,切回主執行緒
                runOnUiThread(
                        new Runnable() {
                            @Override
                            public void run() {
                                Log.e(TAG, "Thread" + Thread.currentThread().getName());
                                textView.setText("Thread finished...");
                            }
                        });
            }
        }).start();
    }
}

可以透過Logcat看到執行緒的切換狀況如下:

E/MainActivity: ThreadBackground Worker
E/MainActivity: Threadmain

使用方式2

這裡使用Handler及HandlerThread,同樣也是透過Thread.sleep(1000)來模擬耗時操作。

首先先初始化HandlerThread物件,接著將HandlerThread的Looper物件,在Handler初始化時傳入即可,如下:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = MainActivity.class.getSimpleName();

    private Button btnThread;

    private TextView textView;

    private Handler mHandler;

    private HandlerThread mBackgroundThread;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mBackgroundThread = new HandlerThread("Background Worker");
        mBackgroundThread.start();

        mHandler = new Handler(mBackgroundThread.getLooper());

        textView = (TextView) findViewById(R.id.textView);
        btnThread = (Button) findViewById(R.id.btnThread);

        btnThread.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 按下按鈕模擬執行耗時操作
                threadRun();
            }
        });
    }

    private void threadRun() {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                Log.e(TAG, "Thread" + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                runOnUiThread(
                        new Runnable() {
                            @Override
                            public void run() {
                                Log.e(TAG, "Thread" + Thread.currentThread().getName());
                                textView.setText("Thread finished...");
                            }
                        }
                );
            }
        });
    }
}

可以透過Logcat看到執行緒的切換狀況如下:

E/MainActivity: ThreadBackground Worker
E/MainActivity: Threadmain

使用方式3

這裡使用AsyncTask的方式,同樣也是透過Thread.sleep(1000)來模擬耗時操作,如下:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = MainActivity.class.getSimpleName();

    private Button btnThread;

    private TextView textView;

    private MyAsyncTask asyncTask;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView = (TextView) findViewById(R.id.textView);
        btnThread = (Button) findViewById(R.id.btnThread);

        btnThread.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 按下按鈕模擬執行耗時操作
                threadRun();
            }
        });
    }

    private void threadRun() {
        asyncTask = new MyAsyncTask();
        asyncTask.execute();
    }

    private class MyAsyncTask extends AsyncTask<Void, Void, String> {

        @Override
        protected void onPreExecute() {
            textView.setText("AsyncTask Start");
            Log.e(TAG, "AsyncTask start... Thread: " + Thread.currentThread().getName());
        }

        @Override
        protected String doInBackground(Void... params) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Log.e(TAG, e.toString());
            }
            Log.e(TAG, "AsyncTask doInBackground... Thread: " + Thread.currentThread().getName());
            return "Thread finished...";
        }

        @Override
        protected void onProgressUpdate(Void... values) {
            textView.setText("AsyncTask running...");
        }

        @Override
        protected void onPostExecute(String result) {
            textView.setText(result);
            Log.e(TAG, "AsyncTask onPostExecute... Thread: " + Thread.currentThread().getName());
        }
    }
}

透過觀察Logcat訊息,可以看到執行緒切換的狀況:

E/MainActivity: AsyncTask start... Thread: main
E/MainActivity: AsyncTask doInBackground... Thread: AsyncTask #1
E/MainActivity: AsyncTask onPostExecute... Thread: main

發佈留言

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