FrontlineSMS 是一款工具軟件,用于聯絡那些無法訪問互聯網但可以用手機通信的人,通常用于互聯網尚未普及地區的選舉監督、天氣預報廣播等。軟件作者Ken Banks借助于移動通信技術為人們提供幫助,他的貢獻大概無人能及。
FrontlineSMS運行在連接了手機的電腦上。電腦和手機共同構成一個短信中轉站,為群內人員提供文本通信服務。無法上網的人可以發送一個特殊代碼來加入群,隨后他們會收到來自中轉站的各種廣播消息。這個中轉站我們稱之為“廣播中心”,對于那些沒有網絡的地方,廣播中心成為與外界聯系的重要手段。
使用App Inventor可以創建自己的短信處理應用。有趣的是,應用需要運行在一部android設備上,但應用的用戶卻不必使用Android手機,他們可以用任何手機,智能的或非智能的,與應用之間進行短信的交流。應用雖然具有圖形化的用戶界面(GUI),但GUI僅供應用的管理者使用,用來監控應用中的各種活動。

本章將創建一個與FrontlineSMS功能類似的廣播中心,不過是運行在Android手機上。一臺具有中轉樞紐作用的移動設備,意味著管理者可以在移動中保持交流,這一點在某些場合下尤其重要,如選舉監督和醫療爭議談判。
假想有一個“快閃舞蹈團”(FlashMob Dance Team,縮寫為FMDT),他們可以召之即來,隨時隨地表演舞蹈,然后瞬間解散,消失得無影無蹤,他們用你創建的廣播中心來組織表演活動。人們只要向中心發送短信“joinFMDT”(參加快閃舞蹈團),即可完成入團注冊,每個注冊成功的人都可以向舞蹈團中的其他人廣播消息。
廣播中心用下面的方式處理收到的短信:
1\. 如果發信人不在廣播中心的成員名單中,則回復短信邀請他加入,并告知他申請代碼;
2\. 如果收到“joinFMDT”,則接收發信人為廣播中心成員;【如果組員發送“joinFMDT”呢?】
3\. 如果發信人已經是廣播中心的成員,則轉發該消息給全體廣播中心成員。
我們來分步實現這些功能模塊。首先,用自動回復來邀請人們加入廣播中心。整個應用完成之后,對于創建這類“以短信為用戶界面的應用”,你將有透徹的了解。
## **學習要點**
本章包括下列App Inventor概念,其中有些你可能已經熟悉了:
* Texting組件:發送短信及處理收到的短信;
* 列表變量:在本例中用來記錄電話號碼清單;
* foreach塊:對列表中的數據進行逐項重復操作。在本例子中,使用foreach塊向電話號碼列表中的所有手機廣播消息;
* TinyDB組件:實現數據的永久存儲,以保證當應用關閉并再次打開時,電話號碼列表不丟失。
## **準備開始**
你需要一部可以接收和發送短信的手機來測試程序,因為App Inventor自帶的模擬器沒有這個功能。您還需要招呼一些朋友給你發送短信,來充分地測試應用。
連接到App Inventor網站,創建新項目“BroadcastHub”,設置Screen1.Title屬性為“廣播中心”,并連接測試手機。
## **設計組件**
廣播中心有利于手機之間的通信:這些手機不需要安裝應用,甚至不必是智能手機。因此在本例中不必為用戶提供操作界面,只需為群管理員提供操作界面。
管理員的操作界面包括兩個簡單的部分,一是顯示當前的“廣播列表”,即已注冊成員的電話號碼清單,二是記錄所有收到并被廣播出去的短信。
為了創建這個界面,要添加表11-1中列出的組件。
**表11-1 廣播中心操作界面中的組件**
| 組件類型 | 面板中分組 | 命名 | 作用 |
| --- | --- | --- | --- |
| Label | User Interface | Label1 | 電話號碼清單的標題 |
| Label | User Interface | BroadcaseListLabel |顯示所有已注冊的電話號碼 |
| Label | User Interface | Label2 | 日志信息的標題 |
| Label | User Interface | LogLabel |顯示收到及廣播短信的記錄 |
| Texting | Social | Texting1 | 處理短信 |
| TinyDB | Storage | TinyDB1 | 保存已注冊的手機號碼清單 |
添加組件之后,還要設置以下屬性:
1\. 設置每個Label的Width屬性為“Fill parent”,讓組件在水平方向上充滿手機;
2\. 設置標題Label的FontSize屬性(Label1和Label2)為18,并勾選FontBold框;
3\. BroadcastListLabel和LogLabel的Height設置為200像素,用于顯示多行;
4\. 設置BroadcastListLabel的Text屬性為“廣播列表...”;
5\. LogLabel的Text屬性設置為空。
圖11-1顯示了應用在組件設計器中的布局。

