分類
Android Uncategorized

Android Handler Thread

相較於實作基本的訊息傳送機制(Handler + Looper + Message) Android 有另一個已經幫我們完成 Looper 連結的 Thread,名稱為 Handler Thread
Handler Thread 其實相當簡單,source code 也僅150行,如下

/*
 * Copyright (C) 2006 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package android.os;
/**
 * Handy class for starting a new thread that has a looper. The looper can then be
 * used to create handler classes. Note that start() must still be called.
 */
public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;
    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }
    /**
     * Constructs a HandlerThread.
     * @param name
     * @param priority The priority to run the thread at. The value supplied must be from
     * {@link android.os.Process} and not from java.lang.Thread.
     */
    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }
    /**
     * Call back method that can be explicitly overridden if needed to execute some
     * setup before Looper loops.
     */
    protected void onLooperPrepared() {
    }
    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }
    /**
     * This method returns the Looper associated with this thread. If this thread not been started
     * or for any reason is isAlive() returns false, this method will return null. If this thread
     * has been started, this method will block until the looper has been initialized.
     * @return The looper.
     */
    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }
    /**
     * Quits the handler thread's looper.
     * <p>
     * Causes the handler thread's looper to terminate without processing any
     * more messages in the message queue.
     * </p><p>
     * Any attempt to post messages to the queue after the looper is asked to quit will fail.
     * For example, the {@link Handler#sendMessage(Message)} method will return false.
     * </p><p class="note">
     * Using this method may be unsafe because some messages may not be delivered
     * before the looper terminates.  Consider using {@link #quitSafely} instead to ensure
     * that all pending work is completed in an orderly manner.
     * </p>
     *
     * @return True if the looper looper has been asked to quit or false if the
     * thread had not yet started running.
     *
     * @see #quitSafely
     */
    public boolean quit() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quit();
            return true;
        }
        return false;
    }
    /**
     * Quits the handler thread's looper safely.
     * <p>
     * Causes the handler thread's looper to terminate as soon as all remaining messages
     * in the message queue that are already due to be delivered have been handled.
     * Pending delayed messages with due times in the future will not be delivered.
     * </p><p>
     * Any attempt to post messages to the queue after the looper is asked to quit will fail.
     * For example, the {@link Handler#sendMessage(Message)} method will return false.
     * </p><p>
     * If the thread has not been started or has finished (that is if
     * {@link #getLooper} returns null), then false is returned.
     * Otherwise the looper is asked to quit and true is returned.
     * </p>
     *
     * @return True if the looper looper has been asked to quit or false if the
     * thread had not yet started running.
     */
    public boolean quitSafely() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quitSafely();
            return true;
        }
        return false;
    }
    /**
     * Returns the identifier of this thread. See Process.myTid().
     */
    public int getThreadId() {
        return mTid;
    }
}

 
在 run 方法已經幫我們做好了 Looper.prepare 以及 Looper.loop,我們只需要實作 HandlerThread 的 subclass,並複寫 onLooperPrepared() 方法,在該方法中定義初始化的工作,建構式可以輸入該 thread 名稱,方便之後除錯。
預設的 priority 為 Process.THREAD_PROCESS_DEFAULT
其他需要注意的部份為
1.與外部 Handler 的連結必須透過 getLooper 方法來完成。
2.啟動 HandlerThread 的方法還是一樣呼叫 start()。
3.可以透過呼叫 quit 以及 quitSafely 來中止 HandlerThread。
呼叫 quit 會立刻中止 looper,如果MessageQueue(MQ) 中還有訊息,這些訊息不會被處理。

4.呼叫 quitSafely 不會立刻中止 looper,looper 會處理完 Message Queue 中可以發送的訊息再停止。
如下

public class CustomizeHandlerThread extends HandlerThread
{
    private CustomizeHandler mHandler;
    public CustomizeHandlerThread(String name, int priority) {
        super(name, priority);
    }
    @Override
    protected void onLooperPrepared()
    {
        super.onLooperPrepared();
        mHandler = new CustomizeHandler(getLooper());
    }
    public Handler getHandler()
    {
        return mHandler;
    }
}

 
CustomizeHandler 則是自定義的 Handler subclass,不使用匿名內部類別的 Handler

class CustomizeHandler extends Handler
{
    private static final String TAG = "CustomizeHandler";
    public CustomizeHandler(Looper looper) {
        super(looper);
    }
    @Override
    public void handleMessage(Message msg)
    {
        super.handleMessage(msg);
        switch (msg.what) {
            case 0:
                Log.d(TAG, "in handle message what 0");
                break;
            case 1:
                Log.d(TAG, "in handle message what 1");
                break;
        }
    }
}

 
MainActivity 使用範例

package com.foxx.handlerthread;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Process;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import com.example.handlerthread.R;
public class MainActivity extends ActionBarActivity implements OnClickListener
{
    private static final String TAG = "MainActivity";
    private Button mStartThreadButton;
    private TextView mResultTextView;
    private CustomizeHandlerThread mCustomizeWorkerThread;
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initUIComponents();
        startWorkerThread();
    }
    private void initUIComponents()
    {
        mStartThreadButton = (Button) findViewById(R.id.startThread);
        mStartThreadButton.setOnClickListener(this);
        mResultTextView = (TextView) findViewById(R.id.textview);
    }
    private void startWorkerThread()
    {
        mCustomizeWorkerThread = new CustomizeHandlerThread("CustomizeHandlerThread",
                Process.THREAD_PRIORITY_BACKGROUND);
        mCustomizeWorkerThread.start();
    }
    @Override
    public void onClick(View view)
    {
        int uiId = view.getId();
        switch (uiId) {
            case R.id.startThread:
                sendMessageToWorkerHandler();
                break;
        }
    }
    private void sendMessageToWorkerHandler()
    {
        Handler handler = mCustomizeWorkerThread.getHandler();
        Message message = handler.obtainMessage(0);
        handler.sendMessage(message);
    }
    public TextView getResultTextView()
    {
        return mResultTextView;
    }
    @Override
    protected void onDestroy()
    {
        super.onDestroy();
        mCustomizeWorkerThread.getHandler().getLooper().quit();
    }
}

 
若想將結果回傳給 MainActivity,可以考慮新增建構子,將 MainActivity 傳入CustomizeHandler.java

class CustomizeHandler extends Handler
{
    ...
    private MainActivity mMainActivity;
    public CustomizeHandler(Looper looper, MainActivity activity) {
        super(looper);
        mMainActivity = activity;
    }
    @Override
    public void handleMessage(Message msg)
    {
        super.handleMessage(msg);
        switch (msg.what) {
            case 0:
                setResultForMainActivity();
                break;
            case 1:
                Log.d(TAG, "in handle message what 1");
                break;
        }
    }
    private void setResultForMainActivity()
    {
        mMainActivity.runOnUiThread(new Runnable() {
            @Override
            public void run()
            {
                mMainActivity.getResultTextView().setText("0");
            }
        });
    }
}

 
修改相對應的 CustomizeHandlerThread

public class CustomizeHandlerThread extends HandlerThread
{
    ...
    private MainActivity mMainActivity;
    public CustomizeHandlerThread(MainActivity activity, String name, int priority){
        super(name, priority);
        mMainActivity = activity;
    }
    ...
}

 
最後修改 MainActivity.java

public class MainActivity extends ActionBarActivity implements OnClickListener
{
    ...
    private void startWorkerThread()
    {
        mCustomizeWorkerThread = new CustomizeHandlerThread(this,"CustomizeHandlerThread",
                Process.THREAD_PRIORITY_BACKGROUND);
        mCustomizeWorkerThread.start();
    }
    ...
}

 
HandlerThread 只使用一個 MessageQueue,所以是執行緒安全的,適合用於循序動作。
多個任務互相依賴結合的情況也適合用於 HandlerThread,可藉由 Message.what 區分不同任務,並判斷是否需要進行下一個任務。
基本上如果任務不需要並發執行,也不需要對 Looper 作太多的控制,使用 Handler Thread會比完全實作訊息傳送機制來的簡單和方便。
 
Android AsyncTask
Android Worker Thread(Handler + Looper + Message)
 

分類
Android Uncategorized

Android Worker Thread Advanced

上篇 Android Looper + Handler + Message + MessageQueue(Worker Thread)介紹 Worker Thread (Android訊息傳送機制)
本篇會持續示範訊息傳送機制的其他進階用法,

1.傳送訊息夾帶參數

這是基本建立訊息並使用Handler傳送訊息的用法

    private void sendMessageToWorkerHandler()
    {
        Handler handler = mCustomizeWorkerThread.getHandler();
        if (handler != null) {
            Message message = handler.obtainMessage(2);
            handler.sendMessage(message);
        }
    }

傳送訊息可以夾帶參數,讓Handler在處理訊息(handleMessage方法)時,可根據參數作更多處理 e.g.,
夾帶簡單整數型態arg1和arg2

    private void sendMessageWithArgsToWorkerHandler()
    {
        Handler handler = mCustomizeWorkerThread.getHandler();
        if (handler != null) {
            Message message = Message.obtain();
            message.what = 3;
            message.arg1 = 10;
            message.arg2 = 100;
            handler.sendMessage(message);
        }
    }

另外還可以夾帶自定義類別的物件,以下為自定義類別,為了簡單示範,只持有一個String和一個Int類別成員

package com.foxx.threads;
public class CustomizeMessageObject
{
    private String mName = "";
    private int mType;
    public CustomizeMessageObject(String name, int type){
        mName = name;
        mType = type;
    }
    public String getName()
    {
        return mName;
    }
    public void setName(String name)
    {
        mName = name;
    }
    public int getType()
    {
        return mType;
    }
    public void setType(int type)
    {
        mType = type;
    }
}

