2018年1月9日 星期二

從 BootAnimation 探索 SurfaceFlinger (一) 世界的建構子們

前情提要

SurfaceFlinger 的架構十分龐大且複雜, 新手進入不易
我們先從 BootAnimation 的實例分析來對 SurfaceFlinger 進行初步的了解  (「・ω・)「

bootanimation 做為一個可執行檔, 是在 init.rc 裡作為一個 service 帶起來的:
    service bootanim /system/bin/bootanimation
查看bootanimation_main.cpp, 你可以發現它會察看一個 property (debug.sf.nobootanimation)來決定是否要執行 boot animation.
如果需要執行的話, 它會生成一個 BootAnimation 物件, 這就是真正運行起開機動畫的 class 了.

那麼, 我們首先從建構子開始下手!
BootAnimation::BootAnimation() : Thread(false), mZip(NULL)
{
    mSession = new SurfaceComposerClient();
}

void BootAnimation::onFirstRef() {
    status_t err = mSession->linkToComposerDeath(this);
    ALOGE_IF(err, "linkToComposerDeath failed (%s) ", strerror(-err));
    if (err == NO_ERROR) {
        run("BootAnimation", PRIORITY_DISPLAY);
    }
}

一開始生成了一個 SurfaceComposeClient 對象, 名為 mSession
可以想見的是要跟 SurfaceFlinger 溝通就是透過這個 mSession 來負責

我們看一下 SurfaceComposeClient 的建構子:
SurfaceComposerClient::SurfaceComposerClient()
    : mStatus(NO_INIT), mComposer(Composer::getInstance())
{
}

void SurfaceComposerClient::onFirstRef() {
    sp<ISurfaceComposer> sm(ComposerService::getComposerService());
    if (sm != 0) {
        sp<ISurfaceComposerClient> conn = sm->createConnection();
        if (conn != 0) {
            mClient = conn;
            mStatus = NO_ERROR;
        }
    }
}

這邊可以看到 Composer 跟 ComposerService 都是一個 singleton 的模式
雖然大家透過不同的 SurfaceComoserClient 來跟 SurfaceFlinger 溝通, 但是一次只有一個人可以使用服務

mComposer
這裡的 mComposer 並不是真正意義上去負責做 Compose 的那個 Composer, 而是比較接近讓 SurfaceComoserClient 有了一個上鎖的機制, 並且跟 ComposerService 來溝通

sm(ComposerService::getComposerSerivce())
這個 getComposerService() 會去取得 SurfaceFlinger 這個 service
(又一個前情提要, SurfaceFlinger 的服務是由 main_surfaceflinger.cpp 加入的, 它也是從 init.rc 帶起來的一個 service)

這裡呼叫了 sm->createConnection(),
在 SurfaceFlinger中會創建一個 ISurfaceComposerClient 對象回來, 並記為 mClient
sp<ISurfaceComposerClient> SurfaceFlinger::createConnection()
{
    sp<ISurfaceComposerClient> bclient;
    sp<Client> client(new Client(this));
    status_t err = client->initCheck();
    if (err == NO_ERROR) {
        bclient = client;
    }
    return bclient;
}

簡單的初始化結束後, 目前 BootAnimation 已經透過了 SurfaceComposerClient 與 SurfaceFlinger 建立起關係

接下來回到 BootAnimation
因為 onFirstRef 呼叫了 run(), 所以這邊會依序執行 readyToRun() 跟 threadLoop()

status_t BootAnimation::readyToRun() {
    // create the native surface
    sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"),
            dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565);

    ...
}

首先透過 SurfaceComposerClient 呼叫 createSurface, 並帶入了 Display 的寬高資訊
在這裡一定就是透過 mClient 對象去呼叫 SurfaceFlinger,
不過可以注意到這邊出現了一些新人物, 分別是:
handle, gbp, 以及 SurfaceControl

