FrontlineSMS 是一款工具軟件,用于聯(lián)絡(luò)那些無法訪問互聯(lián)網(wǎng)但可以用手機(jī)通信的人,通常用于互聯(lián)網(wǎng)尚未普及地區(qū)的選舉監(jiān)督、天氣預(yù)報廣播等。軟件作者Ken Banks借助于移動通信技術(shù)為人們提供幫助,他的貢獻(xiàn)大概無人能及。
FrontlineSMS運行在連接了手機(jī)的電腦上。電腦和手機(jī)共同構(gòu)成一個短信中轉(zhuǎn)站,為群內(nèi)人員提供文本通信服務(wù)。無法上網(wǎng)的人可以發(fā)送一個特殊代碼來加入群,隨后他們會收到來自中轉(zhuǎn)站的各種廣播消息。這個中轉(zhuǎn)站我們稱之為“廣播中心”,對于那些沒有網(wǎng)絡(luò)的地方,廣播中心成為與外界聯(lián)系的重要手段。
使用App Inventor可以創(chuàng)建自己的短信處理應(yīng)用。有趣的是,應(yīng)用需要運行在一部android設(shè)備上,但應(yīng)用的用戶卻不必使用Android手機(jī),他們可以用任何手機(jī),智能的或非智能的,與應(yīng)用之間進(jìn)行短信的交流。應(yīng)用雖然具有圖形化的用戶界面(GUI),但GUI僅供應(yīng)用的管理者使用,用來監(jiān)控應(yīng)用中的各種活動。
本章將創(chuàng)建一個與FrontlineSMS功能類似的廣播中心,不過是運行在Android手機(jī)上。一臺具有中轉(zhuǎn)樞紐作用的移動設(shè)備,意味著管理者可以在移動中保持交流,這一點在某些場合下尤其重要,如選舉監(jiān)督和醫(yī)療爭議談判。
假想有一個“快閃舞蹈團(tuán)”(FlashMob Dance Team,縮寫為FMDT),他們可以召之即來,隨時隨地表演舞蹈,然后瞬間解散,消失得無影無蹤,他們用你創(chuàng)建的廣播中心來組織表演活動。人們只要向中心發(fā)送短信“joinFMDT”(參加快閃舞蹈團(tuán)),即可完成入團(tuán)注冊,每個注冊成功的人都可以向舞蹈團(tuán)中的其他人廣播消息。
廣播中心用下面的方式處理收到的短信:
1. 如果發(fā)信人不在廣播中心的成員名單中,則回復(fù)短信邀請他加入,并告知他申請代碼;
2. 如果收到“joinFMDT”,則接收發(fā)信人為廣播中心成員;【如果組員發(fā)送“joinFMDT”呢?】
3. 如果發(fā)信人已經(jīng)是廣播中心的成員,則轉(zhuǎn)發(fā)該消息給全體廣播中心成員。
我們來分步實現(xiàn)這些功能模塊。首先,用自動回復(fù)來邀請人們加入廣播中心。整個應(yīng)用完成之后,對于創(chuàng)建這類“以短信為用戶界面的應(yīng)用”,你將有透徹的了解。
本章包括下列App Inventor概念,其中有些你可能已經(jīng)熟悉了:
Texting組件:發(fā)送短信及處理收到的短信;
列表變量:在本例中用來記錄電話號碼清單;
foreach塊:對列表中的數(shù)據(jù)進(jìn)行逐項重復(fù)操作。在本例子中,使用foreach塊向電話號碼列表中的所有手機(jī)廣播消息;
你需要一部可以接收和發(fā)送短信的手機(jī)來測試程序,因為App Inventor自帶的模擬器沒有這個功能。您還需要招呼一些朋友給你發(fā)送短信,來充分地測試應(yīng)用。
連接到App Inventor網(wǎng)站,創(chuàng)建新項目“BroadcastHub”,設(shè)置Screen1.Title屬性為“廣播中心”,并連接測試手機(jī)。
廣播中心有利于手機(jī)之間的通信:這些手機(jī)不需要安裝應(yīng)用,甚至不必是智能手機(jī)。因此在本例中不必為用戶提供操作界面,只需為群管理員提供操作界面。
管理員的操作界面包括兩個簡單的部分,一是顯示當(dāng)前的“廣播列表”,即已注冊成員的電話號碼清單,二是記錄所有收到并被廣播出去的短信。
為了創(chuàng)建這個界面,要添加表11-1中列出的組件。
表11-1 廣播中心操作界面中的組件
組件類型 | 面板中分組 | 命名 | 作用 |
---|---|---|---|
Label | User Interface | Label1 | 電話號碼清單的標(biāo)題 |
Label | User Interface | BroadcaseListLabel | 顯示所有已注冊的電話號碼 |
Label | User Interface | Label2 | 日志信息的標(biāo)題 |
Label | User Interface | LogLabel | 顯示收到及廣播短信的記錄 |
Texting | Social | Texting1 | 處理短信 |
TinyDB | Storage | TinyDB1 | 保存已注冊的手機(jī)號碼清單 |
添加組件之后,還要設(shè)置以下屬性:
1. 設(shè)置每個Label的Width屬性為“Fill parent”,讓組件在水平方向上充滿手機(jī);
2. 設(shè)置標(biāo)題Label的FontSize屬性(Label1和Label2)為18,并勾選FontBold框;
3. BroadcastListLabel和LogLabel的Height設(shè)置為200像素,用于顯示多行;
4. 設(shè)置BroadcastListLabel的Text屬性為“廣播列表...”;
5. LogLabel的Text屬性設(shè)置為空。
圖11-1顯示了應(yīng)用在組件設(shè)計器中的布局。
圖 11-1 廣播中心組件設(shè)計
在這個應(yīng)用中,促使程序運行的事件是其他手機(jī)發(fā)來的短信,而不是用戶在界面上的輸入或點擊,因此應(yīng)用的任務(wù)是處理這些短信,并將發(fā)信人手機(jī)號碼保存到列表中,具體操作如下:
如果短信發(fā)送者不在廣播列表中,則回復(fù)一個邀請參加的短信;
如果收到短信“joinFMDT”,則將發(fā)送者注冊為廣播列表的一員;
現(xiàn)在開始創(chuàng)建第一個行為:收到短信時,回復(fù)發(fā)送者,邀請他注冊,方法是向你發(fā)送短信“joinFMDT”。表11-2中列出了需要的塊。
表11-2 邀請人們通過發(fā)短信來加入群組,需要下面的塊
塊的類型 | 所在抽屜 | 作用 | |||
---|---|---|---|---|---|
Texting.MessageReceived | Texting1 | 當(dāng)手機(jī)收到短信時,觸發(fā)該事件 | |||
set Texting1.PhoneNumber to | Texting1 | 設(shè)置短信接收者的電話號碼 | |||
參數(shù)number | Variables | MessageReceived事件的參數(shù):發(fā)送者手機(jī)號 | |||
Set Texting1.Message | Texting1 | 設(shè)置要發(fā)送的邀請短信 | |||
“想加入快閃舞蹈團(tuán),請發(fā)送‘joinFMDT’到此號碼?!?/td> | Text | 邀請短信的內(nèi)容 | Texting1.SendMessage | Texting1 | 發(fā)送短信 |
根據(jù)在第4章“開車不發(fā)短信”中的經(jīng)驗,你應(yīng)該很熟悉這些塊。當(dāng)手機(jī)收到短信時會觸發(fā)Texting1.MessageReceived事件。如圖11-2,在事件處理程序中設(shè)置Texting1組件的PhoneNumber及Message屬性,然后發(fā)送短信。
圖 11-2 收到短信后回復(fù)邀請短信
測試:需要用第二部手機(jī)來測試這一功能;你不能給自己發(fā)短信,否則會永遠(yuǎn)循環(huán)下去!如果沒有其他手機(jī),可以注冊Google Voice或類似的服務(wù),從這些服務(wù)中給自己的手機(jī)發(fā)短信。用第二部手機(jī)發(fā)送“你好”到測試手機(jī),則第二部手機(jī)會收到一個邀請加入“舞蹈團(tuán)”的短信。
現(xiàn)在創(chuàng)建第二個行為:收到短信“joinFMDT”后,將發(fā)信人添加到廣播列表中。首先定義列表變量BroadcastList來保存注冊的電話號碼。從Variables中拖出一個“initialize global name to”塊,將name改為“BroadcastList”,并用make a list塊初始化列表,此時列表為空。如圖11-3(稍后將實現(xiàn)向列表中添加數(shù)據(jù)項的功能)。
圖 11-3 變量BroadcastList用于存儲注冊的電話號碼【也可用create empty list塊】
下面修改Texting1.MessageReceived事件處理程序,如果收到短信“joinFMDT”,則將發(fā)信人手機(jī)號碼添加到BroadcastList中。判斷短信內(nèi)容需要使用Ifelse塊(在第十章“出題”應(yīng)用中使用過),將新號碼添加到列表中需要使用add item to list塊。整個設(shè)置所需的塊見表11-3。在電話號碼添加完之后,用BroadcastListLabel來顯示新列表。
表11-3 檢查來信內(nèi)容,并將發(fā)信人添加到廣播列表中,需要如下塊
塊的類型 | 所在抽屜 | 作用 |
---|---|---|
initialize global BroadcastList to | Variables | 定義廣播列表變量 |
ifelse | Control | 根據(jù)收到短信的內(nèi)容決定做什么事 |
= | Math | 判斷短信內(nèi)容是否等于“joinFMDT” |
get messageText | Variables | 將來信內(nèi)容插入“=”塊(左邊 |
“joinFMDT” | Text | 將固定文本插入“=”塊(右邊) |
add items to list | Lists | 向廣播列表中添加發(fā)信人電話號 |
get number | Variables | 將發(fā)信人手機(jī)號碼插入“add items to list” |
set BroadcaseListLabel.Text to | BroadcaseListLabel | 顯示新列表 |
get global BroadcastList | Variables | 將其插入set BroadcaseListLabel.Text to塊 |
set Texting1.Message to | Texting1 | 設(shè)置短信內(nèi)容,準(zhǔn)備用Texting1回復(fù)發(fā)信人 |
“恭喜你成功加入…” | Text | 祝賀發(fā)信人加入群組成功。 |
如圖11-4所示,對剛收到的短信進(jìn)行回復(fù),第一行的塊將發(fā)信人手機(jī)號碼設(shè)置為接收人手機(jī)號碼,即設(shè)置Texting1.PhoneNumber為number。然后判斷messageText是否為特殊代碼“joinFMDT”:如果是,則將發(fā)送者手機(jī)號添加到BroadcastList并發(fā)短信祝賀;如果不是,則回復(fù)邀請短信。在Ifelse塊之后,回復(fù)短信被發(fā)出(最后一行)。
圖 11-4 如果收到短信“joinFMDT”,則將發(fā)信人手機(jī)號添加到BroadcastList
測試:用第二部手機(jī)發(fā)送短信“joinFMDT”到測試手機(jī),在測試手機(jī)收到短信的同時,第二部手機(jī)的號碼出現(xiàn)在“已注冊的電話號碼”下面,第二部手機(jī)會收到祝賀短信。嘗試發(fā)一個其他內(nèi)容的短信,檢查邀請短信是否能正常發(fā)送。
下面來添加廣播行為:當(dāng)廣播列表BroadcastList中的成員向廣播中心發(fā)來短信時,將此信息轉(zhuǎn)發(fā)給列表中的所有手機(jī)。這一功能稍顯復(fù)雜,需要更多的控制塊:增加一個Ifelse塊和一個foreach塊。新增的Ifelse塊用于檢查發(fā)送短信的手機(jī)號是否在廣播列表中,而foreach塊用于向列表中的所有手機(jī)廣播這條短信。另外還要將之前的Ifelse塊移動到新Ifelse塊的“else”部分。表11-4列出了需要新增的塊。
表11-4 向列表中的成員廣播某個成員發(fā)來的短信需要新增的塊
塊的類型 | 所在抽屜 | 作用 |
---|---|---|
ifelse | Control | 根據(jù)發(fā)信人是否已在廣播列表中來決定做不同的事 |
is in list? | Lists | 檢查某數(shù)據(jù)是否在列表中 |
get global BroadcastList | Variables | 將其插入is in list?的list插槽中 |
get number | Variables | 將其插入is in list?的thing插槽 |
set Texting1.Message to | Texting1 | 設(shè)置將被廣播出去的短信內(nèi)容(列表成員的來信) |
get messageText | Variables | 即將被廣播出去的列表成員來 |
foreach | Control | 向列表中的所有成員發(fā)送同一條短信 |
get global BroadcastList | Variables | 將其插入foreach的list插槽 |
set Texting1.PhoneNumber to | Texting1 | 設(shè)置接收短信的手機(jī)號碼 |
get item | Variables | BroadcaseList中當(dāng)前正在操作的項/變量:保存的是手機(jī)號 |
這里使用了嵌套的ifelse塊,使得程序更加復(fù)雜,如圖11-5所示。嵌套的ifelse塊指的是在一個ifelse塊的“then”或“else”插槽中嵌入了另一個ifelse塊。在本例中,外層的ifelse負(fù)責(zé)檢查發(fā)信人的手機(jī)號是否已在廣播列表中。如果在,則將該短信轉(zhuǎn)發(fā)給列表中的所有人;如果不在,則執(zhí)行內(nèi)層ifelse判斷:短信內(nèi)容messageText是否為“joinFMDT”,并依據(jù)判斷結(jié)果,執(zhí)行不同的分支操作。
圖 11-5 檢查發(fā)信人是否已在廣播列表中,如果是,則廣播此短信
從理論上,if塊和ifelse塊可以做任意層級的嵌套,來實現(xiàn)更加復(fù)雜的行為(更多關(guān)于條件語句塊的內(nèi)容請參見第18章)。
在外層ifelse塊的then分支中,使用foreach塊來廣播短信。foreach遍歷BroadcastList列表中的每一項,并把短信發(fā)送給列表中的每個電話號碼。在foreach執(zhí)行循環(huán)時,BroadcastList中的每個電話號碼依次被保存在item中(item是一個變量,代表了foreach當(dāng)前正在處理的項)。在foreach塊內(nèi),設(shè)置Texting.PhoneNumber的值為當(dāng)前項item,并向其發(fā)送短信。有關(guān)foreach的更多信息,請參見第20章。
測試:首先要有兩部不同的手機(jī)通過發(fā)送“joinFMDT”到測試手機(jī),實現(xiàn)成功注冊。然后,從一部手機(jī)向廣播中心發(fā)一條短信,這時兩部手機(jī)都應(yīng)該收到這條短信(包括發(fā)送短信的那一個)。
廣播短信的功能已經(jīng)實現(xiàn),但管理員的界面尚需改進(jìn)。首先,電話號碼列表的顯得很亂:用Label顯示列表時,列表項之間用空格分隔,并且盡可能占滿一行,像下面這樣:
(+861303318989 +861581235590 +8618902018909 +8613301103355 +8613801237890)
為了改善這種局面,使用表11-5列出的塊創(chuàng)建一個過程displayBroadcastList,來實現(xiàn)每行只顯示一個號碼。請務(wù)必在add items to list塊的下面調(diào)用該過程,以便顯示更新后的列表。
表11-5 改進(jìn)電話號碼列表顯示所需的塊
塊的類型 | 所在抽屜 | 作用 |
---|---|---|
to procedure(“displayBroadcastList”) | Procedures | 創(chuàng)建過程displayBroadcastList |
set BroadcaseListLabel.Text to | BroadcaseListLabel | 用來顯示列表 |
“” | Text | 空文本 |
foreach | Control | 對電話號碼列表進(jìn)行遍歷 |
pnumber | foreach內(nèi)置 | 變量pnumber為遍歷過程中正在訪問的 |
get global BroadcaseList | Variables | 插入foreach塊的in list插槽 |
set BroadcaseListLabel.Text to | BroadcaseListLabel | 顯示電話號碼列表 |
join | Text | 將多個文本片段連接為一個文本對象 |
BroadcaseListLabel.Text | BroadcaseListLabel | 每次循環(huán)都以既有l(wèi)abel內(nèi)容為基礎(chǔ)追加新項 |
“\n” | Text | 換行,以便下一個號碼顯示在下一行 |
get pnumber | foreach內(nèi)置 | 遍歷時列表中正在訪問的項(手機(jī)號碼) |
過程displayBroadcastList中的foreach塊逐行地將每個手機(jī)號碼添加到label的末尾,如圖11-6所示,用換行符(\ n)來分隔每個號碼,使得每個號碼各占一行。
圖 11-6 逐行顯示手機(jī)號碼
不過displayBroadcastList過程不會主動做任何事,除非調(diào)用它。在Texting1.MessageReceived事件處理程序中,緊接著add item to list塊調(diào)用它。過程的調(diào)用取代了列表BroadcastList在 BroadcastListLabel.Text中的默認(rèn)顯示。塊call displayBroadcastList歸屬在Procedures抽屜中。
圖11-7顯示了Texting1.MessageReceived事件處理程序中相關(guān)的塊。
圖 11-7 調(diào)用displayBroadcastList過程
關(guān)于用foreach來顯示列表的詳細(xì)信息請參見第20章,關(guān)于創(chuàng)建和調(diào)用過程的詳細(xì)信息請參見第21章。
測試:重新啟動應(yīng)用來清除列表,然后用至少兩個不同的手機(jī)進(jìn)行注冊(再次)。手機(jī)號碼是否逐行顯示了?
在收到短信并向其他手機(jī)發(fā)出廣播之后,程序應(yīng)該記錄此類事件,以便管理員可以對活動進(jìn)行監(jiān)督。在組件設(shè)計器中,已經(jīng)添加的LogLabel組件就是用于這一目的。下面編寫程序,每當(dāng)收到新的短信時,改變LogLabel的顯示。
要創(chuàng)建像這樣的一段文本:“來自+8613901231234的短信已經(jīng)廣播。”字符“+8613901231234”不是固定數(shù)據(jù),而是MessageReceived事件自帶的參數(shù)值。因此,要創(chuàng)建的文本包括三個部分:①“來自”;②手機(jī)號碼,為參數(shù)number;③“的短信已經(jīng)廣播”。正如在前幾章中所做的一樣,用join將三個部分連接起來,表11-6列出了需要的塊。
表11-6 構(gòu)建廣播日志所需要的塊
塊的類型 | 所在抽屜 | 作用 |
---|---|---|
set LogLabel.Text to | LogLabel | 在此顯示日志 |
join | Text | 由多個文本片段創(chuàng)建成一個文本對象 |
“來自” | Text | 每條日志信息的第①部 |
get number | Texting1.MessageReceived事件內(nèi)置參數(shù) | 日志信息的第②部分:短信發(fā)送者的手機(jī)號碼 |
“的短信已經(jīng)廣播。\n” | Text | 日志信息的第③部分 |
LogLabel.Text | LogLabel | 在原有日志前插入一條新的日志 |
在收到短信后,向BroadcastList列表中的所有號碼廣播此短信,再修改LogLabel,記錄剛才的廣播操作,如圖11-8所示。需要注意的是,我們將消息添加到列表的開始,而不是結(jié)尾,因此最后發(fā)出的消息將顯示在最頂端。
圖 11-8 向廣播日志中添加一條新消息
join塊創(chuàng)建了一條新記錄:來自+8613901231234的短信已經(jīng)廣播。
每次短信廣播之后,這條記錄將被添加到LogLabel.Text的第一行,使最新的記錄一直出現(xiàn)在頂部。join塊中各個文本片段的順序決定了日志中記錄的順序。在本例子中,新消息被編排在前三個插槽中,而LogLabel.Text,已經(jīng)保存的現(xiàn)有記錄,將插入最后一個插槽。
“的短信已經(jīng)廣播。\n”中的“\n”稱為換行符,它讓每條記錄單獨占一行,像這樣:
來自+8613030123668的短信已經(jīng)廣播。
來自+8613901231234的短信已經(jīng)廣播。
關(guān)于使用foreach來顯示列表的詳細(xì)信息,請參見第20章。
現(xiàn)在應(yīng)用算是大功告成了,但通過前幾章的學(xué)習(xí),你可能猜到了一個問題:如果管理員將應(yīng)用關(guān)閉再重新啟動時,廣播列表中的數(shù)據(jù)將會丟失,每個人都得重新注冊。為了解決這個問題,要使用TinyDB組件實現(xiàn)BroadcastList列表在數(shù)據(jù)庫中的存儲和檢索。
這里將使用與“出題”應(yīng)用(第10章)中相類似的方案:
每次添加新項時,將列表保存到數(shù)據(jù)庫中;
應(yīng)用啟動時,從數(shù)據(jù)庫中加載列表,并保存到一個變量中。
用表11-7中所列的塊,將列表存儲到數(shù)據(jù)庫中。TinyDB組件中的tag作為數(shù)據(jù)的標(biāo)識,將保存在數(shù)據(jù)庫中的不同數(shù)據(jù)區(qū)分開來。在本例中,你可以將數(shù)據(jù)標(biāo)記為“broadcastList”。在Texting1.MessageReceived中,將這些塊添加到add items to list塊之下。
表11-7 用TinyDB來存儲列表所需的塊
塊的類型 | 所在抽屜 | 作用 |
---|---|---|
TinyDB1.StoreValue | TinyDB1 | 將數(shù)據(jù)保存到數(shù)據(jù)庫中 |
“broadcastList” | Text | 將其插入StoreValue的tag插槽中 |
get global BroadcastList | Variables | 將其插入StoreValue的value插槽中 |
當(dāng)應(yīng)用收到短信“joinFMDT”,并將新成員的手機(jī)號碼添加到列表時,調(diào)用TinyDB1.StoreValue將BroadcastList保存到數(shù)據(jù)庫中。tag(“broadcastList”)的使用是為了便于之后對數(shù)據(jù)的檢索。如圖11-9,被StoreValue調(diào)用的值(valueToStore)是變量BroadcastList。
圖 11-9 調(diào)用TinyDB來存儲BroadcastList列表
每次應(yīng)用啟動時都要加載廣播列表,按照表11-8中列出的塊來實現(xiàn)這一功能。應(yīng)用的啟動將觸發(fā)Screen1.Initialize事件,因此將在該事件的處理程序中實現(xiàn)加載。使用存儲時的tag(“broadcastList”)來調(diào)用TinyDB.GetValue。就像前幾章一樣,我們需要檢查是否的確有數(shù)據(jù)返回,這里將檢查返回值是否為列表,因為如果列表中沒有數(shù)據(jù),那么它也就不是列表。
應(yīng)用啟動將觸發(fā)Screen1.Initialize事件。如圖11-10所示,使用TinyDB1.GetValue塊向數(shù)據(jù)庫請求數(shù)據(jù),返回的數(shù)據(jù)臨時保存在已定義的變量valueFromDB中。
表11-8 應(yīng)用啟動時加載廣播列表所需要的塊
塊的類型 | 所在抽屜 | 作用 |
---|---|---|
initialize global valueFromDB to | Variables | 用于保存并檢查數(shù)據(jù)庫返回值的臨時變量 |
“” | Text | 設(shè)valueFromDB初始值為空 |
Screen1.Initialize | Screen1 | 應(yīng)用啟動時觸發(fā)該事件 |
set global valueFromDB to | Variables | 將數(shù)據(jù)庫返回值暫時存放在其中 |
TinyDB1.GetValue | TinyDB1 | 向數(shù)據(jù)庫請求數(shù)據(jù) |
“broadcastList” | Text | 將其插入GetValue的tag插槽 |
if | Control | 判斷數(shù)據(jù)庫中是否有數(shù)據(jù) |
is a list | Lists | 如果數(shù)據(jù)庫返回值是一個列表,則返回值不為空 |
get global valueFromDB | Variables | 將其插入is a list? |
set global BroadcaseList to | Variables | 將變量值設(shè)置為數(shù)據(jù)庫的返回值 |
get global valueFromDB | Variables | 數(shù)據(jù)庫返回值不為空時,將返回值寫入廣播列表 |
call displayBroadcastList | Procedures | 加載數(shù)據(jù)成功后,顯示數(shù)據(jù) |
圖 11-10 從數(shù)據(jù)庫中加載廣播列表BroadcastList
事件處理程序中的if塊是必需的,因為在首次啟動應(yīng)用時,數(shù)據(jù)庫將返回空文本(“”),這時還沒有生成廣播列表。通過判斷valueFromDB是否為列表,可以確定是否真的有數(shù)據(jù)返回。如果沒有,則跳過那些保存返回數(shù)據(jù)以及顯示數(shù)據(jù)的塊。
更多建議: