荏苒追寻个人博客

做一个有追求的青年


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 日程表

Java 设计模式02——工厂方法模式

发表于 2018-06-05 | 分类于 Java , 设计模式

模式定义

定义一个创建对象的的接口,然后不同类型的对象提供该接口不同的实现,对象的具体实现交给接口的具体实现类去完成。

模式示例

下面以我们常见的文档处理为例,来说明工厂方法模式。

  1. 定义文档接口:文档支持打开操作和关闭操作。
  2. 创建文档:我们要创建两种类型文档,pdf格式文档和txt格式文档
  3. 定义一个工厂接口,提供一个创建文档的方法。
  4. 提供pdf文档和txt文档的工厂接口实现。
  5. 定义Client来检验模式的运行结果。
阅读全文 »

Android 控件——动画的深入分析

发表于 2018-05-10 | 分类于 Android , 控件

本文链接:https://rainmonth.github.io/posts/A180510.html

摘要

本文简单介绍了Android中的三种动画:View动画、帧动画和属性动画,对各种动画做了详细的说明,同时针对动画使用过程中可能出现的问题提出了相应的解决方案。

阅读全文 »

Android 系统架构——JVM、Dalvik和ART

发表于 2018-05-09 | 分类于 Android , 系统架构

https://rainmonth.github.io/posts/A180509.html

摘要

本文旨在介绍JVM、DALVIK和ART的概念,比较了JVM和Dalvik、Dalvik和ART,并记录了自己对这三者的理解。

阅读全文 »

Android 开源库分析——KLog源码分析

发表于 2018-05-08 | 分类于 Android , 开源库分析

本文链接:https://rainmonth.github.io/posts/A180508.html

Android KLog源码分析

[TOC]

一直使用这个库,但没有仔细研究,今天就来研究一下,该库的地址:

KLog,在这里先感谢下作者,棒棒哒!

阅读全文 »

Android 四大组件——Activity与Fragment的通信方式

发表于 2018-05-07 | 分类于 Android , 四大组件

https://rainmonth.github.io/posts/A180507.html

Activity与Fragment的通信方式

  • Fragment访问Activity的内容
  • Activity访问Fragment的内容
  • Fragment与Activity共享事件

Fragment访问Activity的内容

​ Fragment通过getActivity()这种方式获取Activity实例后,可以访问Activity中定义的public成员。

阅读全文 »

Android Activity启动模式详解

发表于 2018-05-05 | 分类于 Android , 四大组件

https://rainmonth.github.io/posts/A180505.html

Android Activity启动模式详解

四种启动模式分别是

  • standard
  • signleTop
  • singleTask
  • singleInstance
阅读全文 »

Android 控件——View的事件体系

发表于 2018-05-04 | 分类于 Android , 控件

https://rainmonth.github.io/posts/A180504.html

摘要

本文内容有点多,主要介绍View的基础知识、View的事件分发机制、从源码分析View的事件分发机制以及滑动冲出的处理这几方面的知识。

阅读全文 »

Android 适配——状态栏攻略

发表于 2018-05-03 | 分类于 Android , 适配

https://rainmonth.github.io/posts/A180503.html

状态栏攻略

[TOC]

本文目标

  1. MaterialDesign 状态栏
  2. 沉浸式效果(视觉上就是toolbar和status bar颜色相同)
  3. 全屏(带状态栏的全屏)

基本概念

  1. colorPrimary,一般指ActionBar的颜色
  2. textColorPrimary,一般指ActionBar上标题文字的颜色
  3. colorPrimaryDark,一般指状态栏的颜色
  4. colorAccent,一般指强调色,如EditText获取到焦点,RadioButton、CheckBox等的选中状态
  5. windowBackground,视图背景颜色
阅读全文 »

Android 系统功能——震动相关实现

发表于 2018-05-01 | 分类于 Android , 系统功能

https://rainmonth.github.io/posts/A180501.html

Android 震动相关实现

[TOC]

Android中的震动可以通过多种方法实现,其中包括使用Vibrate类和HapticFeedback类,本文主要介绍通过这两个类来实现震动的方式。

Vibrator

震动属于系统服务,获取Vibrator示例的方式如下:

1
mVibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
阅读全文 »

Android 系统源码分析——IPC机制

发表于 2018-04-27 | 分类于 Android , 系统源码分析

https://rainmonth.github.io/posts/A180427.html

摘要

IPC,即Inter-Process Communication,进程间通信(也叫跨进程通信),可以是两个应用(每个应用是一个进程),亦可以是一个应用中的两个进程。由于不同的进程有不同的内存地址分配,所以多进程在通信的时候会发生一些问题,本文从介绍多进程通信可能引发的问题开始,慢慢介绍IPC的基本概念、具体的IPC方式及其具体实现,并在最后对各种通信方式做了简单的比较。

Android中开启多进程的方式

  • 在AndroidManifest.xml文件中给四大组件指定android:process属性,这是最基本的方法,也是必须掌握的方法;
  • 通过JNI在Native层fork一个新进程,不是常用的多进程方式

方法一使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="rainmonth.cn.ipcdemo">

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".SecondActivity"
android:configChanges="screenLayout"
android:label="@string/app_name"
android:process=":remote" />
<activity
android:name=".ThirdActivity"
android:configChanges="screenLayout"
android:label="@string/app_name"
android:process="rainmonth.cn.ipcdemo.remote" />
</application>

</manifest>

然后在MainActivity中分别开启SecondActivity和ThirdActivity,这时候在Android Studio的logcat中即可看到三个如rainmonth.cn.ipcdemo有关的进程,分别是:

  • rainmonth.cn.ipcdemo,应用主进程
  • rainmonth.cn.ipcdemo:process,SecondActivity开启的进程
  • rainmonth.cn.ipcdemo.process,ThirdActivity开启的进程

上面两种开启多进程方式的区别:

  • :process表示在包名后添加:process构成完整的进程名,表示该进程是当前应用的私有进程,其他应用组件不能跑在这个进程中
  • ThirdActivity开启的进程属于全局进程,其他应用可以通过ShareUID的方式和它跑在同一个进程中(前提是这两个应用具有相同的ShareUID

补充说明:

  • 如果process(即主进程的私有进程)被杀死了,对主进程的存货没有影响(这里的没影响指的是不会被干掉)
  • 如果主进程被杀死了,对主进程的私有进程process也没有影响(这里的没影响指的是不会被干掉)
  • 如果需要在主进程被干掉的同时也干掉其私有进程,可以通过遍历当前应用存在的进程,然后手动kill,注意,主进程必须最后再kill

Android多进程的运行机制

多进程会引发什么问题

先给出结论,稍后论证:

  1. 静态成员和单例模式完全失效;
  2. 线程同步机制完全失效;
  3. SharePreferences可靠性下降;
  4. Application会多次创建。

为什么会出现上面的问题,Android为每一个应用(进程)都分配了一个独立的虚拟机,不同虚拟机在内存上对应不同的地址空间,这就导致不同虚拟机在访问同一个类的对象是会产生多个副本,然后问题就发生了。

  • 1发生的原因是因为二者修改的是不同的内存地址上的对象
  • 2发生的原因同1,都不是同一块内存了,锁无意义了
  • 3发生的原因跟SharedPreference的特点有个,SharePreference不允许两个进程同时执行写操作
  • 当一个Activity跑在一个新的进程中的时候,系统在创建进程的同时要分配独立的虚拟机,相当于启动一个应用,所以Application会多次创建。

多进程通信方案

上面的问题发生的原因,归根结底都是因为不同的进程间不能共享内存,多以我们可以绕过这个问题,通过其他方式来共享数据,比如:

  1. Intent传值(Bundle)
  2. 共享文件和SharedPreference
  3. 基于Binder的Messenger和AIDL
  4. 使用ContentProvider
  5. Socket

在了解这些方式之前,先了解一些基本概念,有助于更好的理解进程间的通信方式

IPC基本概念

利用Intent传值或利用共享文件传值都涉及到对象的序列化问题,而Android中可用的序列号方式有如下两种:

Serializable接口和Parcelable接口

Serializable接口

那为什么现在实现Serializable接口不用指定serialVersionUID了呢?

serialVersionUID是用来辅助序列化和反序列化过程的,序列化的时候系统会把当前类的serialVersionUID写入序列化的文件中,当反序列化的时候系统会检测当前文件中的serialVersionUID是否和当前类的serialVersionUID一致,如果一致这个时候可以反序列化成功,否则就说明当前类和序列化的类相比发生了某些变换,比如成员变量的数量、类型可能发生了改变,这个时候是无法正常反序列化的

Java提供的空接口,用来持久化对象,实现时最好指定serialVersionUID,有助于反序列化,如果没有指定,系统会根据当前类的结构自动生成它的hash值,并将其赋值给serialVersionUID。下面给出示例:

序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void serializableObject(User user) {
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
try {
File appFilePath = new File(Const.SD_PATH + File.separator + getPackageName());
if (!appFilePath.exists())
appFilePath.mkdirs();
File file = new File(appFilePath, "cache.txt");
if (!file.exists())
file.createNewFile();
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream(file));
out.writeObject(user);
} catch (IOException e) {
e.printStackTrace();
}
}
}

反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void deserializableObject(String fileName) {
try {
File filePath = new File(Const.SD_PATH + File.separator + getPackageName());
if (!filePath.exists())
filePath.mkdir();
ObjectInputStream in = new ObjectInputStream(new FileInputStream(new File(filePath, fileName)));
User user = (User) in.readObject();
Log.d("user", user.toString());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}

要注意三点:

  • 手动指定serialVersionUID可以很大程度上避免反序列化失败

  • 类的静态变量不参与序列化过程(为什么,因为它并不被该类的某一个对象所特有)

  • transient修饰的变量不参与序列过程

Parcelable接口

继承给接口,实现CREATOR完成反序列化过程,实现writeToParcel完成序列化过程,具体代码实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public class User2 implements Parcelable {

private int userId;
private String username;
private boolean isMale;

public User2(int userId, String username, boolean isMale) {
this.userId = userId;
this.username = username;
this.isMale = isMale;
}

public int getUserId() {
return userId;
}

public void setUserId(int userId) {
this.userId = userId;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public boolean isMale() {
return isMale;
}

public void setMale(boolean male) {
isMale = male;
}

private User2(Parcel in) {
userId = in.readInt();
username = in.readString();
isMale = in.readInt() == 1;
}

public static final Creator<User2> CREATOR = new Creator<User2>() {
@Override
public User2 createFromParcel(Parcel in) {
return new User2(in);
}

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

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

@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(userId);
dest.writeString(username);
dest.writeInt(isMale ? 1 : 0);
}
}

注意:Parcel的writeBoolean方法已经不可见了

两种方式比较:

  • Serializable:Java提供,使用简单,但开销大(比较多的IO操作);
  • Parcelable:Android提供,使用稍微满分,但效率高

建议:如果是内存中的序列化操作,建议使用Parcelable,如果是要序列化存储,建议采用Serializable

另一个概念——Binder

如果说序列化是前面两种通信方式的基础,那么Binder就是第三种(AIDL和Messenger)的实现基础。

在Android Studio中使用AIDL

把这个单独拎出来是因为我在使用的时候出现了问题。步骤:

  1. 在app module下新建AIDL文件(Android Studio提供了对应模板)这时main目录下会有一个aidl文件夹,同时里面会有对应的aidl文件,如下图

    三个文件的内容分别如下:

    Book.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    package rainmonth.cn.ipcdemo;

    import android.os.Parcel;
    import android.os.Parcelable;

    /**
    * Created by RandyZhang on 2018/4/27.
    */

    public class Book implements Parcelable {
    private int bookId;
    private String bookName;

    protected Book(Parcel in) {
    bookId = in.readInt();
    bookName = in.readString();
    }

    public Book(int bookId, String bookName) {
    this.bookId = bookId;
    this.bookName = bookName;
    }

    public int getBookId() {
    return bookId;
    }

    public void setBookId(int bookId) {
    this.bookId = bookId;
    }

    public String getBookName() {
    return bookName;
    }

    public void setBookName(String bookName) {
    this.bookName = bookName;
    }

    public static final Creator<Book> CREATOR = new Creator<Book>() {
    @Override
    public Book createFromParcel(Parcel in) {
    return new Book(in);
    }

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

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

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

    Book.aidl

    1
    2
    3
    4
    5
    6
    // Book.aidl
    package rainmonth.cn.ipcdemo;

    // Declare any non-default types here with import statements

    parcelable Book;

    IBookManager.aidl

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // IBookManager.aidl
    package rainmonth.cn.ipcdemo;

    import rainmonth.cn.ipcdemo.Book;
    // Declare any non-default types here with import statements

    interface IBookManager {
    List<Book> getBookList();

    void addBook(in Book book);
    }
  2. 运行项目,在app/build/generated下会生产对应的Java文件,如下图:

其内容如下(格式化后的内容):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: /Users/RandyZhang/AndroidStudioProjects/RandyDemos/IpcDemo/app/src/main/aidl/rainmonth/cn/ipcdemo/IBookManager.aidl
*/
package rainmonth.cn.ipcdemo;
// Declare any non-default types here with import statements

public interface IBookManager extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements rainmonth.cn.ipcdemo.IBookManager {
private static final java.lang.String DESCRIPTOR = "rainmonth.cn.ipcdemo.IBookManager";

/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}

/**
* Cast an IBinder object into an rainmonth.cn.ipcdemo.IBookManager interface,
* generating a proxy if needed.
*/
public static rainmonth.cn.ipcdemo.IBookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof rainmonth.cn.ipcdemo.IBookManager))) {
return ((rainmonth.cn.ipcdemo.IBookManager) iin);
}
return new rainmonth.cn.ipcdemo.IBookManager.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_getBookList: {
data.enforceInterface(DESCRIPTOR);
java.util.List<rainmonth.cn.ipcdemo.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
rainmonth.cn.ipcdemo.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = rainmonth.cn.ipcdemo.Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}

private static class Proxy implements rainmonth.cn.ipcdemo.IBookManager {
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.util.List<rainmonth.cn.ipcdemo.Book> getBookList() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<rainmonth.cn.ipcdemo.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(rainmonth.cn.ipcdemo.Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}

@Override
public void addBook(rainmonth.cn.ipcdemo.Book book) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((book != null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
}

static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}

public java.util.List<rainmonth.cn.ipcdemo.Book> getBookList() throws android.os.RemoteException;

public void addBook(rainmonth.cn.ipcdemo.Book book) throws android.os.RemoteException;
}

ipc之自动生成的java文件结构

类的结构如上图所示,结合上面的代码和截图做以下说明:

  • 首先IBookManager继承了IInterface,并且自己还是个接口(注意:所有通过Binder传递的接口都必须继承IInterface接口)
  • 定义了一个内部类Stub(继承自Binder并实现了我们AIDL定义的接口方法),内部类结构:
    • Proxy,代理内部类;
    • asInterface,将Binder(来自服务端的对象)转换成客户端的AIDL接口类型的对象;
    • asBinder,返回当前对象;
    • onTransact,运行在服务端的Binder线程池中,当客户端(别的进程)发起请求时,该请求在经过封装后会交由此方法处理;
    • DESCRIPTOR,Binder的唯一标识符;
    • TRANSACTION_getBookList,方法标识符;
    • TRANSACTION_addBook,方法标识符;
  • 我们在AIDL文件中声明的方法getBookList和addBook

其实AIDL(Android Interface Define Language)文件只是一套接口定义模板,我们完全可以自己实现AIDL文件为我们生成的IBookManager.java文件,依样画葫芦,要注意一下几点:

  • 声明一个AIDL性质的接口
  • 实现Stub类和Stub类中Proxy代理类

另外两个重要方法

Binder是运行在服务端的,那么当服务端由于某种原因,Binder调用失败,此时如果不能通知到客户端,那么客户端就一直处于挂起状态,从而受到影响,为此,Binder提供了linkToDeath和unlinkToDeath方法来解决问题。

IBinder接口有一个内部接口DeathRecipient,该接口只有一个方法就是binderDied方法,当binder死亡的时候,系统就会回调这个方法,然后我们就可以移除之前的绑定,做些其他工作了(如重新绑定,重新发起远程调用等)

具体的IPC方式

使用Bundle

四大组件中的三大组件(Activity,Service,BroadcastReceiver)都支持在Intent中传递Bundle数据,而Bundle实现了Parcelable接口,可以很好的在不同的进程中传输。只要我们的数据是可以放入Bundle的,都可以传递(基本类型、实现Serializable接口的类对象、实现Parcelable接口的类对象以及一些其他对象。

这种通信方式最常见也最多,同时也是最简单的。

使用文件或SharedPreference

共享文件也可以完成进程通信,两个进程通过读写同一个文件来交换数据。通过序列化和反序列化我们可以很方便的将对象数据写到文件中,也可以很好的从文件中读取到对象数据。我们需要控制解决的问题应该是如何合理的安排两个进程或多个进程怎么来读写这个共享文件。

根据上面的描述,共享文件的适用于对数据同步要求不高的进程之间通信的场景,并且我们还要处理好并发读写问题。

虽然SharedPreference也是一种xml形式的文件,但是不推荐用它做共享文件来完成进程间通信。为什么呢?

SharedPreference是Android提供的轻量级数据存储方案,具有一定的缓存策略,它在内存中会有一份SharedPreference文件的缓存。涉及到内存,那么多进程(每个进程的内存空间都不一样)模式下就不可靠了,容易丢失数据,所以不建议使用SharedPreference文件来做共享文件来完成进程间通信。

使用Messenger

底层实现是AIDL,类本身实现了Parcelable接口,通过它可以实现进程间通信(通过Message来传递数据信息),一般步骤:

  • 服务端进程

    创建一个Service来处理客户端的链接请求,同时创建一个Handler,利用这个Handler构造一个Messenger对象,然后在Service的onBind的方法中返回这个Messenger对象底层的Binder即可。

  • 客户端进程

    首先绑定服务端的Service,绑定成功后会得到服务端返回的Binder,利用这个Binder构造一个Messenger,利用这个Messenger就可以像Service发送消息了,发送的消息为Message对象。如果需要响应服务端返回的信息,首先客户端要指定装载服务端返回信息的容器Messenger(这个Messenger应该包含一个处理消息的Handler),这样服务端在接收到消息后就可以拿到这个客户端定义的容器,然后利用这个容器发送服务端要发送给客户端的消息,发送后,客户端定义好的消息处理Handler就可以处理服务端返回的消息了

使用AIDL

AIDL支持的数据类型

  1. 基本数据类型(int、long、char、boolean、double等)
  2. String和CharSequence
  3. List,只支持ArrayList,且里面的对象的必须也都是AIDL支持的
  4. Map,只支持HashMap,且里面的对象必须也都是AIDL支持的(包括Key和Value的类型)
  5. 实现Parcelable接口的类对象(必须显示采用import关键字引用,必须创建一个和实现Parcelable对象的同名的AIDL文件)
  6. AIDL

注意:

  • 非基本类型数据参数都必须标明参数方向(in、out或inout);
  • AIDL内不能声明常量(注意与普通接口区别);

使用方法

服务端
  • 创建一个Service用来处理客户端的链接请求;
  • 创建一个AIDL文件,并编写好要暴露给客户端的接口;
  • 在创建的Service中来实现AIDL中的接口
客户端
  • 绑定服务端的Service;
  • 将服务端返回的Binder转换成AIDL中定义的类型对象;
  • 利用转换后的对象调用暴露的方法

使用注意的问题

首先要明确以下几点:

  • 客户端调用的服务端方法,运行在服务端的Binder线程池中,同时客户端线程会被挂起;
  • 服务端调用的客户端回调方法,运行在客户端Binder线程池中,同时服务端会被挂起;

根据以上几点,我们在使用AIDL时要注意:

  1. 客户端线程被挂起时,如果这个线程是UI线程,调用服务端的方法如果是耗时操作,就会ANR;

  2. 服务端线程被挂起时,如果服务端线程是UI线程,调用了客户端的回调方法是耗时操作,就会ANR;

  3. 服务端线程被挂起时,调用客户端回调方法运行在客户端Binder线程池中,如果回调里面有UI处理,请采用Handler将其切换到UI线程中;

  4. 删除跨进程listener时要采用RemoteCallbackList,因为采用跨进程通信的基础是Binder,而Binder依靠的时序列化和反序列化过程中,这写对象虽然值相等,但却不是同一个对象,简单的使用List的remove方法是无法解除listener的注册的。

  5. 重连服务的方法:

    • 设置DeathRecipient,在DeathRecipient的binderDied方法中重连;
    • 在onServiceDisconnected方法中重连;

    二者不同之处在于,前者在客户端的Binder线程池中被调用,后者在客户端的UI线程中被调用。

使用AIDL时的权限验证

  • 在onBind中验证
  • 在服务端的onTransact方法中验证

二者都是在验证不通过的时候,返回一个空Binder对象

使用ContentProvider

ContentProvider设计的目的就是用于应用之间共享数据,因而它本身就是为进程间通信而生的。由于系统做了比较好的封装,我们只要熟悉ContentProvider的使用即可。使用ContentProvider要注意的问题:

  • ContentProvider的定义相关的元素
    • authority的定义
    • 读写权限的定义
    • Uri的拼写规则("content://"+ authority + path),不同的path对应的是不同的表
    • 匹配规则的UriMatcher(自定义的ContentProvider必须定义匹配规则,即不同的path对应不同的path_code,这样可以很好的根据Uri判断是查询那张表)
  • 结合DatabaseOpenHelper使用
  • 多个database对像CRUD操作同步的实现

使用Socket

使用一般步骤:

  • 服务端
    • 定义一个Service,在其创建时,在单独线程中建立TCP服务ServerSocket serverSocket = new ServerSocket(8688)
    • 建立好连接之后,还是在建立TCP服务的线程中,编写处理客户端请求的代码,利用Socket client = setverSocket.accept()得到客户端请求信息
  • 客户端
    • 客户端与服务端(Service)建立服务后,在单独线程中连接服务端Socket = new Socket("localhost", 8688),并采用一定的超时重连机制
    • 连接成功后就可以与服务端通信了,通信中设计到UI操作是要通过Handler切换到主线程处理

Binder连接池

Binder连接池的作用

避免针对每个AIDL文件新建一个与之对应的Service(Service是四大组件之一,是系统的资源),而是自定义每个AIDL中对应接口方法的实现(继承相应的Stub类)通过BinderPool的queryBinder方法根据相应的标识符来查询对应的Binder。

BinderPool主要做以下工作:

  • 链接远程服务
  • 提供queryBinder方法
  • 提供服务重连解决方案

链接远程服务核心代码

1
2
3
4
5
6
7
8
9
10
private synchronized void connectBinderPoolService() {
mConnectBinderPoolCountDownLatch = new CountDownLatch(1);
Intent intent = new Intent(mContext, BinderPoolService.class);
mContext.bindService(intent, mBinderPoolConnection, Context.BIND_AUTO_CREATE);
try {
mConnectBinderPoolCountDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

注意采用CountDownLatch可以将并行服务串行化。

queryBinder的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public IBinder queryBinder(int binderCode) throws RemoteException {
IBinder binder = null;
switch (binderCode) {
case BINDER_SECURITY_CENTER: {
binder = new SecurityCenterImpl();
break;
}
case BINDER_COMPUTE: {
binder = new ComputeImpl();
break;
}
default:
break;
}
return binder;
}

具体实现实在继承IBinderPool的BinderPoolImpl中,根据不同的binderCode,返回不同的Binder对象。

服务重连解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private ServiceConnection mBinderPoolConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBinderPool = IBinderPool.Stub.asInterface(service);
try {
mBinderPool.asBinder().linkToDeath(mBinderPoolDeathRecipient, 0);
} catch (RemoteException e) {
e.printStackTrace();
}
mConnectBinderPoolCountDownLatch.countDown();
}

@Override
public void onServiceDisconnected(ComponentName name) {
// do nothing now
}
};

private IBinder.DeathRecipient mBinderPoolDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
Log.w(Tag, "binder died.");
mBinderPool.asBinder().unlinkToDeath(mBinderPoolDeathRecipient, 0);
mBinderPool = null;
connectBinderPoolService();
}
};