夾帶自定義物件的方式

    private void sendMessageWithObjectToWorkerHandler()
    {
        Handler handler = mCustomizeWorkerThread.getHandler();
        if (handler != null) {
            Message message = Message.obtain();
            message.what = 4;
            message.obj = new CustomizeMessageObject("Foxx", 123);
            handler.sendMessage(message);
        }
    }

修改在Activity發送訊息的部份,點擊按鈕會依序發送3個訊息

    @Override
    public void onClick(View view)
    {
        int uiId = view.getId();
        switch (uiId) {
            case R.id.startThread:
                sendMessageToWorkerHandler();
                sendMessageWithArgsToWorkerHandler();
                sendMessageWithObjectToWorkerHandler();
                break;
        }
    }

修改CustomizeWorkerHandler處理訊息的部份,針對message.what的數值作判斷

    public void handleMessage(Message message)
    {
        switch (message.what) {
            case 2:
                String result = HttpPageData.getPageData("https://www.google.com.tw/");
                setResultToMainActivity(result);
                break;
            case 3:
                Log.d(TAG, "message.arg1:" + message.arg1);
                Log.d(TAG, "message.arg2:" + message.arg2);
                break;
            case 4:
                CustomizeMessageObject messageObject = (CustomizeMessageObject) message.obj;
                Log.d(TAG, "MessageObject.name:" + messageObject.getName());
                Log.d(TAG, "MessageObject.type:" + messageObject.getType());
                break;
        }
    }

以下是Logcat的輸出

06-12 11:34:01.463: D/WorkerHandler(16289): message.arg1:10
06-12 11:34:01.463: D/WorkerHandler(16289): message.arg2:100
06-12 11:34:01.463: D/WorkerHandler(16289): MessageObject.name:Foxx
06-12 11:34:01.463: D/WorkerHandler(16289): MessageObject.type:123

 

2.提醒使用者訊息處理

假設處理訊息時並沒有提醒使用者,會讓使用者誤判點擊按鍵卻沒有反應造成使用者多次點擊,為了避免這種情況,最好在送出訊息時出現提示。
提示可由Dialog或是ProgressDialog組成。
首先在MainActivity加入並建立提示元件,提供外部存取的方法

