Delphi 开发Android Service四种模式初探

前言:

         本篇文章正经来说,其实算是我的学习履历,是我在不断的摸索过程中,总结的经验,不能算是一篇正经的学术文章。现在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,下面针对这两种方式的生命周期,我们用两个表格来做简要说明,详细如下:

表1:StartService生命周期
步骤说明
OnCreate()1、如果 service 没被创建过,调用 startService() 后会执行 onCreate() 回调;
2、如果 service 已处于运行中,调用 startService() 不会执行 onCreate() 方法。
onStartCommand()如果多次执行了 startService() 方法,那么 Service 的 onStartCommand() 方法也会相应的多次调用
OnBind()Service中的onBind()方法是抽象方法,Service类本身就是抽象类,所以onBind()方法是必须重写的,即使我们用不到
onDestory()在销毁的时候会执行Service该方法

表2:BindService生命周期
步骤说明
OnCreate()当服务通过 onStartCommand() 和 onBind() 被第一次创建的时候,系统调用该方法。该调用要求执行一次性安装
OnBind()当其他组件想要通过 bindService() 来绑定服务时,系统调用该方法。如果你实现该方法,你需要返回 IBinder 对象来提供一个接口,以便客户来与服务通信。你必须实现该方法,如果你不允许绑定,则直接返回 null
onUnbind()当客户中断所有服务发布的特殊接口时,系统调用该方法
onRebind()当新的客户端与服务连接,且此前它已经通过onUnbind(Intent)通知断开连接时,系统调用该方法
onDestroy()当服务不再有用或者被销毁时,系统调用该方法。你的服务需要实现该方法来清理任何资源,如线程,已注册的监听器,接收器等

提示:我们先只做理论上的说明,暂不涉及具体的实例与实现的方法,在后面的案例中,我们将用到前面所讲的理论知识点。

1.3 Android Service的类型与区别

在DELPHI中,我们创建Android Service时(创建方式如下图所示)软件提供了四种类型供我们选择,那么这四种Service程序到底有什么区别呢?

表3:Android 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。

表4: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有以下几项:

  • ACTION_VIEW: (android.intent.action.VIEW) 显示指定数据。
  • ACTION_EDIT: (android.intent.action.EDIT) 编辑指定数据。
  • ACTION_DIAL: (android.intent.action.DIAL) 显示拨号面板。
  • ACTION_CALL: (android.intent.action.CALL) 直接呼叫Data中所带的号码。
  • ACTION_ANSWER: (android.intent.action.ANSWER) 接听来电。
  • ACTION_SEND: (android.intent.action.SEND) 向其他人发送数据(例如:彩信/email)。
  • ACTION_SENDTO: (android.intent.action.SENDTO) 向其他人发送短信。
  • ACTION_SEARCH: (android.intent.action.SEARCH) 执行搜索。
  • ACTION_GET_CONTENT: (android.intent.action.GET_CONTENT) 让用户选择数据,并返回所选数据。

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()。

内置的常量属性如下:

  • tel://:号码数据格式,后跟电话号码。
  • mailto://:邮件数据格式,后跟邮件收件人地址。
  • smsto://:短息数据格式,后跟短信接收号码。
  • content://:内容数据格式,后跟需要读取的内容。
  • file://:文件数据格式,后跟文件路径。
  • market://search?q=pname:pkgname:市场数据格式,在Google Market里搜索包名为pkgname的应用。
  • geo://latitude, longitude:经纬数据格式,在地图上显示经纬度所指定的位置。

Category

一个包含Intent额外信息的字符串,表示哪种类型的组件来处理这个Intent。任何数量的Category 描述都可以添加到Intent中,但是很多intent不需要category,你可以通过调用addCagegory()方法来设置category,标准的常量如下:

  • CATEGORY_DEFAULT: (android.intent.category.DEFAULT) Android系统中默认的执行方式,按照普通 Activity的执行方式执行。
  • CATEGORY_HOME: (android.intent.category.HOME) 设置该组件为Home Activity。
  • CATEGORY_PREFERENCE: (android.intent.category.PREFERENCE) 设置该组件为Preference。
  • CATEGORY_LAUNCHER: (android.intent.category.LAUNCHER) 设置该组件为在当前应用程序启动器中优先级最高的Activity,通常与入口ACTION_MAIN配合使用。
  • CATEGORY_BROWSABLE: (android.intent.category.BROWSABLE) 设置该组件可以使用浏览器启动。

Extras

