`
836811384
  • 浏览: 542386 次
文章分类
社区版块
存档分类
最新评论

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

 
阅读更多

在上一篇文章中,我们学习了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 plaincopyprint?
  1. publicclass MyService extends Service {
  2. ......
  3. @Override
  4. publicvoid onCreate() {
  5. super.onCreate();
  6. Log.d(TAG, "onCreate() executed");
  7. try {
  8. Thread.sleep(60000);
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. }
  13. ......
  14. }
public class MyService extends Service {

	......

	@Override
	public void onCreate() {
		super.onCreate();
		Log.d(TAG, "onCreate() executed");
		try {
			Thread.sleep(60000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	......

}

 

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

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

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

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

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

    ......
    
	<service
	    android:name="com.example.servicetest.MyService"
	    android:process=":remote" >
	</service>

</manifest>

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

 

为什么将MyService转换成远程Service后就不会导致程序ANR了呢?这是由于,使用了远程Service后,MyService已经在另外一个进程当中运行了,所以只会阻塞该进程中的主线程,并不会影响到当前的应用程序。
为了证实一下MyService现在确实已经运行在另外一个进程当中了,我们分别在MainActivity的onCreate()方法和MyService的onCreate()方法里加入一行日志,打印出各自所在的进程id,如下所示:

[java] view plaincopyprint?
  1. Log.d("TAG", "process id is " + Process.myPid());
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 plaincopyprint?
  1. package com.example.servicetest;
  2. interface MyAIDLService {
  3. int plus(int a, int b);
  4. String removeSpace(String str);
  5. }
package com.example.servicetest;
interface MyAIDLService {
	int plus(int a, int b);
	String removeSpace(String str);
}

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

 

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

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

	......

	@Override
	public IBinder onBind(Intent intent) {
		return mBinder;
	}

	MyAIDLService.Stub mBinder = new Stub() {

		@Override
		public String toUpperCase(String str) throws RemoteException {
			if (str != null) {
				return str.toUpperCase();
			}
			return null;
		}

		@Override
		public int plus(int a, int b) throws RemoteException {
			return a + b;
		}
	};

}

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

 

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

[java] view plaincopyprint?
  1. publicclass MainActivity extends Activity implements OnClickListener {
  2. private Button startService;
  3. private Button stopService;
  4. private Button bindService;
  5. private Button unbindService;
  6. private MyAIDLService myAIDLService;
  7. private ServiceConnection connection = new ServiceConnection() {
  8. @Override
  9. publicvoid onServiceDisconnected(ComponentName name) {
  10. }
  11. @Override
  12. publicvoid onServiceConnected(ComponentName name, IBinder service) {
  13. myAIDLService = MyAIDLService.Stub.asInterface(service);
  14. try {
  15. int result = myAIDLService.plus(3, 5);
  16. String upperStr = myAIDLService.toUpperCase("hello world");
  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. }
public class MainActivity extends Activity implements OnClickListener {

	private Button startService;

	private Button stopService;

	private Button bindService;

	private Button unbindService;
	
	private MyAIDLService myAIDLService;

	private ServiceConnection connection = new ServiceConnection() {

		@Override
		public void onServiceDisconnected(ComponentName name) {
		}

		@Override
		public void onServiceConnected(ComponentName name, IBinder service) {
			myAIDLService = MyAIDLService.Stub.asInterface(service);
			try {
				int result = myAIDLService.plus(3, 5);
				String upperStr = myAIDLService.toUpperCase("hello world");
				Log.d("TAG", "result is " + result);
				Log.d("TAG", "upperStr is " + upperStr);
			} catch (RemoteException e) {
				e.printStackTrace();
			}
		}
	};

	......

}

我们只是修改了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 plaincopyprint?
  1. Intent bindIntent = new Intent(this, MyService.class);
  2. bindService(bindIntent, connection, BIND_AUTO_CREATE);
Intent bindIntent = new Intent(this, MyService.class);
bindService(bindIntent, connection, BIND_AUTO_CREATE);

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

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

    ......

    <service
        android:name="com.example.servicetest.MyService"
        android:process=":remote" >
        <intent-filter>
            <action android:name="com.example.servicetest.MyAIDLService"/>
        </intent-filter>
    </service>

</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 plaincopyprint?
  1. <LinearLayoutxmlns: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. <Button
  7. android:id="@+id/bind_service"
  8. android:layout_width="match_parent"
  9. android:layout_height="wrap_content"
  10. android:text="Bind Service"
  11. />
  12. </LinearLayout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
     >

   <Button 
       android:id="@+id/bind_service"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:text="Bind Service"
       />

</LinearLayout>

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

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

	private MyAIDLService myAIDLService;

	private ServiceConnection connection = new ServiceConnection() {

		@Override
		public void onServiceDisconnected(ComponentName name) {
		}

		@Override
		public void onServiceConnected(ComponentName name, IBinder service) {
			myAIDLService = MyAIDLService.Stub.asInterface(service);
			try {
				int result = myAIDLService.plus(50, 50);
				String upperStr = myAIDLService.toUpperCase("comes from ClientTest");
				Log.d("TAG", "result is " + result);
				Log.d("TAG", "upperStr is " + upperStr);
			} catch (RemoteException e) {
				e.printStackTrace();
			}
		}
	};

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		Button bindService = (Button) findViewById(R.id.bind_service);
		bindService.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				Intent intent = new Intent("com.example.servicetest.MyAIDLService");
				bindService(intent, connection, BIND_AUTO_CREATE);
			}
		});
	}

}

这部分代码大家一定会非常眼熟吧?没错,这和在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关系不大,所以就不再详细进行讲解了,感兴趣的朋友可以自己去查阅一下相关的资料。

好了,结合上下两篇,这就是关于Service你所需知道的一切。

2
0
分享到:
评论

相关推荐

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

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

    Android中Service完全解析(上)

    此源码对应的是我的博客“Android Service完全解析,关于服务你所需知道的一切(上)”,经过测试完美运行,配合着博客和源码,能够快速理解Android中Service组件的工作原理

    ServiceTest

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

    Service及Notificaition

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

    老罗android开发视频教程全集百度网盘下载

    它包括操作系统、用户界面和应用程序—— 移动电话工作所需的全部软件,而且不存在任何以往阻碍移动产业创新的专有权障碍。 Android以Java为编程语言,使接口到功能,都有层出不穷的变化,其中Activity等同于J2ME...

    新版Android开发教程.rar

    � GPS 卫星导航功能,手机照相, MP3 ,蓝芽等均被列为 Android 所提供支持的基本选项。 � Android 的平台基本上是免费的,虽然有部份原生链接库会要求费用,但大部份是免权利金; Android 的 程序可以采用 JAVA ...

    Google.Android开发入门与实战

     值得一提的是,书中的开发实例很有创意,将传统互联网的内容/服务与移动平台紧密结合起来,如书中的豆瓣网客户端、在线音乐播放器等应用,都体现了移动互联网应用所需的创新精神及良好的用户体验理念,这个设计...

    (完整)Android手机天气预报项目报告.doc

    解析Android 应用程序中获得的列表数据XML文件,对XML文件中的数据实现分离,得到指定的所需要 的信息,然后执行存入数据库或显示到页面等相应的处理。 Android的开发组Activity、Intent Receiver、Service、Content...

    双鱼林安卓Android代码生成器 v2.0.zip

    真正面向对象设计:系统的整体设计,提供通过使用面向对象的方法,设计所需系统中的基础对象(类),并根据专业级的“三层架构模板”生成专业级的界面和源代码,同时设计系统和数据库:采用“数据映射”建立基础对象...

    微服务架构和基于容器参考应用程序eShopOnContainers.zip

    在了解Docker容器和使用.NET Core开发微服务器之后,下一步(eShopOnContainers还没有涵盖),就是选择像Docker Swarm,Kubernetes或DC / OS(Azure容器服务)或Azure中的微服务集群/协调器在大多数情况下,服务结构...

    finalyearproject:天文取景器应用程序

    期末项目该存储库包含我大学四年级的最后一年的项目。 它是用Android开发的,是一个社交媒体项目。 它旨在允许用户通过设置距离参数... 然后,它将在代码中解析为astro音高所需的各种细节。 它将使用Google Directions

    微信公众平台应用开发:方法、技巧与案例.(机械工业.柳峰)

    他还是一位资深的Java软件开发工程师和Android/iOS移动应用开发工程师,活跃于CocoaChina、开源中国、CSDN等社区,CSDN博客专家,在CSDN博客撰写了系列微信公众平台二次开发的教程,深受欢迎并被广泛传播,也因此...

Global site tag (gtag.js) - Google Analytics