前言:
本篇文章正经来说,其实算是我的学习履历,是我在不断的摸索过程中,总结的经验,不能算是一篇正经的学术文章。现在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);
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;
说明: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);
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时,可能有部分手机不会弹出是否允许推送消息的提示,那么我们就需要在手机设置的应用管理中,允许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, // 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”按钮,我们就会发现,已经收到发送端的消息了
提示:之所以用这么长的篇幅来介绍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;
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的相关知识点,应该能自己写出案例来了
六、结束语
这篇文章写得真叫累,感觉有点啰嗦,但为了让新人朋友们能看懂,我宁愿啰嗦点并尽可能写得让大家都看得懂,如果本文存在一些问题,请大家在下面留言,或者大家希望写哪些方面的内容,也请在下面留言。
我们下次再见