Android Service完全解析,关于服务你所需知道的一切(下)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介:

在上一篇文章中,我们学习了Android Service相关的许多重要内容,包括Service的基本用法、Service和Activity进行通信、Service的销毁方式、Service与Thread的关系、以及如何创建前台Service。以上所提到的这些知识点,基本上涵盖了大部分日常开发工作当中可能使用到的Service技术。不过关于Service其实还有一个更加高端的使用技巧没有介绍,即远程Service的用法。使用远程Service甚至可以实现Android跨进程通信的功能,下面就让我们具体地学习一下。

 

如果你还没有看过前面一篇文章,建议先去阅读一下 Android Service完全解析,关于服务你所需知道的一切(上) ,因为本篇文章中涉及到的代码是在上篇文章的基础上进行修改的。

 

在上篇文章中我们知道了,Service其实是运行在主线程里的,如果直接在Service中处理一些耗时的逻辑,就会导致程序ANR。

 

让我们来做个实验验证一下吧,修改上一篇文章中创建的ServiceTest项目,在MyService的onCreate()方法中让线程睡眠60秒,如下所示:

[java]  view plain  copy
 
  1. public class MyService extends Service {  
  2.   
  3.     ......  
  4.   
  5.     @Override  
  6.     public void onCreate() {  
  7.         super.onCreate();  
  8.         Log.d(TAG, "onCreate() executed");  
  9.         try {  
  10.             Thread.sleep(60000);  
  11.         } catch (InterruptedException e) {  
  12.             e.printStackTrace();  
  13.         }  
  14.     }  
  15.       
  16.     ......  
  17.   
  18. }  

 

重新运行后,点击一下Start Service按钮或Bind Service按钮,程序就会阻塞住并无法进行任何其它操作,过一段时间后就会弹出ANR的提示框,如下图所示。

 

 

之前我们提到过,应该在Service中开启线程去执行耗时任务,这样就可以有效地避免ANR的出现。

 

那么本篇文章的主题是介绍远程Service的用法,如果将MyService转换成一个远程Service,还会不会有ANR的情况呢?让我们来动手尝试一下吧。

 

将一个普通的Service转换成远程Service其实非常简单,只需要在注册Service的时候将它的android:process属性指定成:remote就可以了,代码如下所示:

[html]  view plain  copy
 
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     package="com.example.servicetest"  
  4.     android:versionCode="1"  
  5.     android:versionName="1.0" >  
  6.   
  7.     ......  
  8.       
  9.     <service  
  10.         android:name="com.example.servicetest.MyService"  
  11.         android:process=":remote" >  
  12.     </service>  
  13.   
  14. </manifest>  

现在重新运行程序,并点击一下Start Service按钮,你会看到控制台立刻打印了onCreate() executed的信息,而且主界面并没有阻塞住,也不会出现ANR。大概过了一分钟后,又会看到onStartCommand() executed打印了出来。

 

为什么将MyService转换成远程Service后就不会导致程序ANR了呢?这是由于,使用了远程Service后,MyService已经在另外一个进程当中运行了,所以只会阻塞该进程中的主线程,并不会影响到当前的应用程序。

 

为了证实一下MyService现在确实已经运行在另外一个进程当中了,我们分别在MainActivity的onCreate()方法和MyService的onCreate()方法里加入一行日志,打印出各自所在的进程id,如下所示:

 

[java]  view plain  copy
 
  1. Log.d("TAG", "process id is " + Process.myPid());  

再次重新运行程序,然后点击一下Start Service按钮,打印结果如下图所示:

 

 

可以看到,不仅仅是进程id不同了,就连应用程序包名也不一样了,MyService中打印的那条日志,包名后面还跟上了:remote标识。

 

那既然远程Service这么好用,干脆以后我们把所有的Service都转换成远程Service吧,还省得再开启线程了。其实不然,远程Service非但不好用,甚至可以称得上是较为难用。一般情况下如果可以不使用远程Service,就尽量不要使用它。

 

下面就来看一下它的弊端吧,首先将MyService的onCreate()方法中让线程睡眠的代码去除掉,然后重新运行程序,并点击一下Bind Service按钮,你会发现程序崩溃了!为什么点击Start Service按钮程序就不会崩溃,而点击Bind Service按钮就会崩溃呢?这是由于在Bind Service按钮的点击事件里面我们会让MainActivity和MyService建立关联,但是目前MyService已经是一个远程Service了,Activity和Service运行在两个不同的进程当中,这时就不能再使用传统的建立关联的方式,程序也就崩溃了。

 

那么如何才能让Activity与一个远程Service建立关联呢?这就要使用AIDL来进行跨进程通信了(IPC)。

 

