`
iaiai
  • 浏览: 2205073 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Android Service完全解析(下)

 
阅读更多
Service其实是运行在主线程里的,如果直接在Service中处理一些耗时的逻辑,就会导致程序ANR。

让我们来做个实验验证一下吧,修改上一篇文章中创建的ServiceTest项目,在MyService的onCreate()方法中让线程睡眠60秒,如下所示:
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就可以了,代码如下所示:
<?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,如下所示:
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文件,代码如下所示:
package com.example.servicetest;  
interface MyAIDLService {  
    int plus(int a, int b);  
    String removeSpace(String str);  
}

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

然后修改MyService中的代码,在里面实现我们刚刚定义好的MyAIDLService接口,如下所示:
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中的代码,如下所示:
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,示例代码如下:
Intent bindIntent = new Intent(this, MyService.class);  
bindService(bindIntent, connection, BIND_AUTO_CREATE);

这里在构建Intent的时候是使用MyService.class来指定要绑定哪一个Service的,但是在另一个应用程序中去绑定Service的时候并没有MyService这个类,这时就必须使用到隐式Intent了。现在修改AndroidManifest.xml中的代码,给MyService加上一个action,如下所示:
<?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按钮:
<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建立关联的代码,如下所示:
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关系不大,所以就不再详细进行讲解了,感兴趣的朋友可以自己去查阅一下相关的资料。
  • 大小: 37.2 KB
  • 大小: 23.7 KB
  • 大小: 33.5 KB
  • 大小: 13.3 KB
  • 大小: 44 KB
  • 大小: 14.8 KB
分享到:
评论

相关推荐

    Android Service深入解析Demo

    这篇博客"Android Service深入解析Demo"通过实例深入讲解了Service的相关知识,下面将详细阐述Service的核心概念、生命周期、启动与绑定方式以及如何在实践中应用。 1. **Service核心概念** Service是Android系统...

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

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

    Android中Service完全解析(上)

    在"Android中Service完全解析(上)"这个主题中,我们将重点讨论Service的基础知识。 Service的生命周期主要包括onCreate()、onStartCommand()、onBind()、onUnbind()、onDestroy()等方法。当Service创建时,会调用...

    android service 简单实例源代码

    本篇文章将深入解析"android service 简单实例源代码",帮助你理解如何在Android应用中创建和使用Service。 首先,我们来了解一下Service的基本概念。Service主要分为两种类型:标准Service和IntentService。标准...

    Android Service全面解析

    本篇文章将全面解析Android Service的相关知识点,包括Service的生命周期、启动与绑定方式、交互机制以及注意事项。 首先,Service的生命周期主要由四个方法构成:onCreate(), onStartCommand(), onBind(), 和...

    Android_JSON数据解析

    本篇将详细讲解如何在Android应用中解析JSON数据,主要涉及以下几个方面: 1. JSON基本结构 JSON由两种基本结构构成:对象(Object)和数组(Array)。对象是以花括号 `{}` 包围的键值对集合,键用双引号括起,如`...

    Android Framework精编内核解析

    《Android Framework精编内核解析》是一本深入探讨Android系统核心框架层的专著,它主要涵盖了Android系统架构的关键组成部分,旨在帮助读者理解和掌握Android应用开发背后的机制。在这个压缩包中,包含了一份名为...

    字节面试Android11.0最新Framework解析

    总的来说,《字节面试Android11.0最新Framework解析》这份资料将深入探讨这些关键组件在新版本中的变化和优化,帮助面试者和开发者了解如何在Android 11.0环境下更好地设计和优化应用程序,以适应最新的系统规范和...

    android xml多种解析生成

    Android XML 多种解析生成 Android 平台中,XML 文件解析和生成是非常重要的一部分。XML 文件可以存储和交换结构化数据,广泛应用于 Android 应用程序中。本文将介绍 Android 中使用多种解析和生成 XML 文件的方法...

    Android截屏以及解析截屏程序源代码

    为了在不root的情况下进行截图,我们可以利用Android的 AccessibilityService。这个服务允许应用程序监听和响应用户的交互事件,包括屏幕的变化。通过注册一个AccessibilityService,并监听TYPE_WINDOW_STATE_...

    Service完全解析示例代码

    本示例代码将全面解析Service的两种启动模式——`startService`和`bindService`,以及进程内如何与服务进行通信。 1. **startService启动模式** 当使用`startService()`方法启动Service时,Service会经历以下生命...

    android Service example

    本篇文章将深入探讨“Android Service”的使用,以实例解析其功能和重要性。 首先,Service并不创建一个新的UI(用户界面),而是为其他组件(如Activity)提供服务,通常用于执行长时间运行的操作,如播放音乐、...

    android notification完全解析Demo

    本文将深入解析Android Notification的工作原理、设计模式以及如何创建一个完整的Demo。 一、Notification概述 Notification是Android系统提供的一种通知用户的应用程序事件的方式,它可以在状态栏中显示图标、文字...

    Android停止Service的例程

    3. **Service的生命周期源码解析** Android系统的Service生命周期管理主要由ActivityManagerService(AMS)负责。当你调用startService()或bindService()时,AMS会通过Binder机制将请求传递到目标进程的...

    Android Studio解析JSON数组

    在这个场景中,我们将探讨如何在Android Studio中解析JSON数组,并将获取的数据展示在ListView上。这个过程对于任何初学者来说都是一个重要的学习点,因为它涉及到网络数据获取、JSON解析以及UI展示。 首先,我们...

    android Service中onStartCommand返回值.txt

    ### Android Service 中 onStartCommand 返回值解析 在 Android 开发中,`Service` 是一个非常重要的组件,它可以在后台运行长时间的操作而不会为用户提供任何界面。`Service` 的生命周期方法之一是 `...

    Android学习之Service开机启动

    ### Android学习之Service开机启动详解 #### 一、引言 在Android开发中,有时我们需要让应用程序中的某个服务(Service)在设备启动时自动运行,例如进行后台数据同步、监控某些变化等。为了实现这一需求,可以...

    Android代码-Android_Blog_Demos

    Android_Blog_Demos 存储CSDN博客的一些源码...Android IntentService完全解析 当Service遇到Handler 详细 Android 高清加载巨图方案 拒绝压缩图片 ViewDragHelper实战 自己打造Drawerlayout Android UI性能优化实

    android各组件详解- Service

    ### Android各组件详解——Service深度解析 在深入探讨Service组件之前,我们首先简要回顾一下Android框架中的其他关键组件,尤其是ContentProvider。ContentProvider作为数据访问层,它提供了跨应用共享数据的能力...

Global site tag (gtag.js) - Google Analytics