public class MainActivity extends ActionBarActivity implements OnClickListener
{
    ...
    private AlertDialog mProcessingMessageDialog;
    private void initUIComponents()
    {
        initProcessingMessageDialog();
        ...
    }
    private void initProcessingMessageDialog()
    {
        mProcessingMessageDialog = new AlertDialog.Builder(this).create();
        mProcessingMessageDialog.setTitle("Processing...");
        mProcessingMessageDialog.setCanceledOnTouchOutside(false);
    }
    public AlertDialog getProcessingMessageDialog(){
        return mProcessingMessageDialog;
    }

接著在CustomizeWorkerHandler加入操作和呼叫點
第5行當該Handler收到從Looper傳送過來的訊息,立即顯示ProcessingMessageDialog
第21行當該Handler處理完Message,關閉ProcessingMessageDialog

class CustomizeWorkerHandler extends Handler
{
    public void handleMessage(Message message)
    {
        showProcessingDialog();
        switch (message.what) {
            case 2:
                String result = HttpPageData.getPageData("https://www.google.com.tw/");
                setResultToMainActivity(result);
                break;
            case 3:
                Log.d(TAG, "message.arg1:" + message.arg1);
                Log.d(TAG, "message.arg2:" + message.arg2);
                break;
            case 4:
                CustomizeMessageObject messageObject = (CustomizeMessageObject) message.obj;
                Log.d(TAG, "MessageObject.name:" + messageObject.getName());
                Log.d(TAG, "MessageObject.type:" + messageObject.getType());
                break;
        }
        closeProcessingDialog();
    }
    private void showProcessingDialog()
    {
        mMainActivity.runOnUiThread(new Runnable() {
            @Override
            public void run()
            {
                mMainActivity.getProcessingMessageDialog().show();
            }
        });
    }
    private void closeProcessingDialog()
    {
        mMainActivity.runOnUiThread(new Runnable() {
            @Override
            public void run()
            {
                mMainActivity.getProcessingMessageDialog().dismiss();
            }
        });
    }
}

 

3.雙重Handler溝通

CustomizeWorkerHandler 的 showProcessingDialog 和 closeProcessingDialog方法其實是為了調用UI Thread的UI元件。
如果把處理UI Thread的內容集中寫在CustomizeWorkerHandler,會調用多次的 mMainActivity.runOnUiThread方法,
導致CustomizeWorkerHandler類別的冗長,其實可以在UI Thread中建立另一個UI Handler,當CustomizeWorkerHandler有結果產生時
藉由UI Handler發送訊息到UI Thread,訊息包含處理完的結果,把結果的處理轉回到 UI Thread中。
先新增UIHandler類別並建立實體,提供存取方法,把結果的處理放在handleMessage方法中,順便修正Magic Number

public class MainActivity extends ActionBarActivity implements OnClickListener
{
    ...
    public static final int GET_PAGE_DATA = 0;
    public static final int SEND_MESSAGE_ARGS = 1;
    public static final int SEND_MESSAGE_OBJECT = 2;
    private UIHandler mHandler;
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        ...
        initUIHandler();
        ...
    }
    private void initUIHandler()
    {
        mHandler = new UIHandler();
    }
    public UIHandler getHandler()
    {
        return mHandler;
    }
    class UIHandler extends Handler
    {
        @Override
        public void handleMessage(Message msg)
        {
            switch (msg.what) {
                case GET_PAGE_DATA:
                    String result = (String) msg.obj;
                    mResultTextView.setText(result);
                    break;
                case SEND_MESSAGE_ARGS:
                    mResultTextView.setText(""+msg.arg1+msg.arg2);
                    break;
                case SEND_MESSAGE_OBJECT:
                    CustomizeMessageObject messageObject = (CustomizeMessageObject) msg.obj;
                    mResultTextView.setText(""+messageObject.getName()+messageObject.getType());
                    break;
            }
            mProcessingMessageDialog.dismiss();
        }
    }

修改CustomizeWorkerHandler在handlMessage方法取得UI Handler,並使用UI Handler發送包含結果的訊息,

class CustomizeWorkerHandler extends Handler
{
    ...
    @Override
    public void handleMessage(Message message)
    {
        Message messageToUIHandler = mMainActivity.getHandler().obtainMessage(message.what);
        switch (message.what) {
            case MainActivity.GET_PAGE_DATA:
                messageToUIHandler.obj = HttpPageData.getPageData("https://www.google.com.tw/");
                break;
            case MainActivity.SEND_MESSAGE_ARGS:
                messageToUIHandler.arg1 = message.arg1;
                messageToUIHandler.arg2 = message.arg2;
                break;
            case MainActivity.SEND_MESSAGE_OBJECT:
                CustomizeMessageObject messageObject = (CustomizeMessageObject) message.obj;
                messageToUIHandler.obj = messageObject;
                break;
        }
        mMainActivity.getHandler().sendMessage(messageToUIHandler);
    }
}

如此就能在UI Thread中處理結果。
 

分類
Android Uncategorized

Android Looper + Handler + Message + MessageQueue

本篇紀錄如何在Android中使用 Handler + Looper + Message + Queue。
為了讓App保持快速的回應性(避免產生ANR),長期任務不可執行在 UI thread上,這些長期任務包括,網路存取,讀寫檔案,資料庫存取,圖形處理等等。
在Android中使用Thread和在一般的Java中使用方式相當類似,但有一些同步化問題必須去解決,如 Race Condition, Blocking等等
為了方便解決這些問題,Android定義訊息傳送機制來確保訊息傳送的正確性以及安全性,Android訊息傳送機制包含4個角色,各為Looper,Handler,Message,MessageQueue。
 
Message :
訊息,由一組描述和一些資料物件所組成,可藉由Handler傳送到MessageQueue中,並由Looper取出再回傳給同一個Handler,當需要傳遞參數時,可夾帶在Message中。
建立Message的方式盡量使用Message.obtain(), 或是Handler.obtainMessage(),因為訊息會被放到緩衝池以重複使用。
 
MessageQueue :
訊息佇列,透過Looper建立,內容則是訊息組成,結構為無限制的連結串列,一個Looper只能有一個MessageQueue,
訊息會藉由Handler插入,並由Looper取出傳送到相對應的Handler。
 
Looper :
管理MessageQueue,會將存放於MessageQueue中的訊息依序送出到Handler上,讓Handler作進一步的處理,
Handler必須和Looper連結,否則無法傳送或接收訊息,而Thread也必須和Looper連結,才能讓MessageQueue和Thread相關連。
簡單的說Looper是Thread和MessageQueue的協調者。一個Thread只能有一個Looper。
 
Handler :
訊息處理者,在Android訊息傳送機制中,Handler是最常使用的類別,Handler可建立訊息,插入訊息到訊息佇列中,處理訊息。
一個Looper可以有多個Handler,Looper會自動將Message傳送給對應的Handler。
 
藉由以下的範例來說明彼此之間的互動,範例會盡量少用匿名內部類別來實作,而是把各個元件拆開為單一類別,比較容易了解
範例只有一個button和一個textview組成,當點擊button時,會去擷取 https://www.google.com.tw/ 的網頁資料
並顯示到textview上。
 
首先是 MainActivity,因為網路存取是長期任務,且android在Honeycomb SDK新增了NetworkOnMainThreadException,
若在Main thread(UI thread)進行網路操作,會丟出Exception。所以把網路操作放到CustomizeWorkerThread中執行,
第18行建立 mCustomizeWorkerThread 物件
第38行建立CustomizeWorkerThread物件並連結到參考,傳入this是為了把取得的網頁資料設定回MainActivity的textview
第39行呼叫start()方法以啟動mCustomizeWorkerThread的run方法。
第48行當按鈕按下時,呼叫sendMessageToWorkerHandler方法,該方法會建立message插入到MessageQueue
第55行取得mCustomizeWorkerThread的Handler,
第56行建立Message物件,其中數字2為Message的what欄位數值,當Handler處理Message時可以依此數值判斷行為。
第57行利用Handler將Message物件插入到MessageQueue中
第69行當activity結束時也告知Looper停止處理訊息

package com.foxx.threads;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends ActionBarActivity implements OnClickListener
{
    private static final String TAG = "MainActivity";
    private Button mStartThreadButton;
    private TextView mResultTextView;
    private CustomizeWorkerThread mCustomizeWorkerThread;
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initUIComponents();
        startWorkerThread();
    }
    private void initUIComponents()
    {
        mStartThreadButton = (Button) findViewById(R.id.startThread);
        mStartThreadButton.setOnClickListener(this);
        mResultTextView = (TextView) findViewById(R.id.textview);
    }
    private void startWorkerThread()
    {
        mCustomizeWorkerThread = new CustomizeWorkerThread(this);
        mCustomizeWorkerThread.start();
    }
    @Override
    public void onClick(View view)
    {
        int uiId = view.getId();
        switch (uiId) {
            case R.id.startThread:
                sendMessageToWorkerHandler();
                break;
        }
    }
    private void sendMessageToWorkerHandler()
    {
        Handler handler = mCustomizeWorkerThread.getHandler();
        if(handler != null){
            Message message = handler.obtainMessage(2);
            handler.sendMessage(message);
        }
    }
    public TextView getResultTextView()
    {
        return mResultTextView;
    }
    @Override
    protected void onDestroy()
    {
        super.onDestroy();
        mCustomizeWorkerThread.getHandler().getLooper().quit();
    }
}

 
以下為CustomizeWorkerThread的內容,
第10行為了取代匿名內部類別,另外建立了CustomizeWorkerHandler,該Handler負責處理從Looper傳送過來的Message
第18行的run方法相當重要,在run方法中連結了Looper,Thread,Handler三者。
第20行的Looper.prepare方法會建立Looper以及在該Looper中的MessageQueue,並連結到當前的Thread(CustomizeWorkerThread)
第21行建立CustomizeWorkerHandler物件並連結Looper
第22行Looper開始循序處理MessageQueue,為無限迴圈。當Message插入到MessageQueue中,Looper就會擷取該Message並發送到相關的Handler。
第25行回傳Handler讓外部可以藉由取得的Handler插入訊息
第31行即為CustomizeWorkerHandler,繼承 Handler
第41行必須複寫HandleMessage方法,當Looper傳送Message時,該方法即會被呼叫(callback)
第43行根據Message的what數值判斷行為
第45行就是網路存取的行為,HttpPageData.getPageData 會回傳google的網頁資料
第46行把取得的網頁資料設定到MainActivity的textview中

