前言:
? ? ? ? ?本篇文章正經來說,其實算是我的學習履歷,是我在不斷的摸索過程中,總結的經驗,不能算是一篇正經的學術文章。現在DELPHI的學習資料太少了,就算是有也都是基于老版本DELPHI,或VCL相關的內容,涉及FMX、Android編程就顯得明顯不足,我雖然也玩了幾年的DELPHI,但說實話能力水平有限,下面的文章如果有錯誤的地方,也請同行的前輩們指點修正,先在此謝過。
? ? ? ? ?那這篇文章主要說點什么呢,請大家先參考下面的目錄,我盡可能把我知道的這點小經驗分享給大家,這里面有很多的知識都是從網上學習整理而來,我也只是一個資料的整理者而已,考慮到DELPHI目前的學習資料匱乏問題,也為了照顧新人,在本文中先科普一些關鍵知識點,我會將內容盡可能講到重點與詳細說明,希望每個人都能看懂和理解。
提示:安卓系統的更新版本變化比較大,且國內各品牌手機系統都存在定制化問題,本文中所有涉及的實例程序,均是以DELPHI 11版本編寫,如果其它DELPHI版本按照同方法出現問題,請大家自行修改,而測試的手機品牌主要以小米為主。
目錄
前言:
一、Android Service簡介
1.1 什么是Android Service?
1.2 Android Service的生命周期
1.3 Android Service的類型與區別
1.4 Android Service能做什么?
二、Local Service程序
2.1 Local Service特點
2.2 Local Service實例
三、Intent Local Service程序
3.1 Intent Local Service特點
四、Remote Service程序
4.1 Remote Service特點
4.2 Remote Service實例
五、Intent Remote Service程序
六、結束語
一、Android Service簡介
1.1 什么是Android Service?
本文中所寫的Android Service是指運行于安卓系統后臺的服務,主要的特點如下:
- 它運行于系統后臺,沒有界面,無法單獨存在,必須要與前臺程序綁定。
- 可長期運行,即使是前臺的程序關閉或銷毀,它依然可以運行于后臺。
- 優先級高于Activity(內存不足時先殺掉Activity)。
- 運行在主線程,且不能做耗時操作(超時標準請參考下面的說明)?
?Activity:Activity是Android中四大組件之一,而我們的Android應用是由一個或多個Activity組成的。活動(Activity)是一個可視化的用戶界面,負責創建一個屏幕窗口,放置 UI 組件,供用戶交互。
安卓超時標準:Android中長時間無響應會出現ANR
- 后臺Service:200秒(本文重點所講內容)
- 前臺Service:20秒(Foreground Service)后面有機會單獨開一篇重點講述
- Activity:5秒
- 后臺廣播:60秒
- 前臺廣播:10秒
ANR:(Application Not Responding)是指在安卓系統上,響應不夠靈敏時,系統會向用戶顯示的一個對話框。
1.2 Android Service的生命周期
我們先用一張圖來說明Android Service的生命周期,然后再來進行解釋,圖示如下:
?
? ? ? ? 從上圖中我們可以看到,安卓服務有兩種啟動方式:StartService和BindService,下面針對這兩種方式的生命周期,我們用兩個表格來做簡要說明,詳細如下:
步驟 | 說明 |
---|---|
OnCreate() | 1、如果 service 沒被創建過,調用 startService() 后會執行 onCreate() 回調; 2、如果 service 已處于運行中,調用 startService() 不會執行 onCreate() 方法。 |
onStartCommand() | 如果多次執行了 startService() 方法,那么 Service 的 onStartCommand() 方法也會相應的多次調用 |
OnBind() | Service中的onBind()方法是抽象方法,Service類本身就是抽象類,所以onBind()方法是必須重寫的,即使我們用不到 |
onDestory() | 在銷毀的時候會執行Service該方法 |
步驟 | 說明 |
---|---|
OnCreate() | 當服務通過 onStartCommand() 和 onBind() 被第一次創建的時候,系統調用該方法。該調用要求執行一次性安裝 |
OnBind() | 當其他組件想要通過 bindService() 來綁定服務時,系統調用該方法。如果你實現該方法,你需要返回 IBinder 對象來提供一個接口,以便客戶來與服務通信。你必須實現該方法,如果你不允許綁定,則直接返回 null |
onUnbind() | 當客戶中斷所有服務發布的特殊接口時,系統調用該方法 |
onRebind() | 當新的客戶端與服務連接,且此前它已經通過onUnbind(Intent)通知斷開連接時,系統調用該方法 |
onDestroy() | 當服務不再有用或者被銷毀時,系統調用該方法。你的服務需要實現該方法來清理任何資源,如線程,已注冊的監聽器,接收器等 |
提示:我們先只做理論上的說明,暫不涉及具體的實例與實現的方法,在后面的案例中,我們將用到前面所講的理論知識點。
1.3 Android Service的類型與區別
在DELPHI中,我們創建Android Service時(創建方式如下圖所示)軟件提供了四種類型供我們選擇,那么這四種Service程序到底有什么區別呢?
?
?
?
服務類型 | 區別 | 優點 | 缺點 |
---|---|---|---|
Local Service | 該服務依附在主進程上 | 服務依附在主進程上而不是獨立的進程,這樣在一定程度上節約了資源,另外Local服務因為是在同一進程因此不需要IPC,也不需要AIDL。相應BindService會方便很多 | 主進程被Kill后,服務便會終止 |
Intent Local Service | 同上 | 與上面的區別在于可以利用Intent處理異步請求 | |
Remote Service | 該服務是獨立的進程 | 服務為獨立的進程,對應進程名格式為所在包名加上你指定的android:process字符串。由于是獨立的進程,因此在Activity所在進程被Kill的時候,該服務依然在運行,不受其他進程影響,有利于為多個進程提供服務具有較高的靈活性 | 該服務是獨立的進程,會占用一定資源,并且使用AIDL進行IPC稍微麻煩一點 |
Intent Remote Service | 同上 | 與上面的區別在于可以利用Intent處理異步請求 |
- Intent:Intent用于Android程序中各組件(Activity、BroadcastReceive、Service)的交互,并且可以在組件之間傳遞數據。
- AIDL:Android Interface Definition Language是為了實現進程間通信而設計的Android接口語言
- IPC:Inter-Process Communication的縮寫,含義為進程間通信或者跨進程通信,是指兩個進程之間進行數據交換的過程
?上面都是實現進程之間通訊的方式,詳細內容太多,這里不詳細說明,在后面的案例中,我們將會用到這些知識點。?
1.4 Android Service能做什么?
- 網絡事務:聊天(等待他人回復短信)、地圖定位(熄屏后實時定位的播報語音)等
- 本地資源:播放音樂(讀取音樂文件)、文件IO(后臺上傳文件、下載文件)等
- 定時任務:訂單超時(未支付情況下,一定時間后自動銷毀訂單)、鬧鐘提醒等
小結:Android Service的簡要知識點先介紹到這里,更多的內容或更詳細的內容大家可以網上搜索,這里受篇幅問題,不過多介紹,下面我將按類型進行實例的演示介紹
二、Local Service程序
2.1 Local Service特點
如上面《表3:Android Service四種類型說明》所述,Local Service應該是使用最多的一種Service類型,其主要的特點如下:
- 運行在主進程上,并且與前臺應用程序是同一個進程
- 與前臺程序是一對一綁定,無法被其它程序調用
- 與前臺程序的綁定不需要IPC或AIDL
2.2 Local Service實例
根據Local Service的特點,我們創建一個通知類的服務程序,由運行于后臺的Service根據條件,給前臺的程序進行通知的發送,本例設計主要實現以下幾個功能:
- 如何創建一個Local Service服務
- 前臺程序如何啟動服務
- 后臺如何發送通知給前臺
- 如何獲取安卓系統通知權限
- 如何確保鎖屏或待機后仍然收到通知
說明:因為是第一個例子,所以在本例中有些公共的功能我會在此例中實現(如:權限申請,電源管理等),后面其它類型的案例我只針對差異性的地方進行案例說明
下面我們開始正式的案例編程:
后臺Service編程:
首先,我們按照上面1.3所寫的內容,創建一個Local Service的服務,然后在模塊中放上一個NotificationCenter1控件,完成后所得到的界面如下圖所示:
我們將工程與模塊進行保存,這里有2個非常重要的地方要記住:
- 工程名字一定要記住,因為這涉及到前臺程序調用的問題,我這里將工程名保存為:lSysService。
- 整個Service工程與模塊放在獨立的文件夾,最好不要與前臺程序混在一起。
然后我們在DM模塊中創建一個通知過程,我將其命名為:SetNotification,具體如下:
procedure TFData.SetNotification(MyTitle,MyBody:string);
varMyNotification:TNotification;
beginMyNotification:=TNotification.Create;tryMyNotification.Name:='MyNotification1';MyNotification.Title:=MyTitle;MyNotification.AlertBody:=MyBody;MyNotification.FireDate:=Now;NotificationCenter1.PresentNotification(MyNotification);finallyMyNotification.Free;end;
end;
因為只是做個簡單的示例,所以通知相關的具體內容都是直接使用的文本賦值,大家可以使用其它的方式進行值的設定,我這里發送通知的條件設定為每隔5秒,后臺給前臺發送一次通知。
我們選中DM模塊,在OnStartCommand事件中,加上如下代碼:
function TFData.AndroidServiceStartCommand(const Sender: TObject;const Intent: JIntent; Flags, StartId: Integer): Integer;
beginwhile True dobeginSleep(5000);SetNotification('系統通知','這是一個測試AndroidService的程序,此信息來自Service后臺發送');end;Result:=TJService.JavaClass.START_STICKY;
end;
說明:TJService.JavaClass.START_STICKY這一句代碼的功能,是保持服務運行,這里還有其它幾個選項如下:
- START_NOT_STICKY:默認值,服務被系統殺死時,不再啟動
- START_STICKY:服務被系統殺死時,將嘗試重新創建服務?
到這里為止,我們這個簡單的通知服務程序功能基本就完成了,我們可以進行Build,然后給前臺程序進行綁定調用試試,在下圖位置按鼠標右鍵,選擇Build。
當提示成功后,我們的Service就完成了。下面我們開始做前臺程序。
前臺APP編程:
第一步:我們新建一個FMX工程,并在FORM上也放上一個NotificationCenter1,Memo1,Button1控件,完成后所得到的界面如下圖所示:
第二步:綁定我們剛剛做好的Service程序,按如下圖片所示操作即可
在上圖紅框這里點擊,并選擇Service程序存放的目錄,這也是我為什么要在Service程序創建前提醒大家一定要放在獨立目錄的原因
完成后,我們可以查看一下是否導入成功,按下圖位置看看文件是否綁定完成
還記得你制作的Service工程名叫什么嗎,我這個案例的名稱就是ISysService。能夠看到這個文件,我們的綁定就成功了,接下來就可以進行代碼編寫,并完成相應的功能了
第三步:啟動Service
我們將啟動Service服務的功能放在Button1的點擊事件中,并賦上啟動Service的功能代碼,如下所示:
我們先聲明一個變量,名稱為:MyService,如下所示,同時我們還需要引用System.Android.Service單元
然后在Button1的點擊事件中,加上以下代碼,用來啟動Service
procedure TForm2.Button1Click(Sender: TObject);
begintryMyService:=TLocalServiceConnection.Create;MyService.StartService('lSysService');Memo1.Lines.Add('服務已啟動');excepton E:Exception dobeginMemo1.Lines.Add(e.Message);end;end;
end;
這里注意MyService.StartService('lSysService')這一句,其中lSysService就是我們之前創建的Service的工程名稱,大家不要搞錯了,這也是為什么前面一再強調要記住名稱的原因。
為了更好的查看通知內容,我們再加上一個通知的響應,當收到通知后,我們點擊通知就把內容提取到APP的Memo1中顯示,我們選中NotificationCenter1控件,并在OnReceiveLocalNotificationg事件中,加上如下代碼:
procedure TForm2.NotificationCenter1ReceiveLocalNotification(Sender: TObject;ANotification: TNotification);
beginMemo1.Lines.Add(ANotification.AlertBody);
end;
然后我們保存工程與模塊,編譯運行看看
如果我們啟動APP時,系統有上面圖片的詢問,我們當然要選擇允許,要不然我們不可能收到通知,然后進入APP以后,我們點擊“啟動服務”
等待幾秒鐘后,我們就看到上面會出現一個通知,當我們點擊通知時,內容就會顯示在主界面的Memo1里面
提示:如果我們啟動APP時,可能有部分手機不會彈出是否允許推送消息的提示,那么我們就需要在手機設置的應用管理中,允許APP接受通知的手動設置,詳細操作這里不細說,這個應該不難
到這里為止,我們的功能算是完成了,但有一個問題,當我們關閉前臺APP,或者手機鎖屏、待機了以后,我就無法收到消息了,不能像微信一樣可以時時接收信息呢?
這涉及到一個非常復雜的問題,各個軟件商也為此頭疼并付出了很多精力,就是為了讓服務能夠永駐,下面有一篇我在網上看到的文章,大家可以看看,服務永駐將會占用系統資源,我們首先需要考慮的問題是,是否需要真的長期永駐?
Android service 不被殺死“永不退出的服務”(雙進程,服務,多進程,微信)_安卓 進程不退出-CSDN博客
當然,在我們本例中,還是可以做點事情,解決一些手機鎖屏或軟件退入后臺而無法接收通知的問題,可以在電源管理方面進行設置,但需要我們手動設置
在手機系統設置中,完成了以上兩項設置,那么前臺APP轉入后臺,或手機鎖屏仍然可不停的收到通知信息,除非將程序卸載。
至于如何做到像微信一樣,服務永駐系統,即使前臺關閉,后臺服務也一直運行的實現方式,后面我單獨再開一篇說明
三、Intent Local Service程序
3.1 Intent Local Service特點
如上面《表3:Android Service四種類型說明》所述,Intent Local Service大部分功能與Local Service是一致的,只是多了一個Intent而已,那這個Intent到底是什么東西呢,在Service程序中,它能起到什么作用呢,我們下面先簡單的介紹一下。
3.1.1 什么是Intent?
在 Android 開發中,Intent 是一種非常重要的機制,它能夠在應用程序之間傳遞數據并啟動不同的組件,廣泛應用于Android程序中各組件(Activity、BroadcastReceive、Service)的交互,并且可以在組件之間傳遞數據,分為顯式Intent和隱式Intent
- 顯式Intent:明確指出了目標組件名稱的Intent,我們稱之為顯式Intent,更多用于在應用程序內部傳遞消息。比如在某應用程序內,一個Activity啟動一個Service
- 隱式Intent:沒有明確指出目標組件名稱的Intent,則稱之為隱式Intent,它不會用組件名稱定義需要激活的目標組件,它更廣泛地用于在不同應用程序之間傳遞消息,是根據action和category找出合適的目標。可通過Mainfest.xml配置各組件的<intent-filter>,只有當<action>和<category>同時匹配時,才能響應對應的Intent
上面的解釋如果對安卓系統不太了解,可能會聽起來一頭霧水,不知道我在說啥,不過沒有關系,下面我們針對一些重點的知識點,做詳細的說明。
3.1.2 Intent的關鍵屬性有哪些?
為了更清楚的理解,我們用表格的方式來進一步說明Intent。
屬性 | 說明 |
---|---|
Component Name | 要啟動的組件名稱,在創建Intent的時候是可選的,但是它是顯式Intent的重要標志,有它就意味著只有Component name匹配上的那個組件才能接收你發送出來的顯示intent。如果不寫那么你創建的Intent就是隱式的,系統會根據這個intent的其他信息(比如:action、data、category)來確定哪些組件來接收這個intent,所以如果你想明確的啟動哪個組件,就通過component name來指定 |
Action | 意圖,一個字符串變量,用來指定Intent要執行的動作類別(比如:view or pick)。你可以在你的應用程序中自定義action,但是大部分的時候你只使用在Intent中定義的action,標準Action有以下幾項:
|
Data | 一個Uri對象,對應著一個數據,這個數據可能是MIME類型的。當創建一個intent時,除了要指定數據的URI之外,指定數據的類型(MIME type)也很重要,比如,一個activity能夠顯示照片但是無法播放視頻,雖然啟動Activity時URI格式很相似。指定MIME type是很重要的,它能夠幫助系統找到最合適的那個系統組件來處理你的intent請求。然而,MIME type有時能夠通過URI來推測出來,特別是當data是content:的URI,這樣的data表明在設備中由ContentProvider提供. 只設置數據的URI可以調用setData()方法,只設置MIME類型(MIME的類型定義,請參考本文最后的說明)可以調用setType()方法,如果要同時設置這兩個可以調用setDataAndType()。 內置的常量屬性如下:
|
Category | 一個包含Intent額外信息的字符串,表示哪種類型的組件來處理這個Intent。任何數量的Category 描述都可以添加到Intent中,但是很多intent不需要category,你可以通過調用addCagegory()方法來設置category,標準的常量如下:
|
Extras | Intent可以攜帶的額外key-value數據,你可以通過調用putExtra()方法設置數據,每一個key對應一個value數據。你也可以通過創建Bundle對象來存儲所有數據,然后通過調用putExtras()方法來設置數據。對于數據key的名字要盡量用包名做前綴,然后再加上其他,這樣來保證key的唯一性,常用的常量屬性如下:
|
Flags | 用來指示系統如何啟動一個Activity(比如:這個Activity屬于哪個Activity棧)和Activity啟動后如何處理它(比如:是否把這個Activity歸為最近的活動列表中) |
MIME的說明,請參考以下文章:
android intent MIME type_android intent mimetype-CSDN博客
3.1.3 如何創建Intent并發送數據?
Android 中,我們可以使用 Intent 類來創建一個新的 Intent。其構造方法包含兩個參數:Context 參數和目標組件的 Class 對象。 Context 參數通常指當前的 Activity 或 Application 對象,而目標組件則是要啟動的 Activity、Service 或 BroadcastReceiver 等組件的類名,下面我們用一個小例子來說明Intent的創建:
我們新建一個工程,在Form上放一個Button,用來發送Intent,大概如下:
然后我們需要引用的單元與發送Intent的功能代碼如下:
Uses{$IFDEF Android}Androidapi.JNI.GraphicsContentViewText, // JIntentAndroidapi.Helpers, // StringToJStringFMX.Platform.Android; // MainActivity{$ENDIF}
procedure TForm2.Button1Click(Sender: TObject);
varAText: string;Intent: JIntent;
beginAText := '這是來自人馬座星系發來的賀電';Intent := TJIntent.Create;Intent.setType(StringToJString('text/plain')); //設置MIME類型為純文本格式Intent.setAction(TJIntent.JavaClass.ACTION_SEND); //設置意圖為發送數據//調用系統程序發送文本信息Intent.putExtra(TJIntent.JavaClass.EXTRA_TEXT, StringToJString(AText));{使用Android API PackageManager類的queryIntentActivities方法,
確認是否存在可以處理該意圖的應用程序。
如果有可以處理的應用程序,請發送意圖。沒有則提示“未找到接收者”}if MainActivity.getPackageManager.queryIntentActivities(Intent,TJPackageManager.JavaClass.MATCH_DEFAULT_ONLY).size > 0 thenMainActivity.startActivity(Intent) // 啟動IntentelseShowMessage('未找到接收者');
end;
上面這段代碼實現的功能是:發送一段文本信息給另一個應用程序,如果我們按照《表4:Intent的關鍵屬性》的說明去理解,那么上面這一小段代碼就不難理解了,大概結論如下:
- 上面這一段代碼是一個隱式Intent
- 功能是:系統根據Intent的條件(類型:文本,意圖:發送數據)去尋找符合條件的程序,當程序的數量大于1時,會提示軟件清單,由用戶選擇哪一個來實現。比如能發送文本數據的程序有:郵箱,微信等等
3.1.4 如何接收Intent數據?
我們繼續結合3.1.3上面的例子,做一個接收Intent的實例來說明,如果想要做到此功能,我們最少需要做兩個步驟:
- 具備符合接收此類Intent的條件
- 接收Intent的功能代碼
那如何讓我們的程序具備接收Intent的條件,又如何讓程序接收Intent呢,下面我們用一個小例子來具體說明:
我們新建一個工程,在上面放一個Memo,一個Button,大概界面如下,然后進行保存:
首件解決:具備符合接收此類Intent的條件
我們根據3.1.3上面代碼針對Intent的設定,我們要在接收的程序設置符合發送方的條件,手段就是通過修改AndroidManifest.template來實現,我們在下面的代碼中可以看到設置都是根據發送方的條件設置的:
? ? ? ? <intent-filter>
? ? ? ? ? ? <action android:name="android.intent.action.SEND" />
? ? ? ? ? ? <category android:name="android.intent.category.DEFAULT" />
? ? ? ? ? ? <data android:mimeType="text/plain" />
? ? ? ? </intent-filter>
<activityandroid:name="com.embarcadero.firemonkey.FMXNativeActivity"android:label="%activityLabel%"android:configChanges="orientation|keyboard|keyboardHidden|screenSize"android:launchMode="singleTask"><!-- Tell NativeActivity the name of our .so --><meta-data android:name="android.app.lib_name" android:value="%libNameValue%" /><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter><!-- 以下是追加的部分,與發送的程序Intent設置相同 --><intent-filter><action android:name="android.intent.action.SEND" /><category android:name="android.intent.category.DEFAULT" /><data android:mimeType="text/plain" /></intent-filter></activity>
如果不做上面這個動作,那么我們在發送端就無法調用到此接收程序,因為此接收程序不具備接收發送方的條件,發下圖所示,當我在發送方點擊Button時,系統會彈出一個符合接收條件的所有程序出來,紅框標示的程序是我自己寫得接收程序,如果不做上面的步驟,那就無法顯示并無法調用:
接下來,我們繼續寫接收Intent的功能代碼,按照程序的設計,我把接收Intent的功能代碼賦給Button,詳細如下:
先引用單元:
fmx.Platform,{$IFDEF Android}fmx.Platform.Android,androidapi.Jni.GraphicsContentViewText,androidapi.Helpers,Androidapi.JNI.Os,{$ENDIF}
?然后編寫接收Intent代碼:
procedure TForm2.Button1Click(Sender: TObject);
varIntent:JIntent;
beginIntent:=SharedActivity.getIntent;if Intent.hasExtra(TJIntent.JavaClass.EXTRA_TEXT) thenbegin Memo1.Lines.Add(JStringToString(Intent.getStringExtra(TJIntent.JavaClass.EXTRA_TEXT)));end else beginMemo1.Text:='距離太遠,半路丟了';end;
end;
?編譯運行,結束
我們來試試看,為了避免其它一些不必要的問題,我們先關閉發送端與接收端程序,然后重新打開發送程序,點擊Button,并選擇接收程序,圖片同上就不重復傳了,然后在接收程序中點擊“接收Intent”按鈕,我們就會發現,已經收到發送端的消息了
提示:之所以用這么長的篇幅來介紹Intent,是因為它不僅在Android Service中能用,其它地方也可以,所以就寫得相對啰嗦了點。而Intent Local Service的實例演示就不再重復做了,大家可以根據前面的Local Service再結合Intent的知識點,自己嘗試著做一個案例試試
四、Remote Service程序
4.1 Remote Service特點
如上面《表3:Android Service四種類型說明》所述,Remote Service與Local Service大部分功能是相同的,其區別主要有以下幾點:
- Remote Service是獨立進程,Local Service則是與Activity屬于同一進程
- Remote Service可以被多個Activity調用,Local Service與Activity屬于一對一綁定
- Remote Service綁定需要使用AIDL進行IPC,Local Service不需要
- Remote Service在Activity進程被殺死時還能獨立運行,Local Service不可以
基于以上的特點,因此Remote Service一般常用于公共服務,即為系統常駐的Service(如:天氣服務等)。
4.2 Remote Service實例
我們還是利用一個實例來說明Remote Service的應用吧,在實例創建的過程中,我們再做詳細的說明與解析。
本實例我們實現的功能描述大概如下:
在后臺的Service自定義兩條消息,在前臺建立兩個程序,分別接收后臺的兩條消息,這個例子雖然簡單,但主要體現的是,多個程序綁定同一個Service,這也是Remote Service的特性。
我們按照上面1.3所寫的內容,創建一個Remote Service的服務程序,得到的初始界面如下,另外和Local Service一樣,記得工程和單元要保存在獨立的目錄,還有記住工程名字:
第二步:把Service需要用到的單元先引用
第三步:因為可能要給兩個程序調用,而且每個程序調用的消息不同,所以我們先自定義兩組常量來區分消息
第四步:我們編寫發送消息的代碼,點擊DM窗體,雙擊OnHandleMessage事件,并輸入如下代碼
function TDM.AndroidServiceHandleMessage(const Sender: TObject;const AMessage: JMessage): Boolean;
varMyMessage: JMessage;MyBundle: JBundle;
begincase AMessage.what ofCusText1: //自定義消息1beginMyBundle := TJBundle.Create;MyBundle.putString(StringToJString('Message1'), StringToJString('這是Remote Service自定義的第一個信息'));MyMessage := TJMessage.Create;MyMessage.what := ServiceText1;MyMessage.obj := MyBundle;AMessage.replyTo.send(MyMessage);Result := True;end;CusText2: //自定義消息2beginMyBundle := TJBundle.Create;MyBundle.putString(StringToJString('Message2'), StringToJString('這是Remote Service自定義的第二個信息'));MyMessage := TJMessage.Create;MyMessage.what := ServiceText2;MyMessage.obj := MyBundle;AMessage.replyTo.send(MyMessage);Result := True;endelseResult := False;end;
end;
第五步:Build程序,就完成了Service端的創建
接下來我們開始做前臺的程序,先做第一個,我們命名為:Prog1
創建一個FMX工程,在上面放上Label,Memo,Button,界面大致如下:
第二步:引用單元
第三步:建立與Service對應的常量來接收消息
第四步:申明變量與函數
第五步:編寫第一個程序的代碼,為了大家看起來方便,我一次全部導入
unit Unit2;interfaceusesSystem.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Memo.Types,FMX.ScrollBox, FMX.Memo, FMX.Controls.Presentation, FMX.StdCtrls,{$IFDEF Android}system.Android.Service,androidapi.Jni.Os,androidapi.Jni.GraphicsContentViewText,androidapi.Helpers,androidapi.Jni.JavaTypes,{$ENDIF}FMX.Layouts;typeTForm2 = class(TForm)Memo1: TMemo;Layout1: TLayout;Button1: TButton;Button2: TButton;Label1: TLabel;procedure Button1Click(Sender: TObject);procedure Button2Click(Sender: TObject);procedure FormDestroy(Sender: TObject);procedure FormCreate(Sender: TObject);private{ Private declarations }MyService:TRemoteServiceConnection;procedure OnHandleMessage(const AMessage: JMessage);procedure OnServiceConnected(const ServiceMessenger: JMessenger);public{ Public declarations }end;varForm2: TForm2;constCusText1 = 1001;CusText2 = 1002;ServiceText1 = 2001;ServiceText2 = 2002;implementation{$R *.fmx}procedure TForm2.Button1Click(Sender: TObject);
varMyMSG:JMessage;
begin//接收后臺自定義的第一個消息,這里關鍵是CusText1,與后臺對應的消息MyMSG := TJMessage.JavaClass.obtain(nil, CusText1);MyMSG.replyTo := MyService.LocalMessenger;MyService.ServiceMessenger.send(MyMSG);
end;procedure TForm2.OnHandleMessage(const AMessage: JMessage);
varAText: JString;MyBundle: JBundle;
begincase AMessage.what ofServiceText1:beginMyBundle := TJBundle.Wrap(AMessage.obj);AText := MyBundle.getString(TAndroidHelper.StringToJString('Message1'));Memo1.Lines.Add(JStringToString(AText));end;ServiceText2:beginMyBundle := TJBundle.Wrap(AMessage.obj);AText := MyBundle.getString(TAndroidHelper.StringToJString('Message2'));Memo1.Lines.Add(JStringToString(AText));end;elseMyService.Handler.Super.handleMessage(AMessage);end;
end;procedure TForm2.OnServiceConnected(const ServiceMessenger: JMessenger);
beginButton1.Enabled:=True;
end;procedure TForm2.Button2Click(Sender: TObject);
beginif MyService <> nil thenbeginMyService.UnbindService;end;
end;procedure TForm2.FormCreate(Sender: TObject);
beginButton1.Enabled:=False;MyService:=TRemoteServiceConnection.Create;MyService.OnConnected:=OnServiceConnected;MyService.OnHandleMessage:=OnHandleMessage;MyService.BindService('com.embarcadero.Prog1','com.embarcadero.services.MyService');//BindService有兩個字符串參數,都是包名,第一個是前臺程序的包名,第二參數是后臺Service的包名,前臺程序包名的默認值都是com.embarcadero開頭,后臺Service的默認包名是com.embarcadero.services開頭
end;procedure TForm2.FormDestroy(Sender: TObject);
beginMyService.Free;
end;end.
然后我們把Service目錄導入到前臺程序中,導入的步驟與Local Service一樣,這里不重復,請大家參考Local Service前臺編程的第二步操作即可,完成后我們運行程序看看
我們點擊消息1按鈕,就收到了來自后臺Service自定義的第一條消息,那么此前臺程序完成。
但我們做的是Remote Service,其特性是可以給多個程序綁定,那么我們再來建立一個新的前臺程序,我們為其命名為:Prog2,大概界面跟上面這個差不多,完成后如下:
接下來,我們按照第一個前臺程序一樣的步驟完成第二前臺程序的工作,這里需要注意的幾個地方大家要看看,雖然功能是一樣的,但第二個程序的工程名稱不同,而且我們在第二程序里調用的是后臺Service的第二條消息,所以下面幾個地方不能跟第一臺程序一樣
第一個不同:我們調用第二條消息,紅框這里的參數就需要更改
第二個不同:第二個程序的工程名不一樣,所以綁定Service的時候也要變
第二個前臺程序的所有代碼如下:
unit Main;interfaceusesSystem.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Memo.Types,FMX.StdCtrls, FMX.Controls.Presentation, FMX.ScrollBox,fmx.Memo,{$IFDEF Android}system.Android.Service,androidapi.Jni.Os,androidapi.Jni.GraphicsContentViewText,androidapi.Helpers,androidapi.Jni.JavaTypes,{$ENDIF}FMX.Layouts;typeTForm2 = class(TForm)Memo1: TMemo;Layout1: TLayout;Button1: TButton;Button2: TButton;Label1: TLabel;procedure Button1Click(Sender: TObject);procedure Button2Click(Sender: TObject);procedure FormCreate(Sender: TObject);procedure FormDestroy(Sender: TObject);private{ Private declarations }MyService:TRemoteServiceConnection;procedure OnHandleMessage(const AMessage: JMessage);procedure OnServiceConnected(const ServiceMessenger: JMessenger);public{ Public declarations }end;varForm2: TForm2;constCusText1 = 1001;CusText2 = 1002;ServiceText1 = 2001;ServiceText2 = 2002;implementation{$R *.fmx}procedure TForm2.Button1Click(Sender: TObject);
varMyMSG:JMessage;
beginMyMSG := TJMessage.JavaClass.obtain(nil, CusText2);MyMSG.replyTo := MyService.LocalMessenger;MyService.ServiceMessenger.send(MyMSG);end;procedure TForm2.Button2Click(Sender: TObject);
beginif MyService <> nil thenbeginMyService.UnbindService;end;
end;procedure TForm2.FormCreate(Sender: TObject);
beginButton1.Enabled:=False;MyService:=TRemoteServiceConnection.Create;MyService.OnConnected:=OnServiceConnected;MyService.OnHandleMessage:=OnHandleMessage;MyService.BindService('com.embarcadero.Prog2','com.embarcadero.services.MyService');
end;procedure TForm2.FormDestroy(Sender: TObject);
beginMyService.Free;
end;procedure TForm2.OnHandleMessage(const AMessage: JMessage);
varAText: JString;MyBundle: JBundle;
begincase AMessage.what ofServiceText1:beginMyBundle := TJBundle.Wrap(AMessage.obj);AText := MyBundle.getString(TAndroidHelper.StringToJString('Message1'));Memo1.Lines.Add(JStringToString(AText));end;ServiceText2:beginMyBundle := TJBundle.Wrap(AMessage.obj);AText := MyBundle.getString(TAndroidHelper.StringToJString('Message2'));Memo1.Lines.Add(JStringToString(AText));end;elseMyService.Handler.Super.handleMessage(AMessage);end;end;procedure TForm2.OnServiceConnected(const ServiceMessenger: JMessenger);
beginButton1.Enabled:=True;
end;end.
其它操作與第一個前臺程序是一致的,完成后我們運行第二個程序看看
到這里為止,我們前臺的兩個程序綁定到同一個Service全部成功了,其它的功能操作大概與Local Service是一樣的,這里不再重復的寫了。
五、Intent Remote Service程序
關于Intent Remote Service的介紹與實例,這里不再重復的啰嗦了,大家結合Remote Service的案例說明,再加上Intent的相關知識點,應該能自己寫出案例來了
六、結束語
這篇文章寫得真叫累,感覺有點啰嗦,但為了讓新人朋友們能看懂,我寧愿啰嗦點并盡可能寫得讓大家都看得懂,如果本文存在一些問題,請大家在下面留言,或者大家希望寫哪些方面的內容,也請在下面留言。
我們下次再見