700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > Android四大组件之——Service

Android四大组件之——Service

时间:2020-08-03 13:49:01

相关推荐

Android四大组件之——Service


文章目录

程序、进程、线程概念线程的生命周期线程的创建 初识serviceService概念Service生命周期Service种类Service中重要的方法Service 的声明Service的启动StartService启动ServiceBindService启动ServiceStartService启动Service后bindService绑定 Service 进阶IntentService的使用Activity与Service通信前台服务的实现定时后台线程的实现 Service 再进阶Binder机制IBinder和BinderBinder机制和工作流程为何Android使用Binder机制来实现进程间的通信 AIDL(重要)AIDL定义AIDL实现两个进程间的简单通信传递复杂数据的AIDL Service通过Binder的onTransact完成跨进程通信 记录一个高版本的使用AIDL大坑

程序、进程、线程

在了解service相关知识之前,先了解一下多线程的知识。在实际开发中,Service很多时候会和多线程相关进行结合。

概念

程序:为了完成特定任务,用某种语言编写的一组指令集合(一组静态代码)进程:运行中的程序,系统调度与资源分配的一个独立单位,操作系统会 为每个进程分配一段内存空间!程序的依次动态执行,经历代码的加载,执行, 执行完毕的完整过程!线程:比进程更小的执行单元,每个进程可能有多条线程,线程需要放在一个 进程中才能执行,线程由程序负责管理,而进程则由系统进行调度!多线程的理解:并行执行多个条指令,将CPU时间片按照调度算法分配给各个 线程,实际上是分时执行的,只是这个切换的时间很短,用户感觉到"同时"而已

唠叨两句,有很多同学会把进程和线程的概念混淆分不清,这里教大家一个办法,把进程看作一列火车;而把线程看作组成火车的一节节车厢,然后我们再来看线程和进程的概念是不是很符合这个概念呢?

进程是最小的资源分配单位;线程是最小的程序执行单元

线程的生命周期

一共五个阶段 新建、就绪、运行、阻塞、死亡

线程的创建

继承Thread类实现Runnable接口

//创建并启动new Thread(myThread).start();

//使用匿名内部类new Thread(new Runnable(){public void run();}).start();

实现Callable接口

初识service

Service概念

什么是Servie?

Service (服务)是能够在后台执行长时间运行操作并且不提供用户界面的应用程序组件。其他应用程序组件能启动服务,并且即便用户切换到另一个应用程序,服务还可以在后台运行。此外,组件能够绑定到服务并与之交互,甚至执行进程间通信(IPC) 。例如,服务能在后台处理网络事务、播放音乐、执行文件IO或者与ContentProvider通信。

Service生命周期

Service种类

由上图可知,Service主要分为两种

Started:当应用程序组件(如Activity )通过调用startService()方法启动服务时,服务处于started状态。 一旦启动,服务能在后台无限期运行,即使启动它的组件已经被销毁。通常,启动服务执行单个操作并且不会向调用者返回结果。例如,它可能通过网络下载或者上传文件。如果操作完成,服务需要停止自身。Bound :当应用程序组件通过调用bindService()方法绑定到服务时,服务处于bound状态。绑定服务提供客户端-服务器接口,以允许组件与服务交互、发送请求、获得结果,甚至使用进程间通信(IPC) 跨进程完成这些操作。仅当其他应用程序组件与之绑定时,绑定服务才运行。多个组件可以一次绑定到 一个服务上,当它们都解绑定时,服务被销毁。

其实还有一种就是启动Service后,绑定Service,也就是两种兼有的

不管应用程序是否为启动状态、绑定状态或者两者兼有,都能通过Intent使用服务,就像使用Activity那样。然而,开发人员可以在配置文件中将服务声明为私有的,从而阻止其他应用程序访问。

服务运行于管理它的进程的主线程,服务不会创建自己的线程,也不会运行于独立的进程(除非开发人员定义)。这意味着,如果服务要完成CPU密集工作或者阻塞操作(如MP3回放或者联网),开发人员需要在服务中创建新线程来完成这些工作。通过使用独立的线程,能减少应用程序不响应(ANR)错误的风险,并且应用程序主线程仍然能用于用户与Activity的交互。

Service中重要的方法

为了创建服务,开发人员需要创建Service类(或其子类)的子类。在实现类中,需要重写一些处理服务生命周期重要方面的回调方法,并根据需要提供组件绑定到服务的机制。

需要重写的重要回调方法有onStartCommand()onBind()onCreate()onDestroy()

Service 的声明

<service android: enabled= ["true" | " false"]android:exported= ["true" |"false"]android:icon="drawable resource'android:label= "string resource'android:name= "string "android:permission="string"android:process="string" ></service>

android:enabled

服务能否被系统实例化,true表示可以,false 表示不可以,默认值是true。标签也有自己的enabled属性,用于包括服务的全部应用程序组件。 和enabled属性必须同时设置成true (两者的默认值都是true)才能让服务可用。如果任何一个是false,服务被禁用并且不能实例化。android:exported

其他应用程序组件能否调用服务或者与其交互,true 表示可以,false 表示不可以。当该值是false时,只有同一个应用程序的组件或者具有相同用户ID的应用程序能启动或者绑定到服务。

默认值依赖于服务是否包含Intent 过滤器。若没有过滤器,说明服务仅能通过精确类名调用,这意味着服务仅用于应用程序内部( 因为其他程序可能不知道类名)。此时,默认值是false;若存在至少一个过滤器,暗示服务可以用于外部,因此默认值是true.该属性不是限制其他应用程序使用服务的唯一方式。 还可以使用permission屈性限制外部实体与服务交互。android:icon

表示服务的图标。该属性必须设置成包含图片定义的可绘制资源引用。如果没有设置,使用应用程序图标取代。服务图标不管在此设置还是在标签设置,都是所有服务的Intent过滤器默认图标。android:label

显示给用户的服务名称。如果没有设置,使用应用程序标签取代。服务标签不管在此设置还是在标签设置,都是所有服务的Intent过滤器默认标签。标签应该设置为字符串资源引用,这样可以像用户界面的其他字符串那样本地化。然而,为了开发时方便,也可以设置成原始字符串。android:name

实现服务的Service子类名称,应该是-一个完整的类名,如com.googl.RoomService.然而,为了简便,如果名称的第一个符号是点号(如.RoomService) ,则会增加在标签中定义的包名。一旦发布了应用程序,不应该再修改子类名称。该属性没有默认值并且必须指定。android:permission

实体必须包含的权限名称,以便启动或者绑定到服务。如果startService()、bindService()或 stopService()方法调用者没有被授权,方法调用无效,并且Intent对象也不会发送给服务。

如果没有设置该属性,使用标签的permission 属性设置给服务。如果 和标签的permission属性都未设置,服务不受权限保护.android:process

服务运行的进程名称。通常,应用程序的全部组件运行于为应用程序创建的默认进程。进程名称与应用程序包名相同。标签 的process 属性能为全部组件设置一个相同的默认值。但是组件能用自己的process属性重写默认值,从而允许应用程序跨越多个进程。如果分配给该属性的名称以冒号(:)开头,仅属于应用程序的新进程会在需要时创建,服务能在该进程中运行; 如果进程名称以小写字母开头,服务会运行在以此为名的全局进程,但需要提供相应的权限。这允许不同应用程序组件共享进程,减少资源使用。

Service的启动

要想声明并启动Service,开发人员需要创建Service类(或其子类)的子类

Android提供了两个类供开发人员继承以创建启动服务。

Service: 这是所有服务的基类。当继承该类时,创建新线程来执行服务的全部工作是非常重要的。因为服务默认使用应用程序主线程,这可能降低应用程序Activity的运行性能。IntentService: 这是Service类的子类,它每次使用一个工作线程来处理全部启动请求。在不必同时处理多个请求时,这是最佳选择。开发人员仅需要实现onHandleIntent()方法,该方法接收每次启动请求的Intent 以便完成后台任务。

