2016年3月27日

Service in Android

使用者看不見,或是要開放給其他 APP 使用的功能,要用 Service 實作,Service 是在 UI Thread 裡面執行,雖然不跟 UI 互動,但還是會降低程式的回應速度。

Service 可降低 Thread 發生 memory leak 的狀況:BroadcastReceiver 跟 Activity 的生命週期與被境執行緒的執行沒關係,但Service 可在結束時,同時控制執行緒的銷毀。最好的實作方式,就是用 Activity 先啟動 Service,再由 Service 啟動執行緒。

Local, 私有遠端, 全域遠端 Service

  • 區域 Service Service 執行在跟呼叫 Service 的元件的 process中,在同一個 UI Thread 裡面,共用相同的 Heap。因此,Service能夠跟 client component 共用 Java 物件。
<service
    android:name="com.test.TestService">
</service>
  • 私有遠端 Service Service 有自己的 process,只能被屬於相同應用程式的 client component 使用,因此,Service 不會干擾 client component 的 UI Thread。
<service
    android:name="com.test.TestService"
    android:process=":com.test.TestServiceProcess">
</service>
  • 全域遠端 Service Service 開放給其他 APP 使用。但不能以 Service 類別名稱被參考,只能用 intent filter 的方式存取。
<service
    android:name="com.test.TestService"
    android:process="Com.test.TestServiceProcess">
    <intent-filter>
        <action android:name=".." />
        <category android:name=".." />
    </intent-filter>
</service>

Service 的生命週期

服務有兩種

  1. 啟動 context.startService(Intent)傳送啟動 request 給 Service,被啟動的服務要實作 onStartCommand,回傳值有三種:STARTSTICKY(服務會被重新啟動)、STARTNOTSTICKY(服務只有在被終止時,有未完成的啟動request 才會重新啟動)、STARTREDELIVER_INTENT(服務會被重新啟動,並重送先前的啟動 Intent)。

  2. 繫結 bind 在第一個 bind 的時候,會建立 Service,所有元件都 unbind 時會銷毀此 Service。

藍牙 sample

public class BluetoothActivity extends Activity {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_bluetooth);
    }

    public void onStartListening(View v) {
        Intent intent = new Intent(this, BluetoothService.class);
        intent.putExtra(BluetoothService.COMMAND_KEY, BluetoothService.COMMAND_START_LISTENING);
        startService(intent);
    }

    public void onStopListening(View v) {
        Intent intent = new Intent(this, BluetoothService.class);
        stopService(intent);
    }
}

public class BluetoothService extends Service {

    private static final String TAG = "BluetoothService";
    public static final String COMMAND_KEY = "command_key";
    public static final String COMMAND_START_LISTENING = "command_start_discovery";

    private static final UUID MY_UUID = new UUID(323476234, 34587387);
    private static final String SDP_NAME = "custom_sdp_name";


    private BluetoothAdapter mAdapter;
    private BluetoothServerSocket mServerSocket;
    private boolean mListening = false;
    private Thread listeningThread;


    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        mAdapter = BluetoothAdapter.getDefaultAdapter();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (mAdapter != null) {
            // 控制執行緒的數量,一次只有一個配對操作
            if (intent.getStringExtra(COMMAND_KEY).equals(COMMAND_START_LISTENING) && mListening == false) {
                startListening();
            }
        }
        // 臨時終止時,可重做配對工作
        return START_REDELIVER_INTENT;
    }

    private void startListening() {
        mListening = true;
        listeningThread = new Thread(new Runnable() {

            @Override
            public void run() {
                BluetoothSocket socket = null;
                try {
                    mServerSocket = mAdapter.listenUsingInsecureRfcommWithServiceRecord(SDP_NAME, MY_UUID);
                    // blocking
                    socket = mServerSocket.accept();

                    if (socket != null) {
                        // Handle BT connection
                    }

                } catch (IOException e) {
                    Log.d(TAG, "Server socket closed");
                }
            }
        });
        listeningThread.start();
    }

    private void stopListening() {
        mListening = false;
        try {
            if (mServerSocket != null) {
                mServerSocket.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        stopListening();
    }
}

Local Service

以下是使用 Local Service 的範例

public class BoundLocalActivity extends Activity {

    private LocalServiceConnection mLocalServiceConnection = new LocalServiceConnection();
    private BoundLocalService mBoundLocalService;
    private boolean mIsBound;

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        bindService(new Intent(BoundLocalActivity.this, BoundLocalService.class),
                mLocalServiceConnection,
                Service.BIND_AUTO_CREATE);
        mIsBound = true;
    }
    @Override
    protected void onDestroy() { super.onDestroy();
        if (mIsBound) {
            try {
                unbindService(mLocalServiceConnection);
                mIsBound = false;
            } catch (IllegalArgumentException e) {
                // No bound service
            }
        }
    }
    private class LocalServiceConnection implements ServiceConnection {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            mBoundLocalService = ((BoundLocalService.ServiceBinder)iBinder).getService();

            // At this point clients can invoke methods in the Service,
            // i.e. publishedMethod1 and publishedMethod2.
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            mBoundLocalService = null;
        }
    }
}


public class BoundLocalService extends Service {

    private final ServiceBinder mBinder = new ServiceBinder();

    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    public class ServiceBinder extends Binder {
        public BoundLocalService getService() {
            return BoundLocalService.this;
        }
    }

