前言:
本篇文章正经来说,其实算是我的学习履历,是我在不断的摸索过程中,总结的经验,不能算是一篇正经的学术文章。现在DELPHI的学习资料太少了,就算是有也都是基于老版本DELPHI,或VCL相关的内容,涉及FMX、Android编程就显得明显不足,我虽然也玩了几年的DELPHI,但说实话能力水平有限,下面的文章如果有错误的地方,也请同行的前辈们指点修正,先在此谢过。
那这篇文章主要说点什么呢,请大家先参考下面的目录,我尽可能把我知道的这点小经验分享给大家,这里面有很多的知识都是从网上学习整理而来,我也只是一个资料的整理者而已,考虑到DELPHI目前的学习资料匮乏问题,也为了照顾新人,在本文中先科普一些关键知识点,我会将内容尽可能讲到重点与详细说明,希望每个人都能看懂和理解。
目录
一、Android Service简介
1.1 什么是Android Service?
本文中所写的Android Service是指运行于安卓系统后台的服务,主要的特点如下:
- 它运行于系统后台,没有界面,无法单独存在,必须要与前台程序绑定。
- 可长期运行,即使是前台的程序关闭或销毁,它依然可以运行于后台。
- 优先级高于Activity(内存不足时先杀掉Activity)。
- 运行在主线程,且不能做耗时操作(超时标准请参考下面的说明)
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处理异步请求 |
1.4 Android Service能做什么?
- 网络事务:聊天(等待他人回复短信)、地图定位(熄屏后实时定位的播报语音)等
- 本地资源:播放音乐(读取音乐文件)、文件IO(后台上传文件、下载文件)等
- 定时任务:订单超时(未支付情况下,一定时间后自动销毁订单)、闹钟提醒等
二、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);
var
MyNotification:TNotification;
begin
MyNotification:=TNotification.Create;
try
MyNotification.Name:='MyNotification1';
MyNotification.Title:=MyTitle;
MyNotification.AlertBody:=MyBody;
MyNotification.FireDate:=Now;
NotificationCenter1.PresentNotification(MyNotification);
finally
MyNotification.Free;
end;
end;
因为只是做个简单的示例,所以通知相关的具体内容都是直接使用的文本赋值,大家可以使用其它的方式进行值的设定,我这里发送通知的条件设定为每隔5秒,后台给前台发送一次通知。
我们选中DM模块,在OnStartCommand事件中,加上如下代码:
function TFData.AndroidServiceStartCommand(const Sender: TObject;
const Intent: JIntent; Flags, StartId: Integer): Integer;
begin
while True do
begin
Sleep(5000);
SetNotification('系统通知','这是一个测试AndroidService的程序,此信息来自Service后台发送');
end;
Result:=TJService.JavaClass.START_STICKY;
end;
到这里为止,我们这个简单的通知服务程序功能基本就完成了,我们可以进行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);
begin
try
MyService:=TLocalServiceConnection.Create;
MyService.StartService('lSysService');
Memo1.Lines.Add('服务已启动');
except
on E:Exception do
begin
Memo1.Lines.Add(e.Message);
end;
end;
end;
这里注意MyService.StartService('lSysService')这一句,其中lSysService就是我们之前创建的Service的工程名称,大家不要搞错了,这也是为什么前面一再强调要记住名称的原因。
为了更好的查看通知内容,我们再加上一个通知的响应,当收到通知后,我们点击通知就把内容提取到APP的Memo1中显示,我们选中NotificationCenter1控件,并在OnReceiveLocalNotificationg事件中,加上如下代码:
procedure TForm2.NotificationCenter1ReceiveLocalNotification(Sender: TObject;
ANotification: TNotification);
begin
Memo1.Lines.Add(ANotification.AlertBody);
end;
然后我们保存工程与模块,编译运行看看
如果我们启动APP时,系统有上面图片的询问,我们当然要选择允许,要不然我们不可能收到通知,然后进入APP以后,我们点击“启动服务”
等待几秒钟后,我们就看到上面会出现一个通知,当我们点击通知时,内容就会显示在主界面的Memo1里面
到这里为止,我们的功能算是完成了,但有一个问题,当我们关闭前台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归为最近的活动列表中) |
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, // JIntent
Androidapi.Helpers, // StringToJString
FMX.Platform.Android; // MainActivity
{$ENDIF}
procedure TForm2.Button1Click(Sender: TObject);
var
AText: string;
Intent: JIntent;
begin
AText := '这是来自人马座星系发来的贺电';
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 then
MainActivity.startActivity(Intent) // 启动Intent
else
ShowMessage('未找到接收者');
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>
<activity
android: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);
var
Intent:JIntent;
begin
Intent:=SharedActivity.getIntent;
if Intent.hasExtra(TJIntent.JavaClass.EXTRA_TEXT) then
begin
Memo1.Lines.Add(JStringToString(Intent.getStringExtra(TJIntent.JavaClass.EXTRA_TEXT)));
end else begin
Memo1.Text:='距离太远,半路丢了';
end;
end;
编译运行,结束
我们来试试看,为了避免其它一些不必要的问题,我们先关闭发送端与接收端程序,然后重新打开发送程序,点击Button,并选择接收程序,图片同上就不重复传了,然后在接收程序中点击“接收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;
var
MyMessage: JMessage;
MyBundle: JBundle;
begin
case AMessage.what of
CusText1: //自定义消息1
begin
MyBundle := 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: //自定义消息2
begin
MyBundle := TJBundle.Create;
MyBundle.putString(StringToJString('Message2'), StringToJString('这是Remote Service自定义的第二个信息'));
MyMessage := TJMessage.Create;
MyMessage.what := ServiceText2;
MyMessage.obj := MyBundle;
AMessage.replyTo.send(MyMessage);
Result := True;
end
else
Result := False;
end;
end;
第五步:Build程序,就完成了Service端的创建
接下来我们开始做前台的程序,先做第一个,我们命名为:Prog1
创建一个FMX工程,在上面放上Label,Memo,Button,界面大致如下:
第二步:引用单元
第三步:建立与Service对应的常量来接收消息
第四步:申明变量与函数
第五步:编写第一个程序的代码,为了大家看起来方便,我一次全部导入
unit Unit2;
interface
uses
System.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;
type
TForm2 = 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;
var
Form2: TForm2;
const
CusText1 = 1001;
CusText2 = 1002;
ServiceText1 = 2001;
ServiceText2 = 2002;
implementation
{$R *.fmx}
procedure TForm2.Button1Click(Sender: TObject);
var
MyMSG:JMessage;
begin
//接收后台自定义的第一个消息,这里关键是CusText1,与后台对应的消息
MyMSG := TJMessage.JavaClass.obtain(nil, CusText1);
MyMSG.replyTo := MyService.LocalMessenger;
MyService.ServiceMessenger.send(MyMSG);
end;
procedure TForm2.OnHandleMessage(const AMessage: JMessage);
var
AText: JString;
MyBundle: JBundle;
begin
case AMessage.what of
ServiceText1:
begin
MyBundle := TJBundle.Wrap(AMessage.obj);
AText := MyBundle.getString(TAndroidHelper.StringToJString('Message1'));
Memo1.Lines.Add(JStringToString(AText));
end;
ServiceText2:
begin
MyBundle := TJBundle.Wrap(AMessage.obj);
AText := MyBundle.getString(TAndroidHelper.StringToJString('Message2'));
Memo1.Lines.Add(JStringToString(AText));
end;
else
MyService.Handler.Super.handleMessage(AMessage);
end;
end;
procedure TForm2.OnServiceConnected(const ServiceMessenger: JMessenger);
begin
Button1.Enabled:=True;
end;
procedure TForm2.Button2Click(Sender: TObject);
begin
if MyService <> nil then
begin
MyService.UnbindService;
end;
end;
procedure TForm2.FormCreate(Sender: TObject);
begin
Button1.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);
begin
MyService.Free;
end;
end.
然后我们把Service目录导入到前台程序中,导入的步骤与Local Service一样,这里不重复,请大家参考Local Service前台编程的第二步操作即可,完成后我们运行程序看看
我们点击消息1按钮,就收到了来自后台Service自定义的第一条消息,那么此前台程序完成。
但我们做的是Remote Service,其特性是可以给多个程序绑定,那么我们再来建立一个新的前台程序,我们为其命名为:Prog2,大概界面跟上面这个差不多,完成后如下:
接下来,我们按照第一个前台程序一样的步骤完成第二前台程序的工作,这里需要注意的几个地方大家要看看,虽然功能是一样的,但第二个程序的工程名称不同,而且我们在第二程序里调用的是后台Service的第二条消息,所以下面几个地方不能跟第一台程序一样
第一个不同:我们调用第二条消息,红框这里的参数就需要更改
第二个不同:第二个程序的工程名不一样,所以绑定Service的时候也要变
第二个前台程序的所有代码如下:
unit Main;
interface
uses
System.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;
type
TForm2 = 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;
var
Form2: TForm2;
const
CusText1 = 1001;
CusText2 = 1002;
ServiceText1 = 2001;
ServiceText2 = 2002;
implementation
{$R *.fmx}
procedure TForm2.Button1Click(Sender: TObject);
var
MyMSG:JMessage;
begin
MyMSG := TJMessage.JavaClass.obtain(nil, CusText2);
MyMSG.replyTo := MyService.LocalMessenger;
MyService.ServiceMessenger.send(MyMSG);
end;
procedure TForm2.Button2Click(Sender: TObject);
begin
if MyService <> nil then
begin
MyService.UnbindService;
end;
end;
procedure TForm2.FormCreate(Sender: TObject);
begin
Button1.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);
begin
MyService.Free;
end;
procedure TForm2.OnHandleMessage(const AMessage: JMessage);
var
AText: JString;
MyBundle: JBundle;
begin
case AMessage.what of
ServiceText1:
begin
MyBundle := TJBundle.Wrap(AMessage.obj);
AText := MyBundle.getString(TAndroidHelper.StringToJString('Message1'));
Memo1.Lines.Add(JStringToString(AText));
end;
ServiceText2:
begin
MyBundle := TJBundle.Wrap(AMessage.obj);
AText := MyBundle.getString(TAndroidHelper.StringToJString('Message2'));
Memo1.Lines.Add(JStringToString(AText));
end;
else
MyService.Handler.Super.handleMessage(AMessage);
end;
end;
procedure TForm2.OnServiceConnected(const ServiceMessenger: JMessenger);
begin
Button1.Enabled:=True;
end;
end.
其它操作与第一个前台程序是一致的,完成后我们运行第二个程序看看
到这里为止,我们前台的两个程序绑定到同一个Service全部成功了,其它的功能操作大概与Local Service是一样的,这里不再重复的写了。
五、Intent Remote Service程序
关于Intent Remote Service的介绍与实例,这里不再重复的啰嗦了,大家结合Remote Service的案例说明,再加上Intent的相关知识点,应该能自己写出案例来了
六、结束语
这篇文章写得真叫累,感觉有点啰嗦,但为了让新人朋友们能看懂,我宁愿啰嗦点并尽可能写得让大家都看得懂,如果本文存在一些问题,请大家在下面留言,或者大家希望写哪些方面的内容,也请在下面留言。
我们下次再见