2014年2月27日

MQTT(五)SUBSCRIBE Message

前言:

上一篇提到用來發佈訊息的PUBLISH Message,接著就來談談訂閱訊息用的SUBSCRIBE Message。

SUBSCRIBE Message

SUBSCRIBE Message允許client對Server上一或多個感興趣的主題(Topic)進行訂閱。當有訊息對主題發佈時,server會將訊息當作PUBLISH Message送給訂閱的client。SUBSCRIBE Message也定義了QoS level,表示它期望以何種QoS level來接收訊息。

Fixed Header:

bit 7 6 5 4 3 2 1 0
byte 1 Message Type(8) DUP flag QoS level RETAIN
1 0 0 0 0 0 1 x
byte 2 Remaining Length

上面為SUBSCRIBE Message裡面的fixed header,以下依依介紹它們的作用。

  • Message Type:每種類型的Message都要用到,要根據想用的訊息類型填上相對應的值,值可以參考先前貼的文章MQTT(二)Message Type and Flows。這邊由於是SUBSCRIBE Message的關係,因此會填上1000。

  • DUP flag:標記此訊息為重複(duplicate)的訊息,會用在PUBLISH、PUBREL、SUBSCRIBE、UNSUBSCRIBE上。當client或是server要重新傳送上述訊息時,則要將此flag設為1。此flag會適用於QoS大於0的訊息。

    此flag 只能當成一種提示,表示此則訊息可能在之前有收過了,不應該拿它來當成訊息重複的檢查flag。

  • QoS level:SUBSCRIBE Message使用QoS level 1來回應(acknowledge)不同的訂閱主題請求。

  • RETAIN:沒有使用到。

  • Remaining Length:此欄位記錄當前的Message,它的Variable header和Payload總共的長度為何,此欄位不一定就是1 byte,它是可變得,最多到4 byte。

接著讓我們繼續往下看,再來是第二個Herader,也就是Variable Header。

Variable Header:

由於SUBSCRIBE Message QoS level為1,因此其variable header會包含Message ID,用來確保訊息能夠正確傳送。

下面為SUBSCRIBE Message,variable header欄位範例:

Field Value
Message ID 10

Payload:

SUBSCRIBE Message的payload會包含一個主題名稱列表,代表該Client想要訂閱的主題,列表內的每個主題都會包括一個期望接收訊息時的QoS level,可以參考MQTT(二)Message Type and Flows:Subscribe Flow來了解QoS level與其影響為何。

主題的名稱可以使用萬用字元(wildcard characters),關於萬用字元稍後會進行說明,以下為SUBSCRIBE Message的payload範例:

Topic name "a/b"
Requested QoS 1
Topic name "c/d"
Requested QoS 2

上述範例代表該Client想要訂閱兩個主題"a/b"、"c/d",其中主題"a/b"期望用QoS level 1來接收訊息,主題"c/d"期望用QoS level 2來接收訊息。

Response:

當Server接收到從Cleint發送過來的SUBSCRIBE Message,則回應一個SUBACK Message給client。

Server有基於Client已經訂閱的關係,因此可能會開始傳送PUBLISH Message給Client,即使Client還沒收到SUBACK Message。

Server有可能會降低cleint請求的QoS level。這會發生在Server沒辦法提供高level的QoS時。舉例來說,如果Server沒有提供一個可靠的訊息持久機制(persistence mechanism),則它可能只提供QoS level 0給訂閱者。Server提供的QoS level會在SUBACK Message的payload內,依照主題的訂閱順序回應給訂閱者。以下為範例:

Granted QoS 0
Granted QoS 2

主題萬用字元(Topic WildCard Characters):

在訂閱的時候可以使用特出字元,允許你一次訂閱多個主題,此字元稱為萬用字元。

主題層級分隔符號(Topic level separator)被用來對主題做結構化。單一層級的萬用字元和多層級的萬用字元可以被用在訂閱上面,但是不能被用在發佈(publish)訊息上面,也就是說在發佈訊息時,主題要明確定義,不能用萬用字元來同時對多個主題做訊息發佈。

Topic level separator(主題層級分隔符號):

斜線符號(/)用來切割topic tree的每個層級,提供一個有層次的主題空間。使用主題層級分割符號來切割主題,在遇到用萬用字元訂閱該主題時是很有用的,下面接著介紹兩個萬用字元。

Multi-level wildcard(多層級萬用字元):