sp<SurfaceControl> SurfaceComposerClient::createSurface(
        const String8& name,
        uint32_t w,
        uint32_t h,
        PixelFormat format,
        uint32_t flags)
{
    sp<SurfaceControl> sur;
    if (mStatus == NO_ERROR) {
        sp<IBinder> handle;
        sp<IGraphicBufferProducer> gbp;
        status_t err = mClient->createSurface(name, w, h, format, flags,
                &handle, &gbp);
        ALOGE_IF(err, "SurfaceComposerClient::createSurface error %s", strerror(-err));
        if (err == NO_ERROR) {
            sur = new SurfaceControl(this, handle, gbp);
        }
    }
    return sur;
}

先記得有這幾位新人, 我們看一下 createSurface 做了甚麼

status_t Client::createSurface(
        const String8& name,
        uint32_t w, uint32_t h, PixelFormat format, uint32_t flags,
        sp<IBinder>* handle,
        sp<IGraphicBufferProducer>* gbp)
{
    /*
     * createSurface must be called from the GL thread so that it can
     * have access to the GL context.
     */
    // 定義一個 Message +++
    class MessageCreateLayer : public MessageBase {
        SurfaceFlinger* flinger;
        Client* client;
        sp<IBinder>* handle;
        sp<IGraphicBufferProducer>* gbp;
        status_t result;
        const String8& name;
        uint32_t w, h;
        PixelFormat format;
        uint32_t flags;
    public:
        MessageCreateLayer(SurfaceFlinger* flinger,
                const String8& name, Client* client,
                uint32_t w, uint32_t h, PixelFormat format, uint32_t flags,
                sp<IBinder>* handle,
                sp<IGraphicBufferProducer>* gbp)
            : flinger(flinger), client(client),
              handle(handle), gbp(gbp),
              name(name), w(w), h(h), format(format), flags(flags) {
        }
        status_t getResult() const { return result; }
        virtual bool handler() {
            result = flinger->createLayer(name, client, w, h, format, flags,
                    handle, gbp);
            return true;
        }
    };
    // 定義一個 Message ---

    // 生成 Message
    sp<MessageBase> msg = new MessageCreateLayer(mFlinger.get(),
            name, this, w, h, format, flags, handle, gbp);
    // 將 Message 放入 Queue, 並等待回傳
    mFlinger->postMessageSync(msg);
    return static_cast<MessageCreateLayer*>( msg.get() )->getResult();
}

這是在 SurfaceFlinger 裡面一個很經典的呼叫, 這整段可以分成
1.) 定義一個 Message, 他用來呼叫 CreateLayer, 所以是 MessageCreateLayer
2.) 生成這個 Message, 並丟入 MessageQueue 中
3.) 等待 Message 處理完 (postMessageSync), 並回傳處理完的 Result

從這個 Message 的定義來看, 你會發現輪到處理它時, 就是執行這個 Message 的 handler
在這裡就是 SurfaceFlinger::createLayer();

各位的 SAN 值, 現在感覺如何呀?
 (」・ω・)」SAN値!(/・ω・)/ピンチ!

初次看這段的同學應該會覺得東西怎麼又多又亂,
我的建議是先大略看過一次了解每個部件的關鍵是甚麼, 再回頭熟讀.

那麼, 下面繼續展開 createLayer:
status_t SurfaceFlinger::createLayer(
        const String8& name,
        const sp<Client>& client,
        uint32_t w, uint32_t h, PixelFormat format, uint32_t flags,
        sp<IBinder>* handle, sp<IGraphicBufferProducer>* gbp)
{
    status_t result = NO_ERROR;
    sp<Layer> layer;

    switch (flags & ISurfaceComposerClient::eFXSurfaceMask) {
        case ISurfaceComposerClient::eFXSurfaceNormal:
            result = createNormalLayer(client,
                    name, w, h, flags, format,
                    handle, gbp, &layer);
            break;
        case ISurfaceComposerClient::eFXSurfaceDim:
            result = createDimLayer(client,
                    name, w, h, flags,
                    handle, gbp, &layer);
            break;
        default:
            result = BAD_VALUE;
            break;
    }

    result = addClientLayer(client, *handle, *gbp, layer);

    setTransactionFlags(eTransactionNeeded);
    return result;
}