AIDL(Android Interface Definition Language)是Android接口定义语言的意思,它可以用于让某个Service与多个应用程序组件之间进行跨进程通信,从而可以实现多个应用程序共享同一个Service的功能。

 

下面我们就来一步步地看一下AIDL的用法到底是怎样的。首先需要新建一个AIDL文件,在这个文件中定义好Activity需要与Service进行通信的方法。新建MyAIDLService.aidl文件,代码如下所示:

[java]  view plain  copy
 
  1. package com.example.servicetest;  
  2. interface MyAIDLService {  
  3.     int plus(int a, int b);  
  4.     String toUpperCase(String str);  
  5. }  

点击保存之后,gen目录下就会生成一个对应的Java文件,如下图所示:

 

 

 

然后修改MyService中的代码,在里面实现我们刚刚定义好的MyAIDLService接口,如下所示:

[java]  view plain  copy
 
  1. public class MyService extends Service {  
  2.   
  3.     ......  
  4.   
  5.     @Override  
  6.     public IBinder onBind(Intent intent) {  
  7.         return mBinder;  
  8.     }  
  9.   
  10.     MyAIDLService.Stub mBinder = new Stub() {  
  11.   
  12.         @Override  
  13.         public String toUpperCase(String str) throws RemoteException {  
  14.             if (str != null) {  
  15.                 return str.toUpperCase();  
  16.             }  
  17.             return null;  
  18.         }  
  19.   
  20.         @Override  
  21.         public int plus(int a, int b) throws RemoteException {  
  22.             return a + b;  
  23.         }  
  24.     };  
  25.   
  26. }  

这里先是对MyAIDLService.Stub进行了实现,重写里了toUpperCase()和plus()这两个方法。这两个方法的作用分别是将一个字符串全部转换成大写格式,以及将两个传入的整数进行相加。然后在onBind()方法中将MyAIDLService.Stub的实现返回。这里为什么可以这样写呢?因为Stub其实就是Binder的子类,所以在onBind()方法中可以直接返回Stub的实现。

 

接下来修改MainActivity中的代码,如下所示:

[java]  view plain  copy
 
  1. public class MainActivity extends Activity implements OnClickListener {  
  2.   
  3.     private Button startService;  
  4.   
  5.     private Button stopService;  
  6.   
  7.     private Button bindService;  
  8.   
  9.     private Button unbindService;  
  10.       
  11.     private MyAIDLService myAIDLService;  
  12.   
  13.     private ServiceConnection connection = new ServiceConnection() {  
  14.   
  15.         @Override  
  16.         public void onServiceDisconnected(ComponentName name) {  
  17.         }  
  18.   
  19.         @Override  
  20.         public void onServiceConnected(ComponentName name, IBinder service) {  
  21.             myAIDLService = MyAIDLService.Stub.asInterface(service);  
  22.             try {  
  23.                 int result = myAIDLService.plus(3, 5);  
  24.                 String upperStr = myAIDLService.toUpperCase("hello world");  
  25.                 Log.d("TAG", "result is " + result);  
  26.                 Log.d("TAG", "upperStr is " + upperStr);  
  27.             } catch (RemoteException e) {  
  28.                 e.printStackTrace();  
  29.             }  
  30.         }  
  31.     };  
  32.   
  33.     ......  
  34.   
  35. }  

我们只是修改了ServiceConnection中的代码。可以看到,这里首先使用了MyAIDLService.Stub.asInterface()方法将传入的IBinder对象传换成了MyAIDLService对象,接下来就可以调用在MyAIDLService.aidl文件中定义的所有接口了。这里我们先是调用了plus()方法,并传入了3和5作为参数,然后又调用了toUpperCase()方法,并传入hello world字符串作为参数,最后将调用方法的返回结果打印出来。

 

现在重新运行程序,并点击一下Bind Service按钮,可以看到打印日志如下所示:

 

 

由此可见,我们确实已经成功实现跨进程通信了,在一个进程中访问到了另外一个进程中的方法。

 

不过你也可以看出,目前的跨进程通信其实并没有什么实质上的作用,因为这只是在一个Activity里调用了同一个应用程序的Service里的方法。而跨进程通信的真正意义是为了让一个应用程序去访问另一个应用程序中的Service,以实现共享Service的功能。那么下面我们自然要学习一下,如何才能在其它的应用程序中调用到MyService里的方法。

 

在上一篇文章中我们已经知道,如果想要让Activity与Service之间建立关联,需要调用bindService()方法,并将Intent作为参数传递进去,在Intent里指定好要绑定的Service,示例代码如下:

[java]  view plain  copy
 
  1. Intent bindIntent = new Intent(this, MyService.class);  
  2. bindService(bindIntent, connection, BIND_AUTO_CREATE);  

这里在构建Intent的时候是使用MyService.class来指定要绑定哪一个Service的,但是在另一个应用程序中去绑定Service的时候并没有MyService这个类,这时就必须使用到隐式Intent了。现在修改AndroidManifest.xml中的代码,给MyService加上一个action,如下所示:

[html]  view plain  copy
 
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     package="com.example.servicetest"  
  4.     android:versionCode="1"  
  5.     android:versionName="1.0" >  
  6.   
  7.     ......  
  8.   
  9.     <service  
  10.         android:name="com.example.servicetest.MyService"  
  11.         android:process=":remote" >  
  12.         <intent-filter>  
  13.             <action android:name="com.example.servicetest.MyAIDLService"/>  
  14.         </intent-filter>  
  15.     </service>  
  16.   
  17. </manifest>  

 

这就说明,MyService可以响应带有com.example.servicetest.MyAIDLService这个action的Intent。

 

现在重新运行一下程序,这样就把远程Service端的工作全部完成了。

 

然后创建一个新的Android项目,起名为ClientTest,我们就尝试在这个程序中远程调用MyService中的方法。

 

ClientTest中的Activity如果想要和MyService建立关联其实也不难,首先需要将MyAIDLService.aidl文件从ServiceTest项目中拷贝过来,注意要将原有的包路径一起拷贝过来,完成后项目的结构如下图所示:

 

 

然后打开或新建activity_main.xml,在布局文件中也加入一个Bind Service按钮:

[html]  view plain  copy
 
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     android:layout_width="match_parent"  
  3.     android:layout_height="match_parent"  
  4.     android:orientation="vertical"  
  5.      >  
  6.   
  7.    <Button   
  8.        android:id="@+id/bind_service"  
  9.        android:layout_width="match_parent"  
  10.        android:layout_height="wrap_content"  
  11.        android:text="Bind Service"  
  12.        />  
  13.   
  14. </LinearLayout>  

接下来打开或新建MainActivity,在其中加入和MyService建立关联的代码,如下所示:

[java]  view plain  copy
 
  1. public class MainActivity extends Activity {  
  2.   
  3.     private MyAIDLService myAIDLService;  
  4.   
  5.     private ServiceConnection connection = new ServiceConnection() {  
  6.   
  7.         @Override  
  8.         public void onServiceDisconnected(ComponentName name) {  
  9.         }  
  10.   
  11.         @Override  
  12.         public void onServiceConnected(ComponentName name, IBinder service) {  
  13.             myAIDLService = MyAIDLService.Stub.asInterface(service);  
  14.             try {  
  15.                 int result = myAIDLService.plus(50, 50);  
  16.                 String upperStr = myAIDLService.toUpperCase("comes from ClientTest");  
  17.                 Log.d("TAG", "result is " + result);  
  18.                 Log.d("TAG", "upperStr is " + upperStr);  
  19.             } catch (RemoteException e) {  
  20.                 e.printStackTrace();  
  21.             }  
  22.         }  
  23.     };  
  24.   
  25.     @Override  
  26.     protected void onCreate(Bundle savedInstanceState) {  
  27.         super.onCreate(savedInstanceState);  
  28.         setContentView(R.layout.activity_main);  
  29.         Button bindService = (Button) findViewById(R.id.bind_service);  
  30.         bindService.setOnClickListener(new OnClickListener() {  
  31.             @Override  
  32.             public void onClick(View v) {  
  33.                 Intent intent = new Intent("com.example.servicetest.MyAIDLService");  
  34.                 bindService(intent, connection, BIND_AUTO_CREATE);  
  35.             }  
  36.         });  
  37.     }  
  38.   
  39. }  

这部分代码大家一定会非常眼熟吧?没错,这和在ServiceTest的MainActivity中的代码几乎是完全相同的,只是在让Activity和Service建立关联的时候我们使用了隐式Intent,将Intent的action指定成了com.example.servicetest.MyAIDLService。

 

在当前Activity和MyService建立关联之后,我们仍然是调用了plus()和toUpperCase()这两个方法,远程的MyService会对传入的参数进行处理并返回结果,然后将结果打印出来。

 

这样的话,ClientTest中的代码也就全部完成了,现在运行一下这个项目,然后点击Bind Service按钮,此时就会去和远程的MyService建立关联,观察LogCat中的打印信息如下所示:

 

 

不用我说,大家都已经看出,我们的跨进程通信功能已经完美实现了。

 

