//Logo Image
「世大智科/天才家居」-我們創業囉
PDF Version

第五章 行動裝置App建置與功能模組

5.1    WhizMAT App簡介

WhizMAT App是用來顯示天才床邊踏墊透過MQTT所發出的事件,並詳細記錄發出的時間與事件名稱,除此之外,提供了一個天才床邊踏墊的Wi-Fi連線設定工具,讓使用者能簡易的按照步驟將產品安裝至家中環境。以下將介紹WhizMAT App的介面與使用方式。

5-1左方為App的首頁,打開此頁面背景服務會建立與MQTT Broker的連線,連線成功後將自動跳轉自裝置列表的頁面,如圖5-1右。WhizMAT App可以同時接收多個床邊踏墊的資訊,只要通過點擊右下角的加號按鈕進入新增頁面,新增不同的裝置即可,如需要移除可以點擊裝置列表各項裝置右方的設定按鈕即可選擇刪除,此刪除步驟同時會移除App對此裝置MQTT上的訂閱。

5-1. 左圖為登入頁面;右圖為裝置列表頁面

如圖5-2進入新增裝置頁面後,填入此天才床邊踏墊使用者的資訊並輸入裝置編號,此處的裝置編號相對應於天才床邊踏墊IoT微處理器的裝置編號,當確認新增後,App會以此裝置編號加上產品分類的字串組成MQTT訂閱的主題,如輸入M00001將會組成/G-Tech/WhizMAT/M00001/#的主題,井字號代表M00001底下的所有訊息都將會被訂閱。

5-2. 新增裝置頁面

當新增完裝置後回到裝置列表頁面,點,擊左上角Wi-Fi按鈕,即可進入天才床邊踏墊的Wi-Fi連線設定工具,Wi-Fi設定需要三個步驟。首先點擊進去後App會掃描附近的Wi-Fi裝置,若有天才床邊踏墊則會顯示,如圖5-3左圖;選擇裝置後會跳出Wi-Fi連線頁面,如圖5-3中間,此步驟因應Android權限限制,所以無法自動連線至Wi-Fi,需要使用者手動連線,連線成功後會自動跳轉回App頁面,並獲取天才床邊踏墊能連接的Wi-Fi環境;如圖5-3右圖Wi-Fi環境列表包括SSID、加密方式、訊號強度。

5-3. 左圖為裝置列表;中間為系統的Wi-Fi設定;右圖為天才床邊踏墊能連線的Wi-Fi環境列表

選擇天才床邊踏墊欲連線的Wi-Fi環境後,跳出輸入密碼的視窗,如圖5-4左圖;輸入完成後選擇確認,App將會把SSID與密碼傳送至天才床邊踏墊的IoT微處理器進行Wi-Fi連線;傳送成功後跳轉至圖5-4中間圖,開始檢測天才床邊踏墊的連線結果;如圖5-4右圖,連線成功後顯示所被分配的IP位置,按下確認即可回到裝置列表頁面。

5-4. 左圖為輸入密碼的視窗;中間為等待確認網路連線的頁面;右圖為連線成功的頁面

天才床邊踏墊連線成功並在App內新增裝置完成後,即可接收到天才床邊踏墊的即時事件,接收到的事件會以Android Notification的方式顯示佇列於通知欄內,如圖5-5左圖,並儲存至App內的SQLite資料庫提供歷史紀錄查詢,如圖5-5右圖。

5-5. 左圖為Android Notification佇列於通知欄;右圖為儲存於SQLite的歷史紀錄

5.2    Android Studio開發環境

WhizMAT App是以Android Studio集成開發環境撰寫,Android Studio提供了Gradle模組化的依賴管理工具與先前Eclipse加上Android Development Tool(ADT)有著極大的差異,Gradle的出現使得Android對第三方引用的管理更加方便,可以直接以Gradle引用網路上JCenterMaven倉庫的函式庫。同時Android Studio更提供了一個新的函式庫包裝方式為AARAAR相較於過去的JAR函式庫,AAR基本上是完全按照Android的需求所設計,內容可以包括JAVAXMLpropertyAndroid常使用的檔案格式,最大的不同是AAR可以包含視圖(Layout),使得函式庫的開發更加簡便與強大。

Android的架構基本循照Model-View-Controller(MVC)的架構,將資料模組(Model)、呈現模組(View)、控制模組(Controller)三者分離,這樣的做法可以降低系統內的功能邏輯和資料的耦合,使得架構上分工明確、促使開發人員專責分工,進而提昇系統的擴充性。