StartService启动Service

①首次启动会创建一个Service实例,依次调用onCreate()和onStartCommand()方法,此时Service 进入运行状态,如果再次调用StartService启动Service,将不会再创建新的Service对象, 系统会直接复用前面创建的Service对象,调用它的onStartCommand()方法!

②但这样的Service与它的调用者无必然的联系,就是说当调用者结束了自己的生命周期, 但是只要不调用stopService,那么Service还是会继续运行的!

③无论启动了多少次Service,只需调用一次StopService即可停掉Service

FirstService.java

package com.thundersoft.myblogdemo;import android.app.Service;import android.content.Intent;import android.os.IBinder;import android.util.Log;import androidx.annotation.Nullable;/*** @ClassName FirstService* @Description TODO* @Author Yu* @Date /6/30 17:19* @Version 1.0**/public class FirstService extends Service {private final static String TAG="Service1";//必须要实现的方法 @Nullable@Overridepublic IBinder onBind(Intent intent) {Log.i(TAG,"onBind()被调用");return null;}//Service被启动时调用 @Overridepublic int onStartCommand(Intent intent, int flags, int startId) {Log.i(TAG,"onStartCommand()被调用");return super.onStartCommand(intent, flags, startId);}//Service被创建时调用 @Overridepublic void onCreate() {Log.i(TAG,"onCreate()被调用");super.onCreate();}//Service被关闭之前回调 @Overridepublic void onDestroy() {Log.i(TAG,"onDestroy()被调用");super.onDestroy();}}

AndroidManifest.xml

<serviceandroid:name=".FirstService"android:enabled="true"></service>

MainActivity.java

public class MainActivity extends AppCompatActivity {private Button btn1,btn2;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);btn1 = findViewById(R.id.btn1);btn2 = findViewById(R.id.btn2);Intent intent = new Intent(MainActivity.this, FirstService.class);btn1.setOnClickListener(v->{startService(intent);});btn2.setOnClickListener(v->{stopService(intent);});}}

点击开始服务

再次点击

点击关闭服务

这里补充一些知识点

onStartCommand()方法必须返回一个 整数。该值用来描述系统停止服务后如何继续服务onStartCommand()方法返回值必 须是下列常量之一。

START_ NOT STICKY

如果系统在onStartCommand()方法返回后停止服务,不重新创建服务,除非有PendingIntent要发送。为避免不在不需要的时候运行服务,这是最佳选择。

START_ STICKY

如果系统在onStartCommand()方法返回后停止服务,重新创建服务并调用onStartCommand()方法,但是不重新发送最后的Intent;相反,系统使用空Intent调用onStartCommand0方法,除非有PendingIntent来启动服务,此时,这些Intent会被发送。这适合多媒体播放器(或者类似服务),它们不执行命令但是无限期运行并等待工作。

START_REDELIVER_ INTENT

如果系统在onStartCommand0方法返回后停止服务,重新创建服务并使用发送给服务的最后Intent调用onStrtCommand0方法,全部PendingIntent依次发送。这适合积极执行应该立即恢复工作的服务,如下载文件。

说明:这些常 量都定义在Service类中。

BindService启动Service

①当首次使用bindService绑定一个Service时,系统会实例化一个Service实例,并调用其onCreate()和onBind()方法,然后调用者就可以通过IBinder和Service进行交互了,此后如果再次使用bindService绑定Service,系统不会创建新的Sevice实例,也不会再调用onBind()方法,只会直接把IBinder对象传递给其他后来增加的客户端!

②如果我们解除与服务的绑定,只需调用unbindService(),此时onUnbind和onDestory方法将会被调用!这是一个客户端的情况,假如是多个客户端绑定同一个Service的话,情况如下 当一个客户完成和service之间的互动后,它调用 unbindService() 方法来解除绑定。当所有的客户端都和service解除绑定后,系统会销毁service。(除非service也被startService()方法开启)

③另外,和上面那张情况不同,bindService模式下的Service是与调用者相互关联的,可以理解为 “一条绳子上的蚂蚱”,要死一起死,在bindService后,一旦调用者销毁,那么Service也立即终止!

通过BindService调用Service时调用的Context的bindService的解析bindService(Intent Service,ServiceConnection conn,int flags)

service:通过该intent指定要启动的Service

conn:ServiceConnection对象,用户监听访问者与Service间的连接情况, 连接成功回调该对象中的onServiceConnected(ComponentName,IBinder)方法; 如果Service所在的宿主由于异常终止或者其他原因终止,导致Service与访问者间断开 连接时调用onServiceDisconnected(CompanentName)方法,主动通过unBindService() 方法断开并不会调用上述方法!

flags:指定绑定时是否自动创建Service(如果Service还未创建), 参数可以是0(不自动创建),BIND_AUTO_CREATE(自动创建)

Context中的bindService方法:

ServiceConnection对象:监听访问者与Service间的连接情况,如果成功连接,回调 onServiceConnected(),如果异常终止或者其他原因终止导致Service与访问者断开 连接则回调onServiceDisconnected方法,调用unBindService()不会调用该方法!onServiceConnected方法中有一个IBinder对象,该对象即可实现与被绑定Service 之间的通信!我们再开发Service类时,默认需要实现IBinder onBind()方法,该方法返回的 IBinder对象会传到ServiceConnection对象中的onServiceConnected的参数,我们就可以 在这里通过这个IBinder与Service进行通信!

SecondService.java

package com.thundersoft.myblogdemo;import android.app.Service;import android.content.Intent;import android.os.Binder;import android.os.IBinder;import android.util.Log;import androidx.annotation.Nullable;/*** @ClassName SecondService* @Description TODO* @Author Yu* @Date /6/30 18:04* @Version 1.0**/public class SecondService extends Service {private static final String TAG="Service2";private boolean quit;private int count;//onBind()返回的对象private MyBinder binder=new MyBinder();public class MyBinder extends Binder{SecondService getService(){return SecondService.this;}}//必须实现的方法,绑定改Service时回调该方法@Nullable@Overridepublic IBinder onBind(Intent intent) {Log.i(TAG,"onBind()被调用");return binder;}//Service被创建时回调@Overridepublic void onCreate() {Log.i(TAG,"onCreate()被调用");new Thread(new Runnable() {@Overridepublic void run() {//当服务还在就一直累加while (!quit){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}count++;}}}).start();super.onCreate();}//Service断开连接时回调@Overridepublic boolean onUnbind(Intent intent) {Log.i(TAG, "onUnbind方法被调用!");return true;}//Service被关闭前回调@Overridepublic void onDestroy() {super.onDestroy();this.quit = true;Log.i(TAG, "onDestroyed方法被调用!");}@Overridepublic void onRebind(Intent intent) {Log.i(TAG, "onRebind方法被调用!");super.onRebind(intent);}public int getCount(){return count;}}

AndroidManifest.xml

<serviceandroid:name=".SecondService"android:enabled="true"android:exported="true"></service>

MainActivity.java

package com.thundersoft.myblogdemo;import androidx.annotation.Nullable;import androidx.appcompat.app.AppCompatActivity;import android.app.Service;import ponentName;import android.content.Intent;import android.content.ServiceConnection;import android.os.Bundle;import android.os.IBinder;import android.os.PersistableBundle;import android.view.View;import android.widget.Button;import android.widget.Toast;public class MainActivity extends AppCompatActivity {private Button btn1,btn2,btn3;SecondService second_service;ServiceConnection connection = new ServiceConnection(){@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {SecondService.MyBinder binder=( SecondService.MyBinder) service;second_service=binder.getService();}@Overridepublic void onServiceDisconnected(ComponentName name) {}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);btn1 = findViewById(R.id.btn1);btn2 = findViewById(R.id.btn2);btn3= findViewById(R.id.btn3);Intent intent = new Intent(this,SecondService.class);btn1.setOnClickListener(v->{bindService(intent,connection, Service.BIND_AUTO_CREATE);});btn2.setOnClickListener(v->{unbindService(connection);});btn3.setOnClickListener(v->{Toast.makeText(this,second_service.getCount()+"",Toast.LENGTH_SHORT).show();});}}

点击绑定服务

点击服务状态

点击解绑服务

StartService启动Service后bindService绑定

如果Service已经由某个客户端通过StartService()启动,接下来由其他客户端 再调用bindService()绑定到该Service后调用unbindService()解除绑定最后在 调用bindService()绑定到Service的话,此时所触发的生命周期方法如下:

onCreate( )->onStartCommand( )->onBind( )->onUnbind( )->onRebind( )

PS:前提是:onUnbind()方法返回true!!! 这里或许部分读者有疑惑了,调用了unbindService后Service不是应该调用 onDistory()方法么!其实这是因为这个Service是由我们的StartService来启动的 ,所以你调用onUnbind()方法取消绑定,Service也是不会终止的!

得出的结论: 假如我们使用bindService来绑定一个启动的Service,注意是已经启动的Service!!! 系统只是将Service的内部IBinder对象传递给Activity,并不会将Service的生命周期 与Activity绑定,因此调用unBindService( )方法取消绑定时,Service也不会被销毁!

Service 进阶

IntentService的使用

在前面我们提到了可以继承Service或者IntentService来创建并启动自己的Service,前面只对继承Service类的方式做了介绍,下面来详细介绍一下IntentService

既然继承Service类就可以实现自己的Service,为什么还需要IntentService类呢?

事实上,如果我们直接把耗时线程放到Service中的onStart()方法中,很容易 会引起ANR异常(Application Not Responding),虽然可以这样做,但是官方不推荐在Service中进行一些耗时操作。

1.Service不是一个单独的进程,它和它的应用程序在同一个进程中

2.Service不是一个线程,这样就意味着我们应该避免在Service中进行耗时操作

于是,Android给我们提供了解决上述问题的替代品——IntentService; IntentService是继承与Service并处理异步请求的一个类,在IntentService中有 一个工作线程来处理耗时操作,请求的Intent记录会加入队列

IntentService工作流程为:

客户端通过startService(Intent)来启动IntentService; 我们并不需要手动地去控制IntentService,当任务执行完后,IntentService会自动停止; 可以启动IntentService多次,每个耗时操作会以工作队列的方式在IntentService的 onHandleIntent回调方法中执行,并且每次只会执行一个工作线程,执行完一,再到二这样

ThirdService.java

/*** @ClassName ThirdService* @Description TODO* @Author Yu* @Date /7/1 11:18* @Version 1.0**/public class ThirdService extends IntentService {private static final String TAG="ThirdService";//必须实现父类的构造方法public ThirdService() {super("ThirdService");}//必须重写的核心方法@Overrideprotected void onHandleIntent(@Nullable Intent intent) {String param = intent.getExtras().getString("PARAM");if (param.equals("s1")) Log.i(TAG,"service1启动");else if (param.equals("s2")) Log.i(TAG,"service2启动");else if (param.equals("s3")) Log.i(TAG,"service3启动");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}}@Nullable@Overridepublic IBinder onBind(Intent intent) {Log.i(TAG,"onBind()方法启动");return super.onBind(intent);}@Overridepublic void onCreate() {Log.i(TAG,"onCreate()方法启动");super.onCreate();}@Overridepublic int onStartCommand(@Nullable Intent intent, int flags, int startId) {Log.i(TAG,"onStartCommand()方法启动");return super.onStartCommand(intent, flags, startId);}@Overridepublic void onDestroy() {Log.i(TAG,"onDestroy()方法启动");super.onDestroy();}}

AndroidManifest.xml注册下Service

<serviceandroid:name=".ThirdService"></service>

MainActivity.java

public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//启动多次IntentService,每次启动,都会新建一个工作线程//但始终只有一个IntentService实例Intent intent1 = new Intent(this,ThirdService.class);Bundle bundle1 = new Bundle();bundle1.putString("PARAM","s1");intent1.putExtras(bundle1);startService(intent1);Intent intent2 = new Intent(this,ThirdService.class);Bundle bundle2 = new Bundle();bundle2.putString("PARAM","s2");intent2.putExtras(bundle2);startService(intent2);Intent intent3 = new Intent(this,ThirdService.class);Bundle bundle3 = new Bundle();bundle3.putString("PARAM","s3");intent3.putExtras(bundle3);startService(intent3);}}

这就是实现IntentService类所必须的全部操作:无参构造方法和onHandleIntent()方法。

如果开发人员决定重写其他回调方法,如onCreate()、onStartCommand()或 onDestroy(),需要调用父类实现,这样IntentService能正确处理工作线程的生命周期.

小结:

当一个后台的任务,需要分成几个子任务,然后按先后顺序执行,子任务 (简单的说就是异步操作),此时如果我们还是定义一个普通Service然后 在onStart方法中开辟线程,然后又要去控制线程,这样显得非常的繁琐; 此时应该自定义一个IntentService然后再onHandleIntent()方法中完成相关任务!

Activity与Service通信

我们前面的操作都是通过Activity启动和停止Service,假如我们启动的是一个下载 的后台Service,而我们想知道Service中下载任务的进度!那么这肯定是需要Service 与Activity进行通信的,而他们之间交流的媒介就是Service中的onBind()方法! 返回一个我们自定义的Binder对象!

基本流程如下:

自定义Service中,自定义一个Binder类,然后将需要暴露的方法都写到该类中!Service类中,实例化这个自定义Binder类,然后重写onBind()方法,将这个Binder对象返回!Activity类中实例化一个ServiceConnection对象,重写onServiceConnected()方法,然后获取Binder对象,然后调用相关方法即可!

给一个Demo

SecondService.java

/*** @ClassName SecondService* @Description TODO* @Author Yu* @Date /6/30 18:04* @Version 1.0**/public class SecondService extends Service {private static final String TAG="Service2";private boolean quit;private int count;//onBind()返回的对象private MyBinder binder=new MyBinder();public class MyBinder extends Binder{//需要暴露的方法public int getCount(){return count;}}//必须实现的方法,绑定改Service时回调该方法@Nullable@Overridepublic IBinder onBind(Intent intent) {Log.i(TAG,"onBind()被调用");//这个Binder对象返回return binder;}//Service被创建时回调@Overridepublic void onCreate() {Log.i(TAG,"onCreate()被调用");new Thread(new Runnable() {@Overridepublic void run() {//当服务还在就一直累加while (!quit){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}count++;}}}).start();super.onCreate();}//Service断开连接时回调@Overridepublic boolean onUnbind(Intent intent) {Log.i(TAG, "onUnbind方法被调用!");return true;}//Service被关闭前回调@Overridepublic void onDestroy() {super.onDestroy();this.quit = true;Log.i(TAG, "onDestroyed方法被调用!");}@Overridepublic void onRebind(Intent intent) {Log.i(TAG, "onRebind方法被调用!");super.onRebind(intent);}}

MainActivity.java

public class MainActivity extends AppCompatActivity {private Button btn1,btn2,btn3;SecondService.MyBinder binder;ServiceConnection connection = new ServiceConnection(){@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {//获取Binder对象,然后调用相关方法即可binder=(SecondService.MyBinder) service;}@Overridepublic void onServiceDisconnected(ComponentName name) {}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);btn1 = findViewById(R.id.btn1);btn2 = findViewById(R.id.btn2);btn3= findViewById(R.id.btn3);Intent intent = new Intent(this,SecondService.class);btn1.setOnClickListener(v->{bindService(intent,connection, Service.BIND_AUTO_CREATE);});btn2.setOnClickListener(v->{unbindService(connection);});btn3.setOnClickListener(v->{//获取Binder对象调用相关方法Toast.makeText(this,"当前进度:"+binder.getCount(),Toast.LENGTH_SHORT).show();});}}

前台服务的实现

一般情况下我们都知道Service一般都是运行在后台的,但是Service的系统优先级 还是比较低的,当系统内存不足的时候,就有可能回收正在后台运行的Service, 对于这种情况我们可以使用前台服务,从而让Service稍微没那么容易被系统杀死, 当然还是有可能被杀死的…所谓的前台服务就是状态栏显示的Notification!

我们来实现一个前台服务,在自定义的Service类中,重写onCreate(),然后根据自己的需求定制Notification; 定制完毕后,调用startForeground(1,notification对象)即可

给出相关代码

foreService.java

/*** @ClassName foreService* @Description TODO* @Author Yu* @Date /7/1 12:41* @Version 1.0**/public class foreService extends Service {@Overridepublic void onCreate() {String CHANNEL_ID = "com.example.fore_service";String CHANNEL_NAME = "TEST";NotificationChannel notificationChannel = null;if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){notificationChannel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH);NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);notificationManager.createNotificationChannel(notificationChannel);}NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID);builder.setContentIntent(PendingIntent.getActivity(this,0,new Intent(this,MainActivity.class),0));builder.setAutoCancel(false);builder.setSmallIcon(R.drawable.fore_icon);builder.setTicker("Foreground Service Start");builder.setContentTitle("Socket服务端");builder.setContentText("正在运行...");startForeground(1, builder.build());super.onCreate();}@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {return super.onStartCommand(intent, flags, startId);}@Nullable@Overridepublic IBinder onBind(Intent intent) {return null;}@Overridepublic void onDestroy() {super.onDestroy();}}

MainActivity.java

public class MainActivity extends AppCompatActivity {private Button btn1;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);btn1 = findViewById(R.id.btn1);Intent intent = new Intent(this,foreService.class);btn1.setOnClickListener(v->{startService(intent);//bindService(intent,connection, Service.BIND_AUTO_CREATE);});}}

AndroidManifest.xml

API28 以上需要在清单中申请创建前台服务的权限

<serviceandroid:name=".foreService"></service></application><uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

fore_icon.xml

<?xml version="1.0" encoding="utf-8"?><vectorxmlns:android="/apk/res/android"android:viewportHeight="1024"android:viewportWidth="1024"android:width="200px"android:height="200px"><pathandroid:pathData="M512 412.116m-215.393 0a215.393 215.393 0 1 0 430.786 0 215.393 215.393 0 1 0-430.786 0Z"android:fillColor="#1296db"/><pathandroid:pathData="M678.914 607.572c-39.498 48.371-99.595 79.396-166.914 79.396s-127.416-31.094-166.914-79.464C220.987 649.516 135.66 756 135.66 857c0 92 752.68 92 752.68 0 0-101-85.327-207.416-209.426-249.428zM808.888 325.339C773.655 194.214 653.914 97.904 512.001 97.904c-141.821 0-261.498 96.183-296.818 227.175-32.929 6.717-58.723 39.877-58.723 79.802 0 44.772 32.429 80.908 70.991 80.908 19.211 0 28.849-39.789 28.918-80.789h0.801c0-140 114.317-254.672 254.832-254.672 137.2 0 249.403 109.35 254.62 245.04-1.616 43.421 7.959 90.541 28.739 90.541 38.562 0 70.991-36.275 70.991-81.047-0.001-39.417-25.139-72.229-57.464-79.523z"android:fillColor="#1296db"/></vector>

定时后台线程的实现

除了上述的前台服务外,实际开发中Service还有一种常见的用法,就是执行定时任务, 比如轮询,就是每间隔一段时间就请求一次服务器,确认客户端状态或者进行信息更新等(比较容易想到的业务是社交媒体APP在后台会定时与服务器通信)!而Android中给我们提供的定时方式有两种使用Timer类与Alarm机制!

那么这两种有什么区别呢?

前者不适合于需要长期在后台运行的定时任务,CPU一旦休眠,Timer中的定时任务 就无法运行;Alarm则不存在这种情况,他具有唤醒CPU的功能,另外,也要区分CPU 唤醒与屏幕唤醒!

所以大部分业务场景下,还是Alarm使用较多,使用流程如下:

Step 1:获得Service: AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);

Step 2:通过set方法设置定时任务 int anHour = 2 * 1000; long triggerAtTime = SystemClock.elapsedRealtime() + anHour; manager.set(AlarmManager.RTC_WAKEUP,triggerAtTime,pendingIntent);

Step 3:定义一个Service 在onStartCommand中开辟一条事务线程,用于处理一些定时逻辑

Step 4:定义一个Broadcast(广播),用于启动Service 最后别忘了,在AndroidManifest.xml中对这Service与Boradcast进行注册!

set(int type,long startTime,PendingIntent pi)方法的参数说明

type 有五个可选值:

AlarmManager.ELAPSED_REALTIME: 闹钟在手机睡眠状态下不可用,该状态下闹钟使用相对时间(相对于系统启动开始),状态值为3;

AlarmManager.ELAPSED_REALTIME_WAKEUP闹钟在睡眠状态下会唤醒系统并执行提示功能,该状态下闹钟也使用相对时间,状态值为2;

AlarmManager.RTC闹钟在睡眠状态下不可用,该状态下闹钟使用绝对时间,即当前系统时间,状态值为1;

AlarmManager.RTC_WAKEUP表示闹钟在睡眠状态下会唤醒系统并执行提示功能,该状态下闹钟使用绝对时间,状态值为0;

AlarmManager.POWER_OFF_WAKEUP表示闹钟在手机关机状态下也能正常进行提示功能,所以是5个状态中用的最多的状态之一, 该状态下闹钟也是用绝对时间,状态值为4;不过本状态好像受SDK版本影响,某些版本并不支持;

第一个参数决定第二个参数的类型,如果是REALTIME的话就用: SystemClock.elapsedRealtime( )方法可以获得系统开机到现在经历的毫秒数 如果是RTC的就用:System.currentTimeMillis()可获得从1970.1.1 0点到 现在做经历的毫秒数

startTime:闹钟的第一次执行时间,以毫秒为单位,可以自定义时间,不过一般使用当前时间。 需要注意的是,本属性与第一个属性(type)密切相关,如果第一个参数对应的闹钟 使用的是相对时间(ELAPSED_REALTIME和ELAPSED_REALTIME_WAKEUP),那么本属 性就得使用相对时间(相对于系统启动时间来说),比如当前时间就表示为:SystemClock.elapsedRealtime();如果第一个参数对应的闹钟使用的是绝对时间(RTC、RTC_WAKEUP、POWER_OFF_WAKEUP),那么本属性就得使用绝对时间, 比如当前时间就表示为:System.currentTimeMillis()

PendingIntent: 绑定了闹钟的执行动作,比如发送一个广播、给出提示等等。PendingIntent 是Intent的封装类。

如果是通过启动服务来实现闹钟提示的话, PendingIntent对象的获取就应该采用PendingIntent.getService(Context context, int requestCode, Intent intent, int flags)方法;

如果是通过广播来实现闹钟提示的话, PendingIntent对象的获取就应该采用PendingIntent.getBroadcast(Context context, int requestCode, Intent intent, int flags)方法;

如果是采用Activity的方式来实现闹钟提示的话,PendingIntent对象的获取 就应该采用PendingIntent.getActivity(Context context, int requestCode, Intent intent, int flags)方法。

intent就是需要启动的Activity、Service、BroadCastReceiver的intent。

Flags的类型:

FLAG_ONE_SHOT:得到的pi只能使用一次,第二次使用该pi时报错

FLAG_NO_CREATE: 当pi不存在时,不创建,返回null

FLAG_CANCEL_CURRENT: 每次都创建一个新的pi

FLAG_UPDATE_CURRENT: 不存在时就创建,创建好了以后就一直用它,每次使用时都会更新pi的数据(使用较多)

从4.4版本后(API 19),Alarm任务的触发时间可能变得不准确,有可能会延时,是系统 对于耗电性的优化,如果需要准确无误可以调用setExtra()方法

ALongTimeRunningService.java

/*** @ClassName ALongTimeRunningService* @Description 轮询服务,保持长时间通信* @Author Yu* @Date /7/1 15:33* @Version 1.0**/public class ALongTimeRunningService extends Service {private int count=0;@Nullable@Overridepublic IBinder onBind(Intent intent) {return null;}@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {new Thread(new Runnable() {@Overridepublic void run() {//打印服务执行的时间日志Log.i("ServiceRunTime", new Date().toString());count++;//累加}}).start();AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);int point_time =2*1000;//间隔时长long times = SystemClock.elapsedRealtime() + point_time;//每次执行时间Intent intent1 = new Intent(this,AlarmReceiver.class);//每次通过广播发送一条数据Bundle bundle = new Bundle();bundle.putInt("num",count);intent1.putExtras(bundle);PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent1, PendingIntent.FLAG_UPDATE_CURRENT);manager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,times,pendingIntent);return super.onStartCommand(intent, flags, startId);}}

AlarmReceiver.java

/*** @ClassName AlarmReceiver* @Description 接到广播后,再次执行服务* @Author Yu* @Date /7/1 15:39* @Version 1.0**/public class AlarmReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {Intent intent1 = new Intent(context, ALongTimeRunningService.class);//接收长连接服务传来的广播数据Bundle bundle = intent.getExtras();int num = bundle.getInt("num");Log.i("当前数据:",num+"");context.startService(intent1);}}

AndroidManifest.xml

<service android:name=".ALongTimeRunningService"></service><receiver android:name=".AlarmReceiver"></receiver>

MainActivity.java

public class MainActivity extends AppCompatActivity {private Button btn1;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);btn1 = findViewById(R.id.btn1);Intent intent = new Intent(this,ALongTimeRunningService.class);btn1.setOnClickListener(v->{startService(intent);//bindService(intent,connection, Service.BIND_AUTO_CREATE);});}}

Service 再进阶

Binder机制

IBinder和Binder

首先来了解一下它们的概念(官方定义)

IBinder是远程对象的基本接口,是为了高性能而设计的轻量级远程调用机制(RPC)的核心部分。但它不仅用于远程调用,也用于进程内调用。该接口定义了与远程对象间交互的协议。但不要直接实现 这个接口,而是继承(extends)Binder。

IBinder主要的API是transact(),与之对应的API是Binder.onTransact()。通过前者,你能 想远程IBinder对象发送发出调用,后者使你的远程对象能够响应接收到的调用。IBinder的API都是 Syncronous(同步)执行的,比如transact()直到对方的Binder.onTransact()方法调用完后才返回。 调用发生在进程内时无疑是这样的,而在进程间时,在IPC的帮助下,也是同样的效果。

通过transact()发送的数据是Parcel,Parcel是一种一般的缓冲区,除了有数据外还带有 一些描述它内容的元数据。元数据用于管理IBinder对象的引用,这样就能在缓冲区从一个进程移动 到另一个进程时保存这些引用。这样就保证了当一个IBinder被写入到Parcel并发送到另一个进程中, 如果另一个进程把同一个IBinder的引用回发到原来的进程,那么这个原来的进程就能接收到发出的 那个IBinder的引用。这种机制使IBinder和Binder像唯一标志符那样在进程间管理。

系统为每个进程维护一个存放交互线程的线程池。这些交互线程用于派送所有从另外进程发来的IPC 调用。例如:当一个IPC从进程A发到进程B,A中那个发出调用的线程(这个应该不在线程池中)就阻塞 在transact()中了。进程B中的交互线程池中的一个线程接收了这个调用,它调用 Binder.onTransact(),完成后用一个Parcel来做为结果返回。然后进程A中的那个等待的线程在 收到返回的Parcel后得以继续执行。实际上,另一个进程看起来就像是当前进程的一个线程, 但不是当前进程创建的。

Binder机制还支持进程间的递归调用。例如,进程A执行自己的IBinder的transact()调用进程B 的Binder,而进程B在其Binder.onTransact()中又用transact()向进程A发起调用,那么进程A 在等待它发出的调用返回的同时,还会用Binder.onTransact()响应进程B的transact()。 总之Binder造成的结果就是让我们感觉到跨进程的调用与进程内的调用没什么区别。

当操作远程对象时,你经常需要查看它们是否有效,有三种方法可以使用:

transact()方法将在IBinder所在的进程不存在时抛出RemoteException异常。如果目标进程不存在,那么调用pingBinder()时返回false。可以用linkToDeath()方法向IBinder注册一个IBinder.DeathRecipient, 在IBinder代表的进程退出时被调用。

小结:IBinder是Android给我们提供的一个进程间通信的一个接口,而我们一般是不直接实现这个接口的, 而是通过继承Binder类来实现进程间通信!是Android中实现IPC(进程间通信)的一种方式

Binder机制和工作流程

Android中的Binder机制由一系列系统组件构成: Client、Server、Service Manager和Binder驱动程序

Binder的调用流程如下:

Client调用某个代理接口中的方法时,代理接口的方法会将Client传递的参数打包成Parcel对象;然后代理接口把该Parcel对象发送给内核中的Binder driver;;然后Server会读取Binder Driver中的请求数据,假如是发送给自己的,解包Parcel对象, 处理并将结果返回;

代理接口中的定义的方法和Server中定义的方法是一一对应的, 另外,整个调用过程是一个同步的,即Server在处理时,Client会被Block(锁)住! 而这里说的代理接口的定义就是等下要说的AIDL(Android接口描述语言)

为何Android使用Binder机制来实现进程间的通信

可靠性:在移动设备上,通常采用基于Client-Server的通信方式来实现互联网与设备间的内部通信。目前linux支持IPC包括传统的管道,System V IPC,即消息队列/共享内存/信号量,以及socket中只有socket支持Client-Server的通信方式。Android系统为开发者提供了丰富进程间通信的功能接口,媒体播放,传感器,无线传输。这些功能都由不同的server来管理。开发都只关心将自己应用程序的client与server的通信建立起来便可以使用这个服务。毫无疑问,如若在底层架设一套协议来实现Client-Server通信,增加了系统的复杂性。在资源有限的手机 上来实现这种复杂的环境,可靠性难以保证。传输性能:socket主要用于跨网络的进程间通信和本机上进程间的通信,但传输效率低,开销大。消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的一块缓存区中,然后从内核缓存区拷贝到接收方缓存区,其过程至少有两次拷贝。虽然共享内存无需拷贝,但控制复杂。比较各种IPC方式的数据拷贝次数。共享内存:0次。Binder:1次。Socket/管道/消息队列:2次。安全性:Android是一个开放式的平台,所以确保应用程序安全是很重要的。Android对每一个安装应用都分配了UID/PID,其中进程的UID是可用来鉴别进程身份。传统的只能由用户在数据包里填写UID/PID,这样不可靠,容易被恶意程序利用。而我们要求由内核来添加可靠的UID。 所以,出于可靠性、传输性、安全性。android建立了一套新的进程间通信方式。 ——摘自:Android中的Binder机制的简要理解

总而言之,Binder机制给我们带来的最直接的好处就是: 我们无需关心底层如何实现,只需按照AIDL的规则,自定义一个接口文件, 然后调用调用接口中的方法,就可以完成两个进程间的通信了

AIDL(重要)

AIDL定义

前面我们讲到IPC这个名词,他的全名叫做:跨进程通信(interprocess communication), 因为在Android系统中,每个应用程序都运行在自己的进程中,进程之间一般是无法直接进行数据交换的, 而为了实现跨进程,Android给我们提供了上面说的Binder机制,而这个机制使用的接口语言就是: AIDL(Android Interface Definition Language),他的语法很简单,而这种接口语言并非真正的编程 语言,只是定义两个进程间的通信接口而已!而生成符合通信协议的Java代码则是由Android SDK的 platform-tools目录下的aidl.exe工具生成,生成对应的接口文件在:gen目录下,一般是:Xxx.java的接口! 而在该接口中包含一个Stub的内部类,该类中实现了在该类中实现了IBinder接口与自定义的通信接口, 这个类将会作为远程Service的回调类——实现了IBinder接口,所以可作为Service的onBind( )方法的返回值

AIDL实现两个进程间的简单通信

AIDL注意事项:

接口名词需要与aidl文件名相同接口和方法前面不要加访问权限修饰符:public ,private,protected等,也不能用static final!AIDL默认支持的类型包括Java基本类型,String,List,Map,CharSequence,除此之外的其他类型都 需要import声明,对于使用自定义类型作为参数或者返回值,自定义类型需要实现Parcelable接口, 详情请看后面的传递复杂数据类型自定义类型和AIDL生成的其它接口类型在aidl描述文件中,应该显式import,即便在该类和定义 的包在同一个包中。

AS下创建AIDL需要在main目录下新建一个aidl文件夹,AIDL文件所在的路径(包名)要跟项目的包名保持一致,最后创建一个aidl文件,接着按ctrl + f9(Build->Make Project)重新编译,就可以了

编译后输出

aidldemo用作服务端演示项目

在服务端

step1:创建AIDL文件,内容如下

IPerson.aidl

// IPerson.aidlpackage com.thundersoft.aidldemo;// Declare any non-default types here with import statementsinterface IPerson {/*** Demonstrates some basic types that you can use as parameters* and return values in AIDL.*/String queryPerson(int num);}

编译生成的IPerson.java(生成的文件不允许修改)

IPerson.java

/** This file is auto-generated. DO NOT MODIFY.*/package com.thundersoft.aidldemo;// Declare any non-default types here with import statementspublic interface IPerson extends android.os.IInterface{/** Default implementation for IPerson. */public static class Default implements com.thundersoft.aidldemo.IPerson{/*** Demonstrates some basic types that you can use as parameters* and return values in AIDL.*/@Override public java.lang.String queryPerson(int num) throws android.os.RemoteException{return null;}@Overridepublic android.os.IBinder asBinder() {return null;}}/** Local-side IPC implementation stub class. */public static abstract class Stub extends android.os.Binder implements com.thundersoft.aidldemo.IPerson{private static final java.lang.String DESCRIPTOR = "com.thundersoft.aidldemo.IPerson";/** Construct the stub at attach it to the interface. */public Stub(){this.attachInterface(this, DESCRIPTOR);}/*** Cast an IBinder object into an com.thundersoft.aidldemo.IPerson interface,* generating a proxy if needed.*/public static com.thundersoft.aidldemo.IPerson asInterface(android.os.IBinder obj){if ((obj==null)) {return null;}android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);if (((iin!=null)&&(iin instanceof com.thundersoft.aidldemo.IPerson))) {return ((com.thundersoft.aidldemo.IPerson)iin);}return new com.thundersoft.aidldemo.IPerson.Stub.Proxy(obj);}@Override public android.os.IBinder asBinder(){return this;}@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException{java.lang.String descriptor = DESCRIPTOR;switch (code){case INTERFACE_TRANSACTION:{reply.writeString(descriptor);return true;}case TRANSACTION_queryPerson:{data.enforceInterface(descriptor);int _arg0;_arg0 = data.readInt();java.lang.String _result = this.queryPerson(_arg0);reply.writeNoException();reply.writeString(_result);return true;}default:{return super.onTransact(code, data, reply, flags);}}}private static class Proxy implements com.thundersoft.aidldemo.IPerson{private android.os.IBinder mRemote;Proxy(android.os.IBinder remote){mRemote = remote;}@Override public android.os.IBinder asBinder(){return mRemote;}public java.lang.String getInterfaceDescriptor(){return DESCRIPTOR;}/*** Demonstrates some basic types that you can use as parameters* and return values in AIDL.*/@Override public java.lang.String queryPerson(int num) throws android.os.RemoteException{android.os.Parcel _data = android.os.Parcel.obtain();android.os.Parcel _reply = android.os.Parcel.obtain();java.lang.String _result;try {_data.writeInterfaceToken(DESCRIPTOR);_data.writeInt(num);boolean _status = mRemote.transact(Stub.TRANSACTION_queryPerson, _data, _reply, 0);if (!_status && getDefaultImpl() != null) {return getDefaultImpl().queryPerson(num);}_reply.readException();_result = _reply.readString();}finally {_reply.recycle();_data.recycle();}return _result;}public static com.thundersoft.aidldemo.IPerson sDefaultImpl;}static final int TRANSACTION_queryPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);public static boolean setDefaultImpl(com.thundersoft.aidldemo.IPerson impl) {// Only one user of this interface can use this function// at a time. This is a heuristic to detect if two different// users in the same process use this function.if (Stub.Proxy.sDefaultImpl != null) {throw new IllegalStateException("setDefaultImpl() called twice");}if (impl != null) {Stub.Proxy.sDefaultImpl = impl;return true;}return false;}public static com.thundersoft.aidldemo.IPerson getDefaultImpl() {return Stub.Proxy.sDefaultImpl;}}/*** Demonstrates some basic types that you can use as parameters* and return values in AIDL.*/public java.lang.String queryPerson(int num) throws android.os.RemoteException;}

这里我们关注的只是asInterface(IBinder)和我们定义的接口中的queryPerson()方法!

该方法会把IBinder类型的对象转换成IPerson类型的,必要时生成一个代理对象返回结果

step2:自定义我们的Service类,主要完成以下任务:

继承Service类,同时也自定义了一个PersonQueryBinder类用来继承IPerson.Stub类 就是实现了IPerson接口和IBinder接口

实例化自定义的Stub类,并重写Service的onBind方法,返回一个binder对象

AIDLService.java

package com.thundersoft.aidldemo;import android.app.Service;import android.content.Intent;import android.os.IBinder;import android.os.RemoteException;import android.util.Log;import android.widget.Toast;import androidx.annotation.Nullable;/*** @ClassName AIDLService* @Description* @Author Yu* @Date /7/2 13:00* @Version 1.0**/public class AIDLService extends Service {private IBinder binder=new PersonQueryBinder();private final String [] data={"数据一","数据二","数据三","数据四","数据五","数据六"};private String query(int num){if (num>0&&num<7){return data[num-1];}return null;}@Nullable@Overridepublic IBinder onBind(Intent intent) {return binder;}private final class PersonQueryBinder extends IPerson.Stub {@Overridepublic String queryPerson(int num) throws RemoteException {Log.i("Service",num+"");return query(num);}}}

step3: 在AndroidManifest.xml文件中注册Service

<service android:name=".AIDLService"android:exported="false"><intent-filter><action android:name="android.intent.action.AIDLService"/><category android:name="android.intent.category.DEFAULT"/></intent-filter></service>

客户端

把服务端的aidl文件复制过来,然后直接在MainActivity中完成剩余操作,和绑定本地Service的操作

有点类似

自定义PersonConnection类实现ServiceConnection接口以PersonConnection对象作为参数,调用bindService绑定远程Service

bindService(service,conn,BIND_AUTO_CREATE);

第三个参数是设置如果服务没有启动的话,自动创建和本地Service不同,绑定远程Service的ServiceConnection并不能直接获取Service的onBind( )方法

返回的IBinder对象,只能返回onBind( )方法所返回的代理对象,需要做如下处理:

iPerson = IPerson.Stub.asInterface(service);

MainActivity.java

package com.thundersoft.aidlclient;import androidx.appcompat.app.AppCompatActivity;import ponentName;import android.content.Intent;import android.content.ServiceConnection;import android.os.Bundle;import android.os.IBinder;import android.os.RemoteException;import android.view.View;import android.widget.Button;import android.widget.EditText;import android.widget.TextView;import com.thundersoft.aidldemo.IPerson;public class MainActivity extends AppCompatActivity implements View.OnClickListener{private Button btn;private TextView resulttv;private EditText tosearch;private IPerson iPerson;private PersonConnection connection=new PersonConnection();@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);createViews();Intent service = new Intent("android.intent.action.AIDLService");service.setPackage("com.thundersoft.aidldemo");//远程绑定服务bindService(service,connection,BIND_AUTO_CREATE);btn.setOnClickListener(this);}//绑定组件private void createViews() {btn=findViewById(R.id.btn_search);resulttv=findViewById(R.id.result_tv);tosearch=findViewById(R.id.ed);}@Overridepublic void onClick(View v) {Integer num = Integer.valueOf(tosearch.getText().toString());try {String result = iPerson.queryPerson(num);resulttv.setText(result);} catch (RemoteException e) {e.printStackTrace();}tosearch.setText(num+"");}private final class PersonConnection implements ServiceConnection{@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {iPerson = IPerson.Stub.asInterface(service);}@Overridepublic void onServiceDisconnected(ComponentName name) {iPerson=null;}}}

先启动AIDLDemo,再启动aidlclient

传递复杂数据的AIDL Service

上面的例子我们传递的只是要给int类型的参数,然后服务端返回一个String类型的参数,看似满足 我们的基本需求,不过实际开发中,我们可能需要考虑传递复杂数据类型的情况!下面我们来学习下 如何向服务端传递复杂数据类型的数据!开始之前我们先来了解Parcelable接口

Parcelable接口简介:

相信用过序列化的基本上都知道这个接口了,除了他还有另外一个Serializable,同样是用于序列化的, 只是Parcelable更加轻量级,速度更快!但是写起来就有点麻烦了,当然如果你用的as的话可以用 的插件来完成序列化,比如:Android Parcelable Code Generator 当然,这里我们还是手动实现这个接口

首先需要实现:writeToParcel和readFromPacel方法 写入方法将对象写入到包裹(parcel)中,而读取方法则从包裹中读取对象, 请注意,写入属性顺序需与读取顺序相同

接着需要在:该类中添加一个名为CREATOR的static final属性 改属性需要实现:android.os.Parcelable.Creator接口

再接着需要从写接口中的两个方法: createFromParcel(Parcel source)方法:实现从source创建出JavaBean实例的功能 newArray(int size):创建一个类型为T,长度为size的数组,只有一个简单的return new T[size]; (这里的T是Person类)

最后,describeContents():这个我也不知道是拿来干嘛的,直接返回0即可

step1:创建两个实体类PersonSalary,这两个类都实现Parcelable接口,便于传输。

Person.java

package plexaidl;import android.os.Parcel;import android.os.Parcelable;/*** @ClassName Person* @Description TODO* @Author Yu* @Date /7/2 16:03* @Version 1.0**/public class Person implements Parcelable {private Integer ID;private String Name;public Person() {}public Person(Integer ID, String name) {this.ID = ID;Name = name;}public Integer getID() {return ID;}public void setID(Integer ID) {this.ID = ID;}public String getName() {return Name;}public void setName(String name) {Name = name;}//因为我们集合取出元素的时候是根据Person对象来取得,所以比较麻烦,//需要我们重写hashCode()和equals()方法@Overridepublic int hashCode(){final int prime = 31;int result = 1;result = prime * result + ((Name == null) ? 0 : Name.hashCode());return result;}@Overridepublic boolean equals(Object obj){if (this == obj)return true;if (obj == null)return false;if (getClass() != obj.getClass())return false;Person other = (Person) obj;if (Name == null){if (other.Name != null)return false;}else if (!Name.equals(other.Name))return false;return true;}//必须提供一个名为CREATOR的static final属性 该属性需要实现//android.os.Parcelable.Creator<T>接口public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() {//从Parcel中读取数据,返回Person对象@Overridepublic Person createFromParcel(Parcel source) {return new Person(source.readInt(),source.readString());}@Overridepublic Person[] newArray(int size) {return new Person[size];}};@Overridepublic int describeContents() {return 0;}//把对象所包含的数据写入到parcel中@Overridepublic void writeToParcel(Parcel dest, int flags) {dest.writeInt(ID);dest.writeString(Name);}}

Salary.java

package plexaidl;import android.os.Parcel;import android.os.Parcelable;/*** @ClassName Salary* @Description TODO* @Author Yu* @Date /7/2 16:43* @Version 1.0**/public class Salary implements Parcelable {private String occ;private Integer salary;public Salary() {}public Salary(String occ, Integer salary) {this.occ = occ;this.salary = salary;}public String getOcc() {return occ;}public void setOcc(String occ) {this.occ = occ;}public Integer getSalary() {return salary;}public void setSalary(Integer salary) {this.salary = salary;}@Overridepublic int describeContents() {return 0;}@Overridepublic void writeToParcel(Parcel dest, int flags) {dest.writeString(occ);dest.writeInt(salary);}public static final Parcelable.Creator<Salary> CREATOR=new Parcelable.Creator<Salary>(){@Overridepublic Salary createFromParcel(Parcel source) {return new Salary(source.readString(),source.readInt());}@Overridepublic Salary[] newArray(int size) {return new Salary[size];}};@Overridepublic String toString() {return "职业:"+occ+" "+"薪资"+salary;}}

step2:编写对应的AIDL文件

AIDL文件一般有两种类型,一种只包含一个序列化对象 供其他的AIDL文件使用;另一种包含定义的各种方法接口

只包含序列化对象的AIDL文件

// Person.aidlpackage plexaidl;// Declare any non-default types here with import statementsparcelable Person;

// Salary.aidlpackage plexaidl;// Declare any non-default types here with import statementsparcelable Salary;

包含接口的AIDL文件

// ISalary.aidlpackage plexaidl;import plexaidl.Person;import plexaidl.Salary;// Declare any non-default types here with import statementsinterface ISalary {/*** Demonstrates some basic types that you can use as parameters* and return values in AIDL.*/Salary getInfo(in Person p);}

如果使用的是自定义的数据类型的话,需要import

step3:编写服务端:定义一个SalaryBinder类继承Stub,从而实现ISalary和IBinder接口;定义一个存储信息的Map集合! 重新onBind方法,返回SalaryBinder类的对象实例

AIDLService.java

package plexaidl.service;import android.app.Service;import android.content.Intent;import android.os.IBinder;import android.os.RemoteException;import android.util.Log;import androidx.annotation.Nullable;import plexaidl.ISalary;import plexaidl.Person;import plexaidl.Salary;import java.util.HashMap;import java.util.Map;/*** @ClassName AIDLService* @Description TODO* @Author Yu* @Date /7/2 17:01* @Version 1.0**/public class AIDLService extends Service {private static Map<Person, Salary> map=new HashMap<>();private SalaryBinder binder;static {map.put(new Person(1001,"Ming"),new Salary("前台",6007));map.put(new Person(1002,"Ling"),new Salary("程序员",15000));map.put(new Person(1003,"Han"),new Salary("摄影师",10000));}@Overridepublic void onCreate() {Log.i("Service","服务启动!");super.onCreate();binder=new SalaryBinder();}@Nullable@Overridepublic IBinder onBind(Intent intent) {return binder;}private class SalaryBinder extends ISalary.Stub{@Overridepublic Salary getInfo(plexaidl.Person p) throws RemoteException {Log.i("客户端传来的数据",p.getName()+","+p.getID());return map.get(p);}}@Overridepublic void onDestroy() {super.onDestroy();Log.i("Service","服务结束!");}}

在AndroidManifest.xml中注册Service

<service android:name=".service.AIDLService"android:exported="true"><intent-filter><action android:name="com.thundersoft.aidl" /><category android:name="android.intent.category.DEFAULT" /></intent-filter></service>

step4:客户端编写

首先把服务端项目中的AIDL文件拷贝到客户端项目下,这里使用AndroidStudio有个大坑,因为要保证AIDL文件以及其对应的java文件所在的包名是一致的,于是在很多老文章里都告诉你把这些文件都放到aidl包下的同一个包里,实际上这也没什么问题,而且方便随时移动文件。但是在 Android Studio 里并不是这样。如果这样做的话,系统根本就找不到 xxx.java 文件,从而在其他的AIDL文件里面使用 xxx 对象的时候会报 Symbol not found 的错误。为什么会这样呢?因为 Gradle 。大家都知道,Android Studio 是默认使用 Gradle 来构建 Android 项目的,而 Gradle 在构建项目的时候会通过 sourceSets 来配置不同文件的访问路径,从而加快查找速度——问题就出在这里。Gradle 默认是将 java 代码的访问路径设置在 java 包下的,这样一来,如果 java 文件是放在 aidl 包下的话那么理所当然系统是找不到这个 java 文件的。

解决方法也很简单,详情参考AIDL

第一种: 修改 build.gradle 文件:在 android{} 中间加上下面的内容:

sourceSets {main {java.srcDirs = ['src/main/java', 'src/main/aidl']}}

第二种 :把 java 文件放到 java 包下去:把 xxx.java 放到 java 包里任意一个包下,保持其包名不变,与 xxx.aidl 一致。只要它的包名不变,xxx.aidl 就能找到 xxx.java ,而只要 xxx.java 在 java 包下,那么系统也是能找到它的。但是这样做的话也有一个问题,就是在移植相关 .aidl 文件和 .java 文件的时候没那么方便,不能直接把整个 aidl 文件夹拿过去完事儿了,还要单独将 .java 文件放到 java 文件夹里去。

客户端实现: 定义一个ServciceConnection对象,重写对应方法,和前面的普通数据的类似 再接着在bindService,然后再Button的点击事件中获取Salary对象并显示出来

MainActivity.java

package plexaidlclient;import androidx.appcompat.app.AppCompatActivity;import ponentName;import android.content.Intent;import android.content.ServiceConnection;import android.os.Bundle;import android.os.IBinder;import android.os.RemoteException;import android.util.Log;import android.view.View;import android.widget.Button;import android.widget.EditText;import android.widget.TextView;import plexaidl.ISalary;import plexaidl.Person;import plexaidl.Salary;public class MainActivity extends AppCompatActivity implements View.OnClickListener{private Button btn;private TextView resulttv;private EditText tosearch;private ISalary iSalary;private ServiceConnection connection =new ServiceConnection(){@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {Log.i(getLocalClassName(), "服务已连接");iSalary = ISalary.Stub.asInterface(service);}@Overridepublic void onServiceDisconnected(ComponentName name) {iSalary=null;}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);createViews();Intent service = new Intent("com.thundersoft.aidl");service.setPackage("plexaidl");//远程绑定服务bindService(service,connection,BIND_AUTO_CREATE);btn.setOnClickListener(this);}//绑定组件private void createViews() {btn=findViewById(R.id.btn_search);resulttv=findViewById(R.id.result_tv);tosearch=findViewById(R.id.ed);}@Overridepublic void onClick(View v) {String name = tosearch.getText().toString();try {Salary info = iSalary.getInfo(new Person(1001, name));resulttv.setText(name+":"+info.toString());} catch (RemoteException e) {e.printStackTrace();}}@Overrideprotected void onDestroy() {super.onDestroy();unbindService(connection);}}

通过Binder的onTransact完成跨进程通信

上面讲过Android可以通过Binder的onTrensact方法来完成通信,我们来通过一个demo来实现一下

服务端

IPCService.java

package com.thundersoft.ipcdemo.ipcserver;import android.app.Service;import android.content.Intent;import android.os.Binder;import android.os.IBinder;import android.os.Parcel;import android.os.RemoteException;import androidx.annotation.NonNull;import androidx.annotation.Nullable;/*** @ClassName IPCService* @Description TODO* @Author Yu* @Date /7/3 12:48* @Version 1.0**/public class IPCService extends Service {private static final String DESCRIPTOR="IPCService";private final String [] datas={"数据一","数据二","数据三","数据四","数据五","数据六"};private MyBinder binder=new MyBinder();public class MyBinder extends Binder{@Overrideprotected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {switch (code){case 0x001:data.enforceInterface(DESCRIPTOR);int num = data.readInt();reply.writeNoException();reply.writeString(datas[num]);return true;}return super.onTransact(code, data, reply, flags);}}@Nullable@Overridepublic IBinder onBind(Intent intent) {return binder;}}

AndroidManifest.xml

<service android:name=".ipcserver.IPCService"android:exported="true"><intent-filter><action android:name="android.intent.action.IPCService"/><category android:name="android.intent.category.DEFAULT"/></intent-filter></service>

客户端

MainActivity.java

package com.thundersoft.ipcdemoclient;import androidx.appcompat.app.AppCompatActivity;import ponentName;import android.content.Intent;import android.content.ServiceConnection;import android.os.Bundle;import android.os.IBinder;import android.os.RemoteException;import android.view.View;import android.widget.Button;import android.widget.EditText;import android.widget.TextView;import android.widget.Toast;public class MainActivity extends AppCompatActivity implements View.OnClickListener{private Button btn;private TextView resulttv;private EditText tosearch;private IBinder binder;private ServiceConnection connection=new ServiceConnection(){@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {binder=service;}@Overridepublic void onServiceDisconnected(ComponentName name) {binder=null;}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);createViews();Intent service = new Intent("android.intent.action.IPCService");service.setPackage("com.thundersoft.ipcdemo");//远程绑定服务bindService(service,connection,BIND_AUTO_CREATE);btn.setOnClickListener(this);}//绑定组件private void createViews() {btn=findViewById(R.id.btn_search);resulttv=findViewById(R.id.result_tv);tosearch=findViewById(R.id.ed);}@Overridepublic void onClick(View v) {Integer num = Integer.valueOf(tosearch.getText().toString());if (binder == null){Toast.makeText(this, "未连接服务端或服务端被异常杀死", Toast.LENGTH_SHORT).show();} else {android.os.Parcel _data = android.os.Parcel.obtain();android.os.Parcel _reply = android.os.Parcel.obtain();String _result = null;try{_data.writeInterfaceToken("IPCService");_data.writeInt(num);binder.transact(0x001, _data, _reply, 0);_reply.readException();_result = _reply.readString();resulttv.setText(_result);}catch (RemoteException e){e.printStackTrace();} finally{_reply.recycle();_data.recycle();}}}}

记录一个高版本的使用AIDL大坑

如果你的targetSdkVersion在30及以上,按照上述的步骤创建项目后,客户端是拉不起来服务端的,因为在高版本上面,原来的那套逻辑,谷歌单独做了处理,真tm坑。

那么怎么解决呢?

方法一

在你的项目build.gradle文件修改当前版本30以下targetSdkVersion<30

android {compileSdk 32defaultConfig {applicationId "com.ts.timerservice"minSdk 21targetSdk 29versionCode 1versionName "1.0"testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"}

方法二

在你的客户端的注册清单文件(AndroidManifest.xml)中以下标签

<queries><package android:name="你的服务所在的包"/></queries>

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。