Intent可以携带的额外key-value数据,你可以通过调用putExtra()方法设置数据,每一个key对应一个value数据。你也可以通过创建Bundle对象来存储所有数据,然后通过调用putExtras()方法来设置数据。对于数据key的名字要尽量用包名做前缀,然后再加上其他,这样来保证key的唯一性,常用的常量属性如下:

  • EXTRA_BCC:存放邮件密送人地址的字符串数组。
  • EXTRA_CC:存放邮件抄送人地址的字符串数组。
  • EXTRA_EMAIL:存放邮件地址的字符串数组。
  • EXTRA_SUBJECT:存放邮件主题字符串。
  • EXTRA_TEXT:存放邮件内容。
  • EXTRA_KEY_EVENT:以KeyEvent对象方式存放触发Intent的按键。
  • EXTRA_PHONE_NUMBER:存放调用ACTION_CALL时的电话号码。

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的相关知识点,应该能自己写出案例来了


六、结束语

这篇文章写得真叫累,感觉有点啰嗦,但为了让新人朋友们能看懂,我宁愿啰嗦点并尽可能写得让大家都看得懂,如果本文存在一些问题,请大家在下面留言,或者大家希望写哪些方面的内容,也请在下面留言。

我们下次再见

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/17027.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

26计算机操作系统408考研--操作系统设备管理篇章(五)

文章目录 一、设备是什么&#xff1f;设备管理目标和任务I/O设备分类 二、I/O系统控制方式程序直接控制方式中断控制方式DMA控制方式通道控制方式 I/O软件的组成I/O软件设计目标和原则I/O软件结构设备驱动程序设备无关软件用户层软件用户层的I/O软件 具有通道的设备管理通 道通…

Day37 代码随想录打卡|二叉树篇---对称二叉树

题目&#xff1a; 给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 方法&#xff1a;本体可以用递归和迭代两种方法&#xff0c;但我更喜欢迭代的方式&#xff0c;因此使用迭代的方式做一下。首先我们分析一下不对称的情况。因为对称的情况很简单&#xff0c;即两…

实现一个简单的 Google Chrome 扩展程序

目录 &#x1f9ed; 效果展示 # 图示效果 a. 拓展程序列表图示效果&#xff1a; b. 当前选项卡页面右键效果&#xff1a; c. 拓展程序消息提示效果&#xff1a; &#x1f4c7; 项目目录结构 # 说明 # 结构 # 文件一览 ✍ 核心代码 # manifest.json # background.j…

星融元获2024网络开源优秀创新成果奖!

5月25日&#xff0c;星融元受邀参加2024年第四届网络开源技术生态大会&#xff0c;分享主题为“开放的网络 开放的AI生态”主题演讲&#xff0c;深受现场用户的认可&#xff1b;《Easy RoCE&#xff1a;基于SONiC、Klish和Prometheus的极简无损网络解决方案》获得2024网络开源优…

SpheroGPT: 声控自然语言编程 AI 玩具 Demo 具身智能 图文解说 完全开源机器人

背景介绍 因为生病请了长假. 一周前状态开始恢复, 于是尝试用 LLM (ChatGPT3.5) + Sphero 开发一个可以声控自然语言编程的 AI 玩具, 作为学习 ChatGPT 应用开发的方法. 差不多十天时间把开发目标基本都实现了, 这里和大家分享一下心得体会. Demo 示例视频 先把录制的几个 d…

云原生Kubernetes: 云主机部署K8S 1.30版本 单Master架构

目录 一、实验 1.环境 2.Termius连接云主机 3.网络连通性与安全机制 4.云主机部署docker 5.云主机配置linux内核路由转发与网桥过滤 6.云主机部署cri-dockerd 7.云主机部署kubelet,kubeadm,kubectl 8.kubernetes集群初始化 9.容器网络&#xff08;CNI&#xff09;部署…

Docker学习笔记 - 创建自己的image

目录 基本概念常用命令使用docker compose启动脚本创建自己的image 使用Docker是现在最为流行的软件发布方式&#xff0c; 本系列将阐述Docker的基本概念&#xff0c;常用命令&#xff0c;启动脚本和如何生产自己的docker image。 在我们发布软件时&#xff0c;往往需要把我…

解析边缘计算网关的优势-天拓四方

随着信息化、智能化浪潮的持续推进&#xff0c;计算技术正以前所未有的速度发展&#xff0c;而边缘计算网关作为其中的重要一环&#xff0c;以其独特的优势正在逐步改变我们的生活方式和工作模式。本文将详细解析边缘计算网关的优势。 首先&#xff0c;边缘计算网关具有显著的…

uniapp页面vue3下拉触底发送获取新数据请求实现分页功能

页面下拉触底获取新数据实现分页功能实现方式有两种&#xff0c;根据自己的业务需求来定&#xff0c;不同的方案适用场景不一样&#xff0c;有的是一整个页面下拉获取新数据&#xff0c;有的是部分盒子内容滚动到底部时候实现获取新数据&#xff0c;下面讨论一下两种方式的区别…