    // Methods published to clients.
    public void publishedMethod1() { /* TO IMPLEMENT */ }

    public void publishedMethod2() { /* TO IMPLEMENT */ }
}

IntentService

IntentService 有 Service 的特性,又另外內建 HandlerThread 背景執行緒,可以循序處理任務。

但 Service 還是比 IntentService 常用,因為 Service 可由使用者自己控制,可以支援啟動多個執行緒,也可以賦予優先權,優先執行某些任務。

public class WebServiceActivity extends Activity {

    private final static String TAG = WebServiceActivity.class.getSimpleName();
    private final static String getUrl = "http://dn.se"; // Dummy
    private final static String postUrl = "http://dn.se"; // Dummy

    private ResultReceiver mReceiver;

    public WebServiceActivity() {
        // 產生要傳送給 IntentService 的 ResultReceiver
        mReceiver = new ResultReceiver(new Handler()) {
            @Override
            protected void onReceiveResult(int resultCode, Bundle resultData) {
                int httpStatus = resultCode;
                String jsonResult = null;
                if (httpStatus == 200) { // OK
                    if (resultData != null) {
                        jsonResult= resultData.getString(WebService.BUNDLE_KEY_REQUEST_RESULT);
                        // Omitted: Handle response
                    }
                }
                else {
                    // Handle error
                }
            }

        };
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_web_service);
    }

    public void doPost(View v) {
        Intent intent = new Intent(this, WebService.class);
        intent.setData(Uri.parse(postUrl));
        intent.putExtra(WebService.INTENT_KEY_REQUEST_TYPE, WebService.POST);
        intent.putExtra(WebService.INTENT_KEY_JSON, "{\"foo\":\"bar\"}");
        intent.putExtra(WebService.INTENT_KEY_RECEIVER, mReceiver);
        startService(intent);
    }

    public void doGet(View v) {
        Intent intent = new Intent(this, WebService.class);
        intent.setData(Uri.parse(getUrl));
        intent.putExtra(WebService.INTENT_KEY_REQUEST_TYPE, WebService.GET);
        intent.putExtra(WebService.INTENT_KEY_RECEIVER, mReceiver);
        startService(intent);
    }

}

public class WebService extends IntentService {
    private static final String TAG = WebService.class.getName();
    public static final int GET = 1;
    public static final int POST = 2;

    public static final String INTENT_KEY_REQUEST_TYPE = "com.eat.INTENT_KEY_REQUEST_TYPE";
    public static final String INTENT_KEY_JSON = "com.eat.INTENT_KEY_JSON";
    public static final String INTENT_KEY_RECEIVER = "com.eat.INTENT_KEY_RECEIVER";
    public static final String BUNDLE_KEY_REQUEST_RESULT = "com.eat.BUNDLE_KEY_REQUEST_RESULT";

    public WebService() {
        super(TAG);
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        // 取得 Intent Data
        Uri uri = intent.getData();
        int requestType = intent.getIntExtra(INTENT_KEY_REQUEST_TYPE, 0);
        String json = (String)intent.getSerializableExtra(INTENT_KEY_JSON);
        ResultReceiver receiver = intent.getParcelableExtra(INTENT_KEY_RECEIVER);

        try {
            HttpRequestBase request = null;
            switch (requestType) {
                case GET: {
                    request = new HttpGet();
                    // Request setup omitted
                    break;
                }
                case POST: {
                    request = new HttpPost();
                    if (json != null) {
                        ((HttpPost)request).setEntity(new StringEntity(json));
                    }
                    // Request setup omitted
                    break;
                }
            }

            if (request != null) {
                request.setURI(new URI(uri.toString()));
                HttpResponse response = doRequest(request);
                HttpEntity httpEntity = response.getEntity();
                StatusLine responseStatus = response.getStatusLine();
                int statusCode = responseStatus != null ? responseStatus.getStatusCode() : 0;

                if (httpEntity != null) {
                    // 將結果回傳給 WebServiceActivity
                    Bundle resultBundle = new Bundle();
                    resultBundle.putString(BUNDLE_KEY_REQUEST_RESULT, EntityUtils.toString(httpEntity));
                    receiver.send(statusCode, resultBundle);
                }
                else {
                    receiver.send(statusCode, null);
                }
            }
            else {
                receiver.send(0, null);

            }
        }
        catch (IOException e) {
            receiver.send(0, null);
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }
    }

    private HttpResponse doRequest(HttpRequestBase request) throws IOException {
        HttpClient client = new DefaultHttpClient();

        // HttpClient configuration omitted

        return client.execute(request);
    }
}

Reference

Android 高效能多執行緒

2016年3月21日

AsyncTask 讓背景任務繫結到 UI Thread

AsyncTask 是在背景執行緒上運作的非同步技術,並將結果送回 UI Thread。

public class MinimalTask extends AsyncTask {
    protected Object doInBackground(Object... object) {
        // 實作要在背景執行緒上執行的任務
    }
}

AsyncTask 工作流程

限制一次一個 AsyncTask

public class AsyncTaskStatusActivity extends Activity {
    private AsyncTask myAsyncTask;
    
    public void onExecute(View v) {
        if(myAsyncTask!=null && myAsyncTask.getStatus() != AsyncTask.Status.RUNNING) {
            myAsyncTask= new MyAsyncTask().execute();
        }
    }
    
    private static class MyAsyncTask extends AsyncTask<String, Void, Void> {
        protected void doInBackground(String... s) {
            return null;
        }
    }
}

