4.2.3 Service 结丹

2018-07-15  本文已影响0人  努力学习的安同学

标注:本文为个人学习使用,仅做自己学习参考使用,请勿转载和转发
2018-07-11: 初稿,感觉自己的Android的水平还是差了很多很多啊,参考博主coder-pig
2018-07-13: 脑壳痛,就让自己有理由休息了

0. 引言

1. Bander机制

1.1 IBander和Bander

嗯,总体上来说

1.2 Binder机制

流程解析

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

Binder的好处

2. AIDL使用解析

2.1 什么是AIDL
2.2 AIDL实现两个进程间通信

编写AIDL的一些注意事项:

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

AS中可以直接创建AIDL文件

一、服务端

  1. 创建AIDL文件
    IPerson.aidl
package com.jay.aidl;

interface IPerson {
    String queryPerson(int num);
}

IPerson.java

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: C:\\Code\\ASCode\\AIDLServer\\app\\src\\main\\aidl\\com\\jay\\aidl\\IPerson.aidl
 */
package com.jay.aidl;
public interface IPerson extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.jay.aidl.IPerson
{
private static final java.lang.String DESCRIPTOR = "com.jay.aidl.IPerson";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
 * Cast an IBinder object into an com.jay.aidl.IPerson interface,
 * generating a proxy if needed.
 */
public static com.jay.aidl.IPerson asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.jay.aidl.IPerson))) {
return ((com.jay.aidl.IPerson)iin);
}
return new com.jay.aidl.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
{
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;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.jay.aidl.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;
}
@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);
mRemote.transact(Stub.TRANSACTION_queryPerson, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_queryPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public java.lang.String queryPerson(int num) throws android.os.RemoteException;
}
  1. 自定义我们的Service类,完成以下操作
    1)继承Service类,同时也自定义了一个PersonQueryBinder类来继承IPerson.Stub类,就是实现了IPerson接口和IBander接口
    2)实例化自定义的Stub类,并重写Service的onBind方法,返回一个binder对象!

AIDLService.java

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import com.jay.aidl.IPerson.Stub;

/**
 * Created by Jay on 2015/8/18 0018.
 */
public class AIDLService extends Service {

    private IBinder binder = new PersonQueryBinder();
    private String[] names = {"B神","艹神","基神","J神","翔神"};

    private String query(int num)
    {
        if(num > 0 && num < 6){
            return names[num - 1];
        }
        return null;
    }

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

    private final class PersonQueryBinder extends Stub{
        @Override
        public String queryPerson(int num) throws RemoteException {
            return query(num);
        }
    }
}
  1. 在AndroidMainfest.xml中注册Service
<service android:name=".AIDLService">
       <intent-filter>
              <action android:name="android.intent.action.AIDLService" />
              <category android:name="android.intent.category.DEFAULT" />
       </intent-filter>
</service>

二、客户端

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private EditText edit_num;
    private Button btn_query;
    private TextView txt_name;
    private IPerson iPerson;
    private PersonConnection conn = new PersonConnection();


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bindViews();
        //绑定远程Service, android6.0之后采用显示通信
        Intent serviceIntent = new Intent();
        serviceIntent.setComponent(new ComponentName("com.project.ankie.aidlservicedemo", "com.project.ankie.aidlservicedemo.AIDLService"));

        bindService(service, conn, BIND_AUTO_CREATE);
        btn_query.setOnClickListener(this);
    }

    private void bindViews() {
        edit_num = (EditText) findViewById(R.id.edit_num);
        btn_query = (Button) findViewById(R.id.btn_query);
        txt_name = (TextView) findViewById(R.id.txt_name);
    }

    @Override
    public void onClick(View v) {
        String number = edit_num.getText().toString();
        int num = Integer.valueOf(number);
        try {
            txt_name.setText(iPerson.queryPerson(num));
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        edit_num.setText("");
    }

    private final class PersonConnection implements ServiceConnection {
        public void onServiceConnected(ComponentName name, IBinder service) {
            iPerson = IPerson.Stub.asInterface(service);
        }
        public void onServiceDisconnected(ComponentName name) {
            iPerson = null;
        }
    }
}

接下来先启动AIDLServivce,然后再启动AIDLClient,输入查询序号,即可获得对应姓名! 当然也可以直接启动AIDLClient,也会获得同样效果:
效果图如下:

2.4 传递复杂数据的AIDL通信

