2016年5月10日 星期二

關於ActivityLifeCycleCallback

API文件在此。

這東西很有趣。

如果沒有它,「將共用資料擺在Application」這樣的認知或建議其實不太好用。



不過這是「我」。我認為應該要將Activity模組化後,統一套用「讀取或導入資料」的流程。

但是很多人只是需要「有個地方取用資料」。

將資料存在Application中後,用getApplication()取出即可......

很合理很有效率的想法!

但假設「不合用」或「不夠用」吧!

那我們就需要考慮一下LifeCycleCallback的用法。



這是個介面,裡面有完整的相對應了所有Activity LifeCycle的函數。

每個Activity的LifeCycle函數中,都會先在super()中執行所有被Application註冊的Callback,然後「才是」設計師複寫的物件內容。(LifeCycle不是建構子,其實設計師可以隨意調整super()執行的位置。)

(注意!是所有被註冊了的Callback都會被執行!)

(再注意!不是只有Application有能力註冊Callback,其他類別也有能力;相對的,也不是只有Application有能力反註冊Callback。因此一個Callback運作的開始和結束是很有彈性的!不會比Service差,但又比Service精準!)



所以假設......純假設.......

Callback中會在onResume啟動一個Thread,然後在onStop中將Thread關閉。(偶爾會發生從某一個Activity A返回前一個Activity時,Activity A的onDestroy會在下一個Activity的onResume之後才運作,這種時候可能會產生意外!如果允許Callback內產生多個Thread,那該關閉哪個Thread可能就是個要頭痛一下的問題了!...不是無解,也不複雜。)

這樣就可以達到讓背景任務可以順利跨Activity運行、而且又能對應當下的Activity產生變化。(例如視頻撥放的功能,可以將Activity視為「視頻線上存放點」的切換或記錄器,而下載功能和下載資料緩存則建立在Callback中,等於達到常駐的效果,而不會隨著每個Activity的啟動或關閉而有資源管理的問題。...這只是初步構想,還沒實驗過。)



再假設......純假設.......

每個Callback類別都有對應的Interface可以讓Activity實作,然後在Application中註冊多個Callback,只是每個Callback在執行對應的生命週期函數以前,都會檢查傳入的Activity是否有實作自己對應的Interface...如果沒有就不執行,那就可以大幅簡化Activity要處理的工作。



但對我來說,這個介面真正實用之處在於它讓一個跨「多Activity」甚至「多APP」的Thread可以精準的知道「何時有Activity可以取用」。

舉例...

就假設要設計個最簡單的APP內建有獨立邏輯和功能的計時器吧!

計時流程如果要精準可靠,那就不能因為Activity切換而損失效能或時間失準。(注意!它有獨特邏輯,所以不是直接把手機時間拿出來顯示就好。)所以不能採用「每個Activity獨立啟動Thread」的做法。

但在UI1中,時間顯示在右上角,在UI2中,顯示在正中央,而UI3卻又顯示在左下角...所以上面說要記得設定一個Interface讓每個Activity設定自己顯時間值的方法,如果有這樣做,就可以將這個Thread寫在一個Callback中,讓Application在產生Acitivity前就先運行這個Thread。



研究完用法思考了一下...

凡事要在背景常駐處理的都寫個Service...會不會對系統負擔太大呢!

畢竟Service是個功能完整的Context!但LifeCycleCallback只是個簡化介面的實作。

如此一來,Android預設的幾個主要功能類別其實就越來越模糊了!遵守Activity、Service、BroadCast...這類的類別去設計規劃APP的架構,可能反而會是個阻礙。

但同樣的理由...當一隻APP背景存在數十個額外設定的Callback時,效能是不是一樣會變差?

所以設計一項服務真的要把各個功能切割成多個APP(其中有一個是所謂的Main APP)來完成,這樣每個APP前景背景都會變得很單純穩定!

2016年5月9日 星期一

使用byte矩陣傳輸資料到NFC Tag

這是在Safari的官網上找到關於NFC資料的技術文件。主要是讓使用者可以在Web中經由Safari瀏覽器啟動Android的NFC功能後,再用byte矩陣自組要傳輸到Tag、或Tag Reader中的資料。

當初研究它的目的是想要可以讓Android手機可以用NFC Beaming功能來隨意模仿成Tag、具有特定ID,讓其他Tag Reader可以感應。

但奮戰了整整一天......最後是徒勞無功。在這裡寫下我的心得。



用Android的NdefRecord中的public NdefRecord (short tnf, byte[] type, byte[] id, byte[] payload)函數,雖然很快有基本的Beaming功能,但送出去的訊息ID遲遲都是0...而且是一個0,而不是一串0!(一個長度只有1的byte[]。)