下載圖片 的範例

public class FileDownloadActivity extends Activity {

    // 多個圖片的 urls
    private static final String[] DOWNLOAD_URLS = {
            "http://developer.android.com/design/media/devices_displays_density@2x.png",
            "http://developer.android.com/design/media/iconography_launcher_example2.png"
    };

    DownloadTask mFileDownloaderTask;

    // Views from layout file
    private ProgressBar mProgressBar;
    private LinearLayout mLayoutImages;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_file_download);
        mProgressBar = (ProgressBar) findViewById(R.id.progress_bar);
        mProgressBar.setMax(DOWNLOAD_URLS.length);
        mLayoutImages = (LinearLayout) findViewById(R.id.layout_images);

        mFileDownloaderTask = new DownloadTask(this);
        // 執行mFileDownloaderTask
        mFileDownloaderTask.execute(DOWNLOAD_URLS);
    }

    @Override
    protected void onDestroy() {
        // 要同時取消 mFileDownloaderTask,避免 memory leak
        super.onDestroy();
        mFileDownloaderTask.setActivity(null);
        mFileDownloaderTask.cancel(true);
    }

    private static class DownloadTask extends AsyncTask<String, Bitmap, Void> {

        private FileDownloadActivity mActivity;
        private int mCount = 0;

        public DownloadTask(FileDownloadActivity activity) {
            mActivity = activity;
        }

        public void setActivity(FileDownloadActivity activity) {
            mActivity = activity;
        }

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            
            // 執行過程中,更新畫面的 進度mActivity.mProgressBar.setVisibility(View.VISIBLE);
            mActivity.mProgressBar.setProgress(0);
        }

        @Override
        protected Void doInBackground(String... urls) {
            for (String url : urls) {
                if (!isCancelled()) {
                    Bitmap bitmap = downloadFile(url);
                    // 把圖片傳送到 UI Thread
                    publishProgress(bitmap);
                }
            }
            return null;
        }


        @Override
        protected void onProgressUpdate(Bitmap... bitmaps) {
            super.onProgressUpdate(bitmaps);
            if (mActivity != null)  {
                mActivity.mProgressBar.setProgress(++mCount);
                ImageView iv = new ImageView(mActivity);
                iv.setImageBitmap(bitmaps[0]);
                mActivity.mLayoutImages.addView(iv);
            }
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            super.onPostExecute(aVoid);
            if (mActivity != null) {
                mActivity.mProgressBar.setVisibility(View.GONE);
            }
        }

        @Override
        protected void onCancelled() {
            super.onCancelled();
            if (mActivity != null) {
                mActivity.mProgressBar.setVisibility(View.GONE);
            }
        }


        private Bitmap downloadFile(String url) {
            Bitmap bitmap = null;
            try {
                bitmap = BitmapFactory
                        .decodeStream((InputStream) new URL(url)
                                .getContent());
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return bitmap;
        }

    }
}

討論

  1. AsyncTask 具有全域的特性,使用越多,越有可能造成任務異常
  2. 需要使用 Looper 時,就要改用 HandlerThread
  3. 不需要回報進度的任務,可以改用 HandlerThread 或 Thread 實作
  4. 如果是長時間的任務,要改用 Service

Reference

Android 高效能多執行緒

2016年3月14日

Executor Framework in Android

Executor 為執行緒以及在系統上使用的資源,提供更進一步的控制機制。

Executor 的功能

  1. 建立 worker thread 的 pool 及 queue,控制等待執行的任務數量
  2. 檢查讓執行緒異常結束的錯誤
  3. 等待執行緒結束,並擷取結果
  4. 以固定順序批次執行執行緒,並擷取結果
  5. 啟用背景執行緒,讓使用者能快一點取得結果

SimpleExecutor

針對每一個任務建立一個執行緒。

import java.util.concurrent.Executor;

public class SimpleExecutor implements Executor {
    @Override
    public void execute(Runnable runnable) {
        new Thread(runnable).start();
    }
}

SerialExecutor

以任務佇列 ArrauDeque 儲存所有任務,用 FIFO 的順序執行任務。

private static class SerialExecutor implements Executor {
    final ArrayDequq<Runnable> mTasks = new ArrayDequq<Runnable>();
    Runnable mActive;

    @Override
    public synchronized void execute(final Runnable runnable) {
        mTasks.offer(new Runnable() {
            public void run() {
                try {
                    r.run();
                } finally {
                    scheduleNext();
                }
            }
        });
        if(mActive==null) {
            scheduleNext();
        }
    }
    
    protected synchronized void scheduleNext() {
        if( (mActive=mTasks.poll()) !=null ) {
            THREAD_POOL_EXECUTOR.execute(mActive);
        }
    }
}

ThreadPool

為避免 APP 建立太多 Thread,物件的建立與銷毀浪費了系統的資源,所以可使用 ThreadPool。Pool 的大小可以由 processors 數量來決定。

Runtime.getRuntime().availableProcessors();

ThreadPool 的參數

  1. 固定尺寸: 由 Executors.newFixedThreadPool(n) 產生
  2. 動態尺寸: pool隨著任務數量多寡,尺寸會變大或縮小
  3. 單執行緒: Executor.newSingleThreadExecutor

具有自訂執行緒特性的固定尺寸 ThreadPool sample:

class LowPriorityThreadFactory implements ThreadFactory {