Parcelable接口简介:

  1. 首先,需要实现writeToParcel和readFromParcel方法写入方法将对象写入到包裹parcel中,而读取方法则从包裹中读取对象,请注意,写入属性顺序与读取顺序相同
  2. 接着,需要在该类中添加一个名为CREATOR的static final属性,改属性需要实现:android.os.Parcelable.Creator接口
  3. 接着,需要从写接口中的两个方法,createFromParcel(Parcel source)方法,实现source创建出JavaBean实例的功能 newArray(int size),创建一个类型为T,长度为size的数组,只有一个简单的return new T[size],这里的T是Person类
  4. 最后,discribeContents():嗯,这个直接返回0即可
  5. 另外,除了String和CharSequence以外,其余俊宇要一个方向指示符。方向指示符包括in、out和inout。in表示由客户端设置,out表示由服务端设置,inout表示客户端和服务端都设置了该值。

代码示例

  1. 创建Person.aidl和Salary.aidl的文件,因为他们需要实现Parcelable接口,所以就下面一条语句:
Person.aidl:     parcelable Person; 
Salary.aidl:     parcelable Salary; 
  1. 分别建立Person类与Salary类,需实现Parcelable接口,重写对应的方法!
    因为我们后面是根据Person对象来获取Map集合中的数据,所以Person.java中我们重写了hashcode和equals 的方法;而Salary类则不需要!
public class Person implements Parcelable{

    private Integer id;
    private String name;

    public Person() {}

    public Person(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }


    //实现Parcelable必须实现的方法,不知道拿来干嘛的,直接返回0就行了
    @Override
    public int describeContents() {
        return 0;
    }


    //写入数据到Parcel中的方法
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        //把对象所包含的数据写入到parcel中
        dest.writeInt(id);
        dest.writeString(name);
    }

    //必须提供一个名为CREATOR的static final属性 该属性需要实现
    //android.os.Parcelable.Creator<T>接口
    public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() {
        //从Parcel中读取数据,返回Person对象
        @Override
        public Person createFromParcel(Parcel source) {
            return new Person(source.readInt(),source.readString());
        }
        @Override
        public Person[] newArray(int size) {
            return new Person[size];
        }
    };

    //因为我们集合取出元素的时候是根据Person对象来取得,所以比较麻烦,
    //需要我们重写hashCode()和equals()方法
    @Override
    public int hashCode()
    {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }
    @Override
    public 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;
    }
}

Salary类

public class Salary implements Parcelable {

    private String type;
    private Integer salary;

    public Salary() {
    }

    public Salary(String type, Integer salary) {
        this.type = type;
        this.salary = salary;
    }

    public String getType() {
        return type;
    }

    public Integer getSalary() {
        return salary;
    }

    public void setType(String type) {
        this.type = type;
    }

    public void setSalary(Integer salary) {
        this.salary = salary;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(type);
        dest.writeInt(salary);
    }

    public static final Parcelable.Creator<Salary> CREATOR = new Parcelable.Creator<Salary>() {
        //从Parcel中读取数据,返回Person对象
        @Override
        public Salary createFromParcel(Parcel source) {
            return new Salary(source.readString(), source.readInt());
        }

        @Override
        public Salary[] newArray(int size) {
            return new Salary[size];
        }
    };

    public String toString() {
        return "工作:" + type + "    薪水: " + salary;
    }
}

3、创建一个ISalary.aidl的文件,在里面写一个简单的获取工资信息的方法:

package com.jay.example.aidl;
  
import com.jay.example.aidl.Salary;  
import com.jay.example.aidl.Person;  
interface ISalary  
{  
    //定义一个Person对象作为传入参数  
    //接口中定义方法时,需要制定新参的传递模式,这里是传入,所以前面有一个in  
    Salary getMsg(in Person owner);  
}  

ps:这里可以记得如果使用的是自定义的数据类型的话,需要import哦!!!切记!!!
Step 4:核心Service的编写: 定义一个SalaryBinder类继承Stub,从而实现ISalary和IBinder接口;定义一个存储信息的Map集合! 重新onBind方法,返回SalaryBinder类的对象实例!
AidlService.java

public class AidlService extends Service {  
  
    private SalaryBinder salaryBinder;  
    private static Map<Person,Salary> ss = new HashMap<Person, Salary>();  
    //初始化Map集合,这里在静态代码块中进行初始化,当然你可也以在构造方法中完成初始化  
    static  
    {  
        ss.put(new Person(1, "Jay"), new Salary("码农", 2000));  
        ss.put(new Person(2, "GEM"), new Salary("歌手", 20000));  
        ss.put(new Person(3, "XM"), new Salary("学生", 20));  
        ss.put(new Person(4, "MrWang"), new Salary("老师", 2000));  
    }  
      
      
    @Override  
    public void onCreate() {  
        super.onCreate();  
        salaryBinder = new SalaryBinder();  
    }  
      
    @Override  
    public IBinder onBind(Intent intent) {  
        return salaryBinder;  
    }  
  
      
    //同样是继承Stub,即同时实现ISalary接口和IBinder接口  
    public class SalaryBinder extends Stub  
    {  
        @Override  
        public Salary getMsg(Person owner) throws RemoteException {  
            return ss.get(owner);  
        }  
    }  
      