以下將詳述本研究為「天才家居」系列產品所開發的程式庫與Android SQLite資料庫的使用。

5.3    「天才家居」系列產品Wi-Fi連線設定

Wi-Fi連線設定的程式庫主要使用到一個頁面(Activity),並在內容添加三個碎片式頁面(Fragment),並建立執行緒(Thread)使用HTTP GET方式與IoT微處理器通訊。此節將介紹如何使用此函式庫,並說明如何實現此函式庫的接口與通訊功能,頁面(Activity)的建立將不在此介紹。

(1)       函式庫接口

要使用此函式庫首先需要創建一個WifiSettingToolConfig的類別,利用此類別提供的builder()函式加入所需的參數,如圖5-6,帶入產品英文名稱與IoT微處理器的型號,便可簡易的使用此函式庫。

5-6. 「天才家居」Wi-Fi連線設定工具函式庫使用方法

在使用此函式庫的時候會考量到整體App的顏色配置,所以此函式庫也提供設置主題顏色的方法,如圖5-7,設定完成後,設定頁面將會自動轉換成開發者所要求的顏色主題。

5-7. 為「天才家居」Wi-Fi連線設定工具函式庫加入主題顏色

最後使用Android SDK所提供的startActivity()函式打開Wi-Fi連線設定頁面,並帶入上面所述的WiFiSettingToolConfig實例即可,如圖5-8

5-8. 開啟「天才家居」Wi-Fi連線設定工具頁面

為了讓開發者能透過上述簡單的步驟使用此函式庫,首先我們先建立一個抽象(Abstract)的父類別(Class)WiFiSettingToolConfigImplement一個Parceable的接口,此接口允許此類別可以進行Android的進程間通訊(Interprocess communication, IPC)。接著在WiFiSettingToolConfig建立多項抽象函式,提供後續從子類獲得參數的接口,如圖5-9左圖;同時建立一個內部抽象類Builder提供子類繼承,讓後許開發者可以透過此類設置參數。

5-9. 左方為WiFiSettingToolConfig抽象類的抽象方法;右方為內部抽象類

接著如圖5-10,實現一個公開(Public)的靜態(Static)方法,用來實現單一實例獲取,必須帶入產品英文名稱與IoT微處理器型號,並創建一AutoWiFiSettingToolConfig子類使用其內部子類Builder設置預設主題顏色與其他參數,最後回傳此Builder實例。

5-11. 公開靜態方法builder()獲取單一實例

接下來說明AutoWiFiSettingToolConfig的實現,此類繼承父類WiFiSettingToolConfig,故在此類裡需要實現Parceable的接口與各項抽象方法。實現Parceable接口只需創建其CREATOR、以Parcel為參數的創建子與覆寫writeToParcel()方法,如圖5-12

5-12. 實現Parceable接口

如圖5-13,接著便是創建各項所需變數,創建創建子並實現父類各項抽象方法,最後創建內部子類Builder繼承自WiFiSettingToolConfigBuilder類,將其各項抽象類實現,並在build()方法建立AutoWiFiSettingToolConfig實例並回傳,即完成整個函式庫的接口。

5-13. 實現父類各項抽象方法並創立內部子類Builder繼承WiFiSettingConfigBuilder

(2)       通訊功能

此處的通訊功能是使用HTTP GETIoT微處理器獲取與傳送相關資訊,在這裡本研究使用JimmyTai-Utilities函式庫,由GitHub開發者Jimmy Tai所創建,只要在AppGradle內新增依賴compile 'com.jimmytai.library:jimmytai-utilities:1.0.3'即可使用。

Android開發內使用網路通訊的功能,需要先在AndroidManifest.xml獲取網路權限才可以使用,另外網路連接的程式為耗時工作會造成Android Runtime的報錯,需要在子進程(Thread)內使用,不能再主進程(UI Thread)使用。創建子進程後,透過JimmyTai-Utilities的函式庫使用HttpClient.httpGet()方法,如圖5-14,將URL帶入並可以設置HTTPConnection timeoutRead timeout,最後讀取成功後會回傳HTTP所回覆的字串,在「天才家居」系列產品中,所有回傳的字串格式均為JSON格式。但在使用此方法時需要加入Try and Catch的方法去捕獲連接逾時與HTTP回傳碼不為200的情形。