    private static final String TAG = "LowPriorityThreadFactory";
    private static int count = 1;

    public Thread newThread(Runnable r) {
        Thread t = new Thread(r);
        t.setName("LowPrio " + count++);
        t.setPriority(4);
        t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler()
        {
            @Override
            public void uncaughtException(Thread t, Throwable e)
            {
                Log.d(TAG, "Thread = " + t.getName() + ", error = " + e.getMessage());
            }
        });
        return t;
    }
}

追蹤 ThreadPool

public class TaskTrackingThreadPool extends ThreadPoolExecutor{

    private static final String TAG = "CustomThreadPool";

    private AtomicInteger mTaskCount = new AtomicInteger(0);

    public TaskTrackingThreadPool() {
        super(3, 3, 0L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
    }

    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        super.beforeExecute(t, r);
        Log.d(TAG, "beforeExecute - thread = " + t.getName());
        mTaskCount.getAndIncrement();
    }

    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);
        Log.d(TAG, "afterExecute - thread = " + Thread.currentThread().getName() + "t = " + t);
        mTaskCount.getAndDecrement();
    }

    @Override
    protected void terminated() {
        super.terminated();
        Log.d(TAG, "terminated - thread = " + Thread.currentThread().getName());
    }

    public int getNbrOfTasks() {
        return mTaskCount.get();
    }
}

Sample

利用 invokeAll 讓兩個 worker thread 同時執行兩個獨立的任務,在兩個都完成之後,再將結果合併在一起。

public class InvokeActivity extends Activity {


    private static final String TAG = "InvokeActivity";

    private TextView textStatus;

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_invoke);
        textStatus = (TextView) findViewById(R.id.text_status);
    }


    public void onButtonClick(View v) {

         // 讓 UI thread 分擔 invokeAll 的執行器 
        SimpleExecutor simpleExecutor = new SimpleExecutor();
        simpleExecutor.execute(new Runnable() {
            @Override
            public void run() {
                // 存放兩個獨立任務
                List<Callable<String>> tasks = new ArrayList<Callable<String>>();
                tasks.add(new Callable<String>() {
                    @Override
                    public String call() throws Exception {
                        return getFirstPartialDataFromNetwork();
                    }
                });
                tasks.add(new Callable<String>() {
                    @Override
                    public String call() throws Exception {
                        return getSecondPartialDataFromNetwork();
                    }
                });

                // 使用兩個 threads 執行任務
                ThreadPoolExecutor executor = (ThreadPoolExecutor)Executors.newFixedThreadPool(2);
                try {
                    Log.d(TAG, "invokeAll");
                    List<Future<String>> futures = executor.invokeAll(tasks);
                    Log.d(TAG, "invokeAll after");

                    final String mashedData = mashupResult(futures);

                    textStatus.post(new Runnable() {
                        @Override
                        public void run() {
                            textStatus.setText(mashedData);
                        }
                    });
                    Log.d(TAG, "mashedData = " + mashedData);

                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }

                executor.shutdown();
            }
        });
    }

    private String getFirstPartialDataFromNetwork() {
        Log.d(TAG, "ProgressReportingTask 1 started");
        SystemClock.sleep(10000);
        Log.d(TAG, "ProgressReportingTask 1 done");
        return "MockA";
    }

    private String getSecondPartialDataFromNetwork() {
        Log.d(TAG, "ProgressReportingTask 2 started");
        SystemClock.sleep(2000);
        Log.d(TAG, "ProgressReportingTask 2 done");
        return "MockB";
    }

    private String mashupResult(List<Future<String>> futures) throws ExecutionException, InterruptedException {
        StringBuilder builder = new StringBuilder();
        for (Future<String> future : futures) {
            builder.append(future.get());
            // 以 future.get 分同步取得結果
        }
        return builder.toString();
    }
}

當 Acvtivity 建立後,就會在背景下載多個圖片

public class ECSImageDownloaderActivity extends Activity {

    private static final String TAG = "ECSImageDownloaderActivity";

    private LinearLayout layoutImages;

    private class ImageDownloadTask implements Callable<Bitmap> {

        @Override
        public Bitmap call() throws Exception {
            return downloadRemoteImage();
        }

        private Bitmap downloadRemoteImage() {
            SystemClock.sleep((int) (5000f - new Random().nextFloat() * 5000f));
            return BitmapFactory.decodeResource(ECSImageDownloaderActivity.this.getResources(), R.drawable.ic_launcher);
        }
    }

    // 代表產生結果的任務的 Callable instance
    private class DownloadCompletionService extends ExecutorCompletionService {

        private ExecutorService mExecutor;

        public DownloadCompletionService(ExecutorService executor) {
            super(executor);
            mExecutor = executor;
        }

        public void shutdown() {
            mExecutor.shutdown();
        }

        public boolean isTerminated() {
            return mExecutor.isTerminated();
        }
    }

    // 由完成 queue 取得任務執行結果的 consumer thread
    private class ConsumerThread extends Thread {

        private DownloadCompletionService mEcs;

        private ConsumerThread(DownloadCompletionService ecs) {
            this.mEcs = ecs;
        }