package com.foxx.threads;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
public class CustomizeWorkerThread extends Thread
{
    private static final String TAG = "WorkerThread";
    private CustomizeWorkerHandler mHandler;
    private MainActivity mMainActivity;
    public CustomizeWorkerThread(MainActivity activity) {
        mMainActivity = activity;
    }
    @Override
    public void run()
    {
        Looper.prepare();
        mHandler = new CustomizeWorkerHandler(mMainActivity);
        Looper.loop();
    }
    public CustomizeWorkerHandler getHandler()
    {
        return mHandler;
    }
}
class CustomizeWorkerHandler extends Handler
{
    private static final String TAG = "WorkerHandler";
    private MainActivity mMainActivity;
    public CustomizeWorkerHandler(MainActivity activity) {
        mMainActivity = activity;
    }
    public void handleMessage(Message message)
    {
        switch (message.what) {
            case 2:
                String result = HttpPageData.getPageData("https://www.google.com.tw/");
                setResultToMainActivity(result);
                break;
        }
    }
    private void setResultToMainActivity(final String result)
    {
        mMainActivity.runOnUiThread(new Runnable() {
            @Override
            public void run()
            {
                mMainActivity.getResultTextView().setText(result);
            }
        });
    }
}

 
接著簡單描述整個訊息傳送機制的互動過程,

建立訊息傳送機制

在 Activity 中會持有CustomWorkThread的類別成員(mCustomizeWorkerThread),
並在onCreate方法中建立實體,然後呼叫start方法,start方法會啟動CustomizeWorkerThread中的run方法,
該run方法會將worker thread,Looper, Handler 三者連結起來,到目前為止是整個傳遞訊息機制的建立,