這裡因為預設的 flags 是 0, 所以會走 createNormalLayer

status_t SurfaceFlinger::createNormalLayer(const sp<Client>& client,
        const String8& name, uint32_t w, uint32_t h, uint32_t flags, PixelFormat& format,
        sp<IBinder>* handle, sp<IGraphicBufferProducer>* gbp, sp<Layer>* outLayer)
{
    // initialize the surfaces
    switch (format) {
    case PIXEL_FORMAT_TRANSPARENT:
    case PIXEL_FORMAT_TRANSLUCENT:
        format = PIXEL_FORMAT_RGBA_8888;
        break;
    case PIXEL_FORMAT_OPAQUE:
        format = PIXEL_FORMAT_RGBX_8888;
        break;
    }

    *outLayer = new Layer(this, client, name, w, h, flags);
    status_t err = (*outLayer)->setBuffers(w, h, format, flags);
    if (err == NO_ERROR) {
        *handle = (*outLayer)->getHandle();
        *gbp = (*outLayer)->getProducer();
    }

    ALOGE_IF(err, "createNormalLayer() failed (%s)", strerror(-err));
    return err;
}

前面的format不會影響 (我們帶入的是 PIXEL_FORMAT_RGB_565)
因此這邊就是再生成了一個 Layer 對象, 並且把前面的 handle 跟 gbp 也一併賦址了
見到老朋友的感覺真好  (ノ∀`*)

讓我們看看 Layer 的建構子吧:

Layer::Layer(SurfaceFlinger* flinger, const sp<Client>& client,
        const String8& name, uint32_t w, uint32_t h, uint32_t flags)
    :   contentDirty(false),
        sequence(uint32_t(android_atomic_inc(&sSequence))),
        mFlinger(flinger),
        mTextureName(-1U),
        mPremultipliedAlpha(true),
        mName("unnamed"),
        mFormat(PIXEL_FORMAT_NONE),
        mTransactionFlags(0),
        mQueuedFrames(0),
        mSidebandStreamChanged(false),
        mCurrentTransform(0),
        mCurrentScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE),
        mCurrentOpacity(true),
        mRefreshPending(false),
        mFrameLatencyNeeded(false),
        mFiltering(false),
        mNeedsFiltering(false),
        mMesh(Mesh::TRIANGLE_FAN, 4, 2, 2),
        mProtectedByApp(false),
        mHasSurface(false),
        mClientRef(client),
        mPotentialCursor(false),
        mQueueItemLock(),
        mQueueItemCondition(),
        mQueueItems(),
        mLastFrameNumberReceived(0),
        mUpdateTexImageFailed(false)
{
    mCurrentCrop.makeInvalid();
    mFlinger->getRenderEngine().genTextures(1, &mTextureName);
    mTexture.init(Texture::TEXTURE_EXTERNAL, mTextureName);

    uint32_t layerFlags = 0;
    if (flags & ISurfaceComposerClient::eHidden)
        layerFlags |= layer_state_t::eLayerHidden;
    if (flags & ISurfaceComposerClient::eOpaque)
        layerFlags |= layer_state_t::eLayerOpaque;
    if (flags & ISurfaceComposerClient::eSecure)
        layerFlags |= layer_state_t::eLayerSecure;

    if (flags & ISurfaceComposerClient::eNonPremultiplied)
        mPremultipliedAlpha = false;

    mName = name;

    mCurrentState.active.w = w;
    mCurrentState.active.h = h;
    mCurrentState.active.crop.makeInvalid();
    mCurrentState.z = 0;
    mCurrentState.alpha = 0xFF;
    mCurrentState.layerStack = 0;
    mCurrentState.flags = layerFlags;
    mCurrentState.sequence = 0;
    mCurrentState.transform.set(0, 0);
    mCurrentState.requested = mCurrentState.active;

    // drawing state & current state are identical
    mDrawingState = mCurrentState;

    nsecs_t displayPeriod =
            flinger->getHwComposer().getRefreshPeriod(HWC_DISPLAY_PRIMARY);
    mFrameTracker.setDisplayRefreshPeriod(displayPeriod);
}
這麼一長串的參數, 有沒有一種花惹發的感覺呢?
好在我們甚麼 flags 都沒有帶進來呢~
這邊看起來比較重要的人是 mDrawingState, 不過現在還不知道它能做甚麼

這種時候呢...
(ㆆᴗㆆ)

就是先不管它

姑且先看一下 onFirstRef 做了甚麼

void Layer::onFirstRef() {
    // Creates a custom BufferQueue for SurfaceFlingerConsumer to use
    sp<IGraphicBufferProducer> producer;
    sp<IGraphicBufferConsumer> consumer;
    BufferQueue::createBufferQueue(&producer, &consumer);
    mProducer = new MonitoredProducer(producer, mFlinger);
    mSurfaceFlingerConsumer = new SurfaceFlingerConsumer(consumer, mTextureName);
    mSurfaceFlingerConsumer->setConsumerUsageBits(getEffectiveUsage(0));
    mSurfaceFlingerConsumer->setContentsChangedListener(this);
    mSurfaceFlingerConsumer->setName(mName);

    mSurfaceFlingerConsumer->setDefaultMaxBufferCount(3);

    const sp<const DisplayDevice> hw(mFlinger->getDefaultDisplayDevice());
    updateTransformHint(hw);
}

野生的 BOSS 出現啦 (((゚Д゚;)))
這個 Producer 跟 Consumer 非常重要, 我建議各位在這裡先存個檔
然後播放激昂的戰鬥配樂 (像是Granblue Fantasy マグナ戦 BGM)
我們, 下一篇見!

2018年1月2日 星期二

Java筆記 - Exception Catch & Throw

如果function內自己會拋出Exception,
或者再深一層有拋出Exception但你沒有執行catch區塊, 那麼在compile階段就會報error

一個簡單的範例程式如下:

public class throwTest {

    private static void throwTestLayer1() throws Exception {
        System.out.println("throwTestLayer1: throw Exception +++");
        throw new Exception("throwTestLayer1: throwExcept test");
    }

    private static void throwTestLayer2() throws Exception {
        System.out.println("throwTestLayer2: calling throwTestLayer1 +++");
        throwTestLayer1();
        System.out.println("throwTestLayer2: calling throwTestLayer1 ---");
    }

    private static void throwTestLayer2Catch() throws Exception {
        try {
            System.out.println("throwTestLayer2Catch: calling throwTestLayer1 +++");
            throwTestLayer1();
            System.out.println("throwTestLayer2Catch: calling throwTestLayer1 ---");
        } catch (Exception e) {
            System.out.println("throwTestLayer2Catch: Catch throwTestLayer1");
            e.printStackTrace();
            //throw e;
        }
    }

    public static void main(String[] argv) {
        // 單層的 throw
        System.out.println("=============================================================");
        try {
            System.out.println("main: Calling throwTestLayer1 +++");
            throwTestLayer1();
            System.out.println("main: Calling throwTestLayer1 ---");
        } catch (Exception e) {
            System.out.println("main: Catch throwTestLayer1");
            e.printStackTrace();
        }
        System.out.println("=============================================================");
        // 這個 throwTestLayer2 並沒有去執行 try/catch, 他必須宣告 throws
System.out.println("============================================================="); try { System.out.println("main: Calling throwTestLayer2 +++"); throwTestLayer2(); System.out.println("main: Calling throwTestLayer2 ---"); } catch (Exception e) { System.out.println("main: Catch throwTestLayer2"); e.printStackTrace(); } System.out.println("=============================================================");
        // 這個 throwTestLayer2Catch 將會執行 catch, 如果它不再 throw, 這時候 main 是接不到的
System.out.println("============================================================="); try { System.out.println("main: Calling throwTestLayer2Catch +++"); throwTestLayer2Catch(); System.out.println("main: Calling throwTestLayer2Catch ---"); } catch (Exception e) { System.out.println("main: Catch throwTestLayer2Catch"); e.printStackTrace(); } System.out.println("============================================================="); } }


2017年12月20日 星期三

演算法筆記 - 經典河內塔

最近在看CNN的文章的時候, 提到了Hierarchy的概念
目的是以divide-and-conquer的概念來將大問題切割為小問題
以image recognition / computer vision來說, "直覺上"每個pixels只會跟鄰近的pixels有關聯性(correlated to local pixels)
也就是說在Machine Learning的領域上又回歸到了"自然的"、"直覺式"的算法 XD

有點扯遠了, 讓我們先回到第一個關鍵字 "divide-and-conquer"
這個詞被翻譯做分治法, 也就是分而治之
如果你熟悉於科技業, 一定在很多問題/系統上都已經看過了分治法解決問題的能力.
如果你是一個科技新鮮人, 那麽這個經典的河內塔也是你在人生的遞迴之路的重要基石.

河內塔 (Tower of Hanoi) 的故事
 - 引述自 Wiki 百科 <河內塔>

傳說
印度某間寺院有三根柱子,上串64個金盤。寺院裡的僧侶依照一個古老的預言,以一定的規則移動這些盤子;
規則: 有三根杆子A,B,C。A杆上有N個(N>1)穿孔圓盤,盤的尺寸由下到上依次變小。
要求按下列規則將所有圓盤移至C杆:
  1. 每次只能移動一個圓盤;
  2. 大盤不能疊在小盤上面。

預言說當這些盤子移動完畢,世界就會滅亡。這個傳說叫做
梵天寺之塔問題(Tower of Brahma puzzle)。
若傳說屬實,僧侶們需要264 − 1步才能完成這個任務;若他們每秒可完成一個盤子的移動,就需要5845億年才能完成。
(整個宇宙現在也不過137億年。)
這個傳說有若干變體:寺院換成修道院、僧侶換成修士等等。
寺院的地點眾說紛紜,其中一說是位於越南的河內,所以被命名為「河內塔」。

移動河內塔
那麼, 要怎移動河內塔呢?
我們先來推一次, 當瓦片數為 3 的時候,  流程如下
移動次數\塔 A B C
1 2
3

1
2 3 2 1
3 3 1
2

4
1
2
3
5 1 2 3
6 1
2
3
7

1
2
3
* 最少需要移動 2^3 - 1 = 7步

接著, 讓我們簡化問題變成瓦片數為 2 的狀況:
移動次數\塔 A B C
1 2 1
2
1 2
3

1
2
看一下, 瓦片數為 2 的狀況跟 3 的狀況, 對於前 3 動的移動順序是不是非常相似
他們最大的差異, 就是將1號跟2號瓦片, 移動到C柱或者是B柱的差別
當N = 2的時候, 直接移動到C柱即可
當N = 3的時候, 你需要先將(1, 2)移動到B柱, 接著將(3)移到C柱, 最後將再將(1, 2)移到C柱

到這一步, 其實已經分治完成了 ヽ(∀゚)人(゚∀゚)人( ゚∀)人(∀゚)人(゚∀゚)人( ゚∀)ノ
河內塔的關鍵就在於, 要適時的把這些柱子當作暫存的buffer來使用

接著我們試著寫出虛擬碼:
moveDisk(N, from, to, buffer) {
    if (N == 0) {
         // 中止條件! 沒得動啦
        return;
    } else {
        // 把倒數第二片開始的盤子從A柱(from)移動到B柱(buffer)去
        move((N-1), from, buffer, to);
        // 把最底下的盤子N 
從A柱(from)移動到C柱(to)去
        print("Move Disk %d from %d to %d", N, from, to);
        // 把倒數第二片開始的盤子從B柱(buffer)移動到C柱(to)去
        move((N-1), buffer, to, from);
    }
}

以上!

2017年12月14日 星期四

Process/Thread name in Android native layer

看起來在 libc/bionic 裡面有個 pthread_setname_np.cpp 可以去設置 process name,
但是不曉得為什麼沒有提供 getname 方法 (security?)

setname 的使用方法如下:

pthread_setname_np(pthread_self(), "xxx");

他的實作如下:

int pthread_setname_np(pthread_t t, const char* thread_name) {
  ErrnoRestorer errno_restorer;

  size_t thread_name_len = strlen(thread_name);
  if (thread_name_len >= MAX_TASK_COMM_LEN) {
    return ERANGE;
  }

  // Changing our own name is an easy special case.
  if (t == pthread_self()) {
    return prctl(PR_SET_NAME, thread_name) ? errno : 0;
  }

  // We have to change another thread's name.
  pthread_internal_t* thread = __pthread_internal_find(t);
  if (thread == NULL) {
    return ENOENT;
  }
  pid_t tid = thread->tid;

  char comm_name[sizeof(TASK_COMM_FMT) + 8];
  snprintf(comm_name, sizeof(comm_name), TASK_COMM_FMT, tid);
  int fd = open(comm_name, O_CLOEXEC | O_WRONLY);
  if (fd == -1) {
    return errno;
  }
  ssize_t n = TEMP_FAILURE_RETRY(write(fd, thread_name, thread_name_len));
  close(fd);

  if (n < 0) {
    return errno;
  } else if (n != static_cast<ssize_t>(thread_name_len)) {
    return EIO;
  }
  return 0;
}

我們可以如果是在當前的 thread name 進行更名的話,
就會直接透過 prctl(PR_SET_NAME, thread_name) 來完成

否則則會嘗試去開啟 /proc/self/task/%d/comm , 其中 %d 將會帶入目標 thread 的 tid,
並直接對他做 write 的動作.

這個 PR_SET_NAME 定義在 bionic/libc/kernel/uapi/linux/prctl.h
#define PR_SET_NAME 15
#define PR_GET_NAME 16
但沒有看到去呼叫 PR_GET_NAME 的方法

我們可以在 bionic/linker/debugger.cpp 找到一個較為接近的例子:

  char thread_name[MAX_TASK_NAME_LEN + 1]; // one more for termination
  if (prctl(PR_GET_NAME, reinterpret_cast<unsigned long>(thread_name), 0, 0, 0) != 0) {
    strcpy(thread_name, "<name unknown>");
  } else {
    // short names are null terminated by prctl, but the man page
    // implies that 16 byte names are not.
    thread_name[MAX_TASK_NAME_LEN] = 0;
  }

不過較奇妙的是他並沒有去 /proc/self/task 下面尋找對應的 tid
看起來是只要去 $cat /proc/<tid>/comm 就可以拿到 process name 了
(並且 /proc/<tid>/comm 這一路上的權限看起來都是有開啟的...)


2017年11月15日 星期三

Android 系統筆記 - Big/Little endian

雖然是很一般的東西但偶而腦筋還是會打結呢....(´・ω・`;)