        @Override
        public void run() {
            super.run();
            try {
                // 如果執行器被終止,所有任務都已經完成
                while(!mEcs.isTerminated()) {
                    Future<Bitmap> future = mEcs.poll(1, TimeUnit.SECONDS);
                    if (future != null) {
                        addImage(future.get());
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ecs_image_downloader);
        layoutImages = (LinearLayout) findViewById(R.id.layout_images);

        DownloadCompletionService ecs = new DownloadCompletionService(Executors.newCachedThreadPool());
        new ConsumerThread(ecs).start();

        for (int i = 0; i < 5; i++) {
            ecs.submit(new ImageDownloadTask());
        }

        ecs.shutdown();
    }


    private void addImage(final Bitmap image) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                ImageView iv = new ImageView(ECSImageDownloaderActivity.this);
                iv.setImageBitmap(image);
                layoutImages.addView(iv);
            }
        });
    }
}

Reference

Android 高效能多執行緒

2016年3月7日

HandlerThread in Android

HandlerThread 是一種內建了訊息傳遞機制的 Thread,很適合用來當作循序任務處理器。其實也是內建了 Looper 與 MessageQueue,等待要處理的訊息。

HandlerThread handlerThread = new HandlerThread("HandlerThread");
handlerThread.start();

mHandler = new Handler(handlerThread.getLooper()) {
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
    }
};

Handler 的存取能夠透過 HandlerThread 的子類別,設定為私有的,並確保 Looper 不可被存取,由該子類別提供給客戶端公用的存取 method。

public class MyHandlerThread extends HandlerThread {

    private Handler mHandler;

    public MyHandlerThread() {
        super("MyHandlerThread", Process.THREAD_PRIORITY_BACKGROUND);
    }

    @Override
    protected void onLooperPrepared() {
        super.onLooperPrepared();
        mHandler = new Handler(getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                switch(msg.what) {
                    case 1:
                        // Handle message
                        break;
                    case 2:
                        // Handle message
                        break;
                }
            }
        };
    }

    public void publishedMethod1() {
        mHandler.sendEmptyMessage(1);
    }
    public void publishedMethod2() {
        mHandler.sendEmptyMessage(2);
    }
}

生命週期

  • Creation

    預設 priority 為 Process.THREADPRIORITYDEFAULT ,跟 UI Thread 一樣,可以設定為 Process.THREADPRIORITYBACKGROUND,來執行非關鍵性任務。

    HandlerThread(String name);
    HandlerThread(String name, int priority);
  • Execution

    HandlerThread 永遠會準備好接收訊息。

  • Reset

    Message Queue 可被重設,不處理裡面的訊息,但該 thread 會繼續存活,可以處理新的訊息。重設時不會影響已經在處理中的,以及已經被派送的訊息。

    public void resetHandlerThread() {
        mHandler.removeCallbacksAndMessages(null);
    }
  • Termination

    HandlerThread 透過 quit 及 quitSafely 被終止,使用 quitSafely 可以將已經派送的訊息處理完成後,才結束 HandlerThread。

    public void stopHandlerThread(HandlerThread handlerThread) {
        handlerThread.quit();
        handlerThread.quitSafely();
    }

    也可以利用 finalization task 來結束。

    handler.post(new Runnable() {
        @Override
        public void run() {
            Looper.myLooper().quit();
        }
    });

使用案例1: SharedPreferences

public class SharedPreferencesActivity extends Activity {

    TextView mTextValue;