    @Override  
    public void onDestroy() {  
        System.out.println("服务结束!");  
        super.onDestroy();  
    }  
}  

注册下Service:

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

2、客户端编写

Step 1:把服务端的AIDL文件拷贝下,拷贝后目录如下:


Step 2:编写简单的布局,再接着就是核心MainActvitiy的实现了 定义一个ServciceConnection对象,重写对应方法,和前面的普通数据的类似 再接着在bindService,然后再Button的点击事件中获取Salary对象并显示出来!
MainActivity.java
public class MainActivity extends Activity {  
  
    private ISalary salaryService;  
    private Button btnquery;  
    private EditText editname;  
    private TextView textshow;  
    private ServiceConnection conn = new ServiceConnection() {  
          
        @Override  
        public void onServiceDisconnected(ComponentName name) {  
            salaryService = null;  
        }  
          
        @Override  
        public void onServiceConnected(ComponentName name, IBinder service) {  
            //返回的是代理对象,要调用这个方法哦!  
            salaryService = ISalary.Stub.asInterface(service);  
        }  
    };  
      
      
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
          
        btnquery = (Button) findViewById(R.id.btnquery);  
        editname = (EditText) findViewById(R.id.editname);  
        textshow = (TextView) findViewById(R.id.textshow);  
          
        Intent it = new Intent();  
        it.setAction("com.jay.aidl.AIDL_SERVICE");  
        bindService(it, conn, Service.BIND_AUTO_CREATE);  
          
        btnquery.setOnClickListener(new OnClickListener() {           
            @Override  
            public void onClick(View v) {  
                try  
                {  
                    String name = editname.getText().toString();  
                    Salary salary = salaryService.getMsg(new Person(1,name));  
                    textshow.setText(name + salary.toString());  
                }catch(RemoteException e){e.printStackTrace();}  
            }  
        });  
          
    }  
    @Override  
    protected void onDestroy() {  
        super.onDestroy();  
        this.unbindService(conn);  
    }  
} 

运行截图:

2.5 注意事项
  1. 经过测试,上面的例子可以写在一个Demo或者两个Demo里面,无论写在一个或者两个里面,其中AIDL的部分必须复制


    我是写在了两个demo中,测试了很久都没成功,最开始client的IPerson的AIDL的接口是存放在com.project.ankie.aidlclientdemo包下,而不是通过service端复制过来的,所以会出现错误,经查是因为不是在同一个包下所引发的问题!
  2. 参考了一下别的工程,主要是在主工程下的其他的library中添加的服务,然后通过和服务绑定,然后调用AIDL接口进行通信,此举主要是这个AIDL只有一个就可以了啊,因为可以将intent的连接绑定bindService和AIDL接口写在同一个library的包下,就不会产生上面我遇到的错误了,但是两个app之间的通信要么采用上面的复制连包和AIDL,要么采用其他的方式。

  3. Android在6.0之后必须用显示了,所以不能Intent然后再setAction了,需要直接指定包了,由于上面的例子是不同的app,所以采用的是setComponent方法

Intent serviceIntent = new Intent();
// CompontentName 的方法前面是服务的 包名、后面是class的名
serviceIntent.setComponent(new ComponentName("com.project.ankie.aidlservicedemo", "com.project.ankie.aidlservicedemo.AIDLService"));
final boolean b = bindService(serviceIntent, conn, BIND_AUTO_CREATE);
// 可以通过返回值来判断是否绑定成功,也可以在Service的onBind方法那边打log查看
Log.e(TAG, "onCreate: " +  " b: " + b );

3. 总结

  1. 总体思想就是Activity与Service之间的通信,通信依托AIDL的形式进行;
  2. 进行通信的时候,在启动Service的方式就应该是通过Activity绑定服务的形式了,而不是StartService的形式了,因为Service一旦启动之后,可以由其他的Activity来绑定;
  3. 绑定Service的主要方法为bindService(intent,conn, BIND_AUTO_CREATE)方法,其中intent现在只能是显示绑定了,conn是一个new ServiceConnection的回调,最后一个参数是自动绑定,直接写就完了;
  4. 在回调里由返回的IBand的类型的Service,然后通过ISalary.Stub.asInterface(service); 方法,将service转化为AIDL的接口的实例对象,这样就可以调用AIDL中的接口了
  5. ADIL模式只是支持现有的基本的数据模型,若复杂的数据模型需要序列化;
  6. 对于复杂类型模型的传输,也可以通过将复杂模型转化为Json串的类型,然后传输,在接收端将该类型转化为相同过得模型,即可完成序列化和反序列化的形式。
  7. 上文的例子没有验证,也只是对Service的不同情况的分析。还是为具体到Service的实现机制还待研究。

参考文献

  1. Android开发:什么是IBinder
上一篇 下一篇

猜你喜欢

热点阅读