分類
Android Uncategorized

eclipse 設定 code style

1.set customize code style

Window -> Preferences -> java -> code style -> Formatter -> import -> select your own rule xml

分類
Android Java Uncategorized

工具類別的實體化限制(Prevents Utility class instantiated)

工具類別(Utility Class)類似於 java.lang.Math,該類別提供公共靜態方法給外界使用。
因此工具類別並不需要該實體的產生,但編譯器會提供給每個類別一個對外的預設建構式。
因此我們必須修改預設建構式避免外部可以實體化工具類別。

public final class ExceptionPrinter
{
    private ExceptionPrinter(){
        //Prevents utility class being instantiated
        throw new AssertionError();
    }
    public static void printError(String TAG, Exception e)
    {
        StringWriter errorMessage = new StringWriter();
        e.printStackTrace(new PrintWriter(errorMessage));
        Log.e(TAG, errorMessage.toString());
    }
}

 
 
 

分類
Android Uncategorized

get() method in AsyncTask

AsyncTask 提到了基本的用途,這篇紀錄關於 get() 的內容和實驗結果。
AsyncTask 的 get() 方法在 google develop 上的描述如下

public final Result get ()
Waits if necessary for the computation to complete, and then retrieves its result.
-------------------------------------------------------------------------------------------------------------------------
public final Result get (long timeout, TimeUnit unit)
Waits if necessary for at most the given time for the computation to complete, and then retrieves its result.

使用 get(long timeout, TimeUnit unit) 通常是用來作 timeout 的用途。
有幾個的重點如下:

1. get() 會 block UI thread,無論使用哪種方式都不可避免,因此要避免使用在 UI thread 上 。