Big Endian 就是把最高位元組存到地址的最低位元. (這句話hen難理解對吧)

直接上範例:
地址假設我們從 0x00000000 開始
假設你有一個 32 位元的 integer 如下
int32_t big = 0x12345678;

地址最低位元當然就是 0x0000000囉
那麼最高位元組指的是什麼呢? 就是你的 0x12啦
Mapping到記憶體之後會變成這樣:

Address Value
0x00000000 0x12
0x00000001 0x34
0x00000002 0x56
0x00000003 0x78

反過來說 Little-endian 就是會變成這樣囉: 

Address Value
0x00000000 0x78
0x00000001 0x56
0x00000002 0x34
0x00000003 0x12

那假設你是一個 array 會怎麼樣呢?
我們令 int32_t test[2] = {0x12345678, 0x11223344}

記憶體的排列會變成這樣唷!

Address Value
0x00000000 0x78
0x00000001 0x56
0x00000002 0x34
0x00000003 0x12
0x00000004 0x44
0x00000005 0x33
0x00000006 0x22
0x00000007 0x11

也就是 Big-Endian 或者 Little-Endian 只有影響到該變數在記憶體存放的位置而已  (*´∀`)ノ

2017年11月7日 星期二

Android系統筆記 - Java HandlerThread

Java 的 Thread 已經被封裝成了幾種易用的形式, 簡單介紹一下幾個重點:

Runnable 
可以理解為"任務內容"
我們會把要完成的一件工作寫在Runnable的物件裡面.
例如這樣:
Runnable task = new Runnable() {
    public void run() {
        Log.e("Start run!");
    }
};

Thread
單執行序, 負責處理一個 Runnable 的物件.
用法也很簡單, 我們可以直接把上面那個 task 直接丟進去:
Thread oneTimeThread = new Thread(task);
oneTimeThread.start();
這樣就會開另外一個執行序去執行一次 task

另外Thread也有一個建構子是沒有帶入Runnable物件的,
這樣子呼叫start()就會直接執行Thread裡面的run()方法,
一般使用在你的物件去繼承了 Thread 的時候, 例如這樣:

public class myThread extends Thread {
    @Override
    public void run() {
        System.out.println("Thread start running!");
        for (;;) {
            System.out.println("Thread keep running!");
        }
    }

    public static void main(String[] argv) {
        Thread t = new myThread();
        t.start();
        for (;;) {
            System.out.println("Main Thread");
        }
    }
}

執行以後就會交錯印出訊息, 可能像這樣:
Main Thread
Main Thread
Thread start running!
Thread keep running!
Thread keep running!
Main Thread
Thread keep running!
Main Thread
(因為執行序的關係, main通常會先開始跑)

你也可以嘗試將 infinite for loop 換成只跑10次來觀察看看.
另外需要一提的是, 這個 Thread 物件也可以直接呼叫 run() method,
這時就會在 main thread 執行, 而不會另外開一個執行序去跑, 所以執行結果會變成這樣:
(把 infinite for loop 換成只跑10次)

Thread start running!
Thread keep running!
Thread keep running!
Main Thread
Main Thread
=> 等到 run() 方法執行完之後才會往下跑


HandlerThread
相較於上面的單執行序, 這個物件幫你封裝了MessageQueue在裏頭,
它的定位很像是 Native layer 的 Thread 物件, 啟動之後會自動運行 threadLoop(),
並且會等待事件進入時才開始工作 (可以想像成它呼叫了 pollOnce(-1))

handlerThread開始執行之後, 會開啟另外一個執行序來等待工作. (*這時候還沒有task放進去跑)
我會把它拿來處理asynchronous訊息的溝通, 用法如下:
HandlerThread workThread = new HandlerThread("WorkThread");
workThread.start(); // 開始等待事件進入

// 分發事件的方式要使用 Handler 物件來進行分配, 關於 Handler 請往下看
Handler workHandler = new Handler(workThread.getLooper());
workHandler.post(task);

// 除了 post 以外, 還有 postAtTime, postDelayed 等形式可以來控制 task 執行到的時間

* 因為這個執行序沒有所謂的執行結束, 所以不用的時候記得要關閉掉它
(例如, 放在 constructor 跟 destructor)

Handler
這個物件除了可以使用post()分配事件給HandlerThread,
還有一個用法是去override handleMessage(), 將事件預先定義好, 並使用 Message 來開始工作.
Handler workHandler = new Handler(workThread.getLooper()) {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        switch (msg.what) {
            case 1:
                Log.e("Handler", "Received message");
                break;
            default:
                Log.e("Handler", "Unsupprted message");
                break;
        }
    }
}

workHandler.sendEmptyMessage(1);
(有興趣進去看 Handler.java的話, 你會發現 post() 函數也是將 Runnable 物件封裝為 Message)

不定參數印 log

From the UNIXProcess_md.c #ifdef DEBUG_PROCESS   /* Debugging process code is difficult; where to write debug output? */ static void deb...