    /**
     * Show read value in a TextView.
     * UI 執行緒的 Handler
     */
    private Handler mUiHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what == 0) {
                Integer i = (Integer)msg.obj;
                mTextValue.setText(Integer.toString(i));
            }
        }
    };

    // 讀寫 SharedPreference 的 background thread
    private class SharedPreferenceThread extends HandlerThread {

        private static final String KEY = "key";
        private SharedPreferences mPrefs;
        private static final int READ = 1;
        private static final int WRITE = 2;

        private Handler mHandler;

        public SharedPreferenceThread() {
            super("SharedPreferenceThread", Process.THREAD_PRIORITY_BACKGROUND);
            mPrefs = getSharedPreferences("LocalPrefs", MODE_PRIVATE);
        }

        @Override
        protected void onLooperPrepared() {
            super.onLooperPrepared();
            mHandler = new Handler(getLooper()) {
                @Override
                public void handleMessage(Message msg) {
                    switch(msg.what) {
                        case READ:
                            mUiHandler.sendMessage(mUiHandler.obtainMessage(0, mPrefs.getInt(KEY, 0)));
                            break;
                        case WRITE:
                            SharedPreferences.Editor editor = mPrefs.edit();
                            editor.putInt(KEY, (Integer)msg.obj);
                            editor.commit();
                            break;
                    }
                }
            };
        }

        public void read() {
            mHandler.sendEmptyMessage(READ);
        }
        public void write(int i) {
            mHandler.sendMessage(Message.obtain(Message.obtain(mHandler, WRITE, i)));
        }
    }

    private int mCount;
    private SharedPreferenceThread mThread;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_shared_preferences);
        mTextValue = (TextView) findViewById(R.id.text_value);
        mThread = new SharedPreferenceThread();
        // 建立 Activity 時,啟動 SharedPreferenceThread
        mThread.start();
    }

    /**
     * Write dummy value from the UI thread.
     */
    public void onButtonClickWrite(View v) {
        mThread.write(mCount++);
    }

    /**
     * Initiate a read from the UI thread.
     */
    public void onButtonClickRead(View v) {
        mThread.read();
    }

    /**
     * Ensure that the background thread is terminated with the Activity.
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mThread.quit();
    }
}

使用案例2: 鏈結的網路呼叫

當第一個網路資源時,就繼續處理第二個,如果失敗,就停止這個 background thread。

public class ChainedNetworkActivity extends Activity {

    private static final int DIALOG_LOADING = 0;

    private static final int SHOW_LOADING = 1;
    private static final int DISMISS_LOADING = 2;

    // 在 UI thread 上處理 dialog
    Handler dialogHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case SHOW_LOADING:
                    showDialog(DIALOG_LOADING);
                    break;
                case DISMISS_LOADING:
                    dismissDialog(DIALOG_LOADING);
            }
        }
    };

    private class NetworkHandlerThread extends HandlerThread {
        private static final int STATE_A = 1;
        private static final int STATE_B = 2;
        private Handler mHandler;

        public NetworkHandlerThread() {
            super("NetworkHandlerThread", Process.THREAD_PRIORITY_BACKGROUND);
        }

        @Override
        protected void onLooperPrepared() {
            super.onLooperPrepared();
            mHandler = new Handler(getLooper()) {
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    switch (msg.what) {
                        case STATE_A:
                            // 第一個任務的處理結果
                            dialogHandler.sendEmptyMessage(SHOW_LOADING);
                            String result = networkOperation1();
                            if (result != null) {
                                sendMessage(obtainMessage(STATE_B, result));
                            } else {
                                // 失敗時,就終止dialogHandler.sendEmptyMessage(DISMISS_LOADING);
                            }
                            break;
                        case STATE_B:
                            networkOperation2((String) msg.obj);
                            dialogHandler.sendEmptyMessage(DISMISS_LOADING);
                            break;
                    }
                }
            };
            // 開始第一個任務
            fetchDataFromNetwork();
        }

        private String networkOperation1() {
            SystemClock.sleep(2000); // Dummy
            return "A string";
        }

        private void networkOperation2(String data) {
            // Pass data to network, e.g. with HttpPost.
            SystemClock.sleep(2000); // Dummy
        }

        /**
         * Publicly exposed network operation
         */
        public void fetchDataFromNetwork() {
            mHandler.sendEmptyMessage(STATE_A);
        }
    }

    private NetworkHandlerThread mThread;


    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mThread = new NetworkHandlerThread();
        mThread.start();
    }

    @Override
    protected Dialog onCreateDialog(int id) {
        Dialog dialog = null;
        switch (id) {
            case DIALOG_LOADING:
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setMessage("Loading...");
            dialog = builder.create();
            break;
        }
        return dialog;
    }

    /**
     * Ensure that the background thread is terminated with the Activity.
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mThread.quit();
    }
}

Reference

Android 高效能多執行緒

2016年3月4日

SSLv2 DROWN attach

DROWN - Decrypting RSA using Obsolete and Weakened eNcryption

The DROWN Attack

DROWN - Cross-protocol attack on TLS using SSLv2 - CVE-2016-0800

[預警]openssl再爆漏洞了,官方建議禁用SSLv2

檢查是否有支援 SSLv2 的方法

How to check if SSL v2 is enabled using openssl

下指令 openssl s_client -connect kokola.maxkit.com.tw:443 -ssl2 ,如果看到 handshake failure 就是沒有 enable SSLv2。

> openssl s_client -connect kokola.maxkit.com.tw:443 -ssl2
CONNECTED(00000003)
140735250387024:error:1407F0E5:SSL routines:ssl2_write:ssl handshake failure:s2_pkt.c:409:

httpd ssl.conf

How to Disable SSLv2 for Apache httpd

如果沒有修改過 httpd ssl.conf 設定,預設是關閉 SSLv2。

#   SSL Protocol support:
# List the enable protocol levels with which clients will be able to
# connect.  Disable SSLv2 access by default:
SSLProtocol all -SSLv2

2016年3月1日

Android 記憶體管理

Android Dalvik GC 採用 Mark and Sweep 處理機制,mark 會 traverse 整個 object tree,並將所有未被其他物件參考到的物件,標示為 unused,未被使用的物件,就可以被 GC。

APP 執行緒潛藏 memory leak 的風險,只有在 thread 終止時,他們使用到的物件才會被GC。

worker thread

當執行緒在執行時,Thread 物件本身會變成 GC root,他所參考到的物件都是可以到達的。而 Thread 與 Runnable instance 保存了只向其他物件的 references,必須等到 Thread 結束,才能回收那些物件。當 method return 後,方法裡面所建立的物件就能被 GC,除非該 method 將物件回傳給他的呼叫者,讓其他 method 可以使用該物件。

  • 內部類別

inner class 是 outer class 的成員,可以存取外部類別的其他成員,因此內部類別隱含地參考到外部類別,被定義為內部類別的執行緒,保存了指向外部類別的 reference,只要執行緒還在執行,外部類別就不會被標示為可以被 GC。

定義為 local class 與 anonymous inner class 的執行緒跟外部類別的關係,與內部類別相同,在執行期間,可以讓外部類別從 GC Root 到達。

以下範例中,只要 SampleThread 還在執行中,在 Outer 類別裡的任何物件,都必須保存在記憶體中,伴隨著內部的 SampleThread 類別裡的物件。

public class Outer {
    public void sampleMethd() {
        SampleThread sampleThread = new SampleThread();
        sampleThread.start();
    }
    
    private class SampleThread extends Thread {
        public void run() {
            Object sampleObject = new Object();
        }
    }
}
  • 靜態內部類別

static inner class 是外部類別 instance 的成員,定義在靜態內部類別的執行緒保存了指向外部物件的類別的 member reference,而不是直接只向外部物件,所以一旦指向外部物件的其他參考消失,就可以被 GC。

public class Outer {
    public void sampleMethd() {
        SampleThread sampleThread = new SampleThread();
        sampleThread.start();
    }
    
    private static class SampleThread extends Thread {
        public void run() {
            Object sampleObject = new Object();
        }
    }
}

在大多數情況下,我們會想要將 Thread 與 Runnable 分開,如果建立了新的 Runnable 作為內部類別,在執行期間會保存指向外部類別的 reference。

public class Outer {
    public void sampleMethd() {
        SampleThread sampleThread = new SampleThread(new Runnable () {
            @Override
            public void run() {
                Object sampleObject = new Object();
            }
        });
        sampleThread.start();
    }
    
    private static class SampleThread extends Thread {
        public SampleThread(Runnable runnable) {
            super(runnable);
        }
    }
}

執行緒溝通

執行緒跟訊息傳遞機制是 memory leak 的潛在根源。對收到訊息物件的 thread 來說,他必須使用 MessageQueue 來存放待處理的訊息,使用 Looper 派送訊息,使用 Handler 處理訊息,這些物件都會被 worker thread 參考。然而 Handler 是 memory leak 發生的主因,因為他是透過一系列物件,被 consumer thread 參考到的, Handler 跟他所參考到的物件,必須等到 thread 終止時,才能被 GC。

  • 傳送資訊訊息

資料物件能以多種方式被傳遞,選擇哪一種實作方式,會決定 memory leak的規模。

public class Outer {
    Handler mHandler = new Handler() {
        public void handleMessage(Message msg) {
        }
    }
    
    public void doSend() {
        Message message = mHandler.obtainMessage();
        message.obj = new SampleObject();
        mHandler.sendMessageDelayed(message, 60*10000);
    }
}
  • 發布任務訊息

Handler 與 Runnable 都會參考到 Outer,增加了 memory leak 的風險

public class Outer {
    Handler mHandler = new Handler() {
        public void handleMessage(Message msg) {
        }
    }
    
    public void doPost() {
        mHandler.post(new Runnable() {
            public void run() {
            }
        });
    }
}

如何避免 memory leak

  • 使用靜態內部類別 不使用具有外部物件參考的巢狀類別,改用靜態內部類別。因為他們只會參考到痊癒的 class object,而不是 instance object。

  • 使用 WeakReference

WeakReference 可避免 reference counting。

public class Outer {
    private int mField;
    
    pirvate static class SampleThread extends Thread {
        private final WeakReference<Outer> mOuter;
        
        SampleThread(Outer outer) {
            mOuter = new WeakReference<Outer>(outer);
        }
        
        public void run() {
            if(mOuter.get() !=null ) {
                mOuter.get().mField=1;
            }
        }
    }
}
  • 停止 Work Thread

  • 保留 Work Thread

以 retain 的方式,保留舊物件給新的 Activity 使用。

  • 清除 Message Queue
removeCallbacks(Runnable r)
removeCallbacks(Runnable r, Object token)
removeCallbacksAndMessages(Object token)
removeMessages(int what)
removeMessages(int what, Object object)

Android 管理執行緒的生命週期

每個 APP 必須管理他們建立的執行緒,避免發生 memory leak。

  • 定義與啟動

    定義與啟動 Thread 的方式會影響記憶體洩漏的風險與規模。以下是幾種建立執行緒的方式:

    1. 匿名內部類別:容易實作,但保有只向外部類別的參考,可能會產生記憶體洩漏

      public class AnyObject {
          @UiThread
          public void anyMethod() {
              new Thread {
                  public void run() {
                      doLongRunningTask();
                  }
              }.start();
          }
      }
    2. 公用執行緒:將執行緒定義為獨立的類別

      class MyThread extends Thread {
          public void run() {
              doLongRunningTask();
          }
      }
      public class AnyObject {
          private MyThread myThread;
      
          @UiThread
          public void anyMethod() {
              myThread = new MyThread();
              myThread.start();
          }
      }
    3. 靜態內部類別執行緒:在類別物件上定義執行緒,而不是 instance

      public class AnyObject {
          static class MyThread extends Thread {
              public void run() {
                  doLongRunningTask();
              }
          };
      
          private MyThread myThread;
      
          @UiThread
          public void anyMethod() {
              myThread = new MyThread();
              myThread.start();
          }
      }
  • 保留

    因為執行緒的生命週期可能會超過啟動它的元件,或是 Activity 因為設備旋轉而重新產生了新的 Activity,這時候的背景執行緒只有舊的 Activity 才知道,也需要重新啟動一次執行緒。因此我們需要一個保留工作執行緒的方法。

    public Object onRetainNonConfigurationInstance() 在組態改變前由平台呼叫,可以回傳想要保留並傳遞給新 Activity 的物件

    public Object getLastNonConfigurationInstance() 組態改變後,可在 onCreate 或 onStart 中被呼叫,取回先前紀錄的物件

    在 Activity 中保留 thread 的範例:

    public class ThreadRetainActivity extends Activity {
    
        // worker thread 宣告為靜態內部類別
        private static class MyThread extends Thread {
            private ThreadRetainActivity mActivity;
    
            public MyThread(ThreadRetainActivity activity) {
                mActivity = activity;
            }
    
            // attach 用來記錄 Activity 的 reference
            private void attach(ThreadRetainActivity activity) {
                mActivity = activity;
            }
    
            @Override
            public void run() {
                final String text = getTextFromNetwork();
                mActivity.setText(text);
            }
    
            // Long operation
            private String getTextFromNetwork() {
                // Simulate network operation
                SystemClock.sleep(5000);
                return "Text from network";
            }
        }
    
        private static MyThread t;
        private TextView textView;
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_retain_thread);
            textView = (TextView) findViewById(R.id.text_retain);
    
            // 查詢有沒有先前紀錄的物件
            Object retainedObject = getLastNonConfigurationInstance();
            if (retainedObject != null) {
                t = (MyThread) retainedObject;
                // 修改 worker thread 裡面記錄的 activity reference
                t.attach(this);
            }
        }
    
        @Override
        public Object onRetainNonConfigurationInstance() {
            // 在 worker thread 還在處理中時,回傳 t
            if (t != null && t.isAlive()) {
                return t;
            }
            return null;
        }
    
        public void onStartThread(View v) {
            // 啟動 thread
            t = new MyThread(this);
            t.start();
        }
    
        private void setText(final String text) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    textView.setText(text);
                }
            });
        }
    }

    在 Fragement 保留 thread 的方法:Fragment 通常用來處理部分 UI,可以將保留 thread instance 的責任轉交給 Fragment。

    public class ThreadRetainWithFragmentActivity extends Activity {
    
        private static final String TAG = "ThreadRetainActivity";
    
        private static final String KEY_TEXT = "key_text";
    
        private ThreadFragment mThreadFragment;
    
        private TextView mTextView;
    
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_retain_thread);
            mTextView = (TextView) findViewById(R.id.text_retain);
    
            // Activity 第一次啟動,要產生 Fragment
            FragmentManager manager = getFragmentManager();
            mThreadFragment = (ThreadFragment) manager.findFragmentByTag("threadfragment");
    
            if (mThreadFragment == null) {
                FragmentTransaction transaction = manager.beginTransaction();
                mThreadFragment = new ThreadFragment();
                transaction.add(mThreadFragment, "threadfragment");
                transaction.commit();
            }
        }
    
        @Override
        protected void onRestoreInstanceState(Bundle savedInstanceState) {
            super.onRestoreInstanceState(savedInstanceState);
            mTextView.setText(savedInstanceState.getString(KEY_TEXT));
        }
    
        @Override
        protected void onSaveInstanceState(Bundle outState) {
            super.onSaveInstanceState(outState);
            outState.putString(KEY_TEXT, (String)mTextView.getText());
        }
    
        // Method called to start a worker thread
        public void onStartThread(View v) {
            // 啟動 worker thread
            mThreadFragment.execute();
        }
    
        public void setText(final String text) {
            // 讓 fragment 回頭更新 UI
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    mTextView.setText(text);
                }
            });
        }
    }
    
    public class ThreadFragment extends Fragment {
    
        // 保留上層的 Activity reference
        private ThreadRetainWithFragmentActivity mActivity;
        private MyThread t;
    I
        private class MyThread extends Thread {
    
            @Override
            public void run() {
                final String text = getTextFromNetwork();
                mActivity.setText(text);
            }
    
            // Long operation
            private String getTextFromNetwork() {
                // Simulate network operation
                SystemClock.sleep(5000);
                return "Text from network";
            }
        }
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            // 在狀態改變時,保留狀態
            setRetainInstance(true);
        }
    
        @Override
        public void onAttach(Activity activity) {
            super.onAttach(activity);
            mActivity = (ThreadRetainWithFragmentActivity) activity;
        }
    
        @Override
        public void onDetach() {
            super.onDetach();
            mActivity = null;
        }
    
        public void execute() {
            // 啟動 worker thread
            t = new MyThread();
            t.start();
        }
    }
  • 例外

    如果執行緒因例外而結束,可以用 UncaughtExceptionHandler 捕捉此狀況。

    1. 執行緒全域處理器

      static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler handler);
    2. 執行緒區域處理器:比全域處理器還早被執行

      void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler handler);

    範例程式:

    Thread t = new Thread(new Runnable() {
        @Override
        public void run() {
            throw new RuntimeException("unexpected error");
        }
    });
    
    t.setUncaughtExceptionHandler(new Thread.UncaughtExceptioHandler() {
        @Override
        pbulic void uncaughtException(Thread thread, Throwable throwable) {
            Log.d(TAG, throwable.toString());
        }
    });

    APP 裡面有一個全域的 UncaughtExceptionHandler,他會以 kill process 的方式,處理所有例外。可以針對所有 thread,以全域的方式複寫,將例外重導到此 handler。

    Thread.setDefaultUncaughtEcceptionHandler(new ErrorReportExceptionHandler());
    
    public class ErrorReportExceptionHandler implements Thread.UncaightExceptionHandler {
        private final Thread.UncaughtExceptionHandler defaultHandler;
    
        public ErrorReportExceptionHandler() {
            this.defaultHandler=Thread.getDefaultUncaughtExceptionHandler();
        }
    
        @Override
        public void uncaughtException(Thread thread, Throwable throwable) {
            reportErrorToFile(throwable);
            defaultHandler.uncaughtException(thread, throwable);
        }
    }

Reference

Android 高效能多執行緒