public class TimeOutActivity extends Activity implements OnClickListener, IActivity
{
    public static final String TAG = TimeOutActivity.class.getSimpleName();
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        runTestTask();
    }
    private void runTestTask()
    {
        TestTask task = new TestTask();
        task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
        try {
            task.get(3, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }
    }
    class TestTask extends AsyncTask<Void, Void, Void>
    {
        @Override
        protected Void doInBackground(Void... params)
        {
            for(int i=0; i<10; ++i){
                Log.d(TAG,"i:"+i);
                try {
                    Thread.currentThread().sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return null;
        }
    }

第18行呼叫  task.get(3, TimeUnit.SECONDS會讓 UI thread block 3秒,
無論第16行使用 AsyncTask.THREAD_POOL_EXECUTOR 或是 AsyncTask.SERIAL_EXECUTOR 都會讓 UI Thread block。
 

2. get() 作為 Timeout 用途可以用在非 UI thread 上,但要小心執行 AsyncTask 的參數選擇

(AsyncTask.THREAD_POOL_EXECUTOR or AsyncTask.SERIAL_EXECUTOR)

public class TimeOutActivity extends Activity implements OnClickListener, IActivity
{
    public static final String TAG = TimeOutActivity.class.getSimpleName();
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        executeCountTimeTask();
    }
    class TestTask extends AsyncTask<Void, Void, Void>
    {
        @Override
        protected Void doInBackground(Void... params)
        {
            executeCountTimeTask();
            return null;
        }
        private void executeCountTimeTask()
        {
            SimpleTask simpleTask = new SimpleTask();
            simpleTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
            try {
                simpleTask.get(3, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            } catch (TimeoutException e) {
                e.printStackTrace();
            }
        }
    }
    class SimpleTask extends AsyncTask<Void, Void, Void>
    {
        @Override
        protected Void doInBackground(Void... params)
        {
            for (int i = 0; i < 10; ++i) {
                Log.d(TAG, "i:" + i);
                try {
                    Thread.currentThread().sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return null;
        }
    }
}

第14和第39行分別不同的 AsyncTask,第14行的 TestTask為外層的 Task,其 executeCountTimeTask() 會再建立另一個 task(SimpleTask)
因為第26行的參數為 AsyncTask.THREAD_POOL_EXECUTOR,代表 Task 上的任務是可以被並行執行。
也就是說雖然第28行呼叫了 get() 方法,但因為 Task 任務是並行的,因此 get() 並不會 block 第26行的動作。
LogCat如下

 i:0
 i:1
 i:2
 java.util.concurrent.TimeoutException
 i:3
 i:4
 i:5
 i:6
 i:7
 i:8
 i:9

可以看到 get() 和 executeOnExecutor() 並不會互相干擾,TimeException 的訊息也會順利發出。
另外若是executeOnExecutor的動作在 Timeout 的時間(3 seconds)內完成,TimeException不會發出。
若是將第26行的參數改為 AsyncTask.SERIAL_EXECUTOR,代表 Task 上的任務是循序執行。

simpleTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);

也就是說 get() 會先執行,等到執行完畢才會進行 executeOnExecutor() 的動作。
LogCat如下

//wait 3 seconds
java.util.concurrent.TimeoutException
 i:0
 i:1
 i:2
 i:3
 i:4
 i:5
 i:6
 i:7
 i:8
 i:9

等待3秒後才會丟出 TimeoutException ,接著才會執行 executeOnExecutor()
 
其實使用這種 double asynctask 的作法未竟完善,
1. new AsyncTask() 的動作建議要在 UI thread 上,但 SimpleTask 的建構式卻是在 TestTask中建立。
3. 若要做 Timeout 的機制,其實可以在 SimpleTask 的 onPreExecute() 建立另一個計算時間的機制(CountDownTimer),如下

    class SimpleTask extends AsyncTask<Void, Void, Void>
    {
        @Override
        protected Void doInBackground(Void... params)
        {
            for (int i = 0; i < 10; ++i) {
                Log.d(TAG, "i:" + i);
                try {
                    Thread.currentThread().sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return null;
        }
        @Override
        protected void onPreExecute()
        {
            super.onPreExecute();
            new CountDownTimer(3000, 1000) {
                @Override
                public void onTick(long millisUntilFinished)
                {
                }
                @Override
                public void onFinish()
                {
                    Log.d(TAG,"timeout !!!");
                }
            };
        }
    }

一樣可以達到 Timeout 作用。
 

分類
Android Uncategorized

Android 存取 全域資料 (access global data in android application)

建立 Application 的子類別,並將其設計成類似於 Singleton Pattern
全域資料放到這個子類別中,並提供存取方法。

package com.foxx.utility;
import android.app.Application;
public class ApplicationEx extends Application
{
    private static ApplicationEx sUniqueInstance;
    public static ApplicationEx getInstance()
    {
        return sUniqueInstance;
    }
    @Override
    public void onCreate()
    {
        super.onCreate();
        sUniqueInstance = this;
        sUniqueInstance.initData();
    }
    private void initData()
    {
        //init some data here
    }
}

第14行複寫 Application 的 onCreate 方法,該方法的 comment 敘述了幾個重點

/**
     * Called when the application is starting, before any activity, service,
     * or receiver objects (excluding content providers) have been created.
     * Implementations should be as quick as possible (for example using
     * lazy initialization of state) since the time spent in this function
     * directly impacts the performance of starting the first activity,
     * service, or receiver in a process.
     * If you override this method, be sure to call super.onCreate().
     */
    public void onCreate() {
    }

1.onCreate()會在所有 activity , service , receiver 建立之前被呼叫。

2.onCreate()會影響啟動第一個元件的時間。

3.onCreate()記得呼叫 super.onCreate()。

4.記得在AndroidManifest.xml 指定使用 Application name

<application
    ...
    android:name="com.foxx.utility.ApplicationEx"
    ...
>

顯而易見的好處是當你需要 context 的實體完成一些工作(如 getResource()),可以直接呼叫 getInstance().getResources() 來完成。
不需要 Context 當作參數傳來傳去。
使用範例

ApplicationEx.getInstance().getResources();
ApplicationEx.getInstance().getFilesDir();

 
Note 1:
ApplicationEx 並不需要私有化其建構式,e.g.

private ApplicationEx(){
...
}

如果多做這一步反而會產生 IllegalAccessException: access to constructor not allowed 的異常。
因為使用 static 保證只有一個實體被建立,因此這種方法可以確保正確同步化。
Note 2:
經測試無法當做建立Dialog的參數用,e.g.

new Dialog(ApplicationEx.getInstance());
new Dialog(ApplicationEx.getInstance().getApplicationContext());

Dialog建構式的參數必須為 Activity 的 Context
 

分類
Android Uncategorized

Android emulator Management

1.查詢可使用的android version
開啟Terminal並使用 android sdk 中的tools/android

android list targets

記下想使用的id即可
2.建立emulator,建立名稱為 id_14_android_19 的emulator,其使用的id為14

android create avd -n id_14_android_19 -t 14

3.啟動emulator,若os為32bit , 需要加上 -force-32bit

emulator -avd id_14_android_19

4.查詢系統中的emulator,emulator 的config 都會留存在~/.android/avd/ 中,只要查詢該資料夾即可知道有哪些emulator。

ls ~/.android/avd/

5.刪除emulator

android delete avd -n nameofemulator

 
詳細內容可參考:
http://developer.android.com/intl/zh-tw/tools/devices/managing-avds-cmdline.html
http://developer.android.com/intl/zh-tw/tools/devices/emulator.html
http://developer.android.com/intl/zh-tw/tools/help/emulator.html
 
 

分類
Android git 使用紀錄 Jenkins Uncategorized

合併 Jenkins 和 GitHub for Andriod Project

Jenkins 除了可以和 Local 端專案整合,也可和github一起工作。
這篇紀錄如何整合jenkins和github。
1.先在github上建立空專案。
2.在Build Server(jenkins),先 git clone github下來,並建立 android project,注意要使用 ant 成功建立專案(ant release or ant debug),
注意,不要加入local.properties 和 ant.properties,可以加入.gitignore來忽略。
完成後git push到github上
3.在jenkins建立專案並設定git path為github的路徑。
4.若有需要先在github上merge第3步驟的push commit,完成後jenkins 可以build成功。
其他詳細可參考 https://wiki.jenkins-ci.org/display/JENKINS/Building+an+Android+app+and+test+project

分類
Android Uncategorized

Android AsyncTask

相較於 HandlerThreadAndroid Worker Thread(Message+Handler+Looper) 這類比較底層的實作,確實可以掌控訊息傳遞機制的流程。
但在目的比較簡單的情況下,Android也有提供使用上較為方便的類別,AsyncTask。
事實上 AsyncTask 較廣為人知,在UI Thread 上的工作幾乎都可以勝任,其source code註解也有提到

AsyncTask enables proper and easy use of the UI thread.
This class allows to perform background operations and publish results on the UI thread
without having to manipulate threads and/or handlers

簡單的說 AsyncTask 讓你不需要操作 thread 和 handler 就能完成 UI thread 的工作。
但在註解第2段也有提到AsyncTask比較適合短時間的操作(幾秒鐘),長時間的操作請考慮使用 java.util.concurrent 套件。

AsyncTask is designed to be a helper class around Thread and Handler and does not constitute a generic threading framework.
AsyncTasks should ideally be used for short operations (a few seconds at the most.)
If you need to keep threads running for long periods of time, it is highly recommended youuse the various APIs provided by the java.util.concurrent package such as Executor, ThreadPoolExecutor and FutureTask.

首先 AsyncTask 提供4個方法可以複寫,每個方法對應不同的目的。

onPreExecute() :

大多數的初始化工作都可以寫在這裡。除了建構式之外,onPreExecute是第一個被呼叫的。

doInBackground() :

需時操作,可以有參數以及返回值,參數型態必須在定義類別時決定,並在呼叫execute方法時傳入。
返回值也必須在定義類別時決定型態,該型態會相等於onPostExecute()的參數型態。

onProgressUpdate() :

可搭配呼叫 publishProgress方法,由於在doInBackground中不可對 UI thread 的 UI widget 作動作(會產生RuntimeException),若有需要在每次循環中更新UI,
可寫在onProgressUpdate()中。

onCancelled() :

當AsyncTask運行時可以中斷該AsyncTask,而中斷AsyncTask的後續處理,就寫在本方法中。
可以有參數,該參數型態也必須和doInBackground方法的參數型態相同。

onPostExecuted() :

doInBackground()完成的後續處理,當doInBackground完成後會回傳數值,其數值會被傳入onPostExecuted()方法中。
這裡通常放置需時工作完成後,更新UI的動作。

其中 onPreExecute , onPostExecute , onCancelled , onProgressUpdate 都是 run on ui thread ,

也就是說可以在這4個方法中調整 UI 。

首先是外部呼叫範例

package com.foxx.basic.asyctask;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import com.example.asyctask.R;
public class MainActivity extends Activity implements OnClickListener
{
    public static final String TAG = "asyctask";
    private TextView mTextView;
    private Button mStartButton;
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = (TextView) findViewById(R.id.timeshower);
        mStartButton = (Button) findViewById(R.id.startbutton);
        mStartButton.setOnClickListener(this);
        mStartButton.setText("start a count time dialog");
    }
    @Override
    public void onClick(View view)
    {
        int resId = view.getId();
        switch (resId) {
            case R.id.startbutton:
                executeCountTimerTask();
                break;
        }
    }
    private void executeCountTimeTask()
    {
        SimpleCountTimer countTimer = new SimpleCountTimer(this);
        countTimer.execute(10d);
    }
    public TextView getTextView(){
        return mTextView;
    }
}

只需要注意第40行的 executeCountTimerTask 方法,該方法會在按鈕被按下時被呼叫,其中 10d 為計算秒數的結束時間。
以下是SimpleCountTimer.java

package com.foxx.basic.asyctask;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.os.AsyncTask;
import android.util.Log;
public class SimpleCountTimer extends AsyncTask<Double, Integer, String>
{
    protected static final String TAG = "SimpleCountTimer";
    private static final long TIME_SLEEP_INTERVAL = 1000;
    private int mCountTime;
    private ProgressDialog mProgressDialog;
    private MainActivity mMainActivity;
    private Double mCountMax;
    public SimpleCountTimer(MainActivity activity) {
        Log.d(TAG, "in consturctor");
        mMainActivity = activity;
    }
    @Override
    protected String doInBackground(Double... params)
    {
        Log.d(TAG, "in doInBackground");
        mCountMax = params[0];
        while (!isCancelled() && mCountTime < mCountMax) {
            mCountTime++;
            publishProgress(mCountTime);
            threadSleep();
        }
        return "" + mCountTime;
    }
    private void threadSleep()
    {
        try {
            Thread.sleep(TIME_SLEEP_INTERVAL);
        } catch (InterruptedException e) {
            Log.d(TAG, "InterruptedException from threadSleep");
        }
    }
    @Override
    protected void onPreExecute()
    {
        Log.d(TAG, "onPreExecute");
        super.onPreExecute();
        mProgressDialog = new ProgressDialog(mMainActivity);
        mProgressDialog.setCanceledOnTouchOutside(false);
        mProgressDialog.setButton(DialogInterface.BUTTON_POSITIVE, "Cancel Count",
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which)
                    {
                        mProgressDialog.dismiss();
                        cancel(true);
                    }
                });
        mProgressDialog.show();
    }
    @Override
    protected void onProgressUpdate(Integer... values)
    {
        Log.d(TAG, "onProgressUpdate");
        mProgressDialog.setMessage("count time :" + values[0]);
    }
    @Override
    protected void onCancelled(String result)
    {
        Log.d(TAG, "onCancelled");
        mMainActivity.getTextView().setText("AsyncTask canceled!! count time at:" + result);
    }
    @Override
    protected void onPostExecute(String result)
    {
        Log.d(TAG, "onPostExecute");
        mProgressDialog.dismiss();
        mMainActivity.getTextView().setText("final time is: " + result);
    }
}

SimpleCountTimer 的工作相當簡單,從外部被呼叫以後,會產生一個Progress Dialog,並在其中顯示計算秒數,計算的時間取決於外部呼叫時傳入的參數而定。
第8行 extends AsyncTask<Double, Integer, String> , 如果沒有傳入參數的需要,可以設定為Void,以下說明各數值影響的方法

Double 為外部呼叫 execute 方法時傳入的參數型態定義,以及doInackground的參數

    private void executeCountTimeTask()
    {
        ...
        countTimer.execute(10d);
    }

此數值會被傳入到doInBackground方法中。

    @Override
    protected String doInBackground(Double... params)
    {
      ...
    }

 

Integer 會影響publishProgress傳入的參數,

    private int mCountTime;
    @Override
    protected String doInBackground(Double... params)
    {
        ...
        publishProgress(mCountTime);
        ...
    }

也會影響onProgressUpdate方法的參數

    @Override
    protected void onProgressUpdate(Integer... values)
    {
        ...
    }

 

String 影響 doInBackground 的回傳值,

    @Override
    protected String doInBackground(Double... params)
    {
        ...
    }

以及onCancelled 和 onPostExecute 的參數

    @Override
    protected void onCancelled(String result)
    {
     ...
    }
    @Override
    protected void onPostExecute(String result)
    {
     ...
    }

接著看看個別方法中的內容
第23行 doInBackground 取得外部傳入的數值,並設定為結束值,在while迴圈中遞增計算秒數,當秒數超過最大值或是有中斷發生,即會結束迴圈。
第45行 onPreExecute 方法初始化了 mProgressDialog,並在 mProgressDialog 設定了一個 Button,
button 被按下會中斷 doInBackground 方法,當 cancell(false) , 會讓 isCancelled() 回傳 true,但這種作法對被 block 住的 doInBackground 是沒有效果的 ,
為了保證被中斷,這裡使用 cancell(true),當 cancell(true),除了讓 isCancelled() 回傳 true 以外,還會丟出 InterruptedException ,
InterruptedException 會被 threadSleep 捕捉, 捕捉之後便會呼叫 onCancelled 方法。
第65行更新數值並顯示在 mProgressDialog中
第72行為中斷後的 UI 更新動作。
第79行為完成 doInBackground方法的 UI 更新動作。
接著來看看正常完成的log內容

07-10 17:07:40.535: D/SimpleCountTimer(11457): in consturctor
07-10 17:07:40.535: D/SimpleCountTimer(11457): onPreExecute
07-10 17:07:40.635: D/SimpleCountTimer(11457): in doInBackground
07-10 17:07:40.725: D/SimpleCountTimer(11457): onProgressUpdate
07-10 17:07:41.635: D/SimpleCountTimer(11457): onProgressUpdate
07-10 17:07:42.635: D/SimpleCountTimer(11457): onProgressUpdate
07-10 17:07:43.635: D/SimpleCountTimer(11457): onProgressUpdate
07-10 17:07:44.635: D/SimpleCountTimer(11457): onProgressUpdate
07-10 17:07:45.645: D/SimpleCountTimer(11457): onProgressUpdate
07-10 17:07:46.645: D/SimpleCountTimer(11457): onProgressUpdate
07-10 17:07:47.645: D/SimpleCountTimer(11457): onProgressUpdate
07-10 17:07:48.645: D/SimpleCountTimer(11457): onProgressUpdate
07-10 17:07:49.645: D/SimpleCountTimer(11457): onProgressUpdate
07-10 17:07:50.645: D/SimpleCountTimer(11457): onPostExecute

順序為 consturctor -> onPreExecute -> doInBackground -> onProgressUpdate(重複) -> onPostExecute
 
發生中斷的log內容

07-10 17:10:36.685: D/SimpleCountTimer(11457): in consturctor
07-10 17:10:36.685: D/SimpleCountTimer(11457): onPreExecute
07-10 17:10:36.765: D/SimpleCountTimer(11457): in doInBackground
07-10 17:10:36.855: D/SimpleCountTimer(11457): onProgressUpdate
07-10 17:10:37.765: D/SimpleCountTimer(11457): onProgressUpdate
07-10 17:10:38.765: D/SimpleCountTimer(11457): onProgressUpdate
07-10 17:10:39.765: D/SimpleCountTimer(11457): onProgressUpdate
07-10 17:10:40.765: D/SimpleCountTimer(11457): onProgressUpdate
07-10 17:10:41.775: D/SimpleCountTimer(11457): onProgressUpdate
07-10 17:10:41.775: D/SimpleCountTimer(11457): InterruptedException from threadSleep
07-10 17:10:41.805: D/SimpleCountTimer(11457): onCancelled

順序為 consturctor -> onPreExecute -> doInBackground -> onProgressUpdate(重複) -> onCancelled
 
 
 
 
 
 

分類
Android Uncategorized

Add UI Layout by java code

一般產生 Layout 的作法大多數是使用 xml 來達成,但在一些特殊情況下必須在 java code 建立 Layout。
本篇紀錄如何在 java code 中產生 Layout。
首先貼上示意圖
device-2015-07-16-131853
可以看到示意圖中主要有4個區塊,黑,藍,紅,綠,其中黑跟綠的width都佔滿屏,藍和紅的width佔滿屏的0.5。
而Height部份,黑佔0.2,其他區塊佔0.4。
以下為source code,
第18行的藉由 Display 來計算螢幕的 width 和 height 。
第38行為了在各元件中手動設定id,提供了generateID()方法,內容參考View.generateID()。
第52行createLayout()方法提供給Activity設定Layout。
第67,84,103,121行的initxxx方法,則是各個區塊的初始化內容。

import java.util.concurrent.atomic.AtomicInteger;
import android.content.Context;
import android.graphics.Color;
import android.os.Build;
import android.view.Display;
import android.view.View;
import android.view.WindowManager;
import android.widget.RelativeLayout;
import android.widget.TextView;
public class ViewCreater
{
    private static final String TAG = "ViewCreater";
    private static int mId = 0;
    private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);
    private Context mContext;
    private Display mDisplay;
    private RelativeLayout upLayout;
    private RelativeLayout mMiddleLeftLayout;
    private RelativeLayout mMiddleRightLayout;
    private RelativeLayout mDownLayout;
    public ViewCreater(Context context) {
        mContext = context;
        initDisplay();
    }
    private void initDisplay()
    {
        WindowManager manager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
        mDisplay = manager.getDefaultDisplay();
    }
    public static int generateId()
    {
        if (Build.VERSION.SDK_INT < 17) {
            for (;;) {
                final int result = sNextGeneratedId.get();
                int newValue = result + 1;
                if (newValue > 0x00FFFFFF)
                    newValue = 1;
                if (sNextGeneratedId.compareAndSet(result, newValue)) {
                    return result;
                }
            }
        } else {
            return View.generateViewId();
        }
    }
    public RelativeLayout createLayout()
    {
        RelativeLayout rootlayout = new RelativeLayout(mContext);
        rootlayout.setId(generateId());
        initUpLayout();
        initMiddleLeftLayout();
        initMiddleRightLayout();
        initDownLayout();
        rootlayout.addView(mDownLayout);
        rootlayout.addView(mMiddleLeftLayout);
        rootlayout.addView(upLayout);
        rootlayout.addView(mMiddleRightLayout);
        return rootlayout;
    }
    private void initUpLayout()
    {
        upLayout = new RelativeLayout(mContext);
        upLayout.setId(generateId());
        upLayout.setTag("upLayout");
        RelativeLayout.LayoutParams layoutParamsOfUp = new RelativeLayout.LayoutParams(
                mDisplay.getWidth() / 1, mDisplay.getHeight() / 5);
        layoutParamsOfUp.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE);
        upLayout.setLayoutParams(layoutParamsOfUp);
        TextView textViewOfUplayout = new TextView(mContext);
        textViewOfUplayout.setId(generateId());
        textViewOfUplayout.setText("upLayout");
        textViewOfUplayout.setTextSize(20);
        upLayout.addView(textViewOfUplayout);
        upLayout.setBackgroundColor(Color.BLACK);
    }
    private void initMiddleRightLayout()
    {
        mMiddleRightLayout = new RelativeLayout(mContext);
        mMiddleRightLayout.setId(generateId());
        RelativeLayout.LayoutParams layoutParamsOfMiddleRightLayout = new RelativeLayout.LayoutParams(
                mDisplay.getWidth() / 2, mDisplay.getHeight() * 2 / 5);
        TextView textViewOfMiddleRightLayout = new TextView(mContext);
        textViewOfMiddleRightLayout.setId(generateId());
        textViewOfMiddleRightLayout.setText("middleRightLayout");
        textViewOfMiddleRightLayout.setTextSize(20);
        textViewOfMiddleRightLayout.setTextColor(Color.parseColor("#ffff00"));
        mMiddleRightLayout.addView(textViewOfMiddleRightLayout);
        layoutParamsOfMiddleRightLayout.addRule(RelativeLayout.BELOW, upLayout.getId());
        layoutParamsOfMiddleRightLayout
                .addRule(RelativeLayout.RIGHT_OF, mMiddleLeftLayout.getId());
        mMiddleRightLayout.setLayoutParams(layoutParamsOfMiddleRightLayout);
        mMiddleRightLayout.setBackgroundColor(Color.RED);
    }
    private void initMiddleLeftLayout()
    {
        mMiddleLeftLayout = new RelativeLayout(mContext);
        mMiddleLeftLayout.setId(generateId());
        RelativeLayout.LayoutParams layoutParamsOfMiddleLeftLayout = new RelativeLayout.LayoutParams(
                mDisplay.getWidth() / 2, mDisplay.getHeight() * 2 / 5);
        TextView textViewOfMiddleLeftLayout = new TextView(mContext);
        textViewOfMiddleLeftLayout.setId(generateId());
        textViewOfMiddleLeftLayout.setText("middleLeftLayout");
        textViewOfMiddleLeftLayout.setTextSize(20);
        textViewOfMiddleLeftLayout.setTextColor(Color.parseColor("#ff0000"));
        mMiddleLeftLayout.addView(textViewOfMiddleLeftLayout);
        layoutParamsOfMiddleLeftLayout.addRule(RelativeLayout.BELOW, upLayout.getId());
        layoutParamsOfMiddleLeftLayout.addRule(RelativeLayout.ALIGN_LEFT, upLayout.getId());
        mMiddleLeftLayout.setLayoutParams(layoutParamsOfMiddleLeftLayout);
        mMiddleLeftLayout.setBackgroundColor(Color.BLUE);
    }
    private void initDownLayout()
    {
        mDownLayout = new RelativeLayout(mContext);
        mDownLayout.setId(generateId());
        RelativeLayout.LayoutParams layoutParamsOfDownLayout = new RelativeLayout.LayoutParams(
                mDisplay.getWidth() / 1, mDisplay.getHeight() * 2 / 5);
        layoutParamsOfDownLayout.addRule(RelativeLayout.BELOW, mMiddleLeftLayout.getId());
        TextView textViewOfDownLayout = new TextView(mContext);
        textViewOfDownLayout.setId(generateId());
        textViewOfDownLayout.setText("DownLayout");
        textViewOfDownLayout.setTextSize(20);
        textViewOfDownLayout.setTextColor(Color.parseColor("#ffffff"));
        mDownLayout.addView(textViewOfDownLayout);
        mDownLayout.setLayoutParams(layoutParamsOfDownLayout);
        mDownLayout.setBackgroundColor(Color.GREEN);
    }
}

 
有幾個必須注意的要點。

1.即使加入所有UI widget之後,還是可以調整各個UI widget的屬性。

比如說若修改createLayout方法, 其中第14,15,16行重新修改了綠色區塊的width。

public RelativeLayout createLayout()
{
        RelativeLayout rootlayout = new RelativeLayout(mContext);
        rootlayout.setId(generateId());
        initUpLayout();
        initMiddleLeftLayout();
        initMiddleRightLayout();
        initDownLayout();
        rootlayout.addView(mDownLayout);
        rootlayout.addView(mMiddleLeftLayout);
        rootlayout.addView(upLayout);
        rootlayout.addView(mMiddleRightLayout);
       RelativeLayout.LayoutParams scaleLayoutParams = new RelativeLayout.LayoutParams(mDisplay.getWidth()/2, mDisplay.getHeight()*2/5);
       scaleLayoutParams.addRule(RelativeLayout.BELOW, mMiddleLeftLayout.getId());
       mDownLayout.setLayoutParams(scaleLayoutParams);
        return rootlayout;
}

綠色區塊的width即會變為滿版的0.5。如下圖
device-2015-07-08-160007
 

2.對Parent View來說,addView的順序和各child View的相依性並沒有一定的關係。

以本範例來說,Parent View就是rootView, 而child view就是 upLayout, mMiddleLeftLayout, mMiddleRightLayout, mDownLayout。
各元件的相依性指的是以藍色區塊來說,它必須是在黑色區塊下方,且貼齊左邊,code如下

private void initMiddleLeftLayout()
{
        ...
      layoutParamsOfMiddleLeftLayout.addRule(RelativeLayout.BELOW, upLayout.getId());
      layoutParamsOfMiddleLeftLayout.addRule(RelativeLayout.ALIGN_LEFT, upLayout.getId());
        ...
}

也就是說藍色區塊在位置的相依性上必須依賴黑色區塊,沒有黑色區塊,藍色區塊就不知道自己的位置在哪。
然而位置的相依性與被加入到rootView中的順序沒有關係。對rootView而言可以先加入藍色區塊再加入黑色區塊,如下

public RelativeLayout createLayout()
{
        ...
        rootlayout.addView(mMiddleLeftLayout);
        rootlayout.addView(upLayout);
        ...
}

 

3.調整UI Widget的重點在於LayoutParams。

以initMiddleLeftLayout方法來說,
第4~5行初始化layoutParamsofMiddleLeftLayout可設定其元件所佔的width, height。
第6~7行則是指定該元件的位置屬性。

private void initMiddleLeftLayout()
{
        ...
        RelativeLayout.LayoutParams layoutParamsOfMiddleLeftLayout = new RelativeLayout.LayoutParams(
                mDisplay.getWidth() / 2, mDisplay.getHeight() * 2 / 5);
        layoutParamsOfMiddleLeftLayout.addRule(RelativeLayout.BELOW, upLayout.getId());
        layoutParamsOfMiddleLeftLayout.addRule(RelativeLayout.ALIGN_LEFT, upLayout.getId());
        mMiddleLeftLayout.setLayoutParams(layoutParamsOfMiddleLeftLayout);
        ...
}

 

分類
Android

Android Error : The specified child already has a parent. You must call removeView() on the child's parent first.

Description:

當一個child view已經被加到A parent view,若有另一個B parent view再對該child view進行add view的動作,即會產生該Error。
簡單的說child view只能屬於一個parent view。
 

Solution:

在child view加入B parent view之前,A parent view 必須先移除child view。
 

Example:

最外層的RelativeLayout為了方便操縱,給予id為root_view,內層的text_view即為child view的角色。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/root_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />
</RelativeLayout>

 
在第17行已經設定完UI Layout,也就是說根據xml的內容,mTextView已經被加到mRootParentView中。
第24行有另一個Parent View對mTextView進行addView的動作,若沒有在第21行 remove child view,即會產生錯誤。

package com.foxx.errortest;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.widget.RelativeLayout;
import android.widget.TextView;
public class MainActivity extends ActionBarActivity
{
    private TextView mTextView;
    private RelativeLayout mRootParentView;
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mRootParentView = (RelativeLayout) findViewById(R.id.root_view);
        mTextView = (TextView) findViewById(R.id.text_view);
        mRootParentView.removeView(mTextView);
        RelativeLayout anotherParentLayout = new RelativeLayout(this);
        anotherParentLayout.addView(mTextView);
    }
}

 
 

分類
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)