井字號(#)用來匹配任何層級的主題。舉例來說,如果你用此萬用字元訂閱了 finance/stock/ibm/#,則你將會收到來自於下列主題的訊息:

  • finance/stock/ibm
  • finance/stock/ibm/closingprice
  • finance/stock/ibm/currentprice

多層級萬用字元可以表示零到多個層級,因此 finance/# 可以匹配到 finance

多層級萬用字元在以下狀況是有效的,如 # 或是 finance/# ,而以下狀況下是無效的,如 finance#finance/#/closingprice

Single-level wildcard(單一層級萬用字元):

加字號(+)用來匹配單一層級的主題。舉例來說 finance/stock/+ 會匹配 /finance/stock/ibm/finance/stock/xyz ,但是不會匹配到 finance/stock/ibm/closingprice 。也由於它只會匹配到一個層級,因此 finance/+ 不會匹配到 finance

單一層級萬用字元可以使用在任何層級的主題樹,也可以和多層級萬用字元結合使用。

單一層級萬用字元在以下狀況是有效的,如 +finance/+ 以及 finance/+/ibm 。而以下狀況是無效的,如 finance+

小結

本篇介紹了MQTT的SUBSCRIBE Message,此訊息用於訂閱主題上,而每個想訂閱的主題需要設定期望的QoS level,藉此來表達client對該主題的訊息品質需求。

另外也介紹了主題萬用字元,讓client在訂閱主題時,可以透過萬用字元同時對多個主題進行訂閱。

參考:

MQTT V3.1 Protocol Specification

2014年2月26日

使用Eclipse寫Erlang Rabbitmq Client

我們可以使用多種程式語言來撰寫Rabbitmq的client,包括java, perl, python, erlang...等, 這篇是介紹如何使用Eclipse來寫Erlang Rabbitmq Client,此篇著重在開發的步驟並未詳細介紹程式碼的細節,細節部份可參考這篇文章Erlang Client Guide

步驟如下

1.安裝Erlang, Rabbitmq, Eclipse

2.在Eclise安裝Erlan plugin Erlide

在Eclipse點選Help → Install new software..., 填入http://erlide.org/update後執行安裝

3.下載Erlang library

下載amqp_client-3.2.3, rabbit_common-3.2.3,下載完後解縮到任意位置,例如/Users/james/Develop/amqp_client-3.2.3/, /Users/james/Develop/rabbit_common-3.2.3/

4.設定Erlang library路徑

在自已的家目錄下建立.erlang,並寫入步驟3所下載的2個library路徑,記得路徑要指到ebin目錄,例如下列

code:add_pathz("/Users/james/Develop/amqp_client-3.2.3/ebin"). code:add_pathz("/Users/james/Develop/rabbit_common-3.2.3/ebin").

這樣才會讓erlang在執行時期在上列的路徑找到所下載的library

5.使用eclipse建立erlang project

在Eclipse裡點File->New->Project...,找Erlang Project,填入project name後按finish即可。

再將amqp_client.hrl, rabbit.hrl, rabbit_framing.hrl放到project裡的include目錄,如下圖

sample

註:amqp_client.hrl檔案在下載回來的amqp_client-3.2.3/include裡,rabbit.hrl, rabbit_framing.hrl檔案在下載回來的rabbit_common-3.2.3/include裡

6.撰寫erlang client

在src目錄下建立amqp_example.erl並填入下列內容

-module(amqp_example).

-include("amqp_client.hrl").

-compile([export_all]).

test() ->
    %% Start a network connection
    {ok, Connection} = amqp_connection:start(#amqp_params_network{}),
    %% Open a channel on the connection
    {ok, Channel} = amqp_connection:open_channel(Connection),

    %% 建立一個queue
    #'queue.declare_ok'{queue = Q}
        = amqp_channel:call(Channel, #'queue.declare'{}),

    %% 送一則message至queue
    Payload = <<"foobar">>,
    Publish = #'basic.publish'{exchange = <<>>, routing_key = Q},
    amqp_channel:cast(Channel, Publish, #amqp_msg{payload = Payload}),

    %% 再將message從queue中取出
    Get = #'basic.get'{queue = Q},
    {#'basic.get_ok'{delivery_tag = Tag}, Content}
         = amqp_channel:call(Channel, Get),

    %% Do something with the message payload
    %% (some work here)

    %% Ack the message
    amqp_channel:cast(Channel, #'basic.ack'{delivery_tag = Tag}),

    %% 關閉channel
    amqp_channel:close(Channel),

    %% 關閉connection
    amqp_connection:close(Connection),

    ok.

7.執行

Eclipse會自動將erl compile後放至ebin目錄下例如amqp_example.beam,(如果沒有看到的話代表編譯失敗,請檢查是否有語法錯誤),我們要到erlang環境裡執行該檔案,步驟如下

  1. 在shell裡切換到ebin目錄 ex:cd /Users/james/Documents/workspaces/kokola/RabbitmqTest/ebin
  2. 進入erl
  3. 列出erlang當前載入的清單 io:format("~p~n", [code:get_path()]).。如果畫面顯示的載入清單沒有看到amqp_client, rabbit_common的話請檢查步驟4描述的.erlang檔內的路徑是否正確,該檔如果有修改的話需要重新進入shell
  4. 執行amqp_example

註:如果有繼續在eclipse裡修改.erl檔時記得要在在erl裡重新載入該module,否則erlang會繼續執行先前編譯的版本。載入的指令如l(amqp_example).

實際執行畫面大致如下

jamestekiMacBook-Pro:~ james$ cd /Users/james/Documents/workspaces/kokola/RabbitmqTest/ebin
jamestekiMacBook-Pro:ebin james$ erl
Erlang R16B02 (erts-5.10.3) [source] [64-bit] [smp:4:4] [async-threads:10] [kernel-poll:false]

Eshell V5.10.3  (abort with ^G)
1> io:format("~p~n", [code:get_path()]).
[".","/usr/local/lib/erlang/lib/kernel-2.16.3/ebin",
 "/usr/local/lib/erlang/lib/stdlib-1.19.3/ebin",
 "/usr/local/lib/erlang/lib/xmerl-1.3.4/ebin",
 ......(略)
 "/usr/local/lib/erlang/lib/common_test-1.7.3/ebin",
 "/usr/local/lib/erlang/lib/asn1-2.0.3/ebin",
 "/usr/local/lib/erlang/lib/appmon-2.1.14.2/ebin",
 "/Users/james/Develop/erlang-lib/amqp_client-3.2.3/ebin",
 "/Users/james/Develop/erlang-lib/rabbit_common-3.2.3/ebin"]
ok
2> amqp_example:test().
ok
3> l(amqp_example).
{module,amqp_example}
4> amqp_example:test().
ok

erlang basics - binary, bitstring, BIF

BIF: Built-In Function

BIF 是 erlang 內建的函數,例如 tuple_to_list/1 可將 tuple 轉換為 list,time/0 可取得當前的時間,大部分的 BIF 都屬於 erlang 模組,但因為 BIF 是自動匯入的,所以使用 tuple_to_list(...) 不需要寫成 erlang:tuple_to_list(...)。

http://www.erlang.org/doc/man/erlang.html 可找到 module:erlang 的文件。

函數規格 @spec

在 function 中,參數與回傳值的資料型別並不是清楚地寫在 function 的定義上,我們需要一個方式,來告訴使用這個函數的progarmmer,該怎麼使用它。erlang 社群開發了一種記號法,但這個記號法並不是 erlang 程式碼的一部分,而只是一種寫文件的工具。

這個記號法只能用在文件上,在程式碼中,會用 %% 將該行視為註解。通常會這樣寫 %% @spec

-module(math)
-export([fac/1]).

%% @spec fac(int()) -> int().

fac(0) -> 1;
fac(N) -> N * fac(N-1).

在使用此型別記號法時,要定義兩件事:型別與函數的規格。

定義型別

名稱為 typeName 的型別會寫成 typeName()

預先定義的型別是:

  1. any(): 指任何 erlang 的資料型別,term() 是 any() 的別名
  2. atom(), binary(), float(), function(), integer(), pid(), port(), reference(): erlang 的基本資料型別
  3. bool(): atom(true 或 false)
  4. char(): integer() 的子集合,代表字元
  5. iolist(): 遞迴地定義為 [char() | binary() | iolist()],通常用來產生高效率的字元輸出
  6. tuple()
  7. list(L): 是 [L] 的別名
  8. nil(): 就是 []
  9. string(): list(char()) 的別名
  10. depp_string(): 遞迴地定義為 [char()|deep_string()]
  11. none(): 沒有資料型別,用在不會產生回傳值的函數,例如無窮的接收迴圈,表示此函數不會返回

使用者自己定義型別可以寫成
@type newType() = TypeExpression

範例
@type onOff() = on|off.
@type person() = {person, name(), age()}.
@type people() = [person()].
@type name() = {firstname, string()}.
@type age() = integer().

指定函數的輸入與輸出型別

寫法為
@spec fuinctionName(T1, T2, ..., Tn) -> Tret
T1, T2, ..., Tn 是 參數的型別, Tret 是回傳值的資料型別

每個 T 都有三種可能的形式

  1. TypeVar
    型別變數,這代表未知型別(跟 erlang 的變數無關)
  2. TypeVar::Type
    型別變數後面跟著一個型別
  3. Type
    型別表示式

範例

@spec file:open(FileName, Mode) -> {ok, Handle} | {error, Why}.
@spec file:read_line(Handle) -> {ok, Line} | eof.

file:open/2 意思是,要開啟 FileName,會取得回傳值 {ok, Handle} 或是 {error, Why}
FileName 跟 Mode 是型別變數,但我們不知道它確切的型別是什麼。

範例

@spec lists:map(fun(A)->B, [A]) -> [B].
@spec lists:filter(fun(X) -> bool(), [X]) -> [X].

範例

@spec file:open(FileName::string(), [mode()]) -> {ok, Handle::file_handle()} | {error, Why::string()}.
@type mode() = read|write|compressed|raw|binary| ...

範例

@spec file:open(string(), Modes) -> {ok, Handle} | {error, string()}
    Handle() = file_handle(),
    Modes = [Mode],
    Mode = read|write|compressed|raw|binary| ...

範例

@spec file:open(string(), [mode()]) -> {ok,file_handle()} | error().
@type error() = {error, string()}.
@type mode() = read|write|compressed|raw|binary| ...

在文件中的定義

在文件裡,我們會省略 @spec

file:open(FileName, Mode) -> {ok, Handle} | {error, Why}.
    根據 Mode 開啟檔案 FileName。Mode 為....
file:read_line(Handle) -> {ok, Line} | eof.
    從開啟的檔案 Handle 中讀取一行資料,傳出 Line,檔案結尾則傳出 eof

使用到 @spec 的工具

EDoc
erlang 的文件產生器,類似 javadoc,以 @name, @doc, @type, @author等annotation,將文件嵌入到 source code 的註解中

Dialyzer
是靜態分析工具,可找出程式的型別錯誤、無法執行到的程式碼、無必要的測試...

binary

binary 可儲存大量的原始資料,以 << 與 >> 將一串整數或字串包夾在中間,整數必須要在 0 ~ 255 中間,<<"cat">> 是 <<99,97,116>> 的速寫,如果 binary 裡面都是可列印的字元,shell 就會自動當作字串列印出來。

1> <<5,10,20>>.
<<5,10,20>>
2> <<"cat">>.
<<"cat">>
3> <<99,97,116>>.
<<"cat">>

處理 binary 的 BIF

@spec list_to_binary(IoList) -> binary()
list_to_binary 以 IoList 內的整數與二元,產生 binary,IoList 是一個 list,裡面的元素是 0~255 整數、binary或 IoList。

4> Bin1 = <<1,2,3>>.
<<1,2,3>>
5> Bin2 = <<4,5>>.
<<4,5>>
6> Bin3 = <<6>>.
<<6>>
7> list_to_binary([Bin1, 1, [2,3,Bin2], 4|Bin3]).
<<1,2,3,1,2,3,4,5,4,6>>

@spec split_binary(Bin, Pos) -> {Bin1, Bin2}
在 Pos 位置,將 Bin 分割為兩個部份

8> split_binary(<<1,2,3,4,5,6,7,8,9,10>>, 3).
{<<1,2,3>>,<<4,5,6,7,8,9,10>>}

@spec term_to_binary(Term) -> Bin
將任意的 erlang term 轉換為 binary,將 term 轉成 binary 之後,就可以儲存到檔案、傳送到網路上,而且可以重建出原始的 term。

@spec binary_to_term(Bin) -> Term
term_to_binary的相反,可將 Bin 轉換為 Term

9> B = term_to_binary({binaries, "are", useful}).
<<131,104,3,100,0,8,98,105,110,97,114,105,101,115,107,0,3,
  97,114,101,100,0,6,117,115,101,102,117,108>>
10> binary_to_term(B).
{binaries,"are",useful}

@spec size(Bin) -> Int
這會傳出記憶體中的位元組個數

11> size(B).
29
12> size(<<1,2,3,4>>).
4

位元語法

這是 pattern matching 的擴充,用來取出並打包位元資料中的個別位元或位元序列。這個功能很適合用來撰寫低階程式碼,或是網路通訊程式,這是 erlang 最強的功能。

在變數 M 中,X 佔用 3 個位元,Y 佔用 7 個位元,Z 佔用 6 個位元。

14> X=2.
2
15> Y=10.
10
16> Z=15.
15
17> M = <<X:3, Y:7, Z:6>>.
<<66,143>>

範例:16 位元 RGB

如果 16位元的 RGB 顏色,R 佔用5位元,G 佔用 6 個位元, B 佔用 5 個位元。

19> Red = 2.
2
20> Green=61.
61
21> Blue=20.
20
22> Mem = <<Red:5,Green:6,Blue:5>>.
<<23,180>>
23> <<R1:5,G1:6,B1:5>> = Mem.
<<23,180>>
24> R1.
2
25> G1.
61
26> B1.
20

位元語法表示式

位元語法表示格式如下,每個 Ei 是四種形式之一。位元的總個數必須要是 8 的倍數。建構 binary 時,Value 必須要是已繫結的變數、字串、整數、浮點數、binary。而用在 pattern matching 時,Valued 可以是已繫結或未繫結的變數、字串、整數、浮點數、binary。

TypeSpecifierList 是型別指定子清單,一個用減號分隔項目的list,End-Sign-Type-Unit

<<>>
<<E1, E2, ..., En>>

Ei = Value |
    Value:Size|
    Value/TypeSpecifierList |
    Value:Size/TypeSpecifierList

@type End = big|little|native

@type Sign = signed | unsigned

@type Type = integer | float | binary

@type Unit = 1|2|...255

End 跟機器有關,預設值為 big,這是 endianess,當資料是 16#12345678,如果是 little-endian,要寫到從0x0000開始的記憶體位址時,就存為 16#78 16#56 16#34 16#12,如果是big-endian,在記憶體中就存為 16#12 16#34 16#56 16#78,最高位元組在位址最低位元。native 則表示是在執行時才由 CPU 決定。

以目前常見的CPU為例:INTEL X86、DEC VAX 使用 LITTLE-ENDIAN 設計;HP、IBM、MOTOROLA 68K 系列使用 BIG-ENDIAN 設計;POWERPC 同時支援兩種格式,稱為 BI-ENDIAN。

Sign 只用在 pattern matching,預設值為 unsigned
Type 預設值為 integer
Unit 的值由 Type 決定,如果 Type 是 integer 或 float,Unit 為 1,如果 Type 是 binary,Unit 則為 8。Size * Unit 的結果,就是整個 binary 的體積,總體積必須要是 8 的倍數。

27> {<<16#12345678:32/big>>, <<16#12345678:32/little>>, <<16#12345678:32/native>>, <<16#12345678:32>>}.
{<<18,52,86,120>>,
 <<120,86,52,18>>,
 <<120,86,52,18>>,
 <<18,52,86,120>>}

從範例可看出,這台機器是使用 little-endian。

參考

Erlang and OTP in Action
Programming Erlang: Software for a Concurrent World

2014年2月25日

SatrIO SM-S220 藍芽印表機連線測試 Android & IOS 配對

SatrIO SM-S220 藍芽印表機連線測試 Android & IOS 配對

在開發行動APP軟體系統,在商務行為上的需求,可能需要即時的印發票、收據,因此輕便型的藍芽印表機則為最方便攜帶及容易使用的列印設備。

這次從廠商那拿到這款Star IO 熱感印表機,型號是SM-S220,是由 Star Micronics所研發的,在官網上也放有安裝及開文件及各相關開發API。可惜目前說明文件只有英文和日文兩種,隨箱也沒附上印表機的操作說明書。

 



我們簡單的測試連線,先用IOS連線配對連線,

1 先設定印表機 -> 從左側打開電源按鈕後,前方的POWER會亮著綠燈,此時先看一樣面版上字樣是否為Bluetooth(iAP) ,如果不是的話,可能ios配對後則無法連線使用。
此時按住MODE鈕不放,ERROR燈號會閃礫5下,然後進入設定畫面,繼續按MODE鈕切換到 Bluetooth(iAP),然後同時按住MODE鈕及FEED鈕, 即會列印一張設定明細,此時設定完成。
2 設定IOS設備的藍芽裝置 -> 在設定中打開藍芽,就可以搜尋到Star Micronics這個裝置,點選未配對,輸入PIN Code,通常是「1111」或「1234」以原廠出廠設定為準。

完成配對後,就可以下載軟體來測試。





----------
接著我們試著來配對Android系統設備,

1 先設定印表機 -> 從左側打開電源按鈕後,前方的POWER會亮著綠燈,此時先看一樣面版上字樣是否為Bluetooth Mode ,如果不是的話,可能Android OS 配對後則無法連線使用。
此時按住MODE鈕不放,ERROR燈號會閃礫5下,然後進入設定畫面,繼續按MODE鈕切換到 Bluetooth Mode,然後同時按住MODE鈕及FEED鈕, 即會列印一張設定明細,此時設定完成。



2 設定Android設備的藍芽裝置 -> 在設定中打開藍芽,就可以搜尋到Star Micronics這個裝置,點選未配對,輸入PIN Code,通常是「1111」或「1234」以原廠出廠設定為準。

手機設定與ios 雷同。


-----------

接著我們試著來安裝Android的開發環境
需要工具如下:
1 JDK6
2 Eclipse
3 Android SDK Manager
4 ADT
5 USB Windows Driver
6 StarIO Android SDK

前5項則不再詳述如何下載安裝,StarIO Android SDK 可由此處下載
http://www.starmicronics.com/support/sdkdocumentation.aspx

下載完解壓縮,打開eclipse將StarIO 這個Project Import進來,接著我們就可以執行來跑一跑,您可以用ADT,也可以接上手機直接安裝來測試一下。

如果只是想測試列印效果,可至 google play 下載demo app https://play.google.com/store/apps/details?id=com.StarMicronics.StarIOSDK&hl=zh_TW 

以下為測試列印的效果:
效果還算清晰,但因為紙張小(大約是2inch寬),能列印的範圍有限。


因為這款列印機可以直接叫用圖檔來列印,因此我拿了一張比較大圖檔來縮印,看它的效果如何。
列印之後,發現文字擠壓破碎,看來效果不是想像中的完美。


那至於文字小又要清晰,應該設定多少呢?預設字18 印出來的效果,還算清晰,中文英文列印都沒問題,若加粗體效果更好。

後來我試著印出10~12 size 的字體,實在小的很不清楚


測試14size 的字型是免強最小又算清晰的字體了。


以上為初步測試列印結果,若對這款藍芽印表機有興趣,可以參考看看!

使用 DexClassLoader 動態載入 DEX


DEX 文件檔

  1. 虛擬器會在應用程式啟動時,動態的從儲存裝置讀取並解壓縮個別的 Classes 到記憶體中,在沒有經過適度優化的架構下,每個獨立的虛擬器行程,都會包含自己一份的相關 Classes 記憶體空間,而這些 Classes 就儲存在 dex 檔案中
  2. dex 檔案會以唯讀方案載入到記憶體中,並跨行程共享
  3. 可以把多個 Classes 檔案整合到一個 dex 檔案中
  4. 通常儲存於 apk 中,解壓縮 apk 檔後,即可以得到 classes.dex 文件檔
  5. 因為 Android 的虛擬機 (Dalvik VM) 是不認識 Java 打出 jar 的 byte code,需要通过 dex 工具來優化轉換成 Dalvik byte code 才行。在 apk 中可以看出:引入其他 Jar 的内容都被打包進了 classes.dex 

為何要動態載入 DEX 文件檔

  1. 當專案日益龐大,所產生的 dex 文件也會隨著變大。但在 Android 2.3 或以下版本中,限制 DEX 文件檔案最大為 5MB,再較新的版本則支援 8 或 16MB。如果您的 app 需支援 Android 2.3 或以下版本,且 DEX 超過 5MB,會出現 LinearAlloc exceeded capacity Problem,此時就需要將 DEX 檔案切割並動態載入
  2. DEX 檔案可跨行程共享,所以如果知道 DEX 中所含的 class 與 method 的話,其實也可以跨 app 共享 

如何產生 DEX 文件檔

  1. 先產生 JAR file:
    (1) 對專案右鍵 → Export → Java → JAR file
    (2) 選擇要輸出的 java file,並選擇儲存位置與檔名
    (3) 下一步到底,產出 JAR file
  2. 將 JAR file 優化換成 Dalvik byte code
    (1) 需使用 ADT 中所提供的 Ant 腳本來打包,版本為 Android SDK 12,可直接下載 platform-tools 與 tools 並解壓到對應目錄
    (2) 將 JAR file 拷貝到 SDK 安装目錄 android-sdk-windows\platform-tools 下,從 DOS 進入這個目錄,執行
    
    dx --dex --output=test.jar TestDex.jar  //dx --dex --output=輸出 輸入
    
    ※ 將 JAR 優化時應該重新打成 JAR (jar → dex → jar),上述指令就是把 TestDex.jar 里面的 .class 文件優化成 .dex 文件然後又打包成 test.jar
    ※ 無法直接加載 dex 文件會出現 "unable to open 'dex' file" Error
  3. 若 Android SDK Build-tools 版本為 19 的,請降版至 18.1,否則編譯時會出現
    
    Unable to execute dex: java.nio.BufferOverflowException
    
  4. 將優化過後的 JAR file 複製到手機中,接著就可以準備 LoadClass
    ※ .dex文件在 4.1 之後,Google 基於安全考量在 DexFile 函數中增加一個驗證文件歸屬權的步骤,故如果直接使用 Environment.getExternalStorageDirectory() 讀取成 file 會出現
    
    java.lang.IllegalArgumentException: Optimized data directory /storage/sdcard0 is not owned by the current user. Shared storage cannot protect your application from code injection attacks.
    
    剩至會出現 NoClassDefFoundError (本篇範例主要是提供 android 2.3 或以下系統解決 LinearAlloc 問題,故暫不考慮 android 4.X 版系統的運行結果,android 4.X 版系統可透過 getDir 來取得 file)
※ res目錄下檔案,AndroidManifest 不須打包成 dex 文件,因為打包成 apk 時就會包含 

動態載入 dex 中 class

※ 動態載入 class 有兩種方法:
(1) PathClassLoader:加載路徑必須在 /data/app 路徑下
(2) DexClassLoader:加載 sdcard 目錄下的 apk 或 jar 文件

這裡示範使用 DexClassLoader 來載入 class

//載入手機中優化過的 JAR
final File optimizedDexOutputPath = new File(Environment.getExternalStorageDirectory().toString() + File.separator + "wasync.jar");

//載入 JAR 中的  class
DexClassLoader cl = new DexClassLoader(optimizedDexOutputPath.getAbsolutePath(), 
  Environment.getExternalStorageDirectory().toString(),
  null,
  KoKoLaApplication.getInstance().getClassLoader());

Class libProviderClazz = null;

try {
 libProviderClazz = cl.loadClass("org.slf4j.LoggerFactory");  //class 完整名稱

 Method getInstance = libProviderClazz.getDeclaredMethod("getLogger", String.class);  //呼叫 class 中 method
 getInstance.setAccessible(true);  //設定 method 是否可訪問,不加會出現 access to method denied
    Object logger = getInstance.invoke(null, classname); //第一個参数為 null 表示此為靜態方法
    
 return logger;

} catch (Exception exception) {
 exception.printStackTrace();
}

應用範圍:

app 連動程式,例如:line 遊戲連動,可直接載入 line 使用者資料,不需再連網路下載使用者資訊,減少使用者等待時間

References

2014年2月23日

用一行 list comprehension 解魔方陣

小朋友的學校給了一個特殊的魔方陣題目,基本上用列舉的方式,每一種可能發生的狀況慢慢列出來,然後排除不合理的狀況,就可以得出結果了。

題目:
空格是 1~14 正整數,數字不會重複出現,且要滿足直與橫的運算式。(我把空格都先加上了變數名稱)

A1 + A2 - A3 = A4
B1 + B2 - B3 = B4
C1 * C2 * C3 = C4

A1 + B1 - C1 = D1
A2 / B2 / C2 = D2
A3 + B3 + C3 = 15

計算:
如果要直接算,應該可以用 1 ~ 14 每個數字的正因數列表,來列出 A2 / B2 / C2 = D2 的所有狀況,另外再列出 A3 + B3 + C3 = 15 的所有狀況,然後再搭配 C1 C2 C3 = C4 應該就能找出解答。

不過我沒耐心慢慢去列舉,就想到其實列舉,並滿足某個條件,正是 list comprehension 最強大的用途。

第一版

L=[{A1,A2,A3,A4,B1,B2,B3,B4,C1,C2,C3,C4,D1,D2}||
     A1 <- lists:seq(1,14),
     A2 <- lists:seq(1,14),
     A3 <- lists:seq(1,14),
     A4 <- lists:seq(1,14),

     B1 <- lists:seq(1,14),
     B2 <- lists:seq(1,14),
     B3 <- lists:seq(1,14),
     B4 <- lists:seq(1,14),

     C1 <- lists:seq(1,14),
     C2 <- lists:seq(1,14),
     C3 <- lists:seq(1,14),
     C4 <- lists:seq(1,14),

     D1 <- lists:seq(1,14),
     D2 <- lists:seq(1,14),

     A1=/=A2,
     A2=/=A3,
     A3=/=A4,
     A4=/=B1,
     B1=/=B2,
     B2=/=B3,
     B3=/=B4,
     B4=/=C1,
     C1=/=C2,
     C2=/=C3,
     C3=/=C4,
     C4=/=D1,
     D1=/=D2,
     D2=/=A1,

     A1+A2-A3=:=A4,
     B1+B2-B3=:=B4,
     C1*C2*C3=:=C4,
     A1+B1-C1=:=D1,
     (A2 div B2) div C2=:=D2,
     A3+B3+C3=:=15
    ].

跑了幾分鐘還沒得到結果,所以就停掉,改第二版

K = lists:seq(1,14).
L=[{A1,A2,A3,A4,B1,B2,B3,B4,C1,C2,C3,C4,D1,D2}||
     A1 <- K,
     A2 <- K--[A1],
     A3 <- K--[A1,A2],
     A4 <- K--[A1,A2,A3],

     B1 <- K--[A1,A2,A3,A4],
     B2 <- K--[A1,A2,A3,A4,B1],
     B3 <- K--[A1,A2,A3,A4,B1,B2],
     B4 <- K--[A1,A2,A3,A4,B1,B2,B3],

     C1 <- K--[A1,A2,A3,A4,B1,B2,B3,B4],
     C2 <- K--[A1,A2,A3,A4,B1,B2,B3,B4,C1],
     C3 <- K--[A1,A2,A3,A4,B1,B2,B3,B4,C1,C2],
     C4 <- K--[A1,A2,A3,A4,B1,B2,B3,B4,C1,C2,C3],

     D1 <- K--[A1,A2,A3,A4,B1,B2,B3,B4,C1,C2,C3,C4],
     D2 <- K--[A1,A2,A3,A4,B1,B2,B3,B4,C1,C2,C3,C4,D1],

     A1+A2-A3=:=A4,
     B1+B2-B3=:=B4,
     C1*C2*C3=:=C4,
     A1+B1-C1=:=D1,
     (A2 div B2) div C2=:=D2,
     A3+B3+C3=:=15
    ].

等了很久還是沒結果,判斷應該是前面列舉出來的結果太多了,改第三版

K = lists:seq(1,14).
L=[{A1,A2,A3,A4,B1,B2,B3,B4,C1,C2,C3,C4,D1,D2}||
     A1 <- K,
     A2 <- K--[A1],
     A3 <- K--[A1,A2],
     A4 <- K--[A1,A2,A3],
     A1+A2-A3=:=A4,

     B1 <- K--[A1,A2,A3,A4],
     B2 <- K--[A1,A2,A3,A4,B1],
     B3 <- K--[A1,A2,A3,A4,B1,B2],
     B4 <- K--[A1,A2,A3,A4,B1,B2,B3],
     B1+B2-B3=:=B4,

     C1 <- K--[A1,A2,A3,A4,B1,B2,B3,B4],
     C2 <- K--[A1,A2,A3,A4,B1,B2,B3,B4,C1],
     C3 <- K--[A1,A2,A3,A4,B1,B2,B3,B4,C1,C2],
     C4 <- K--[A1,A2,A3,A4,B1,B2,B3,B4,C1,C2,C3],
     C1*C2*C3=:=C4,

     D1 <- K--[A1,A2,A3,A4,B1,B2,B3,B4,C1,C2,C3,C4],
     D2 <- K--[A1,A2,A3,A4,B1,B2,B3,B4,C1,C2,C3,C4,D1],

     A1+B1-C1=:=D1,
     (A2 div B2) div C2=:=D2,
     A3+B3+C3=:=15
    ].

測試結果

1> test:resolve().
[{7,14,8,13,10,4,5,9,6,1,2,12,11,3},
 {8,12,7,13,11,4,6,9,5,1,2,10,14,3}]

答案有兩組,但實際上驗算,會發現整數除法有問題,第一組答案是錯的。

因此,我們再加上一個條件,來排除整數除法的問題。

D2*C2*B2=:=A2

這就行了,最終再加上計算的時間統計

resolve() ->
    statistics(runtime),
    statistics(wall_clock),
    K = lists:seq(1,14),
    L=[{A1,A2,A3,A4,B1,B2,B3,B4,C1,C2,C3,C4,D1,D2}||
     A1 <- K,
     A2 <- K--[A1],
     A3 <- K--[A1,A2],
     A4 <- K--[A1,A2,A3],
     A1+A2-A3=:=A4,

     B1 <- K--[A1,A2,A3,A4],
     B2 <- K--[A1,A2,A3,A4,B1],
     B3 <- K--[A1,A2,A3,A4,B1,B2],
     B4 <- K--[A1,A2,A3,A4,B1,B2,B3],
     B1+B2-B3=:=B4,

     C1 <- K--[A1,A2,A3,A4,B1,B2,B3,B4],
     C2 <- K--[A1,A2,A3,A4,B1,B2,B3,B4,C1],
     C3 <- K--[A1,A2,A3,A4,B1,B2,B3,B4,C1,C2],
     C4 <- K--[A1,A2,A3,A4,B1,B2,B3,B4,C1,C2,C3],
     C1*C2*C3=:=C4,

     D1 <- K--[A1,A2,A3,A4,B1,B2,B3,B4,C1,C2,C3,C4],
     D2 <- K--[A1,A2,A3,A4,B1,B2,B3,B4,C1,C2,C3,C4,D1],

     A1+B1-C1=:=D1,
     (A2 div B2) div C2=:=D2,
     D2*C2*B2=:=A2,
     A3+B3+C3=:=15
    ],
    {_, Time1} = statistics(runtime),
    {_, Time2} = statistics(wall_clock),
    io:format("runtime=~p wall_clock=~p ~n", [Time1, Time2]),
    L.

測試結果

1> test:resolve().
runtime=96284 wall_clock=104801
[{8,12,7,13,11,4,6,9,5,1,2,10,14,3}]

最後答案就是

===========

原本的計算時間花太久了,今天想到應該要先計算這兩個式子,才能有效在前面就把篩選的可能狀況減少

A2 / B2 / C2 = D2
C1 * C2 * C3 = C4

再修改程式,首先把 =:= 完全相等 換成 == 邏輯相等,也不用整數除法 div,改為 / ,接下來再調整運算的順序。

resolve() ->
    statistics(runtime),
    statistics(wall_clock),
    K = lists:seq(1,14),
    L=[{A1,A2,A3,A4,B1,B2,B3,B4,C1,C2,C3,C4,D1,D2}||
       A2 <- K,
       B2 <- K--[A2],
       C2 <- K--[A2,B2],
       D2 <- K--[A2,B2,C2],
       (A2 / B2) / C2==D2,

       C1 <- K--[A2,B2,C2,D2],
       C3 <- K--[A2,B2,C2,D2,C1],
       C4 <- K--[A2,B2,C2,D2,C1,C3],
       C1*C2*C3==C4,

       A3 <- K--[A2,B2,C2,D2,C1,C3,C4],
       B3 <- K--[A2,B2,C2,D2,C1,C3,C4,A3],
       A3+B3+C3==15,

       A1 <- K--[A2,B2,C2,D2,C1,C3,C4,A3,B3],
       A4 <- K--[A2,B2,C2,D2,C1,C3,C4,A3,B3,A1],
       A1+A2-A3==A4,

       B1 <- K--[A2,B2,C2,D2,C1,C3,C4,A3,B3,A1,A4],
       B4 <- K--[A2,B2,C2,D2,C1,C3,C4,A3,B3,A1,A4,B1],
       B1+B2-B3==B4,

       D1 <- K--[A2,B2,C2,D2,C1,C3,C4,A3,B3,A1,A4,B1,B4],
       A1+B1-C1==D1
      ],
    {_, Time1} = statistics(runtime),
    {_, Time2} = statistics(wall_clock),
    io:format("runtime=~p wall_clock=~p ~n", [Time1, Time2]),
    L.

測試結果很驚人,花掉的時間變得非常短。

1> test:resolve().
runtime=0 wall_clock=0
[{8,12,7,13,11,4,6,9,5,1,2,10,14,3}]
2> test:resolve().
runtime=15 wall_clock=15
[{8,12,7,13,11,4,6,9,5,1,2,10,14,3}]

結論,寫程式還是需要 follow 手算的想法,好的演算法,才能有效減少電腦計算的時間。

在Objective-C中使用Block,以及在Block中存取Block外定義的變數之探討

前言

Block是非標準的C語言擴充功能,用以在C語言中定義類似匿名函式的區塊。 與其他語言的匿名函式類似,Block能夠存放在變數中傳遞,在適當的時機再執行。Block非常適合用於撰寫需要進行非同步處理的程式,使多執行緒程式的開發更為容易。

Block是定義在C層級的語法。因此,在C++與Objective-C語言中也能夠使用(雖然,實際上還是得看編譯工具是否支援)。

簡單的Block範例如下:

//宣告一個block
^(int x, int y){
    NSLog("in block...");
    return x * y;
};

Block可以想成是一個沒有名稱的匿名函式,可以再block後面加上參數立即執行:

//宣告一個block並立即執行 (不常這樣使用)
int result = ^(int x, int y){
    NSLog("in block...");
    return x * y;
}(5,6);

不過,Block比較常見的使用方式, 是存放在變數中,透過變數來傳遞,並在適當的時候才執行。

能夠存放Block的變數為Block型態的變數。

Block型態的變數,其宣告較為複雜,格式如下:

返回值 (^block變數名稱)(參數1,參數2,...) = /* block語法 */;

使用Block型態的變數來執行Block的範例如下:

//宣告block形態的變數myMulFunc,並指向一個block
int (^myMathFunc)(int, int) = ^(int x, int y){
    return x * y;
};

//執行myMathFunc
int result = myMathFunc(1,2);

使用typedef簡化Block型態變數宣告

由於Block型態變數的宣告較為複雜,若大量地在程式碼中重複出現,勢必大幅降低程式的可讀性。 因此,若是返回值與參數列同類型的Block形態變數,可以使用typedef來簡化其宣告:

typedef int (^MyMathFunction)(int,int);

//使用typedef宣告的MyMathFunction
MyMathFunction myMul = ^int(int a, int b) {
    return a*b;
};

MyMathFunction myDiv = ^int(int a, int b) {
    return a/b;
};

在Block中讀取Block外定義的變數

int outVal = 10;
void (^myBlock)(void) = ^{
    NSLog(@"MyBlock, outVal:%i",outVal);
};
myBlock();

Output:

2014-02-22 10:25:13.240 BlockTest[740:70b] MyBlock, outVal:10

在Block中可以讀取到Block外定義的變數。乍看之下非常直覺,但其實這之中卻也隱藏著一些問題;

由於Block並不一定是在被定義之後就馬上被執行,因此,若是在Block中讀取Block外定義的變數,該變數的生命週期很有可能早已經結束。Block如何處理這種情況呢?我們先來看看,在Block中讀取Block外定義的變數時的其他情況:

在block中修改Block外定義的變數

結果:此行為是不被允許的,且在編譯時期就會被編譯器阻止。

在block中讀取Block外定義的變數,該變數內容不斷異動的情況

int outVal = 10;
/* do something... */
outVal = 20;
void (^myBlock)(void) = ^{
    NSLog(@"MyBlock, outVal:%i",outVal);
};
outVal = 30;
myBlock();

Output:

2014-02-22 10:57:36.252 BlockTest[1012:70b] MyBlock, outVal:20

結果:在Block中讀取Block外定義的變數,若此變數值不斷變動, 則Block中讀取到的值,為Block定義當下擷取到到的值。

在block中讀取Block外定義的變數,執行時該變數生命週期已經結束

@implementation ViewController {
    int (^myBlock)(void);
}

-(void)setupMyFunction {
    int c = 100;
    myBlock = ^int{
        return c;
    };
    c = 200;
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    [self setupMyFunction];
    int result = myBlock();
    NSLog(@"result=%i",result);
}

Output:

2014-02-23 15:13:15.347 BlockTest[3867:70b] result=100

結果:在Block中讀取Block外定義的變數,即使執行時該變數生命週期已經結束也能讀取到值,其值為Block定義當下擷取到到的值。

至此,可以做個簡單的結論:在Block中可以讀取Block外定義的變數,也無需顧慮該變數的生命週期,但有些限制:

1. 無法在Block中修改該變數值。
2. 讀取到的值為Block產生時的值,且不再變動。

在Apple的官方文件的Working with Blocks中也有提到:Blocks Can Capture Values from the Enclosing Scope ,以及:the value captured by the block is unaffected.

在Block中讀取Block外定義的物件

上述的探討,皆是關於基本數值形態的變數。若在Block中讀取Block外定義的物件形態之變數,結果又會是如何? 我們以NSMutableDictionary來測試看看以下幾種情況:

在Block中讀取Block外定義的物件變數,該變數不改變參考但物件內容異動

NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithCapacity:4];
dict[@"val"] = @25;
void (^myBlock)(void) = ^{
    NSLog(@"MyBlock, dict[val]:%i",[dict[@"val"] integerValue]);
};
dict[@"val"] = @55;
myBlock();

Output:

2014-02-22 11:00:36.119 BlockTest[1040:70b] MyBlock, dict[val]:55

結果:在Block中讀取Block外定義的可變物件變數,該變數不改變參考但物件內容異動,則Block中讀取到的是異動後的內容。

在Block中讀取Block外定義的物件變數,而該變數改變參考

NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithCapacity:4];
dict[@"val"] = @25;
void (^myBlock)(void) = ^{
    NSLog(@"MyBlock, dict[val]:%i",[dict[@"val"] integerValue]);
};
dict = [NSMutableDictionary dictionaryWithCapacity:4];
dict[@"val"] = @55;
myBlock();

Output:

2014-02-22 11:02:01.678 BlockTest[1071:70b] MyBlock, dict[val]:25

結果:在Block中讀取Block外定義的可變物件變數,該變數改變參考,則Block中讀取到Block產生當下所參考的舊物件,而非新物件。

在Block中修改Block外定義的物件變數,修改物件內容但不改變物件參考

NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithCapacity:4];
dict[@"val"] = @25;
void (^myBlock)(void) = ^{
    dict[@"val"] = @55;
    NSLog(@"MyBlock, dict[val]:%i",[dict[@"val"] integerValue]);
};
NSLog(@"Before executing block, dict[val]:%i",[dict[@"val"] integerValue]);
myBlock();
NSLog(@"After executing block, dict[val]:%i",[dict[@"val"] integerValue]);

Output:

2014-02-23 15:35:33.591 BlockTest[4052:70b] Before executing block, dict[val]:25
2014-02-23 15:35:33.593 BlockTest[4052:70b] MyBlock, dict[val]:55
2014-02-23 15:35:33.593 BlockTest[4052:70b] After executing block, dict[val]:55

結果:在Block中修改Block外定義的物件變數,則Block外該物件內容也會跟著被改變。

在Block中修改Block外定義的物件變數之參考

結果:此行為是不被允許的,且在編譯時期就會被編譯器阻止。

至此,若你熟悉指標,以上結果其實不難理解:指向某個物件的變數,猶如整數形態變數,其實為所指向的物件的記憶體位址。因此,在Block不能改變其指向的物件(不能改變其值:記憶體位址),但是可以透過該變數修改該物件。

因此,在Block中存取整數形態的變數,與存取指向某個物件的變數,其狀況是一樣的。

在Block中讀取Block外定義的物件變數,執行時該變數生命週期已經結束

然而,還有一個狀況需要探究,那就是在block中讀取Block外定義的物件變數,執行時該變數生命週期已經結束的情形:

@implementation ViewController {
    int (^myBlock)(void);
}

-(void)setupMyFunction {
    NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithCapacity:4];
    dict[@"val"] = @25;
    myBlock = ^int{
        return [dict[@"val"] integerValue];
    };
    dict[@"val"] = @55;
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    [self setupMyFunction];
    int result = myBlock();
    NSLog(@"result=%i",result);
}

Output:

2014-02-23 15:51:02.471 BlockTest[4157:70b] result=55

如上,程式可以順利執行。

其實,當Block在擷取Block外定義的物件變數時,也會保留住該變數,增加其保留計數,直到該Block執行完畢被釋放後,才隨著一起被釋放。 因此,雖然區域變數dict結束生命週期後,該NSMutableDictionary物件還有Block參考,因此不會被回收。

此行為,其實與將一個物件加入NSMutableArray的狀況類似。

在Block中存取實例變數(instance varaible)

@implementation ViewController {
    int myValue;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    myValue = 123;
    void(^myBlock)(void) = ^{
        NSLog(@"myValue in block:%i",myValue);
        myValue = 456;
    };
    NSLog(@"Before executing block, myValue=%i",myValue);
    myBlock();
    NSLog(@"After executing block, myValue=%i",myValue);
}

Output:

2014-02-23 16:13:35.460 BlockTest[4327:70b] Before executing block, myValue=123
2014-02-23 16:13:35.461 BlockTest[4327:70b] myValue in block:123
2014-02-23 16:13:35.462 BlockTest[4327:70b] After executing block, myValue=456

在Block中可以存取實例變數。即便實例變數是整數形態變數,在Block中也可以修改,而在Block外也能讀取到修改後的結果。這是因為存取實例變數時都是透過實例本身(self變數),只是self被省略,原語法如下:

self->myValue = 123;
void(^myBlock)(void) = ^{
    NSLog(@"myValue in block:%i",self->myValue);
    self->myValue = 456;
};

因此,Block實際上保留住的是self物件,因為存取實例變數都必須透過self。

而其他規則,則與上述存取物件變數的情況無異。

結語

許多新版的API皆已採用Block,像是處理網路存取的NSURLConnection:

+ (void)sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler

Block的使用的確方便,但若對於Block如何處理Block外的定義的變數一知半解的話,對於往後除錯時,反而會產生更多的困擾。這是在使用Block之前,務必要了解與熟悉的事情。

Refernce

Working with Blocks

Block Programming Topics

Block (programming))

2014年2月20日

erlang basics - list comprehension

list comprehension 是 erlang 裡面很重要的一項功能,它能有效縮減程式碼,讓程式更容易理解。quick sort wiki 收集了很多語言的實作,erlang 很神奇地以三行程式碼大勝。

語法

以下是數學在描述一個集合時,可列出屬於 N 的自然數集合,且大於 0 的所有 x,也就是所有正整數的集合。

{x | x∈N, x>0}

在 erlang 中,可用 list comprehension 語法,描述出類似的概念。 <- 是生成器,|| 的右邊,除了生成器之外,其他的都是約束條件(filter)

[X || X <- ListOfIntegers, X>0]

以下是 list comprehension 的一般形式,Qualifier 可以是生成器 generator 或是 filter

[X || Qualifier1, Qualifier2, ...]
generator: X <- L
filter: 元素的條件判斷

範例

以下可得到,所有正偶數平方的 list

[math:pow(X,2) || X <- ListOfIntegers, X>0, X rem 2 ==0]

以下可取得面積大於等於 10 的矩形 list。

[{area, H * W} || {rectangle, H, W} <- Shape, H*W>=10 ]

lists:map 與 list comprehension

list comprehension 可用來快速建立一組匹配的 list。

要得到 L 裡面所有元素的兩倍的 list,可以用 lists:map ,也可以直接用 list comprehension 取代處理。

(erlangotp@yaoclNB)5> L=[1,2,3,4,5].
[1,2,3,4,5]
(erlangotp@yaoclNB)6> lists:map(fun(X) -> 2*X end, L).
[2,4,6,8,10]
(erlangotp@yaoclNB)7> [2*X || X <- L].
[2,4,6,8,10]
map(F,L) -> [F(X) || X <- L]

tuple 元素的處理

當 list 裡的元素是 tuple 時,同樣也能使用 list comprehension,來處理。

(erlangotp@yaoclNB)8> Buy = [{oranges, 4}, {newspaper, 1}, {apples, 10}, {pears, 6}, {milk, 5}].
[{oranges,4},{newspaper,1},{apples,10},{pears,6},{milk,5}]
(erlangotp@yaoclNB)9> [{Name, 2*Number} || {Name, Number} <- Buy].
[{oranges,8},{newspaper,2},{apples,20},{pears,12},{milk,10}]

quick sort

quick sort wiki 收集了很多語言的實作,erlang 很神奇地以三行程式碼大勝。

qsort([]) -> [];
qsort([Pivot|T]) ->
    qsort([X || X <-T, X<Pivot]) ++ [Pivot] ++ qsort([X || X <-T, X>=Pivot]).

測試

(erlangotp@yaoclNB)3> L=[23,6,2,9,120,15].
[23,6,2,9,120,15]
(erlangotp@yaoclNB)4> test:qsort(L).
[2,6,9,15,23,120]

運作過程

  1. L = [23,6,2,9,120,15] 符合第二個子句,L 會被切割成 [Pivot|T],所以 Pivot = 23, T = [6,2,9,120,15]

  2. qsort([X || X <-T, X<Pivot]) 就等於 qsort([X || X <-[6,2,9,120,15], X<23]),也就是把 T 裡面所有小於 23 的元素都取出來產生一個新的 list,然後再放進 qsort 再處理一次,也就是 qsort([6,2,9,15])

  3. qsort([X || X <-T, X>=Pivot]) 就等於 qsort([X || X <-[6,2,9,120,15], X>=23]),也就是把 T 裡面所有大於等於 23 的元素都取出來產生一個新的 list,然後再放進 qsort 再處理一次,也就是 qsort([120])

  4. 第一次處理後的結果為 qsort([6,2,9,15]) ++ [23] ++ qsort([120])

  5. 然後再處理 qsort([6,2,9,15]),同樣符合第二個子句,Pivot = 6,T = [2,9,15]

  6. 結果為 qsort([2]) ++ [6] ++ qsort([9,15])

  7. qsort([2]) 同樣符合第二個子句,Pivot = 2,T = [],結果為 qsort([]) ++ [2] ++ qsort([])

  8. qsort([]) 符合第一個子句,結果為 []

因此整個運作的過程可寫成
qsort([23,6,2,9,120,15])
= qsort([6,2,9,15]) ++ [23] ++ qsort([120])
= qsort([2]) ++ [6] ++ qsort([9,15]) ++ [23] ++ [] + [120] + []
= [] + [2] + [] ++ [6] ++ [] ++ [9] ++ qsort([15]) ++ [23] ++ [] ++ [120] ++ []
= [] ++ [2] ++ [] ++ [6] ++ [] ++ [9] ++ [] ++ [15] ++ [] ++ [23] ++ [] ++ [120] ++ []
= [2,6,9,15,23,120]

畢氏定理

畢氏定理:直角三角形的邊長 {A,B,C},必須滿足兩股的平方和要等於斜邊的平方這個條件,A^2 + B^2 = C^2。

lists:seq(1,N) 可取得 1 到 N 所有正整數的 list。

pythag(N) 可以取得小於N,並滿足畢氏定理的所有正整數 {A,B,C} 的集合 list。

pythag(N) ->
    [ {A,B,C} ||
      A <- lists:seq(1,N),
      B <- lists:seq(1,N),
      C <- lists:seq(1,N),
      A+B+C =< N,
      A*A+B*B =:= C*C
    ].

測試

(erlangotp@yaoclNB)9> test:pythag(16).
[{3,4,5},{4,3,5}]
(erlangotp@yaoclNB)10> test:pythag(30).
[{3,4,5},{4,3,5},{5,12,13},{6,8,10},{8,6,10},{12,5,13}]
(erlangotp@yaoclNB)11> test:pythag(40).
[{3,4,5},
 {4,3,5},
 {5,12,13},
 {6,8,10},
 {8,6,10},
 {8,15,17},
 {9,12,15},
 {12,5,13},
 {12,9,15},
 {15,8,17}]

anagram 回文

perms 可取得一個英文字串,能找到的字母的所有排列組合。

perms([]) ->
    [[]];
perms(L) ->
    [[H|T] || H <- L, T <- perms(L--[H])].

測試

(erlangotp@yaoclNB)12> test:perms("123").
["123","132","213","231","312","321"]
(erlangotp@yaoclNB)13> test:perms("cats").
["cats","cast","ctas","ctsa","csat","csta","acts","acst",
 "atcs","atsc","asct","astc","tcas","tcsa","tacs","tasc",
 "tsca","tsac","scat","scta","sact","satc","stca","stac"]

運作過程

  1. perms("123") 因為字串就等同於 list,所以滿足第二個子句,有三種狀況
    1.1 H 為 "1",T 為 perms("23")
    1.2 H 為 "2", T 為 perms("13")
    1.3 H 為 "3", T 為 perms("12")

  2. perms("23") 滿足第二個子句,有兩種狀況
    2.1 H 為 "2", T 為 perms("3")
    2.2 H 為 "3", T 為 perms("2")

  3. perms("3") 滿足第二個子句,有一種狀況
    3.1 H 為 "3", T 為 perms(""), T 就滿足第一個子句,結果為 [[]],因此 [H|T] 就是 ["3"]

  4. 回到 2.1,H 為 "2", T 為 "3",因此 [H|T] 就是 ["23"],而2.2 的結果為 ["32"]

  5. 回到 1.1,H 為 "1",T 為 "23" 或 "32",結果為 ["123", "132"]

  6. 加上 1.2 與 1.3 的結果為 ["123","132","213","231","312","321"]

以自然順序建立 list

建立 list 最有效率的方式,就是把新的元素加入到 list 的頭部,我們常會看到有這樣的程式碼,這會走入 list,取出 list 的頭部,然計算得到 H1,最後將 H1 加入 Result 中,當輸入的資料耗盡,會滿足第二個條件,此函數會輸出 Result 結果。

some_function([H|T], ..., Result, ...) ->
    H1 = ... H ...,
    some_function(T, ..., [H1|Result], ...);
some_function([], ..., Result, ...) ->
    {..., Result, ...}.

注意的事項

  1. 增加元素一定要放在 list 的頭部
  2. 從頭部取出元素,處理後加入到另一個 list 的頭部,這會讓 Result list 跟輸入的 list 的元素順序相反。
  3. 如果順序一定要一樣,就呼叫 lists:reverse/1 進行反轉
  4. 反轉 list 要呼叫 lists:reverse/1,絕對不能用 List ++ [H],這會造成效能問題

從一個函數取得兩個 list

如果要寫一個函數,將 list 分為 奇數跟偶數 兩個 list,直觀的作法,就直接用 list comprehension 處理兩次。

odds_and_evens(L) ->
    Odds = [X || X<-L, (X rem 2) =:= 1],
    Evens = [X || X<-L, (X rem 2) =:= 0],
    {Odds, Evens}.

但 traverse list 兩次,並不是個好方法,應該改為 traverse 一次,然後根據 X rem 2 的 結果,將 H 放入不同的 result list 中,最後再以 lists:reverse 把順序反轉。

odds_and_evens_acc(L) ->
    odds_and_evens_acc(L, [], []).

odds_and_evens_acc([H|T], Odds, Evens) ->
    case (H rem 2) of
        1 -> odds_and_evens_acc( T, [H|Odds], Evens);
        0 -> odds_and_evens_acc( T, Odds, [H|Evens])
    end;

odds_and_evens_acc([], Odds, Evens) ->
    {lists:reverse(Odds), lists:reverse(Evens)}.

測試

(erlangotp@yaoclNB)1> test:odds_and_evens([1,2,3,4,5,6,7,8]).
{[1,3,5,7],[2,4,6,8]}
(erlangotp@yaoclNB)2> test:odds_and_evens_acc([1,2,3,4,5,6,7,8]).
{[1,3,5,7],[2,4,6,8]}

結語

當我們需要滿足某個條件的集合時,我們要先思考到在數學的集合表示式,然後再想到,以 list comprehension 來將這個集合實作出來。

參考

Erlang and OTP in Action
Programming Erlang: Software for a Concurrent World

2014年2月19日

LinearAlloc exceeded capacity Problem


在開發 Android App 的過程中,專案會因為功能需求引入一些 opensource 套件包、lib、jar等資源,當專案日益龐大,就有可能發生 LinearAlloc exceeded capacity 的問題。本篇就來簡單介紹一下原因以及可行的解決方法
首先,先簡單介紹 LinearAlloc

LinearAlloc (線性分配器)

  1. source code:android_src/dalvik/vm/ 下 LinearAlloc.h 和 LinearAlloc.c
  2. 目的:主要用来管理 Dalvik 中 class 加載時的内存,使其簡單、快速地分配内存。簡單來說就是讓app在執行時,減少系統內存的佔用。
  3. 屬於 Android Running 層,由 Dalvik Virtual Machine 執行

Dalvik 與 DEX 文件檔

  1. Android 上所提供的 ByteCode 虛擬器
  2. 目標:
    (1) 要能運作在效能需求不高、較少的記憶體,與使用慢速的內部 Flash 儲存
    (2) 作業系統要能支援虛擬記憶體,執行行程與執行緒(Process/Thread)
    (3) 要具備使用者帳號安全管理機制(UID-based security mechanisms),並能透過 GNU C 編譯器編譯後運作在包括 Linux,BSD 與 Mac OS X 等 Unix 環境下
    (4) 支援 Little-endian 與 Big-endian 處理器執行環境
    (5) Class Data 尤其是共用的 ByteCode,要能跨行程共用,節省系統整體記憶體需求(參考Process Memory Map 目前包括相關的 jar 與所包含的 Bytecode Dex 檔案,都能跨行程在不同的 Dalvik 行程中共用
    (6) 減少 Dalvik 應用程式載入啟動的成本,加速應用程式的反應時間
    (7) Class Data 儲存在個別的檔案中,導致額外的儲存成本,針對儲存空間的需求需要多加注意
    (8) 要從 Class Data 中讀取出資料數值(例如:整數或是字串)會增加不必要的成本,評估如何採用 C 的形式存取,會是有必要的
    (9) ByteCode 的驗證雖耗時但卻是必要的,應該試著在程式執行前完成驗證
    (10) 透過快速指令集與機制的優化進行 ByteCode 的最佳化對於執行效率與電池壽命是相當重要的
    (11) 為了安全需求,執行中的行程不能修改共享的程式碼
  3. 核心的函式庫主要是承襲 Open Source 的 Java SE 實作 Apache Harmony 而來,並基於 Open Source 中相關 OpenSSL, zlib 與 ICU(International Components for Unicode) 的計畫成果
    ※ 一般虛擬器的設計,會在應用程式啟動時,動態的從儲存裝置讀取並解壓縮個別的 Classes 到記憶體中,在沒有經過適度優化的架構下,每個獨立的虛擬器行程,都會包含自己一份的相關 Classes 記憶體空間
  4. 基於上述目標, Dalvik 在設計時,作了以下的決定:
    (1) 可以把多個 Classes 檔案整合到一個 DEX 檔案中
    (2) DEX 檔案會以唯讀方案載入到記憶體中,並跨行程共享
    (3) 因應支援的系統架構,調整 Byte Ordering 與 Word Alignment
    (4) ByteCode 驗證對所有的 Classes 都是必要的,且會儘可能進行事先的驗證加速系統效率
    (5) 對於需修改 ByteCode 的最佳化動作,會在執行前完成

APK文件檔

  1. 屬於一種壓縮檔案,可用 zip 解壓縮
  2. 包含:
    (1) META-INF:清單信息(Manifest file),應用程序的證書和授權信息,SHA-1 信息資源列表
    (2) res:APK所需要的資源文件夾
    (3) AndroidManifest.xml:一個傳統的 Android 清單文件,用於描述該應用程序的名字、版本號、所需許可權、註冊的服務、連結的其他應用程序。該文件使用 XML 文件格式,可以編譯為二進制的XML
    (4) classes.dex:classes 文件通過 DEX 編譯後的文件格式,用於在 Dalvik 虛擬機上運行的主要代碼部分
    (5) resources.arsc:編譯後的二進制資源文件

LinearAlloc exceeded capacity Problem

  1. 原因:app 在安裝時會將 apk 中 dex 文件安裝到 dalvik-cache 目錄下,在安裝過程中發生的錯誤。在 Android 2.2 與 2.3 版本中,LinearAlloc buffer 只有 5MB,但較新的版本 buffer 則有 8 或 16MB ,也就是說如果您的app要支援 Android 2.3 或以下版本, dex 文件檔必須小於 5MB,如果超出則會出現類似以下的錯誤訊息

12-17 14:59:56.959: D/PackageManager(183): Scanning package tw.com.maxkit.android.kokola.mobile.activity
12-17 14:59:56.959: E/PackageManager(183): Package tw.com.maxkit.android.kokola.mobile.activity has mismatched uid: 10091 on disk, 10098 in settings
12-17 14:59:56.959: I/PackageManager(183): Linking native library dir for /data/app/tw.com.maxkit.android.kokola.mobile.activity-1.apk
12-17 14:59:59.639: W/dalvikvm(1891): Superclass of 'Lorg/apache/http/params/SyncBasicHttpParams;' is final 'Lorg/apache/http/params/BasicHttpParams;'
12-17 14:59:59.989: E/AlarmManagerService(183): android_server_AlarmManagerService_set to type=1, 1387263691.768000000
12-17 14:59:59.989: E/AlarmManagerService(183): android_server_AlarmManagerService_set to type=1, 1387263660.000000000
12-17 14:59:59.989: D/PowerManagerService(183): acquireWakeLock flags=0x1 tag=AlarmManager
12-17 15:00:00.009: D/DigitalClock(32302): syw.action = android.intent.action.TIME_TICK
12-17 15:00:00.029: D/PowerManagerService(183): releaseWakeLock flags=0x1 tag=AlarmManager
12-17 15:00:02.939: E/dalvikvm(1891): LinearAlloc exceeded capacity (5242880), last=100
12-17 15:00:02.939: E/dalvikvm(1891): VM aborting
12-17 15:00:03.049: I/DEBUG(521): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
12-17 15:00:03.049: I/DEBUG(521): Build fingerprint: 'samsung/GT-S7500/GT-S7500:2.3.6/GINGERBREAD/ZSLD3:user/release-keys'
12-17 15:00:03.049: I/DEBUG(521): pid: 1891, tid: 1891  >>> /system/bin/dexopt <<<
12-17 15:00:03.049: I/DEBUG(521): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr deadd00d
12-17 15:00:03.049: I/DEBUG(521):  r0 fffffe7c  r1 deadd00d  r2 00000026  r3 00000000
12-17 15:00:03.049: I/DEBUG(521):  r4 8009d5b0  r5 004fffd4  r6 0050003c  r7 0001a82c
12-17 15:00:03.049: I/DEBUG(521):  r8 00000064  r9 00000001  10 00000000  fp befb99b8
12-17 15:00:03.049: I/DEBUG(521):  ip 8009d6bc  sp befb96e8  lr afd192e9  pc 80043ae4  cpsr 20000030
12-17 15:00:03.049: I/DEBUG(521):  d0  74726f6261204d69  d1  646564656563786e
12-17 15:00:03.049: I/DEBUG(521):  d2  0000000000000067  d3  000000000000000a
12-17 15:00:03.049: I/DEBUG(521):  d4  0000000200000002  d5  0000000200000002
12-17 15:00:03.059: I/DEBUG(521):  d6  00000002402c5020  d7  0000000200000002
12-17 15:00:03.059: I/DEBUG(521):  d8  0000000000000000  d9  0000000000000000
12-17 15:00:03.059: I/DEBUG(521):  d10 0000000000000000  d11 0000000000000000
12-17 15:00:03.059: I/DEBUG(521):  d12 0000000000000000  d13 0000000000000000
12-17 15:00:03.059: I/DEBUG(521):  d14 0000000000000000  d15 0000000000000000
12-17 15:00:03.059: I/DEBUG(521):  d16 0000000200000002  d17 0000000200000002
12-17 15:00:03.059: I/DEBUG(521):  d18 0000000200000002  d19 0000000200000002
12-17 15:00:03.059: I/DEBUG(521):  d20 0000000c00000002  d21 0000000200000002
12-17 15:00:03.059: I/DEBUG(521):  d22 402926d000000002  d23 4014ccf04014e928
12-17 15:00:03.059: I/DEBUG(521):  d24 0000000000000000  d25 0000000000000000
12-17 15:00:03.069: I/DEBUG(521):  d26 0000000000000000  d27 0000000000000000
12-17 15:00:03.069: I/DEBUG(521):  d28 0000000000000000  d29 0000000000000000
12-17 15:00:03.069: I/DEBUG(521):  d30 0000000000000000  d31 0000000000000000
12-17 15:00:03.069: I/DEBUG(521):  scr 00000000
12-17 15:00:03.109: I/DEBUG(521):          #00  pc 00043ae4  /system/lib/libdvm.so (dvmAbort)
12-17 15:00:03.109: I/DEBUG(521):          #01  pc 0004b85e  /system/lib/libdvm.so (dvmLinearAlloc)
12-17 15:00:03.109: I/DEBUG(521):          #02  pc 0006535e  /system/lib/libdvm.so
12-17 15:00:03.109: I/DEBUG(521):          #03  pc 00065ca0  /system/lib/libdvm.so (dvmLinkClass)
12-17 15:00:03.109: I/DEBUG(521):          #04  pc 00067210  /system/lib/libdvm.so
12-17 15:00:03.109: I/DEBUG(521):          #05  pc 000673e6  /system/lib/libdvm.so (dvmFindSystemClassNoInit)
12-17 15:00:03.109: I/DEBUG(521):          #06  pc 00066c5e  /system/lib/libdvm.so (dvmFindClassNoInit)
12-17 15:00:03.109: I/DEBUG(521):          #07  pc 00059908  /system/lib/libdvm.so (dvmOptResolveClass)
12-17 15:00:03.109: I/DEBUG(521):          #08  pc 00055f66  /system/lib/libdvm.so
12-17 15:00:03.109: I/DEBUG(521):          #09  pc 00057e64  /system/lib/libdvm.so
12-17 15:00:03.109: I/DEBUG(521):          #10  pc 0005803a  /system/lib/libdvm.so (dvmVerifyCodeFlow)
12-17 15:00:03.109: I/DEBUG(521):          #11  pc 000597c8  /system/lib/libdvm.so
12-17 15:00:03.109: I/DEBUG(521):          #12  pc 0005982a  /system/lib/libdvm.so (dvmVerifyClass)
12-17 15:00:03.109: I/DEBUG(521):          #13  pc 0005875a  /system/lib/libdvm.so
12-17 15:00:03.119: I/DEBUG(521):          #14  pc 0005880e  /system/lib/libdvm.so
12-17 15:00:03.119: I/DEBUG(521):          #15  pc 0005890a  /system/lib/libdvm.so
12-17 15:00:03.119: I/DEBUG(521):          #16  pc 00058af4  /system/lib/libdvm.so (dvmContinueOptimization)
12-17 15:00:03.119: I/DEBUG(521):          #17  pc 00008c46  /system/bin/dexopt
12-17 15:00:03.119: I/DEBUG(521):          #18  pc 00008d32  /system/bin/dexopt
12-17 15:00:03.119: I/DEBUG(521):          #19  pc 00008dc8  /system/bin/dexopt
12-17 15:00:03.119: I/DEBUG(521):          #20  pc 000091ce  /system/bin/dexopt
12-17 15:00:03.119: I/DEBUG(521):          #21  pc 00014c62  /system/lib/libc.so (__libc_init)
12-17 15:00:03.119: I/DEBUG(521): libc base address: afd00000
12-17 15:00:03.119: I/DEBUG(521): code around pc:
12-17 15:00:03.119: I/DEBUG(521): 80043ac4 447a4479 f7d34c0b 2000edca eecef7d3 
12-17 15:00:03.119: I/DEBUG(521): 80043ad4 447c4809 6bdb5823 4798b103 22264902 
12-17 15:00:03.119: I/DEBUG(521): 80043ae4 f7d3700a bf00ef36 deadd00d 00041204 
12-17 15:00:03.119: I/DEBUG(521): 80043af4 00042a09 00059ad6 fffffe7c 4b09b40e 
12-17 15:00:03.119: I/DEBUG(521): 80043b04 4c09b517 aa05447b f852591b 6b5b1b04 
12-17 15:00:03.119: I/DEBUG(521): code around lr:
12-17 15:00:03.119: I/DEBUG(521): afd192c8 4a0e4b0d e92d447b 589c41f0 26004680 
12-17 15:00:03.119: I/DEBUG(521): afd192d8 686768a5 f9b5e006 b113300c 47c04628 
12-17 15:00:03.119: I/DEBUG(521): afd192e8 35544306 37fff117 6824d5f5 d1ef2c00 
12-17 15:00:03.119: I/DEBUG(521): afd192f8 e8bd4630 bf0081f0 00028258 ffffff84 
12-17 15:00:03.119: I/DEBUG(521): afd19308 b086b570 f602fb01 9004460c a804a901 
12-17 15:00:03.119: I/DEBUG(521): stack:
12-17 15:00:03.119: I/DEBUG(521):     befb96a8  00000002  
12-17 15:00:03.119: I/DEBUG(521):     befb96ac  00015270  
12-17 15:00:03.119: I/DEBUG(521):     befb96b0  0001a828  
12-17 15:00:03.119: I/DEBUG(521):     befb96b4  004ffdc8  
12-17 15:00:03.119: I/DEBUG(521):     befb96b8  afd4272c  
12-17 15:00:03.119: I/DEBUG(521):     befb96bc  afd426d8  
12-17 15:00:03.119: I/DEBUG(521):     befb96c0  00000000  
12-17 15:00:03.119: I/DEBUG(521):     befb96c4  afd192e9  /system/lib/libc.so
12-17 15:00:03.119: I/DEBUG(521):     befb96c8  00059ad6  
12-17 15:00:03.119: I/DEBUG(521):     befb96cc  004fffd4  
12-17 15:00:03.119: I/DEBUG(521):     befb96d0  0050003c  
12-17 15:00:03.119: I/DEBUG(521):     befb96d4  0001a82c  
12-17 15:00:03.119: I/DEBUG(521):     befb96d8  00000064  
12-17 15:00:03.119: I/DEBUG(521):     befb96dc  afd183e1  /system/lib/libc.so
12-17 15:00:03.119: I/DEBUG(521):     befb96e0  df002777  
12-17 15:00:03.119: I/DEBUG(521):     befb96e4  e3a070ad  
12-17 15:00:03.119: I/DEBUG(521): #00 befb96e8  0001a828  
12-17 15:00:03.119: I/DEBUG(521):     befb96ec  8004b863  /system/lib/libdvm.so
12-17 15:00:03.119: I/DEBUG(521): #01 befb96f0  00000064  
12-17 15:00:03.119: I/DEBUG(521):     befb96f4  00000064  
12-17 15:00:03.119: I/DEBUG(521):     befb96f8  402f9080  
12-17 15:00:03.119: I/DEBUG(521):     befb96fc  8009d5b0  
12-17 15:00:03.119: I/DEBUG(521):     befb9700  00000019  
12-17 15:00:03.119: I/DEBUG(521):     befb9704  00000001  
12-17 15:00:03.119: I/DEBUG(521):     befb9708  fffffff7  
12-17 15:00:03.119: I/DEBUG(521):     befb970c  00000001  
12-17 15:00:03.119: I/DEBUG(521):     befb9710  00000000  
12-17 15:00:03.119: I/DEBUG(521):     befb9714  80065363  /system/lib/libdvm.so
12-17 15:00:03.139: I/DEBUG(521): dumpstate /data/log/dumpstate_app_native.txt

※ FaceBook也遇到相同問題:Dalvik patch for Facebook for Android,FaceBook Solve:Rebuilding Facebook for Android
2. Solve:
(1) 由上連結可知 FB 的 solve 是修改 LinearAlloc buffer,並盡可能減少內存記憶體的佔用
(2) 將 dex 文件切割成多個 dex 文件,並將其先下載至手機儲存後,在使用 DexClassLoader 動態載入 class (作法請參考下篇 使用 DexClassLoader 動態載入 DEX)

References