發送訊息

在Activity中為了發送訊息到MessageQueue中,必須先建立訊息再藉由取得mCustomerWorkThread的Handler將訊息發送到MessageQueue中。

處理訊息

當訊息已經被發送到MessageQueue中,Looper便會自動擷取訊息,並將訊息發送到相對應的Handler,
相對應的意思為哪個Handler發送訊息,哪個Handler就會接收到訊息。
Handler藉由handleMessage方法來處理訊息。


以下為HttpPageData.java

package com.foxx.threads;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
public class HttpPageData
{
    /**
     * 取得網頁資料
     * @return : 回傳string型態的網頁資料
     */
    public static String getPageData(String httpUrl){
        java.net.CookieManager cm = new java.net.CookieManager();
        java.net.CookieHandler.setDefault(cm);
        URL u = null;
        InputStream in = null;
        InputStreamReader r = null;
        BufferedReader br = null;
        StringBuffer message = null;
        try {
           u = new URL(httpUrl);
           in = u.openStream();
           r = new InputStreamReader(in, "BIG5");//UTF-8
           br = new BufferedReader(r);
           String tempstr = null;
           message = new StringBuffer();
           while ((tempstr = br.readLine()) != null) {
               message.append(tempstr);
           }
        } catch (Exception e) {
           e.getStackTrace();
           System.out.println(e.getMessage());
        } finally {
           try {
              u = null;
              in.close();
              r.close();
              br.close();
           } catch (Exception e) {
           }
    }
        return message.toString();
    }
}

 

分類
Android Uncategorized

Set unban ip in fail2ban

How to set unban ip in fail2ban??

 
1. Edit /etc/fail2ban/jail.local and find ignoreip

[DEFAULT]
# "ignoreip" can be an IP address, a CIDR mask or a DNS host
ignoreip = 127.0.0.1/8 xxx.xxx.xxx.xxx

 
2. Input your unban ip as xxx.xxx.xxx.xxx
 
Note:
1. Remember use space to identify different ip
2. fail2ban commands
 

分類
Android Uncategorized

jenkins build error : error while writing Main: Main.class (Permission denied)

Description:

Build java project in jenkins and show error message

[javac] /path_of_class.java:1: error while writing Main: /path_of_class.class (Permission denied)
[javac] public class Main
[javac]        ^
[javac] 1 error

 

Root caused:

permission of directory is not enough
 

Solution:

1.
Open Terminal and move to parent of director which at java project
2.
sudo chmod 777 director_name
 

分類
Android Uncategorized

如何簡單的從TextView, EditView取得數值

通常輸入數值都會使用EditText等等的文字框當作對象, 如何才能很快速的從這些view取得數值呢??
使用 getText().toString() 加上各個數值類別(Integer, Double, Float, Long, Byte, Short)的ParseXXX() 最快 如下

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class GetValueFromTextView extends Activity
{
    private static final int PERCENT = 100;
    private static final int MONTH_COUNT = 12;
    private EditText mInput_lend;
    private EditText mInputRate;
    private EditText mInputNumber;
    private Button mSubmit;
    private TextView mAmount;
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        initComponents();
    }
    private void initComponents()
    {
        mInput_lend = (EditText) findViewById(R.id.input_lend);
        mInputNumber = (EditText) findViewById(R.id.input_number);
        mInputRate = (EditText) findViewById(R.id.input_rate);
        mSubmit = (Button) findViewById(R.id.submit);
        mSubmit.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v)
            {
                mAmount.setText("" + countAmount());
            }
        });
        mAmount = (TextView) findViewById(R.id.amount);
    }
    private double countAmount()
    {
        int lend = Integer.parseInt(mInput_lend.getText().toString());
        double rateOfMonth = Double.parseDouble(mInputRate.getText().toString()) / MONTH_COUNT
                / PERCENT;
        int number = Integer.parseInt(mInputNumber.getText().toString());
        return lend * (Math.pow(rateOfMonth + 1, number));
    }
}

重點在 countAmount 方法中, 以下列方式取得string並轉換成數字

int lend = Integer.parseInt(mInput_lend.getText().toString());
double rateOfMonth = Double.parseDouble(mInputRate.getText().toString()) / MONTH_COUNT / PERCENT;
int number = Integer.parseInt(mInputNumber.getText().toString());

但要小心轉型溢位的問題
main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
    <TextView
        android:id="@+id/main_tv_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/title"/>
    <TextView
        android:id="@+id/main_tv_lendHint"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/lend"/>
    <EditText
        android:id="@+id/input_lend"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:inputType="number"/>
    <TextView
        android:id="@+id/main_tv_rateHint"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/rate"/>
    <EditText
        android:id="@+id/input_rate"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:inputType="numberDecimal"/>
    <TextView
        android:id="@+id/main_tv_number"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/rate"/>
    <EditText
        android:id="@+id/input_number"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:inputType="number"/>
    <Button
        android:id="@+id/submit"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/btn"/>
    <TextView
        android:id="@+id/amount"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</LinearLayout>