5-14. HttpClient.httpGet()的使用方法

5.4    MQTT物聯網通訊

最廣泛使用的AndroidMQTT函式庫為Paho MQTT,此函式庫提供了完整的MQTT方法包括各項MQTT庫戶端所需的方法,使用一個背景服務(Service)達成MQTT底層的TCP/IP連線,同時每60秒傳送維持TCP/IP的心跳值。在一般的開發上面,此函式庫的功能已經足夠,但是做為即時通知的通訊方式,此函式庫並為保護此背景服務的運行,當Android裝置的記憶體不夠時、使用者強制關閉此App或關機後,PahoMQTT背景服務將會被關閉,直到使用者再次打開App,這樣的情形將會造成即時通知的資訊被漏掉。研究開發一項基於Paho MQTT函式庫的MQTT函式庫JMQTT,實現守護進程(Daemon Proccess)保護MQTT連線的背景服務,並簡化與優化整體的函式庫接口。以下將介紹如何使用JMQTT函式庫、守護進程、JMQTT函式庫內部實現方法。

(1)       JMQTT的使用方法

使用Gradle新增JMQTT的依賴後,如圖5-15需要在AndroidManifest.xml檔案內配置網路使用權限與檔案讀取的權限,並宣告守護進程所需使用的背景服務(Service)與廣播(BroadcastReceiver)

5-15. JMQTT AndroidManifest.xml的配置

上述步驟設定完成後,需為AndroidManifest.xml檔案內的Application標籤設置JMQTT要求的Application,開發者需要創建一個Application繼承JMQTT函式庫的JMqttApplication,如圖5-16繼承此類別需實現其兩項抽象方法分別為getBrokerConfigurations()getPackageName() getBrokerConfigurations()方法需要回傳JBrokerConfigurations實例,其中需要帶入App識別名稱、MQTT Broker位置、MQTT Broker連線埠;getPackageName()方法需要回傳此Android專案的包名(Package Name)。在所有的配置完成後,當App開啟後,將會自動連線至MQTT Broker,並維持其連線狀態,包括重新開機和切換網路造成的斷線,都將自動重啟連線。

5-16. JMQTT繼承JMqttApplication

如圖5-17,設定完成後即可以在Android的各個進程當中使用JMQTT的函式庫裡JMqttClient的各項方法。首先可以透過JMqttClient.getInstance()的靜態方法取得JMqttClient的單一實例,取得實例後便可以使用此客戶端的所有功能,舉例來說,可以透過client.getSubscribeSet()方法取得已經訂閱過的主題集合,回傳型態為JSubscribeSet,可以使用Iterator的方法取處集合內的物件,此物件型別為JSubscribeItem,物件內包括訂閱的主題與QoS層級;使用client.isConnected()方法並帶入監聽器獲得MQTT當前連線的狀態;使用client.publish()方法帶入主題與負載(Payload)字串與QoS層級,即可發送一則MQTT訊息;可以使用client.subscribe()client.unSubscribe()方法訂閱與取消訂閱特定主題,這兩個方法需要帶入JSubscribeSet集合與設置一監聽器,用以監聽訂閱與取消訂閱的狀態。

5-17. JMQTT函式庫的各項方法

當訂閱完成後,從其他裝置傳送的主題若符合此App的訂閱主題,將會被傳送至此App並以”com.jimmytai.jmqtt.library.ACTION_MESSAGE_ARRIVED”字串為名稱廣播至Android App個進程內,故需要創建一個廣播接收器(BroadcastReceiver),如圖5-18,廣播接收器需要在AndroidManifest.xml檔案裡進行配置,並加入廣播訊息過濾器(IntentFilter),獲取JMQTT廣播的訊息,即可在所創建的廣播類中獲取廣播資訊,廣播的資訊夾帶於Intent當中,開發者須以intent.getParcelableExtra(JMqttClient.EXTRA_MESSAGE)方法獲取以JMqttMessage類所包裝的MQTT資訊實例,在實例中即可獲得主題(Topic)、負載(Payload)QoS層級等資訊。獲取此資訊後即可做出相對應的功能,如儲存至資料庫或發出提醒。

5-18. AndroidManifest.xml廣播配置與廣播類內部獲取訊息的方法

(2)       守護進程