**圖 11-1 廣播中心組件設計**
## **為組件添加行為**
在這個應用中,促使程序運行的事件是其他手機發來的短信,而不是用戶在界面上的輸入或點擊,因此應用的任務是處理這些短信,并將發信人手機號碼保存到列表中,具體操作如下:
* 如果短信發送者不在廣播列表中,則回復一個邀請參加的短信;
* 如果收到短信“joinFMDT”,則將發送者注冊為廣播列表的一員;
* 如果短信發送者已經在廣播列表中,則將該短信廣播到列表中的所有手機。
現在開始創建第一個行為:收到短信時,回復發送者,邀請他注冊,方法是向你發送短信“joinFMDT”。表11-2中列出了需要的塊。
**表11-2 邀請人們通過發短信來加入群組,需要下面的塊**
| 塊的類型 | 所在抽屜 | 作用 |
| --- | --- | --- |
| Texting.MessageReceived | Texting1 |當手機收到短信時,觸發該事件 |
| set Texting1.PhoneNumber to | Texting1 |設置短信接收者的電話號碼 |
| 參數number | Variables |MessageReceived事件的參數:發送者手機號 |
| Set Texting1.Message | Texting1 | 設置要發送的邀請短信 |
| “想加入快閃舞蹈團,請發送‘joinFMDT’到此號碼。” | Text|邀請短信的內容 | Texting1.SendMessage | Texting1 | 發送短信 |
#### **塊的作用**
根據在第4章“開車不發短信”中的經驗,你應該很熟悉這些塊。當手機收到短信時會觸發Texting1.MessageReceived事件。如圖11-2,在事件處理程序中設置Texting1組件的PhoneNumber及Message屬性,然后發送短信。

**圖 11-2 收到短信后回復邀請短信**
>  測試:需要用第二部手機來測試這一功能;你不能給自己發短信,否則會永遠循環下去!如果沒有其他手機,可以注冊Google Voice或類似的服務,從這些服務中給自己的手機發短信。用第二部手機發送“你好”到測試手機,則第二部手機會收到一個邀請加入“舞蹈團”的短信。
### **將某人加入廣播列表**
現在創建第二個行為:收到短信“joinFMDT”后,將發信人添加到廣播列表中。首先定義列表變量BroadcastList來保存注冊的電話號碼。從Variables中拖出一個“initialize global name to”塊,將name改為“BroadcastList”,并用make a list塊初始化列表,此時列表為空。如圖11-3(稍后將實現向列表中添加數據項的功能)。