以及 strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">Count Round-amount savings</string>
    <string name="title">Round-amount savings:</string>
    <string name="lend">capital(NT$):</string>
    <string name="rate">rate of year(%):</string>
    <string name="number">number of periods:</string>
    <string name="btn">count</string>
</resources>

 

分類
Android Uncategorized

取得 wifi scan ap result : ScanResult

在 android 手機中 -> 設定 -> Wi-Fi , 每隔一段時間會自動搜尋附近的wifi ap的訊息並顯示在列表上, 本篇介紹如何同步取得這些wifi ap的訊息
這些wifi ap的訊息是藉由ScanResult來顯示, ScanResult 類別相當簡單, 請參考官網
而每次搜尋wifi ap的動作都是以Broadcast的方式來達成, 所以我們只要建立一個專門來接收該動作的Broadcast Receiver 就能自動同步取得其搜尋結果(ScanResult)

package com.foxx.wifiapreceiver;
import java.util.List;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiManager;
import android.util.Log;
public class WifiApReceiver extends BroadcastReceiver
{
    private static final String TAG = "WifiApResult";
    private Activity mActivity;
    public WifiApReceiver(Activity activity) {
        mActivity = activity;
        mActivity.registerReceiver(this, new IntentFilter(
                WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
    }
    @Override
    public void onReceive(Context context, Intent intent)
    {
        getScanResults();
    }
    private void getScanResults()
    {
        List<ScanResult> scanResults = getWifiManager().getScanResults();
        Iterator<ScanResult> iter = scanResults.iterator();
        while (iter.hasNext()) {
            showAllFieldOfScanResult(iter.next());
        }
    }
    private WifiManager getWifiManager()
    {
        return (WifiManager) mActivity.getSystemService(Context.WIFI_SERVICE);
    }
    private void showAllFieldOfScanResult(ScanResult scanResult)
    {
        Log.d(TAG, "--------------------------------------");
        Log.d(TAG, "scanResult.SSID:" + scanResult.SSID);
        Log.d(TAG, "scanResult.BSSID:" + scanResult.BSSID);
        Log.d(TAG, "scanResult.capabilities:" + scanResult.capabilities);
        Log.d(TAG, "scanResult.frequency:" + scanResult.frequency);
        Log.d(TAG, "scanResult.level:" + scanResult.level);
        Log.d(TAG, "scanResult.BSSID:" + scanResult.describeContents());
    }
    public void unregisterReceiver()
    {
        try {
            mActivity.unregisterReceiver(this);
        } catch (Exception e) {
            Log.d(TAG, "execption in unregisterReceiver");
        }
    }
}

1. 在constructor註冊了receiver
2. OnReceiver 為 callback function , 只要有搜尋wifi ap的動作, OnReceiver就會被呼叫
 
在AndroidManifest.xml加入2個必要的permission

<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"></uses-permission>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"></uses-permission>

 
使用的方式相當簡單

package com.foxx.wifiapreceiver;
import android.app.Activity;
import android.os.Bundle;
public class MainActivity extends Activity
{
    private WifiApReceiver mWifiReceiver;
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initWifiReceiver();
    }
    private void initWifiReceiver(){
        mWifiReceiver = new WifiApReceiver(this);
    }
    @Override
    protected void onStop()
    {
        mWifiReceiver.unregisterReceiver();
        super.onStop();
    }
}

只要注意在onStop要unregister receiver即可
 
 
 

分類
Uncategorized

How to run Shell Script in Eclipse

Description :

Use eclipse to run shell script file
 

Step :

Open eclipse ->Run -> External tools -> External Tools Configurations… -> select Program ->
click “new launch configuration” -> select your .sh file at “Location” -> select your .sh file root directoy at “Working Directory” ->
click “Apply” -> click “Run” -> now you can see result at eclipse console

分類
Uncategorized

Run "About" sample on Alljoyn for linux version

參考官網步驟
以下為紀錄成功過程

Export path

1. Open terminal and move to root dir
Note: In my case, root dir name is alljoyn-14.12.00a-src
 
2. Terminal type

export AJ_ROOT=`pwd`

 
 
3. Terminal type

export TARGET_CPU=x86

Note: <TARGET CPU> can be either x86_64, x86, or whatever value you set for “CPU=” when running SCons.
value of TARGET CPU must matchas path, e.g.
/alljoyn-14.12.00a-src/build/linux/x86_64/  or  /alljoyn-14.12.00a-src/build/linux/x86/
 
4. Terminal type

export LD_LIBRARY_PATH=$AJ_ROOT/build/linux/$TARGET_CPU/debug/dist/cpp/lib:$LD_LIBRARY_PATH

 
 

Run the AboutService Sample App

1. In root of dir(alljoyn-14.12.00a-src), type

$AJ_ROOT/build/linux/$TARGET_CPU/debug/dist/cpp/bin/samples/AboutService

Note: some message will print on terminal e.g.

BusAttachment started.
   0.037 ****** ERROR NETWORK                   common/os/posix/Socket.cc:346 | Binding (sockfd = 59) to 0.0.0.0 9955: 98 - Address already in use: ER_OS_ERROR
BusAttachment connect succeeded. BusName :AVq3OlHG.2
AboutObj Announce Succeeded.
   0.041 ****** ERROR NETWORK                   common/os/posix/Socket.cc:346 | Binding (sockfd = 58) to 0.0.0.0 9955: 98 - Address already in use: ER_OS_ERROR
   1.863 ****** ERROR NETWORK iodisp2_0         common/os/posix/Socket.cc:463 | Shutdown socket (sockfd = 11): 107 - Transport endpoint is not connected: ER_OS_ERROR

it will show message when client connects.
 

Run the AboutClient Sample App

1. In root of dir(alljoyn-14.12.00a-src), type

$AJ_ROOT/build/linux/$TARGET_CPU/debug/dist/cpp/bin/samples/AboutClient

Note: terminal print message e.g.

BusAttachment started.
BusAttachment connect succeeded.
WhoImplements called.
*********************************************************************************
Announce signal discovered
	From bus :R8gNQJgr.2
	About version 1
	SessionPort 900
	ObjectDescription:
		/example/path
			com.example.about.feature.interface.sample
	AboutData:
		Key: AppId	01 b3 ba 14 1e 82 11 e4 86 51 d1 56 1d 5d 46 b0
		Key: DefaultLanguage	en
		Key: DeviceId	93c06771-c725-48c2-b1ff-6a2a59d445b8
		Key: ModelNumber	123456
		Key: AppName	Application
		Key: DeviceName	My Device Name
		Key: Manufacturer	Manufacturer
*********************************************************************************
...
Calling /example/path/com.example.about.feature.interface.sample
Echo method reply: ECHO Echo echo...

 
2. AboutServer will print message e.g.

...
Session Joined SessionId = 3156666061
Echo method called: ECHO Echo echo...

 

分類
Uncategorized

Build AllJoyn framework for linux version

參考官網提供基本流程
以下是紀錄成功的流程
 

1. Setup

1-1. Build tools and libs

sudo apt-get install build-essential libgtk2.0-dev libssl-dev xsltproc ia32-libs libxml2-dev

 
 
1-2. Install Python v2.6/2.7 (Python v3.0 is not compatible and will cause errors)

sudo apt-get install python

Note:測試python 2.6.5, 2.7.3皆可, 若已安裝過則省略
 
1-3. Install SCons v2.0

sudo apt-get install scons

 
1-4. OpenSSL

sudo apt-get install libssl-dev

Note:似乎和Build tools and libs 有重複
 
1-5. Download the AllJoyn Source zip and extract source. The tree should look like below. Note, extra directories may exist.

root-source-dir/
  core/
      alljoyn/
      ajtcl/
  services/
      base/
      base_tcl/

Note: download source zip 選擇alljoyn-14.12.00a-src.tar.gz 解壓縮完會產生 alljoyn-14.12.00a-src 目錄,目錄內容如下

alljoyn_c     alljoyn_java  alljoyn_objc   build_core  common        README.md   SConstruct
alljoyn_core  alljoyn_js    alljoyn_unity  build.xml   manifest.txt  README.txt  services

除了選擇alljoyn-14.12.00a-src.tar.gz 以外還可使用git clone的方式來下載 source code 參考這裡, 但未測試

2. Build Sample

2-1. 開啟終端機移動到 alljoyn-14.12.00a-src 目錄中並輸入

scons BINDINGS=cpp WS=off BT=off ICE=off

terminal會出現一連串build step, 如下

scons: Reading SConscript files ...
Building bindings: cpp
Building services:
BULLSEYE_BIN not specified
GTEST_DIR not specified skipping common unit test build
BULLSEYE_BIN not specified
GTEST_DIR not specified skipping About Service unit test build
GTEST_DIR not specified skipping alljoyn_core unit test build
scons: warning: Ignoring missing SConscript 'build/linux/x86_64/debug/obj/services/about/SConscript'
File "/home/rcserver/projects/jellyfish/alljoyn-14.12.00a-src/alljoyn_core/samples/eventaction/SConscript", line 35, in <module>
scons: done reading SConscript files.
scons: Building targets ...
Install file: "alljoyn_core/docs/README.linux.txt" as "build/linux/x86_64/debug/dist/README.txt"
...

Note: 完成後會產生build資料夾, 後續 run sample 動作都與此有關
 
最後輸入

SERVICES="about,notification,controlpanel,config,onboarding,sample_apps"