就是利用前面提到的linkToDeath和unlinkToDeath方法,通过Binder的死亡代理来实现。

各种IPC方式对比

名称 优点 缺点 适用场景
Bundle 简单易用 只能传输Bundle支持的类型 四大组件间的进程通信
文件共享 简单易用 处理高并发能力弱,不及时 无并发情形,数据交换实时性要求不高
AIDL 功能强大,支持一对多并发通信,支持实时通信 使用复杂,要处理好线程同步 一对多通信且要求RPC(远程过程调用)
Messenger 功能一般,支持一对多串行通信,支持实时通信 不能处理高并发,由于是通过Message传递数据的,所以只支持传递Bundle支持的数据类型,不支持RPC 低并发的一对多即时通信,无RPC需求
ContentProvider 支持一对多的并发数据共享 受约束的AIDL,只支持数据的CRUD操作 一对多进程间数据共享
Socket 功能强大,可以通过网络传输字节流,支持一对多并发实时通信 实现略显麻烦,不支持直接的RPC 网络数据交换
<i class="fa fa-angle-left" aria-label="上一页"></i>1…181920…22<i class="fa fa-angle-right" aria-label="下一页"></i>

216 日志
43 分类
43 标签
GitHub
© 2025 Randy Zhang
由 Hexo 强力驱动
|
主题 — NexT.Gemini v6.1.0