所以就異想天開用public NdefMessage (byte[] data)的方式來組看看,或許ID顯示為0是NdefRecord物件的問題。




NFC的每一筆資料,稱為Message,可以由多組Record組成。

每一組Record又由RecordHeader和RecordPayLoad組成。

每個RecordPayLoad資料長度上限為2的32次方。除此以外沒有限制。(但似乎也不能為0。)

「文件中」說Header由6~9個程度不等的Byte矩陣組成,為何會有6和9的差異?因為其中有所謂ShortRecord和Non-ShortRecord的差異。如果是ShortRecord,則為6,反之則為9。

(為何要強調「文件中」...等一下解釋。先聲明這是文件中非常不精準的地方。)

RecordHeader中的第一個Byte稱為TNF+Flag(Message Flags),我帶入的是217。

因為一個Byte由八個bit組成,在這個欄位中,每個bit都有自己獨特的意義。而我用的是11011001。每個數字代表著一個bite,所以內容只有0或一,換算成十進位後數值是217。

第四個bit正好決定了這筆Record為ShortRecord或Non-ShortRecord,而我帶入的是1,也就表示「True」,所以PayLoadLength的部分只有一個Byte。要等一下計算玩PlayLoad長度後決定數值。

但如果TNF+Flag(Message Flags)是11001001,就會佔去四個Byte,因為第四個數值是0,也就表示這筆資料不是ShortRecord。(Record的長度是四個Byte或一個Byte的差異。)



開頭是{1,1},因為我的Message中只有一組Record,所以都是Begin兼End,如果是Begin,就是10,如果是End就是01。(假設共有三組Record,則第一組10,第二組00,第三組01。)



第三個0/1是Chunk...還是搞不懂這是什麼東西。

第五個0/1是ID Length,如果0,就表示沒有ID,自然也不需要設定ID Length,RecordHead就少了兩個欄位-----設ID Length的欄位和ID本身。

所以上面要強調「文件中」,因為如果ShortRecord為false,而ID Length又為True,則Header的長度上限絕對不只9個Byte。

第六到第八個0/1是TypeNameFormat,是一組的。三個bit可以表示0~7,而Type剛好有八種組合,Empty/Well-Known/URI.......一般都是用WellKnown和URI。(我帶入其他的數值都失敗。)



到此為止,資料很成功送出去,Read很成功的讀取到了我用這方式所寫入的資料。



但Header除了第一個Byte為TNF+Flag(Message Flags)以外,接著還有Type Length(一個Byte),PayLoadLength,(一或四個Byte,視資料是否為ShortRecord而定,)還有IDLength(一個Byte)。

1.TNF+Flag。(長度1)
2.TypeLength。(長度1)
3.PayLoadLength。(長度1)
4.IDLength。(長度1)

這六個Byte之後是所謂的長度浮動的階段,第一個是PayLoadType,它是一個字串轉換成Byte矩陣形式後插入,長度必須要精準計算後寫入TypeLength欄位中,(所以長度大小自然不能超過255。)但PayLoadType的命名邏輯文件中的解釋很模糊,但始終是那八大類,這只是個用來顯示的字串罷了!

接著就是所謂的ID。不管是數值或字串,它一樣也是轉換成Byte矩陣後計算長度插入,長度要寫在IDLength欄位中。

Activity無法接觸的非主線呈Thread可主動更新Activity內容

會特地寫這篇心得,是因為Handler Leaking一直困擾我。

在Activity可直接控制的範圍內觸發的Thread,可以用Handler.obj的方式來解決。

但如果是Activity完全接觸不到的Thread內,甚至是「第三方APP」內的Thread,這時要怎麼將Thread工作的結果回傳到Activity中?

在Guide文件「Display a location address」中曾經短暫提到的一個物件「ResultReciever」就是被設計來解決這個問題的工具。



問題是按照Google文件一慣的原則,它們完全不認為這種概念需要獨立成一個章節來介紹,仔細查了一下...確實只有這篇Guide文件有提到過這個物件,剩下的就是要進入個別API說明才會發現了。



這東西其實很簡單...

ResultReciever物件是個Parcelable介面的實作,將它當作參數傳入Intent,再經由Intent傳入獨立Thread或IntentService中,就可以在Thread結束時,(似乎是有能力偵測這個線程結束被關閉,)執行使用者實作的onRecieverResult(int code, Bundle data)中的內容。

(若是Activity可以直接控制影響的範圍,大可以直接啟動,不需要經由Intent。談到Intent,自然就是跨Activity,而且兩個Activity之間無法互通互相影響的情況。)



但是我原始的想法是在ActivityA中設置一個Intent Reciever,(通常是BroadCastReciever,)然後讓Activity接觸不到的Thread將工作結果也用Intent回傳。

如果有這個機制,這個方法就顯得很笨重多餘。