JSON-RPC跨域通信:Python服务器端解决方案与Js客户端 Mozilla扩展程序

问题背景 构建一个 Mozilla 扩展程序&#xff0c;与远程服务器上的 Python 应用程序进行通信以发送和接收数据。Python 应用程序可以通过 Python 控制台使用 xml-rpc 调用。尝试设计一个 JSON-RPC 来联系同一个应用程序。开发 Python 服务器端&#xff0c;可以通过 python 控制…

【高数】重点内容,公式+推导+例题,大学考试必看

目录 1 隐函数求导1.1 公式1.2 说明1.3 例题 2 无条件极值2.1 运用2.2 求解2.3 例题 3 条件极值3.1 运用3.2 求解3.3 例题 4 二重积分4.1 直角坐标下4.2 极坐标下4.3 例题 5 曲线积分5.1 第一型曲线积分5.2 第二型曲线积分5.3 例题 6 格林公式6.1 公式6.2 说明6.3 例题 &#x…

Postman进阶功能-集合分支管理与编写接口文档

大家好&#xff0c;在接口测试的领域中&#xff0c;我们不断追求更高效、更便捷、更强大的方法与工具。而 Postman 作为一款备受青睐的接口测试工具&#xff0c;其进阶功能更是为我们打开了新的天地。在这其中&#xff0c;集合分支管理与编写接口文档的功能显得尤为重要。 当面…

作业-day-240527

Cday1思维导图 定义自己的命名空间my_sapce&#xff0c;在my_sapce中定义string类型的变量s1&#xff0c;再定义一个函数完成对字符串的逆置 #include <iostream>using namespace std;namespace my_space {string s1"abc123";string recover(string s){int i0…

go-zero 实战(3)

引入 Redis 在之前的 user 微服务中引入 redis。 1. 修改 user/internal/config/config.go package configimport ("github.com/zeromicro/go-zero/core/stores/cache""github.com/zeromicro/go-zero/zrpc" )type Config struct {zrpc.RpcServerConfMys…

Overall Accuracy(OA)、Average Accuracy(AAcc)计算公式

以二分类为例&#xff1a;1.总体精度(Overall Accuracy, OA)&#xff1a;样本中正确分类的总数除以样本总数。 OA(TPTN)/(TPFNFPTN)2.平均精度(Average Accuracy, AA)&#xff1a;每一类别中预测正确的数目除以该类总数&#xff0c;记为该类的精度&#xff0c;最后求每类精度的…

2022全国大学生数学建模竞赛ABC题(论文+代码)

文章目录 &#xff08;1&#xff09;2022A波浪能最大输出功率&#xff08;2&#xff09;2022B无人机定位&#xff08;3&#xff09;2022C古代玻璃制品成分分析&#xff08;4&#xff09;论文和代码链接 &#xff08;1&#xff09;2022A波浪能最大输出功率 &#xff08;2&#x…

su模型转3d模型不够平滑怎么办?---模大狮

当将SU模型转换为3D模型时&#xff0c;可能会遇到模型不够平滑的情况&#xff0c;这会影响到最终的渲染效果和视觉体验。本文将探讨在此情况下应该如何解决&#xff0c;帮助读者更好地处理这一常见的问题。 一、检查SU模型细分程度 首先要检查的是原始的SU模型的细分程度。在S…

XSKY CTO 在英特尔存储技术峰会的演讲:LLM 存储,架构至关重要

5 月 17 日&#xff0c;英特尔存储技术峰会在北京顺利举办。作为英特尔长期的合作伙伴&#xff0c;星辰天合受邀参加了此次峰会。星辰天合 CTO 王豪迈作为特邀嘉宾之一&#xff0c;作了主题为《LLM 存储&#xff1a;架构至关重要》的演讲&#xff0c;分享了大语言模型&#xff…

2024年中国金融行业网络安全案例集

随着科技的飞速发展,金融行业与信息技术的融合日益加深,网络安全已成为金融行业发展的生命线。金融行业作为国家经济的核心支柱&#xff0c;正在面临着日益复杂严峻的网络安全挑战。因此&#xff0c;深入研究和探讨金融行业的网络安全问题&#xff0c;不仅关乎金融行业的稳健运…

MIPI竖屏解决方案,普立晶POL8901升级POL8903 两PORT LVDS桥接到MIPI,加旋转

POL8903描述&#xff1a; 系统&#xff1a; •采用高性能MIPS 32位CPU内核&#xff1b; •高性能DSP内核图像处理单元&#xff1b; •16 KB指令Cache&#xff1b;16 KB数据Cache&#xff1b; •96 KB SRAM&#xff1b;内置DDR 3控制器&#xff1b; LVDS输入&#xff1a; …