使用Android所提供的背景服務運行MQTT,對於需要即時通信與信息推送的App存在一些風險,Android的背景服務並不保證會被永久運行,這樣的風險會使得使用者無法接收到即時訊息直到重新開啟App,會造成Android背景服務關閉的原因有三種。

           I.                Android系統的記憶體回收機制

        II.                各家製造廠商對於背景服務程序的管理制度

     III.                第三方App的進程清理

其中Android系統的自動回收機制是透過各個進程的優先級自動管理是否關閉此進程,可以透過在背景服務設置START_STICKY_COMPATIBILITY來避免此情況的發生,但剩下的兩個風險並非可以透過Android SDK去避免,故需要使用Native的進程守護。Android是基於Linux系統開發出的作業系統,在最上層的應用程式開發使用的是JAVA語言,而底層的進程管理是使用C++語言所編寫,Android提供一套NDK開發工具讓開發者可以將底層Linux的開發與Android Framework應用程序層相互溝通,本研究的守護進程及是基於Linux底層進程管理的開發。此部分僅說明守護進程實現概念,因為守護進程有許多細節於不同版本的Android系統有些許的不一樣,細節與程式碼不一一詳述。

首先當App的背景服務被關閉無法重啟時,必定是因為App所屬的系統進程被殺死,故可以在主進程中創建一C語言進程,並在C語言進程中執行輪詢服務主動發送可以喚醒關閉的背景服務,但各家廠商的Android系統在關閉進程時,常會同時關閉C語言進程,故上述方法並不適用於各家廠商的Android裝置上。以這樣的思路,達成進程守護需要雙向的保護,舉例來說,創建兩個進程分別為ab,當ab進程被殺死後,另一進程需要在對方被關閉的瞬間把被關閉的進程創建。

按照上述的邏輯,可以從主進程中使用fork()方法分岔出一子進程,利用Linux的進程接管機制,當主進程被殺死後子進程會被Linux接管,子進程所對應父進程的id會變為1。利用此機制在子進程中輪詢訪問父進程的id,當變為1時表示父進程被殺死,需要重新開啟背景服務;在父進程使用waitpid()方法,此方法為阻塞方法,意即子進程被殺死後程式才會繼續執行,所以當子進程被殺死後,主進程可以馬上fork出新的子進程。這個方法會有兩個問題,因為輪詢機制造成耗電量非常大、從主進程fork出子進程會複製主進程的所有記憶體,造成App消耗太多記憶體,使用者體驗不好。

為解決上述問題,需要創建一不屬於此進程的新進程,因此創建一C語言程式,編譯成二進位檔案,讓進程去開啟此二進位檔案成為新的進程,此處不能直接在主進程中執行此二進位檔案,會造成主進程被佔用,需要先從主進程中fork出一子進程,再讓此進程執行二進位檔案,再馬上關閉fork出的子進程節省記憶體的使用。當新的進程創建完成後,創建PIPE管道使兩個進程間能夠溝通,但新的進程不在主進程的管轄內,waitpid()的阻塞方法不能使用,只能尋找新的方法。首先父與子進程皆向對方建立PIPE,關閉寫出的通道,兩方皆用read()阻塞方法,當其中一個進程被殺死,另一進程的讀取端將被關閉,程式繼續運行,即可馬上開啟對方,以此方式達成雙向進程保護。此方法解決了耗電量與內存的問題,但在Android 5.0以上的系統沒辦法被使用,原因是Android 5.0以上的系統在關閉背景程式時新增了killProcessGroup()方法,會將子進程的PIPE通訊同時凍結,故需要使用另一方法。

Android 5.0以上需要創建兩個native進程,放棄原先java的主進程,而PIPE通道也不能使用,在這個情形下,可以讓每個進程創建兩個文件,使用Linux的文件鎖功能,當進程被殺死時將文件上鎖,意即利用文件枷鎖製作布林值標示位,即使在PIPE管道被關閉的情況下,透過讀取文件的加鎖狀態,即可知道對方進程是否被殺死。

此進程守護方法,目前支援至Android 6.0系統,但在每個系統上的實現方法都有些許的差異,因為各個版本的系統在關閉背景程式與重新開啟程式的機制上都有些許不同,故在守護進程內對個版本皆有不同的策略,在此不再詳細說明。

(3)       JMQTT函式庫內部實現方法