不过还有一点需要说明的是,由于这是在不同的进程之间传递数据,Android对这类数据的格式支持是非常有限的,基本上只能传递Java的基本数据类型、字符串、List或Map等。那么如果我想传递一个自定义的类该怎么办呢?这就必须要让这个类去实现Parcelable接口,并且要给这个类也定义一个同名的AIDL文件。这部分内容并不复杂,而且和Service关系不大,所以就不再详细进行讲解了,感兴趣的朋友可以自己去查阅一下相关的资料。

 


本文转自农夫山泉别墅博客园博客,原文链接:http://www.cnblogs.com/yaowen/p/6414261.html,如需转载请自行联系原作者

相关文章
|
21天前
|
Java 开发工具 Android开发
Android与iOS开发环境搭建全解析####
本文深入探讨了Android与iOS两大移动操作系统的开发环境搭建流程,旨在为初学者及有一定基础的开发者提供详尽指南。我们将从开发工具的选择、环境配置到第一个简单应用的创建,一步步引导读者步入移动应用开发的殿堂。无论你是Android Studio的新手还是Xcode的探索者,本文都将为你扫清开发道路上的障碍,助你快速上手并享受跨平台移动开发的乐趣。 ####
|
14天前
|
存储 Linux API
深入探索Android系统架构:从内核到应用层的全面解析
本文旨在为读者提供一份详尽的Android系统架构分析,从底层的Linux内核到顶层的应用程序框架。我们将探讨Android系统的模块化设计、各层之间的交互机制以及它们如何共同协作以支持丰富多样的应用生态。通过本篇文章,开发者和爱好者可以更深入理解Android平台的工作原理,从而优化开发流程和提升应用性能。
|
14天前
|
Java 调度 Android开发
安卓与iOS开发中的线程管理差异解析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自拥有独特的魅力。如同东西方文化的差异,它们在处理多线程任务时也展现出不同的哲学。本文将带你穿梭于这两个平台之间,比较它们在线程管理上的核心理念、实现方式及性能考量,助你成为跨平台的编程高手。
|
1月前
|
域名解析 缓存 网络协议
浏览器中输入URL返回页面过程(超级详细)、DNS域名解析服务,TCP三次握手、四次挥手
浏览器中输入URL返回页面过程(超级详细)、DNS域名解析服务,TCP三次握手、四次挥手
|
21天前
|
开发框架 Dart Android开发
安卓与iOS的跨平台开发:Flutter框架深度解析
在移动应用开发的海洋中,Flutter作为一艘灵活的帆船,正引领着开发者们驶向跨平台开发的新纪元。本文将揭开Flutter神秘的面纱,从其架构到核心特性,再到实际应用案例,我们将一同探索这个由谷歌打造的开源UI工具包如何让安卓与iOS应用开发变得更加高效而统一。你将看到,借助Flutter,打造精美、高性能的应用不再是难题,而是变成了一场创造性的旅程。
|
23天前
|
安全 Java Linux
深入解析Android系统架构及其对开发者的意义####
【10月更文挑战第21天】 本文旨在为读者揭开Android操作系统架构的神秘面纱,探讨其如何塑造现代移动应用开发格局。通过剖析Linux内核、硬件抽象层、运行时环境及应用程序框架等关键组件,揭示Android平台的强大功能与灵活性。文章强调了理解Android架构对于开发者优化应用性能、提升用户体验的重要性,并展望了未来技术趋势下Android的发展方向。 ####
36 0
|
Android开发
【Android 进程保活】应用进程拉活 ( 系统 Service 机制拉活 | Service 组件 onStartCommand 方法分析 | 源码资源 )(三)
【Android 进程保活】应用进程拉活 ( 系统 Service 机制拉活 | Service 组件 onStartCommand 方法分析 | 源码资源 )(三)
337 0
【Android 进程保活】应用进程拉活 ( 系统 Service 机制拉活 | Service 组件 onStartCommand 方法分析 | 源码资源 )(三)
|
Android开发
【Android 进程保活】应用进程拉活 ( 系统 Service 机制拉活 | Service 组件 onStartCommand 方法分析 | 源码资源 )(二)
【Android 进程保活】应用进程拉活 ( 系统 Service 机制拉活 | Service 组件 onStartCommand 方法分析 | 源码资源 )(二)
263 0
|
API Android开发
【Android 进程保活】应用进程拉活 ( 系统 Service 机制拉活 | Service 组件 onStartCommand 方法分析 | 源码资源 )(一)
【Android 进程保活】应用进程拉活 ( 系统 Service 机制拉活 | Service 组件 onStartCommand 方法分析 | 源码资源 )(一)
204 0

推荐镜像

更多