**圖 11-3 變量BroadcastList用于存儲注冊的電話號碼【也可用create empty list塊】**
下面修改Texting1.MessageReceived事件處理程序,如果收到短信“joinFMDT”,則將發信人手機號碼添加到BroadcastList中。判斷短信內容需要使用Ifelse塊(在第十章“出題”應用中使用過),將新號碼添加到列表中需要使用add item to list塊。整個設置所需的塊見表11-3。在電話號碼添加完之后,用BroadcastListLabel來顯示新列表。
**表11-3 檢查來信內容,并將發信人添加到廣播列表中,需要如下塊**
| 塊的類型 | 所在抽屜 | 作用 |
| --- | --- | --- |
| initialize global BroadcastList to | Variables |定義廣播列表變量 |
| ifelse | Control | 根據收到短信的內容決定做什么事 |
| = | Math | 判斷短信內容是否等于“joinFMDT” |
| get messageText | Variables | 將來信內容插入“=”塊(左邊 |
| “joinFMDT” | Text | 將固定文本插入“=”塊(右邊) |
| add items to list | Lists | 向廣播列表中添加發信人電話號 |
| get number | Variables| 將發信人手機號碼插入“add items to list” |
| set BroadcaseListLabel.Text to | BroadcaseListLabel |顯示新列表 |
| get global BroadcastList | Variables | 將其插入set BroadcaseListLabel.Text to塊 |
| set Texting1.Message to | Texting1 |設置短信內容,準備用Texting1回復發信人 |
| “恭喜你成功加入…” | Text | 祝賀發信人加入群組成功。 |
#### **塊的作用**
如圖11-4所示,對剛收到的短信進行回復,第一行的塊將發信人手機號碼設置為接收人手機號碼,即設置Texting1.PhoneNumber為number。然后判斷messageText是否為特殊代碼“joinFMDT”:如果是,則將發送者手機號添加到BroadcastList并發短信祝賀;如果不是,則回復邀請短信。在Ifelse塊之后,回復短信被發出(最后一行)。

**圖 11-4 如果收到短信“joinFMDT”,則將發信人手機號添加到BroadcastList**
>  測試:用第二部手機發送短信“joinFMDT”到測試手機,在測試手機收到短信的同時,第二部手機的號碼出現在“已注冊的電話號碼”下面,第二部手機會收到祝賀短信。嘗試發一個其他內容的短信,檢查邀請短信是否能正常發送。
### **廣播消息**
下面來添加廣播行為:當廣播列表BroadcastList中的成員向廣播中心發來短信時,將此信息轉發給列表中的所有手機。這一功能稍顯復雜,需要更多的控制塊:增加一個Ifelse塊和一個foreach塊。新增的Ifelse塊用于檢查發送短信的手機號是否在廣播列表中,而foreach塊用于向列表中的所有手機廣播這條短信。另外還要將之前的Ifelse塊移動到新Ifelse塊的“else”部分。表11-4列出了需要新增的塊。
**表11-4 向列表中的成員廣播某個成員發來的短信需要新增的塊**
| 塊的類型 | 所在抽屜| 作用 |
| --- | --- | --- |
| ifelse | Control |根據發信人是否已在廣播列表中來決定做不同的事|
| is in list?|Lists | 檢查某數據是否在列表中 |
| get global BroadcastList | Variables | 將其插入is in list?的list插槽中 |
| get number | Variables | 將其插入is in list?的thing插槽 |
| set Texting1.Message to | Texting1 |設置將被廣播出去的短信內容(列表成員的來信) |
| get messageText | Variables | 即將被廣播出去的列表成員來 |
| foreach | Control | 向列表中的所有成員發送同一條短信 |
| get global BroadcastList | Variables |將其插入foreach的list插槽 |
| set Texting1.PhoneNumber to| Texting1 |設置接收短信的手機號碼 |
| get item | Variables |BroadcaseList中當前正在操作的項/變量:保存的是手機號 |
#### **塊的作用**
這里使用了嵌套的ifelse塊,使得程序更加復雜,如圖11-5所示。嵌套的ifelse塊指的是在一個ifelse塊的“then”或“else”插槽中嵌入了另一個ifelse塊。在本例中,外層的ifelse負責檢查發信人的手機號是否已在廣播列表中。如果在,則將該短信轉發給列表中的所有人;如果不在,則執行內層ifelse判斷:短信內容messageText是否為“joinFMDT”,并依據判斷結果,執行不同的分支操作。

**圖 11-5 檢查發信人是否已在廣播列表中,如果是,則廣播此短信**
從理論上,if塊和ifelse塊可以做任意層級的嵌套,來實現更加復雜的行為(更多關于條件語句塊的內容請參見第18章)。
在外層ifelse塊的then分支中,使用foreach塊來廣播短信。foreach遍歷BroadcastList列表中的每一項,并把短信發送給列表中的每個電話號碼。在foreach執行循環時,BroadcastList中的每個電話號碼依次被保存在item中(item是一個變量,代表了foreach當前正在處理的項)。在foreach塊內,設置Texting.PhoneNumber的值為當前項item,并向其發送短信。有關foreach的更多信息,請參見第20章。
>  測試:首先要有兩部不同的手機通過發送“joinFMDT”到測試手機,實現成功注冊。然后,從一部手機向廣播中心發一條短信,這時兩部手機都應該收到這條短信(包括發送短信的那一個)。
### **整理列表的顯示**
廣播短信的功能已經實現,但管理員的界面尚需改進。首先,電話號碼列表的顯得很亂:用Label顯示列表時,列表項之間用空格分隔,并且盡可能占滿一行,像下面這樣:
> (+861303318989 +861581235590 +8618902018909 +8613301103355 +8613801237890)
為了改善這種局面,使用表11-5列出的塊創建一個過程displayBroadcastList,來實現每行只顯示一個號碼。請務必在add items to list塊的下面調用該過程,以便顯示更新后的列表。
**表11-5 改進電話號碼列表顯示所需的塊**
| 塊的類型 | 所在抽屜 | 作用 |
| --- | --- | --- |
| to procedure(“displayBroadcastList”) | Procedures|創建過程displayBroadcastList|
| set BroadcaseListLabel.Text to | BroadcaseListLabel |用來顯示列表 |
| “” | Text | 空文本 |
| foreach |Control | 對電話號碼列表進行遍歷 |
| pnumber | foreach內置 |變量pnumber為遍歷過程中正在訪問的 |
| get global BroadcaseList | Variables | 插入foreach塊的in list插槽 |
| set BroadcaseListLabel.Text to | BroadcaseListLabel |顯示電話號碼列表 |
| join | Text | 將多個文本片段連接為一個文本對象 |
| BroadcaseListLabel.Text | BroadcaseListLabel |每次循環都以既有label內容為基礎追加新項 |
| “\n” | Text | 換行,以便下一個號碼顯示在下一行 |
| get pnumber| foreach內置 |遍歷時列表中正在訪問的項(手機號碼) |
#### **塊的作用**
過程displayBroadcastList中的foreach塊逐行地將每個手機號碼添加到label的末尾,如圖11-6所示,用換行符(\ n)來分隔每個號碼,使得每個號碼各占一行。

**圖 11-6 逐行顯示手機號碼**
不過displayBroadcastList過程不會主動做任何事,除非調用它。在Texting1.MessageReceived事件處理程序中,緊接著add item to list塊調用它。過程的調用取代了列表BroadcastList在 BroadcastListLabel.Text中的默認顯示。塊call displayBroadcastList歸屬在Procedures抽屜中。
圖11-7顯示了Texting1.MessageReceived事件處理程序中相關的塊。

**圖 11-7 調用displayBroadcastList過程**
關于用foreach來顯示列表的詳細信息請參見第20章,關于創建和調用過程的詳細信息請參見第21章。
>  測試:重新啟動應用來清除列表,然后用至少兩個不同的手機進行注冊(再次)。手機號碼是否逐行顯示了?
### **錄廣播過的短信**
在收到短信并向其他手機發出廣播之后,程序應該記錄此類事件,以便管理員可以對活動進行監督。在組件設計器中,已經添加的LogLabel組件就是用于這一目的。下面編寫程序,每當收到新的短信時,改變LogLabel的顯示。
要創建像這樣的一段文本:“來自+8613901231234的短信已經廣播。”字符“+8613901231234”不是固定數據,而是MessageReceived事件自帶的參數值。因此,要創建的文本包括三個部分:①“來自”;②手機號碼,為參數number;③“的短信已經廣播”。正如在前幾章中所做的一樣,用join將三個部分連接起來,表11-6列出了需要的塊。
**表11-6 構建廣播日志所需要的塊**
| 塊的類型 | 所在抽屜 | 作用 |
| --- | --- | --- |
| set LogLabel.Text to | LogLabel | 在此顯示日志 |
| join | Text | 由多個文本片段創建成一個文本對象 |
| “來自” | Text | 每條日志信息的第①部 |
| get number | Texting1.MessageReceived事件內置參數 |日志信息的第②部分:短信發送者的手機號碼 |
| “的短信已經廣播。\n” | Text| 日志信息的第③部分 |
| LogLabel.Text| LogLabel | 在原有日志前插入一條新的日志 |
#### **塊的作用**
在收到短信后,向BroadcastList列表中的所有號碼廣播此短信,再修改LogLabel,記錄剛才的廣播操作,如圖11-8所示。需要注意的是,我們將消息添加到列表的開始,而不是結尾,因此最后發出的消息將顯示在最頂端。

**圖 11-8 向廣播日志中添加一條新消息**
join塊創建了一條新記錄:來自+8613901231234的短信已經廣播。
每次短信廣播之后,這條記錄將被添加到LogLabel.Text的第一行,使最新的記錄一直出現在頂部。join塊中各個文本片段的順序決定了日志中記錄的順序。在本例子中,新消息被編排在前三個插槽中,而LogLabel.Text,已經保存的現有記錄,將插入最后一個插槽。
“的短信已經廣播。\n”中的“\n”稱為換行符,它讓每條記錄單獨占一行,像這樣:
> 來自+8613030123668的短信已經廣播。
>
> 來自+8613901231234的短信已經廣播。
關于使用foreach來顯示列表的詳細信息,請參見第20章。
### **將BroadcastList保存在數據庫中**
現在應用算是大功告成了,但通過前幾章的學習,你可能猜到了一個問題:如果管理員將應用關閉再重新啟動時,廣播列表中的數據將會丟失,每個人都得重新注冊。為了解決這個問題,要使用TinyDB組件實現BroadcastList列表在數據庫中的存儲和檢索。
這里將使用與“出題”應用(第10章)中相類似的方案:
> 每次添加新項時,將列表保存到數據庫中;
>
> 應用啟動時,從數據庫中加載列表,并保存到一個變量中。
用表11-7中所列的塊,將列表存儲到數據庫中。TinyDB組件中的tag作為數據的標識,將保存在數據庫中的不同數據區分開來。在本例中,你可以將數據標記為“broadcastList”。在Texting1.MessageReceived中,將這些塊添加到add items to list塊之下。
**表11-7 用TinyDB來存儲列表所需的塊**
| 塊的類型 | 所在抽屜 | 作用 |
| --- | --- | --- |
| TinyDB1.StoreValue| TinyDB1 | 將數據保存到數據庫中 |
| “broadcastList” | Text| 將其插入StoreValue的tag插槽中 |
| get global BroadcastList | Variables|將其插入StoreValue的value插槽中 |
#### **塊的功能**
當應用收到短信“joinFMDT”,并將新成員的手機號碼添加到列表時,調用TinyDB1.StoreValue將BroadcastList保存到數據庫中。tag(“broadcastList”)的使用是為了便于之后對數據的檢索。如圖11-9,被StoreValue調用的值(valueToStore)是變量BroadcastList。

**圖 11-9 調用TinyDB來存儲BroadcastList列表**
### **從數據庫加載廣播列表(BroadcastList)**
每次應用啟動時都要加載廣播列表,按照表11-8中列出的塊來實現這一功能。應用的啟動將觸發Screen1.Initialize事件,因此將在該事件的處理程序中實現加載。使用存儲時的tag(“broadcastList”)來調用TinyDB.GetValue。就像前幾章一樣,我們需要檢查是否的確有數據返回,這里將檢查返回值是否為列表,因為如果列表中沒有數據,那么它也就不是列表。
#### **塊的作用**
應用啟動將觸發Screen1.Initialize事件。如圖11-10所示,使用TinyDB1.GetValue塊向數據庫請求數據,返回的數據臨時保存在已定義的變量valueFromDB中。
**表11-8 應用啟動時加載廣播列表所需要的塊**
| 塊的類型 | 所在抽屜 | 作用|
| --- | --- | --- |
| initialize global valueFromDB to | Variables |用于保存并檢查數據庫返回值的臨時變量 |
| “” | Text | 設valueFromDB初始值為空 |
| Screen1.Initialize | Screen1 | 應用啟動時觸發該事件 |
| set global valueFromDB to | Variables |將數據庫返回值暫時存放在其中 |
| TinyDB1.GetValue | TinyDB1 | 向數據庫請求數據 |
| “broadcastList” | Text | 將其插入GetValue的tag插槽 |
| if | Control | 判斷數據庫中是否有數據 |
| is a list | Lists|如果數據庫返回值是一個列表,則返回值不為空 |
| get global valueFromDB | Variables | 將其插入is a list? |
| set global BroadcaseList to| Variables | 將變量值設置為數據庫的返回值|
| get global valueFromDB | Variables |數據庫返回值不為空時,將返回值寫入廣播列表 |
| call displayBroadcastList | Procedures |加載數據成功后,顯示數據|

**圖 11-10 從數據庫中加載廣播列表BroadcastList**
事件處理程序中的if塊是必需的,因為在首次啟動應用時,數據庫將返回空文本(“”),這時還沒有生成廣播列表。通過判斷valueFromDB是否為列表,可以確定是否真的有數據返回。如果沒有,則跳過那些保存返回數據以及顯示數據的塊。
>  測試:對于涉及數據庫操作的應用,不適合做實時測試,因為每次連接測試設備時,都會清空數據庫。為了測試數據庫以及Screen.Initialize事件處理程序,需要將應用打包并下載到手機【點擊“build?App(provide QR code for .apk)”,下載并安裝應用】。在手機上啟動應用,用另外兩部手機發送“joinFMDT”加入群組,再退出應用。當重啟應用時,如果那些電話號碼還在,說明數據庫部分工作正常。
## **完整的廣播中心應用**

**圖 11-11 完整的廣播中心應用中的塊**
## **改進**
在慶祝一個如此復雜的應用完工的時候,你也許想要做進一步的改進。例如:
* 在廣播短信環節,廣播中心向所有人發出短信,也包括發送這條短信的列表成員。修改此功能,將短信群發給除了發送者之外的所有成員;
* 允許列表成員退出群組,用手機發送短信“quitabc”給廣播中心,請求從列表中刪除自己。需要使用remove from list塊;
* 管理員可以在操作界面上添加或刪除廣播列表中的成員(手機號);
* 管理員可以指定某些不允許加入列表的手機號;
* 細化應用的功能,讓任何人都可以加入到列表并接收廣播,但只有管理員可以廣播消息;
* 進一步細化應用,讓任何人都可以加入到接收廣播,但只有一個固定列表中的電話號碼可以向全體成員廣播消息(這正式赫爾辛基事件 的成功之處);
* 應用中的廣播列表可以永久保存,但日志卻不能。每次關閉該應用再重新打開時,日志也從頭開始。改進一下,讓日志也能永久保存。
## **小結**
以下是本章涵蓋的內容:
* 應用不僅可以響應用戶發起的事件,也可以響應非用戶發起的事件,像收到短信這樣的事件。這意味著應用的用戶也可以是其他手機;
* ifelse與foreach塊的嵌套使用可以構造出復雜的行為。有關條件語句if和循環語句foreach的詳細信息,請參見第18章及第20章;
* join塊可以用來創建一個多重內容組成的文本對象;
* TinyDB可以用于數據庫操作:存儲及檢索數據。通用方案是,在數據發生變化時,調用StoreValue更新數據庫;在應用啟動時,調用GetValue從數據庫中讀取數據。
- 簡介
- 序言
- 前言
- 第 1 章 Hello 貓咪
- 第 2 章 油漆桶
- 第 3 章 打地鼠
- 第 4 章 開車不發短信
- 第 5 章 瓢蟲快跑
- 第 6 章 巴黎地圖旅游
- 第 7 章 安卓,我的車在哪?
- 第 8 章 總統測驗
- 第 9 章 木琴
- 第 10 章 出題及答題
- 第 11 章 廣播中心
- 第 12 章 遙控機器人
- 第 13 章 亞馬遜掌上書店
- 第 14 章 理解應用的結構
- 第 15 章 軟件工程與應用調試
- 第 16 章 應用中的存儲
- 第 17 章 創建動畫應用
- 第 18 章 程序中的決策:條件塊
- 第 19 章 數據列表編程
- 第 20 章 循環
- 第 21 章 定義過程
- 第 22 章 數據庫
- 第 23 章 傳感器
- 第 24 章 與Web API通信