在Android学习之Service(上)中学习了Service的基本使用方式,接下来需要研究一下使用Service实现跨进程通信的功能。
要实现Service的跨进程通信就需要提到远程Service。远程Service和普通的Service注册方式是一样的,只不过是在注册时,设定android:process属性为:remote,这个“:remote”并不是固定这样写的可以随意写,例如,包名是com.servicedemo那么,不设定”:remote”的时候,默认的进程名称为com.servicedemo,当设置该属性后,就变成了com.servicedemo:remote。
除此之外,还需要添加一个action属性,记得在Android学习之Service(上) 中我的启动服务是这样写的final Intent intent = new Intent(this, StartService.class);
其中StartService就是我的自定义服务类,那么,如果是其他应用程序的话,其他程序中是不可能存在StartService这个类的,所以就要使用action属性,用Intent的隐式调用,具体代码如下:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.servicedemo">
<application>
.............
<service android:name=".service.StartService"
android:process=":remote" >
<intent-filter>
<action android:name="com.servicedemo.CalculateService"/>
intent-filter>
service>
application>
manifest>
解决了配置文件后,又看到一个新的东西,AIDL(Android接口定义语言),它实现了让Activity与一个远程Service建立关联,这里需要定一个.aidl文件由于我用的是Android Studio开发工具,作为新手着实费了一番周折,下面做一个记录。
首先,.aidl文件是单独放在aidl文件夹下,当新建一个.aidl的文件的时候就会自动生成这样一个文件夹,既然是自动的就直在app上新建一个AIDL就好了方法是,选中app->右键->New->AIDL->AIDL File 然后输入名字即可这里是ADLIService.aidl,如下图:
这里基本没什么问题,.aidl文件的内容如下:
// ADLIService.aidl
package com.servicedemo;
interface ADLIService {
int addCalculate(int a, int b);
int factorialCalculate(int num);
}
这就是一个接口,里边添加了两个需要子类去实现的方法,分别用来在服务启动的时候进行加法计算和阶乘运算。这一步完成后使用Eclipse就会直接生成一个与.aidl名字一样的.java文件,但是,使用Android Studio就是自动的,我起初以为是目录结构不一样,生成以后我又不知道,然后强行使用,发现没有这个类,次法行不通。百度后发现Android Studio需要Build->ReBuild Project一下就好了,但是,并不是直接可见的,需要改变一下目录结构,如下:
维护好这些后就可以在自定义的Service中使用这个接口了,这里还是用上一篇代码中的例子工程,代码如下:
package com.servicedemo.service;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import com.servicedemo.ADLIService;
public class StartService extends Service {
ADLIService.Stub mBinder = new ADLIService.Stub() {
@Override
public int addCalculate(int a, int b) throws RemoteException {
return a + b;
}
@Override
public int factorialCalculate(int num) throws RemoteException {
int result = 1;
do {
result *= num;
} while (num-- > 1);
return result;
}
};
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
将上个例子中没有用的代码去掉,留下有用的部分,首先是创建一个提供给obBind()返回的对象,这个对象就是刚刚定义的AIDL接口,这个对象是通过new ADLIService.Stub()得到的,而Stub是IBinder的子类。在创建对象的同时就可以像上边的代码中一样,实现那两个接口中的方法。
现在要实现的是跨进程通信,所以,首先是要有两个程序不是。这里先不急,先看看这个服务运行起来是什么样的,代码基本和之前的代码基本一样,就修改了ServiceConnection对象是重写的方法,如下所示:
package com.servicedemo.activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import com.servicedemo.ADLIService;
import com.servicedemo.R;
import com.servicedemo.service.StartService;
public class MainActivity extends AppCompatActivity {
private Button mStart, mStop, mBind, mUnBind;
// 改变一:自定义的AIDL接口对象
private ADLIService mADLIService;
private ServiceConnection connection = new ServiceConnection() {
/**
* 与Service断开连接后调用
*/
@Override
public void onServiceDisconnected(ComponentName name) {
}
/**
* 与Service连接后调用
*/
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 改变二:// 得到自定义的AIDL接口对象
mADLIService = ADLIService.Stub.asInterface(service);
try {
// 使用服务的方法
int result = mADLIService.addCalculate(10, 10);
Log.d("Service_remote", "add = " + result);
result = mADLIService.factorialCalculate(5);
Log.d("Service_remote", "factorial = " + result);
} catch (RemoteException e) {
e.printStackTrace();
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initUI();
}
private void initUI() {
mBind = (Button) findViewById(R.id.btn_bind_service);
final Intent intent = new Intent(this, StartService.class);
mBind.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
bindService(intent, connection, BIND_AUTO_CREATE);
}
});
}
}
与之前的代码比去掉了一些用不到的按钮事件只看绑定Service事件。当点击”绑定Service”的时候就会将计算结果传回来,计算过程就是在之前Service中实现的,效果如下:
知道了Service的正常使用和运行效果,再看看跨进程的效果,首先,在创建一个工程ServiceClient,然后,将先前工程中的aidl文件夹整个拷贝到ServiceClient工程下与java文件夹同级即可(可以直接到目录里复制和粘贴),如下所示:
然后,到ServiceClient工程,ReBuild一下。至此已完成多半,剩下的就是给界面添加按钮,然后,设置点击事件,代码如下:
package com.serviceclient;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import com.servicedemo.ADLIService;
public class MainActivity extends AppCompatActivity {
private Button mBind;
private ADLIService mADLIService;
// 与之前一样创建ServiceConnection对象,并称写两个监听方法
private ServiceConnection connection = new ServiceConnection() {
/**
* 与Service断开连接后调用
*/
@Override
public void onServiceDisconnected(ComponentName name) {
}
/**
* 与Service连接后调用
*/
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mADLIService = ADLIService.Stub.asInterface(service);
try {
int result = mADLIService.addCalculate(30, 10);
Log.d("Service_remote", "add = " + result);
result = mADLIService.factorialCalculate(10);
Log.d("Service_remote", "factorial = " + result);
} catch (RemoteException e) {
e.printStackTrace();
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initUI();
}
private void initUI() {
mBind = (Button) findViewById(R.id.btn_bind_service);
// 注意,要与启动的Service的action属性一致
final Intent intent = new Intent("com.servicedemo.CalculateService");
mBind.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
bindService(intent, connection, BIND_AUTO_CREATE);
}
});
}
}
与之前项目中MainActivity.java是一模一样的代码,所不同是Intent在创建的时候传递的是action属性的值,这种方式属于隐性调用,其他的代码没有区别。实验效果,就是通过点击新创建app的按钮,去启动之前项目中的服务,将参数拿到远端Service去计算,得到一个计算结果,然后打印。
注意final Intent intent = new Intent("com.servicedemo.CalculateService");
这种写法只是用于5.0以前,5.0以后就不能这样隐性调用了,要改成如下代码:
private void initUI() {
mBind = (Button) findViewById(R.id.btn_bind_service);
final Intent intent = new Intent();
// 还是设定action
intent.setAction("com.servicedemo.CalculateService");
// 这里要明确设定要启动的服务的包名(程序名)
intent.setPackage("com.servicedemo");
mBind.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
bindService(intent, connection, BIND_AUTO_CREATE);
}
});
}
这样就可以实现远程启动服务了,至此,Service的学习就结束了,最终效果如下:
上面的代码传递就是Java的基础类型,例如,传个整型做加减乘除什么的,远程服务所传递的参数基本也就是这些还有Map和List什么的,如果想传递一些自定义类型,这就必须要让这个类去实现Parcelable接口,并且要给这个类也定义一个同名的AIDL文件,我的理解就是将自动生成的那个.java文件改成手写的,这个类实现Parcelable接口接口以了,我大概创建了一下,还是挺复杂的(功力不足),回头用的时候再说吧!
以上是关于Service学习和理解!