JMQTT函式庫是基於Paho Android MQTT函式庫所開發,但加入了守護進程,此功能將使MQTT的背景服務與Android App位於不同的程序(Process)上,所以創建一個客戶端的類別提供開發者能輕鬆使用JMQTT的各項功能。此客戶端的實現主要的技術在於如何跨Process的進程通訊,本研究使用Android SDK所提供的Messenger方法,此方法可以獲取並傳送Handler藉此建立雙向溝通的管道,如圖5-19,客戶端在綁定JMQTT背景服務後會獲取到一個Messenger實例,並建立另一個Messenger實例作為回傳管道,建立完後即在各項訂閱、發佈等方法中使用Messenger傳送指令至JMQTT的背景服務,再由背景服務執行MQTT的各項動作。

5-19. 綁定JMQTT背景服務後建立雙相溝通的管道

 

5.5    SQLite資料庫

資料庫可以記錄各項物聯網通訊紀錄,利用資料庫方式儲存必要資訊,以不同的資料表分類各項資訊,提供後續使用者資料查找的功能,甚至上傳至雲端系統做大數據分析的處理。以WhizMAT為例,可以將每次傳送至智慧型裝置的床邊事件儲存至資料庫中,當使用者想觀察過去一個月的紀錄時,將紀錄讀出,並可以使用圖表的方式將床邊事件與發生的時間一併呈現,即可觀察此使用者的生活習慣。

Android提供了資料庫的函式庫,使用的是SQLite資料庫語言,首先需要創建一個類別繼承SQLiteOpenHelper類,並實現其onCreate()onUpgrade()方法如圖5-20onCreate()方法將會創建所需的資料表,onUpgrade()方法會根據資料庫的版本更新,檢測是否有新增尚未存在的資料表,若有則新增。

5-20. SQLite 資料庫與資料表的創建與新稱

創建完成資料庫與資料表後,就可以執行儲存資料與獲取資料的動作,下面將以WhizMAT的離床資訊為例介紹如何使用Android SQLite資料庫。

(1)       新增紀錄

此資料表儲存的欄位包括UID(流水編號)、DEVICE_ID(裝置編號)、DATE_TIME(字串時間)、UNIX_TIMEUnix時間),用一個物件類去裝載此四項資訊,直接使用DBHelper實例的insert方法傳入資料表的名字、要新增資料的欄位、欄位相對應的值,新增成功即會回傳自動新增的流水編號。

5-21. SQLite資料庫新增資訊

(2)       更新紀錄

使用DBHelper實例的update()方法,傳入資料表的名字、更改過後的資料欄位、欄位相對應的值,此方法回傳被更改資訊的流水編號,若大於0及是更新成功。

5-22. SQLite資料庫更新資訊

(3)       獲取紀錄

要在SQLite獲取資訊非常簡單,只需要使用DBHelper實例中的select()方法,即可獲取資料,select()方法回傳一個Cursor物件,此物件像是一個Excel的表單,先使用cursor.moveToNext()Cusor的游標移動到第一個位置後,使用While迴圈將個列的資訊放入一個物件類,再將此物件加入List直到沒有下一列的資訊。但這樣的用法非常的簡略,並不能加入查詢的條件,在SQLite的指令集可以使用WHERE關鍵字搜尋特定的資料群,ORDER BY可以按照特定條件做序列排序,排序的方式可以DESCASC做遞減或遞增的排序,例如在WhizMAT App裡使用”SELECT FROM record WHERE deviceId = M00001 AND unixTime >= start AND unixTime <= end ORDER BY unixTime DESC”的語法指令獲取特定裝置在特定時間區間內的資料,並將此資料以Unix時間遞減排序。

5-23SQLite資料庫獲取資訊

(4)       刪除記錄

SQLite中刪除資訊可以透過DBHelper實例中的delete()方法帶入資料表名字、刪除的條件,執行後回傳受影響的列數,如圖5-24WhizMAT App中可以以裝置編號做為刪除條件,刪除資訊,這樣會只有不只一列資訊受到刪除影響,若對流水編號做為刪除條件,因為流水編號為唯一值,故只會影響一列資訊。

5-24. SQLite資料庫刪除資訊

(5)       獲取資料總數

SQLite中獲取資料筆數,DBHelper實例中並沒有提供其方法,如圖5-25,需要自行使用SQLite語法指令”SELECT COUNT(*) FROM record”,獲取此資料表中資料的總數,若需要加入條件限制,也可以使用如下指令” SELECT COUNT(*) FROM record WHERE unixTime >= start AND unixTime <= end”,此指令即可獲取特定時間區間的資料筆數。

5-25. SQLite資料庫獲取資料筆數