Atopom博客

分享快乐


  • 首页

  • 分类

  • 归档

  • 标签

DesignPattern-Adapter

发表于 2016-01-01 | 分类于 DesignPattern

适配器模式 Adapter

适配器模式种类

  1. 对象适配器(组合)
  2. 类适配器(多重继承)

定义

将一个类的接口,转换成客户期望的另一个接口。
适配器让原本接口不兼容的类可以合作无间。

目的

在不改变原有设计的基础上(或者说在无法改变原有设计的时候),为适应新的需求而作出的一种方案。

应用场景

Android中的ListView、RecyclerView的Adapter,通过相同的Adapter接口方法,使用不同的Adapter算法,展现出不同的布局效果。

例子

  1. 插座和插头
  2. Java中的枚举器
    1. 旧APIEnumeration,
      hasMoreElements()、nextElement();
    2. 新APIIterator,
      hasNext()、next()、remove();
    3. 设计适配器兼容新旧API(继承Interator,组合Enumeration)
      EnumerationInterator,
      hasNext()、next()、remove();
    4. 类图
      设计适配器兼容新旧API类图

优点

可复用,系统需要使用现有的类,而此类的接口不符合系统的需要。那么通过适配器模式就可以让这些功能得到更好的复用。
可扩展,在实现适配器功能的时候,可以调用自己开发的功能,从而自然地扩展系统的功能。

缺点

过多的使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是A接口,其实内部被适配成了B接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。

注意

适配器的工作量,和目标接口的大小成正比。

类图

适配器模式类图

代码示例

目标接口

1
2
3
4
5
6
7
8
9
10
11
package com.designpattern.adapter;
/**
* 目标接口
* @author atopom
*
*/
public interface Duck {
void quack();
void fly();
}

目标接口实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.designpattern.adapter;
/**
* 目标接口实现类
* @author atopom
*
*/
public class MallardDuck implements Duck {
@Override
public void quack() {
System.out.println("Quack");
}
@Override
public void fly() {
System.out.println("I'm flying");
}
}

被适配者Adaptee

1
2
3
4
5
6
7
8
9
/**
* 被适配者Adaptee:Turkey
* @author atopom
*
*/
public interface Turkey {
void gobble();
void fly();
}

Adaptee实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.designpattern.adapter;
/**
* 被适配者实现类
* @author atopom
*
*/
public class WildTurkey implements Turkey {
@Override
public void gobble() {
System.out.println("Gobble gobble");
}
@Override
public void fly() {
System.out.println("I'm flying a short distance");
}
}

适配者Adapter

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
package com.designpattern.adapter;
/**
* 适配器Adapter
* @author atopom
*
*/
public class TurkeyAdapter implements Duck {
Turkey turkey;
public TurkeyAdapter(Turkey turkey) {
this.turkey = turkey;
}
@Override
public void quack() {
turkey.gobble();
}
@Override
public void fly() {
for(int i = 0; i < 5; i++) {
turkey.fly();
}
}
}

测试类

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
package com.designpattern;
import com.designpattern.adapter.*;
/*
* 适配器模式
*
* 将一个类的接口,转换成客户期望的另一个接口。
* 适配器让原本接口不兼容的类可以合作无间。
*
* 适配器的工作量,和目标接口的大小成正比。
*
* 目的:
* 在不改变原有设计的基础上(或者说,在无法改变原有设计的时候),
* 为适应新的需求而做出的一个方案。
*/
/**
* 适配器模式测试类
* @author atopom
*
*/
public class AdapterTestDrive {
public static void main(String[] args) {
MallardDuck duck = new MallardDuck();
WildTurkey turkey = new WildTurkey();
Duck turkeyAdapter = new TurkeyAdapter(turkey);
System.out.println("The Turkey says...");
turkey.gobble();
turkey.fly();
System.out.println("\nThe Duck says...");
testDuck(duck);
System.out.println("\nThe TurkeyAdapter says...");
testDuck(turkeyAdapter);
}
static void testDuck(Duck duck) {
duck.quack();
duck.fly();
}
}

结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
The Turkey says...
Gobble gobble
I'm flying a short distance
The Duck says...
Quack
I'm flying
The TurkeyAdapter says...
Gobble gobble
I'm flying a short distance
I'm flying a short distance
I'm flying a short distance
I'm flying a short distance
I'm flying a short distance

代码地址

https://github.com/atopom/java_familiar_strange/tree/master/Code/AdapterPattern

Android-Parcelable

发表于 2015-05-01 | 分类于 Android

Parcelable接口

Parcelable是android.io包下的接口,是Android中推荐使用的序列化接口。

只要实现Parcelable接口的类,就可以使用Parcel进行序列化和反序列化操作。

实现Pacelable接口

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
public class Book implements Parcelable {
private String name;
private int price;
public String getName() {
return name;
}
public int getPrice() {
return price;
}
public void setName(String name) {
this.name = name;
}
public void setPrice(int price) {
this.price = price;
}
public Book() {
}
protected Book(Parcel in) {
name = in.readString();
price = in.readInt();
}
// 固定写法,在Pacel进行反序列化的时候,会通过反射调用CREATOR的方法。
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.writeString(name);
dest.writeInt(price);
}
}

Parcel是什么

Parcel简单来说是一套机制。可以将序列化之后的数据写入到一个共享内存中,其他进程通过Parcel可以从这块共享内存中读出字节流,并反序列化成对象。
Parcel的工作流程

Parcel的读写源码

Parcel.writeParcelable

1
2
3
4
5
6
public final void writeParcelable(Parcelable p, int parcelableFlags) {
// 先向流中写入p的ClassName
writeParcelableCreator(p);
// 然后直接调用p的writeToParcel,这个方法也就是我们自己重写的
p.writeToParcel(this, parcelableFlags);
}

这里需要注意的是,每一个Parcelable对象在写入流之前,都会在前面首先写入这个对象的ClassName,主要是方便后面读的时候,能够知道是哪个类,感觉这个地方还是做的比较粗糙,在Serializable中对应一个序列化类的信息刻画比这简单的一个类名要靠谱得多,所以官方文档上才会说,如果你想进行持久化存储,那么Parcelable不是你的菜,道理很简单,这里不会有任何版本的概念,只要你的类名不改,旧版本的数据就可以被新版本的class进行反序列化,然而class里面的属性可能已经完全不一样了。

Parcel.readParcelable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public final <T extends Parcelable> T readParcelable(ClassLoader loader) {
// 1. 首先会调用到readParcelableCreator,通过反射读取我们类中定义的CREATOR
Parcelable.Creator<?> creator = readParcelableCreator(loader);
if (creator == null) {
return null;
}
if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
Parcelable.ClassLoaderCreator<?> classLoaderCreator =
(Parcelable.ClassLoaderCreator<?>) creator;
return (T) classLoaderCreator.createFromParcel(this, loader);
}
// 2. 然后直接调用CREATOR.createFromParcel(parcel)
return (T) creator.createFromParcel(this);
}
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
// 首先会调用到readParcelableCreator,通过反射读取我们类中定义的CREATOR
public final Parcelable.Creator<?> readParcelableCreator(ClassLoader loader) {
// 首先把类名读取出来
String name = readString();
Parcelable.Creator<?> creator;
// mCreators做了一下缓存,如果之前某个classloader把一个parcelable的Creator获取过
// 那么就不需要通过反射去查找了
synchronized (mCreators) {
HashMap<String,Parcelable.Creator<?>> map = mCreators.get(loader);
if (map == null) {
map = new HashMap<>();
mCreators.put(loader, map);
}
creator = map.get(name);
if (creator == null) {
try {
// If loader == null, explicitly emulate Class.forName(String) "caller
// classloader" behavior.
ClassLoader parcelableClassLoader =
(loader == null ? getClass().getClassLoader() : loader);
// 加载我们自己实现Parcelable接口的类
Class<?> parcelableClass = Class.forName(name, false,
parcelableClassLoader);
Field f = parcelableClass.getField("CREATOR");
Class<?> creatorType = f.getType();
creator = (Parcelable.Creator<?>) f.get(null);
}
catch (Exception e) {
// catch exception
}
if (creator == null) {
throw new BadParcelableException("Parcelable protocol requires a "
+ "non-null Parcelable.Creator object called "
+ "CREATOR on class " + name);
}
map.put(name, creator);
}
}
return creator;
}

Serializable接口

Serializable是java.io包下的一个接口,是Java类进行序列化和反序列化的标记接口。

一个类实现了Serializable接口,就可以被ObjectOutputStream和ObjectInputStream进行序列化和反序列化的操作。

被transient描述的属性或类的静态变量是不会被序列化的。

如果一个实现了Serializable接口的类,继承了另外一个类。那么这个类的父类,必须继承Serializable或者提供一个空构造方法。

反序列化产生的对象并不是通过构造器创造的,所以依赖构造器保证的约束条件在对象反序列化时都无法保证。比如一个设计成单利的类,如果能序列化,那么可以反序列化出N个对象。

序列化和反序列化中隐藏的方法

具体的方法修饰符,private protected public 都可以,序列化和反序列化都是通过反射来调用的。

private Object writeReplace() throws ObjectStreamException ObjectOutPutStream

在序列化一个对象时,会首先调用这个方法,此方法中可以替换原有序列化的对象。

private void writeObject(java.io.ObjectOutputStream out) throws IOException

调用ObjectOutputStream.defaultWriteObject()使用的是默认的序列化过程,在调用defaultWriteObject方法后,可以往out流中写入一些数据,在readObject中可以读取。

private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException

在writeObject中写入的数据,在此方法中需要以写入顺序读取,同时我们注意到这里已经把参数inputstream回调给我们了,我们可以在这个位置注册一个回调,可以在validateObject方法中检查这个反序列化对象是否合法。

private Object readResolve() throws ObjectStreamException

此方法中可以替换反序列化出来的对象。

实现Serializable接口

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
/**
* Person类除了writeReplace在序列化时用到,其他的黑科技方法都不会被调用
* 原因是我们在writeReplace中将序列化对象替换成了内部代理类,所以以后的序列化过程就是针对SerializableProxy
*/
class Person implements Serializable {
private static final long serialVersionUID = 1L;
public String desc;
public Person(String desc) {
this.desc = desc;
}
static class SerializableProxy implements Serializable {
private static final long serialVersionUID = 1L;
private String desc;
private SerializableProxy(Person s) {
this.desc = s.desc;
}
/**
* 在这里恢复外围类 注意看这里!!!最大的好处就是我们最后得到的外围类是通过构造器构建的!
*
* @return
*/
private Object readResolve() {
return new Person(desc);
}
}
/**
* 外围类直接替换成静态内部代理类作为真正的序列化对象
*
* @return
*/
private Object writeReplace() {
return new SerializableProxy(this);
}
/**
* 这里主要是为了防止攻击,任何以Persion声明的对象字节流都是流氓!!
* 因为我在writeReplace中已经把序列化的实例指向了SerializableProxy
*
* @param stream
* @throws InvalidObjectException
*/
private void readObject(ObjectInputStream stream) throws InvalidObjectException {
throw new InvalidObjectException("proxy requied!");
}
}

自定义Serializable序列化

ObjectOutputStream的defaultWriteObject和defaultReadObject,是通过反射的方式实现序列化和反序列化的。
这种默认方式,会导致效率上比Android的Parcelable慢。
但是如果通过自己实现writeObject和readObject,那么在效率上会比Parcelable快。点击链接,查看测试结果

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
public class TreeNode implements Serializable, Parcelable {
private static final long serialVersionUID = 1L;
public List<TreeNode> children;
public String string0;
public String string1;
public String string2;
public int int0;
public int int1;
public int int2;
public boolean boolean0;
public boolean boolean1;
public boolean boolean2;
public TreeNode() {
}
protected TreeNode(Parcel in) {
if (in.readByte() == 0x01) {
children = new ArrayList<TreeNode>();
in.readList(children, TreeNode.class.getClassLoader());
} else {
children = null;
}
string0 = in.readString();
string1 = in.readString();
string2 = in.readString();
int0 = in.readInt();
int1 = in.readInt();
int2 = in.readInt();
boolean0 = in.readByte() != 0x00;
boolean1 = in.readByte() != 0x00;
boolean2 = in.readByte() != 0x00;
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.writeUTF(string0);
out.writeUTF(string1);
out.writeUTF(string2);
out.writeInt(int0);
out.writeInt(int1);
out.writeInt(int2);
out.writeBoolean(boolean0);
out.writeBoolean(boolean1);
out.writeBoolean(boolean2);
if (children != null) {
out.writeInt(children.size());
for (TreeNode child : children) {
child.writeObject(out);
}
} else {
out.writeInt(0);
}
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
string0 = in.readUTF();
string1 = in.readUTF();
string2 = in.readUTF();
int0 = in.readInt();
int1 = in.readInt();
int2 = in.readInt();
boolean0 = in.readBoolean();
boolean1 = in.readBoolean();
boolean2 = in.readBoolean();
int childCount = in.readInt();
if (childCount > 0) {
children = new ArrayList<TreeNode>(childCount);
for (int i = 0; i < childCount; i++) {
TreeNode child = new TreeNode();
child.readObject(in);
children.add(child);
}
}
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
if (children == null) {
dest.writeByte((byte) (0x00));
} else {
dest.writeByte((byte) (0x01));
dest.writeList(children);
}
dest.writeString(string0);
dest.writeString(string1);
dest.writeString(string2);
dest.writeInt(int0);
dest.writeInt(int1);
dest.writeInt(int2);
dest.writeByte((byte) (boolean0 ? 0x01 : 0x00));
dest.writeByte((byte) (boolean1 ? 0x01 : 0x00));
dest.writeByte((byte) (boolean2 ? 0x01 : 0x00));
}
public static final Parcelable.Creator<TreeNode> CREATOR = new Parcelable.Creator<TreeNode>() {
@Override
public TreeNode createFromParcel(Parcel in) {
return new TreeNode(in);
}
@Override
public TreeNode[] newArray(int size) {
return new TreeNode[size];
}
};
}

Android-IPC-AIDL

发表于 2015-04-05 | 分类于 Android

一、AIDL是什么

AIDL全称是Android Interface Definition Language,也就是Android接口定义语言。

二、AIDL是干什么的

设计这门语言的目的是为了实现进程间通信,尤其是在涉及多进程并发情况下的进程间通信。

Android中其他进程间通信的方式

Bundle、文件共享、、ContentProvider、Socket、BroadcastReceiver,每种方式都有自己的特点,这里不做过多的扩展。

三、AIDL怎么用

1、AIDL语法

文件类型

用AIDL书写的文件的后缀是 .aidl。

数据类型

  1. 基本数据类型(byte,short,int,long,float,double,boolean,char)
  2. String 和 CharSequence
  3. List:只支持ArrayList,里面每个元素都必须能被AIDL支持
  4. Map:只支持HashMap,里面的Key和Value都必须能被AIDL支持
  5. Parcelable:所有实现Parcelable接口的对象
  6. AIDL:所有的AIDL接口本身也可以在AIDL文件中使用

注意:
在编写AIDL文件时,自定义的Parcelable对象必须显示 import ,不管它是否和当前AIDL文件位于同一个包内。
比如,现在我们编写了两个文件,一个叫做 Book.java ,另一个叫做 BookManager.aidl,它们都在 com.wangyanan.aidldemo 包下。现在我们需要在 .aidl 文件里使用 Book 对象,那么我们就必须在 .aidl 文件里面写上 import com.wangyanan.aidldemo.Book 。

定向tag

AIDL中的定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。

其中,数据流向是针对在客户端中的那个传入方法的对象而言的。
in 为定向 tag 的话表现为服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动;
out 的话表现为服务端将会接收到那个对象的的空对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动;
inout 为定向 tag 的情况下,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。

2、具体操作

AIDL代码

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
// Book.java
package com.gn100.ipcserver.aidl;
import android.os.Parcel;
import android.os.Parcelable;
public class Book implements Parcelable {
private String name;
private int price;
public String getName() {
return name;
}
public int getPrice() {
return price;
}
public void setName(String name) {
this.name = name;
}
public void setPrice(int price) {
this.price = price;
}
public Book() {
}
protected Book(Parcel in) {
name = in.readString();
price = in.readInt();
}
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.writeString(name);
dest.writeInt(price);
}
public void readFromParcel(Parcel dest) {
name = dest.readString();
price = dest.readInt();
}
@Override
public String toString() {
return "name : " + name + " , price : " + price;
}
}
1
2
3
4
5
6
7
8
9
// Book.aidl
//第一类AIDL文件的例子
//这个文件的作用是引入了一个序列化对象 Book 供其他的AIDL文件使用
//注意:Book.aidl与Book.java的包名应当是一样的
package com.gn100.ipcserver.aidl;
//注意parcelable是小写
parcelable Book;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// BookManager.aidl
package com.gn100.ipcserver.aidl;
import com.gn100.ipcserver.aidl.Book;
import com.gn100.ipcserver.aidl.OnNewBookArrivedListener;
interface BookManager {
// 所有的返回值前都不需要加任何东西
List<Book> getBooks();
Book getBook(String name);
int getBookCount();
// 传参时除了Java基本类型以及String,CharSequence之外的类型
// 都需要在前面加上定向tag,具体加什么根据需求而定
void addBookIn(in Book book);
void addBookOut(out Book book);
void addBookInout(inout Book book);
void registerListener(OnNewBookArrivedListener listener);
void unregisterListener(OnNewBookArrivedListener listener);
}

注意:这里有一个坑!大家可能注意到了,在 Book.aidl 文件中,我一直在强调:Book.aidl与Book.java的包名应当是一样的。这似乎理所当然的意味着这两个文件应当是在同一个包里面的——事实上,很多比较老的文章里就是这样说的,他们说最好都在 aidl 包里同一个包下,方便移植——然而在 Android Studio 里并不是这样。如果这样做的话,系统根本就找不到 Book.java 文件,从而在其他的AIDL文件里面使用 Book 对象的时候会报 Symbol not found 的错误。为什么会这样呢?因为 Gradle 。Android Studio 是默认使用 Gradle 来构建 Android 项目的,而 Gradle 在构建项目的时候会通过 sourceSets 来配置不同文件的访问路径,从而加快查找速度。Gradle 默认是将 java 代码的访问路径设置在 java 包下的,这样一来,如果 java 文件是放在 aidl 包下的话那么理所当然系统是找不到这个 java 文件的。那应该怎么办呢?

即要 java 文件和 aidl 文件的包名是一样的,又要能找到这个 java 文件——那么仔细想一下的话,其实解决方法是很显而易见的。首先我们可以把问题转化成:如何在保证两个文件包名一样的情况下,让系统能够找到我们的 java 文件?这样一来思路就很明确了:要么让系统来 aidl 包里面来找 java 文件,要么把 java 文件放到系统能找到的地方去,也即放到 java 包里面去。

方法一
修改 build.gradle 文件:在 android{} 中间加上下面的内容。
也就是把 java 代码的访问路径设置成了 java 包和 aidl 包,这样一来系统就会到 aidl 包里面去查找 java 文件,也就达到了我们的目的。只是有一点,这样设置后 Android Studio 中的项目目录会有一些改变,我感觉改得挺难看的。

1
2
3
4
5
sourceSets {
main {
java.srcDirs = ['src/main/java', 'src/main/aidl']
}
}

方法二
把 java 文件放到 java 包下去:把 Book.java 放到 java 包里任意一个包下,保持其包名不变,与 Book.aidl 一致。只要它的包名不变,Book.aidl 就能找到 Book.java ,而只要 Book.java 在 java 包下,那么系统也是能找到它的。但是这样做的话也有一个问题,就是在移植相关 .aidl 文件和 .java 文件的时候没那么方便,不能直接把整个 aidl 文件夹拿过去完事儿了,还要单独将 .java 文件放到 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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
// AIDLService.java
package com.gn100.ipcserver.service;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import com.gn100.ipcserver.aidl.Book;
import com.gn100.ipcserver.aidl.BookManager;
import com.gn100.ipcserver.aidl.OnNewBookArrivedListener;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
public class AIDLService extends Service {
private static final String TAG = AIDLService.class.getCanonicalName();
private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);
private List<Book> mBooks = new CopyOnWriteArrayList<>();
private RemoteCallbackList<OnNewBookArrivedListener> mNewBookArrivedListeners = new RemoteCallbackList<OnNewBookArrivedListener>();
private BookManager.Stub mBookManager = new BookManager.Stub() {
@Override
public List<Book> getBooks() throws RemoteException {
synchronized (this) {
Log.e(TAG, "invoking getBooks() method , now the list is : " + mBooks.toString());
return mBooks != null ? mBooks : new ArrayList<Book>();
}
}
@Override
public Book getBook(String name) throws RemoteException {
synchronized (this) {
Book book = null;
if (mBooks != null && !TextUtils.isEmpty(name)) {
for (Book b : mBooks) {
if (name.equals(b.getName())) {
book = b;
}
}
}
Log.e(TAG, "invoking getBook() method , now the book is : " + book.toString());
return book;
}
}
@Override
public int getBookCount() throws RemoteException {
synchronized (this) {
Log.e(TAG, "invoking getBookCount() method , now the bookCount is : " + mBooks.size());
return mBooks == null ? 0 : mBooks.size();
}
}
@Override
public void addBookIn(Book book) throws RemoteException {
synchronized (this) {
if (mBooks == null) {
mBooks = new ArrayList<>();
}
if (book == null) {
Log.e(TAG, "Book is null in In");
book = new Book();
}
//尝试修改book的参数,主要是为了观察其到客户端的反馈
book.setPrice(11111);
if (!mBooks.contains(book)) {
mBooks.add(book);
}
//打印mBooks列表,观察客户端传过来的值
Log.e(TAG, "invoking addBookIn() method , now the list is : " + mBooks.toString());
}
}
@Override
public void addBookOut(Book book) throws RemoteException {
synchronized (this) {
if (mBooks == null) {
mBooks = new ArrayList<>();
}
if (book == null) {
Log.e(TAG, "Book is null in Out");
book = new Book();
}
//尝试修改book的参数,主要是为了观察其到客户端的反馈
book.setPrice(22222);
if (!mBooks.contains(book)) {
mBooks.add(book);
}
//打印mBooks列表,观察客户端传过来的值
Log.e(TAG, "invoking addBookOut() method , now the list is : " + mBooks.toString());
}
}
@Override
public void addBookInout(Book book) throws RemoteException {
synchronized (this) {
if (mBooks == null) {
mBooks = new ArrayList<>();
}
if (book == null) {
Log.e(TAG, "Book is null in Inout");
book = new Book();
}
//尝试修改book的参数,主要是为了观察其到客户端的反馈
book.setPrice(33333);
if (!mBooks.contains(book)) {
mBooks.add(book);
}
//打印mBooks列表,观察客户端传过来的值
Log.e(TAG, "invoking addBookInout() method , now the list is : " + mBooks.toString());
}
}
@Override
public void registerListener(OnNewBookArrivedListener listener) throws RemoteException {
boolean success = mNewBookArrivedListeners.register(listener);
if(success) {
Log.d(TAG, "register success");
} else {
Log.e(TAG, "not found, can not register");
}
}
@Override
public void unregisterListener(OnNewBookArrivedListener listener) throws RemoteException {
boolean success = mNewBookArrivedListeners.unregister(listener);
if(success) {
Log.d(TAG, "unregister success");
} else {
Log.e(TAG, "not found, can not unregister");
}
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind");
return mBookManager.asBinder();
}
@Override
public boolean onUnbind(Intent intent) {
Log.d(TAG, "onUnbind");
// return super.onUnbind(intent);
return true; // for invoke onRebind method
}
@Override
public void onRebind(Intent intent) {
Log.d(TAG, "onRebind");
super.onRebind(intent);
}
@Override
public void onCreate() {
Log.d(TAG, "onCreate");
super.onCreate();
new Thread(new ServiceWorker()).start();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy");
super.onDestroy();
mIsServiceDestoryed.set(true);
}
private void onNewBookArrived(Book book) throws RemoteException {
Log.d(TAG, "onNewBookArrived");
mBooks.add(book);
final int N = mNewBookArrivedListeners.beginBroadcast();
for (int i = 0; i < N; i++) {
OnNewBookArrivedListener l = mNewBookArrivedListeners.getBroadcastItem(i);
if (l != null) {
try {
l.onNewBookArrived(book);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
mNewBookArrivedListeners.finishBroadcast();
}
private class ServiceWorker implements Runnable {
@Override
public void run() {
// do background processing here.....
while (!mIsServiceDestoryed.get()) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int bookId = mBooks.size() + 1;
Book newBook = new Book();
newBook.setName("newBook#" + bookId);
newBook.setPrice(100);
try {
onNewBookArrived(newBook);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}
}

AIDLService是服务端代码,大致可以分为三块:第一块是初始化,在 onCreate() 方法里面我进行了一些数据的初始化操作。第二块是重写 BookManager.Stub 中的方法(在这里面实现AIDL里面定义的方法接口的具体实现逻辑)。第三块是重写 onBind() 方法,在里面返回写好的 BookManager.Stub 。

接下来在 Manefest 文件里面注册这个我们写好的 Service :

1
2
3
4
5
6
7
8
<service
android:name=".service.AIDLService"
android:exported="true">
<intent-filter>
<action android:name="com.gn100.aidl"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>

客户端代码

前面说过,在客户端我们要完成的工作主要是调用服务端的方法,但是在那之前,我们首先要连接上服务端,完整的客户端代码是这样的:

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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
// AIDLActivity.java
package com.gn100.ipcclient;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import com.gn100.ipcserver.aidl.Book;
import com.gn100.ipcserver.aidl.BookManager;
import com.gn100.ipcserver.aidl.OnNewBookArrivedListener;
import java.util.List;
public class AIDLActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = AIDLActivity.class.getCanonicalName();
private BookManager mBookManager;
private static final int MESSAGE_NEW_BOOK_ARRIVED = 1;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_NEW_BOOK_ARRIVED:
Log.d(TAG, "receive new book :" + msg.obj);
break;
default:
super.handleMessage(msg);
}
}
};
private OnNewBookArrivedListener mOnNewBookArrivedListener = new OnNewBookArrivedListener.Stub() {
@Override
public void onNewBookArrived(Book newBook) throws RemoteException {
mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, newBook)
.sendToTarget();
}
};
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
Log.d(TAG, "binder died. tname:" + Thread.currentThread().getName());
if (mBookManager == null)
return;
mBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
mBookManager = null;
// TODO:这里重新绑定远程Service
}
};
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "onServiceConnected");
mBookManager = BookManager.Stub.asInterface(service);
try {
Log.i(TAG, "register listener:" + mOnNewBookArrivedListener);
mBookManager.registerListener(mOnNewBookArrivedListener);
mBookManager.asBinder().linkToDeath(mDeathRecipient, 0);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG, "onServiceDisconnected. tname:" + Thread.currentThread().getName());
}
};
@Override
protected void onStart() {
super.onStart();
}
@Override
protected void onStop() {
super.onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mBookManager != null
&& mBookManager.asBinder().isBinderAlive()) {
try {
Log.i(TAG, "unregister listener:" + mOnNewBookArrivedListener);
mBookManager.unregisterListener(mOnNewBookArrivedListener);
unbindService(mServiceConnection);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_aidl_service);
findViewById(R.id.btn_add_book_in).setOnClickListener(this);
findViewById(R.id.btn_add_book_out).setOnClickListener(this);
findViewById(R.id.btn_add_book_in_out).setOnClickListener(this);
findViewById(R.id.btn_get_books).setOnClickListener(this);
bindService();
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_add_book_in:
addBookIn();
break;
case R.id.btn_add_book_out:
addBookOut();
break;
case R.id.btn_add_book_in_out:
addBookInout();
break;
case R.id.btn_get_books:
getBooks();
break;
}
}
private void addBookIn() {
new Thread(new Runnable() {
@Override
public void run() {
try {
Book book = new Book();
book.setName("Android築基篇");
book.setPrice(199);
Log.d(TAG, "book info addBookIn before : " + book);
mBookManager.addBookIn(book);
Log.d(TAG, "book info addBookIn after : " + book);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}).start();
}
private void addBookOut() {
new Thread(new Runnable() {
@Override
public void run() {
Book book = new Book();
book.setName("Android築基篇");
book.setPrice(199);
try {
Log.d(TAG, "book info addBookOut before : " + book);
mBookManager.addBookOut(book);
Log.d(TAG, "book info addBookOut after : " + book);
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
private void addBookInout() {
new Thread(new Runnable() {
@Override
public void run() {
Book book = new Book();
book.setName("Android築基篇");
book.setPrice(199);
try {
Log.d(TAG, "book info addBookInout before : " + book);
mBookManager.addBookInout(book);
Log.d(TAG, "book info addBookInout after : " + book);
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
private void getBooks() {
new Thread(new Runnable() {
@Override
public void run() {
try {
List<Book> books = mBookManager.getBooks();
Log.d(TAG, "books info = " + books.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
private void bindService() {
Intent intent = new Intent();
intent.setAction("com.gn100.aidl");
intent.setPackage("com.gn100.ipcserver");
bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
}
}

AIDLActivity 是客户端代码,首先bindService建立连接,然后在 ServiceConnection 里面获取 BookManager 对象,接着通过它来调用服务端的方法。

3、注意事项

  1. 在使用AIDL进行跨进程通信时,在调用远程进程方法时,尽量在子线程处理,避免远程进程方法做了耗时操作,阻塞自己的UI线程。
  2. 服务端需要处理并发问题。

四、AIDL工作原理

AIDL工作原理

五、AIDL结构类图

AIDL结构类图

代码地址

https://github.com/atopom/android_first_draft/tree/master/Code/IPC%E9%80%9A%E4%BF%A1

Android-IPC-Messenger

发表于 2015-03-12 | 分类于 Android

Messenger信使

官方文档解释:它引用了一个Handler对象,以便others能够向它发送消息(使用mMessenger.send(Message msg)方法)。该类允许跨进程间基于Message通信(即两个进程间可以通过Message进行通信)。简单理解,就是在服务端使用Handler创建一个Messenger,客户端持有这个Messenger就可以与服务端通信了。

以前我们使用Handler+Message的方式进行通信,都是在同一个进程中,从线程持有一个主线程的Handler对象,并向主线程发送消息。

Android可以使用binder机制进行跨进行通信,所以我们就可以将Handler与binder结合起来进行跨进程发送消息。查看API就可以发现,Messenger就是通过这种方式的实现。

一般使用方法如下:

  1. 远程通过 mMessenger = new Messenger(mHandler) 创建一个信使对象

  2. 客户端使用 binderService 请求连接远程

  3. 远程 onBind 方法返回一个binder

    return mMessenger.getBinder();

  4. 客户端使用远程返回的binder得到一个信使(即得到远程信使)

    1
    2
    3
    4
    public void onServiceConnected(ComponentName name, IBinder service) {
    rMessenger = new Messenger(service);
    ......
    }

这里虽然是new了一个Messenger,但我们查看它的实现

1
public Messenger(IBinder target) { mTarget = IMessenger.Stub.asInterface(target); }

发现它的mTarget是通过AIDL得到的,实际上就是远程创建的那个。

  1. 客户端可以使用这个远程信使对象向远程发送消息:
    1
    rMessenger.send(msg);

这样远程服务端的Handler对象就能收到消息了,然后可以在其handlerMessage(Message msg)方法中进行处理。(该Handler对象就是第一步服务端创建Messenger时使用的参数mHandler)。

经过这5个步骤貌似只有客户端向服务端发送消息,这样的消息传递是单向的,那么如何实现双向传递呢?

首先需要在第5步稍加修改,在send(msg)前通过msm.replyTo = mMessenger将自己的信使设置到消息中,这样服务端接收到消息时同时也得到了客户端的信使对象了,然后服务端可以通过得到客户端的信使对象,并向它发送消息。

1
2
cMessenger = msg.replyTo;
cMessenger.send(message);

即完成了从服务端向客户端发送消息的功能,这样客服端可以在自己的Handler对象的handlerMessage方法中接收服务端发送来的message进行处理。

双向通信宣告完成。

Messenger工作原理

Messenger的工作原理

以下代码来自ApiDemo

Service code:

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
public class MessengerService extends Service {
/** For showing and hiding our notification. */
NotificationManager mNM;
/** Keeps track of all current registered clients. */
ArrayList<Messenger> mClients = new ArrayList<Messenger>();
/** Holds last value set by a client. */
int mValue = 0;
/**
* Command to the service to register a client, receiving callbacks
* from the service. The Message's replyTo field must be a Messenger of
* the client where callbacks should be sent.
*/
static final int MSG_REGISTER_CLIENT = 1;
/**
* Command to the service to unregister a client, ot stop receiving callbacks
* from the service. The Message's replyTo field must be a Messenger of
* the client as previously given with MSG_REGISTER_CLIENT.
*/
static final int MSG_UNREGISTER_CLIENT = 2;
/**
* Command to service to set a new value. This can be sent to the
* service to supply a new value, and will be sent by the service to
* any registered clients with the new value.
*/
static final int MSG_SET_VALUE = 3;
/**
* Handler of incoming messages from clients.
*/
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_REGISTER_CLIENT:
mClients.add(msg.replyTo);
break;
case MSG_UNREGISTER_CLIENT:
mClients.remove(msg.replyTo);
break;
case MSG_SET_VALUE:
mValue = msg.arg1;
for (int i = mClients.size() - 1; i >= 0; i --) {
try {
mClients.get(i).send(Message.obtain(null,
MSG_SET_VALUE, mValue, 0));
} catch (RemoteException e) {
// The client is dead. Remove it from the list;
// we are going through the list from back to front
// so this is safe to do inside the loop.
mClients.remove(i);
}
}
break;
default:
super.handleMessage(msg);
}
}
}
/**
* Target we publish for clients to send messages to IncomingHandler.
*/
final Messenger mMessenger = new Messenger(new IncomingHandler());
@Override
public void onCreate() {
mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
// Display a notification about us starting.
showNotification();
}
@Override
public void onDestroy() {
// Cancel the persistent notification.
mNM.cancel(R.string.remote_service_started);
// Tell the user we stopped.
Toast.makeText(this, R.string.remote_service_stopped, Toast.LENGTH_SHORT).show();
}
/**
* When binding to the service, we return an interface to our messenger
* for sending messages to the service.
*/
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
/**
* Show a notification while this service is running.
*/
private void showNotification() {
// In this sample, we'll use the same text for the ticker and the expanded notification
CharSequence text = getText(R.string.remote_service_started);
// Set the icon, scrolling text and timestamp
Notification notification = new Notification(R.drawable.stat_sample, text,
System.currentTimeMillis());
// The PendingIntent to launch our activity if the user selects this notification
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
new Intent(this, Controller.class), 0);
// Set the info for the views that show in the notification panel.
notification.setLatestEventInfo(this, getText(R.string.remote_service_label),
text, contentIntent);
// Send the notification.
// We use a string id because it is a unique number. We use it later to cancel.
mNM.notify(R.string.remote_service_started, notification);
}
}

自己的DEMO:

  1. MyActivity Code

    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
    public class MyActivity extends Activity {
    protected static final int HELLO_CLIENT = 0;
    private MyServiceConnection conn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    }
    @Override
    protected void onStart() {
    super.onStart();
    Intent service = new Intent(this, MyService.class);
    conn = new MyServiceConnection();
    bindService(service, conn, BIND_AUTO_CREATE);
    }
    @Override
    protected void onDestroy() {
    super.onDestroy();
    unbindService(conn);
    }
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.main, menu);
    return true;
    }
    private Handler handler = new Handler() {
    public void handleMessage(Message msg) {
    switch(msg.what) {
    case HELLO_CLIENT:
    Log.d("wangyanan", ">>>>>>>>>> hello client <<<<<<<<<<");
    break;
    }
    };
    };
    private Messenger clientMessenger;
    private Messenger serviceMessenger;
    class MyServiceConnection implements ServiceConnection {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
    clientMessenger = new Messenger(handler);
    serviceMessenger = new Messenger(service);
    sendServiceMessage(MyService.HELLO_SERVICE);
    }
    @Override
    public void onServiceDisconnected(ComponentName name) {
    }
    }
    public void sendServiceMessage(int what) {
    try {
    Message message = Message.obtain(null, MyService.HELLO_SERVICE);
    message.replyTo = clientMessenger;
    serviceMessenger.send(message);
    } catch (RemoteException e) {
    e.printStackTrace();
    Toast.makeText(MyActivity.this, "与服务器连接失败", Toast.LENGTH_SHORT).show();
    }
    }
    }
  2. MyService Code

    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
    public class MyService extends Service {
    protected static final int HELLO_SERVICE = 0;
    @Override
    public IBinder onBind(Intent intent) {
    Log.d("wangyanan", ">>>>>>>>>> MyService onBind......");
    return serviceMessenger.getBinder();
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
    Log.d("wangyanan", ">>>>>>>>>> MyService onStartComand......");
    return super.onStartCommand(intent, flags, startId);
    }
    private Handler handler = new Handler() {
    public void handleMessage(Message msg) {
    switch(msg.what) {
    case HELLO_SERVICE:
    Log.d("wangyanan", ">>>>>>>>>> hello service <<<<<<<<<<");
    clientMessenger = msg.replyTo;
    sendClientMessage(MyActivity.HELLO_CLIENT);
    break;
    }
    }
    };
    private Messenger serviceMessenger = new Messenger(handler);
    private Messenger clientMessenger = new Messenger(handler);
    private void sendClientMessage(int what) {
    try {
    Message message = Message.obtain(null, what);
    clientMessenger.send(message);
    } catch (RemoteException e) {
    e.printStackTrace();
    }
    };
    }
  3. Log打印结果

    1
    2
    3
    07-27 10:46:54.049: D/wangyanan(8876): >>>>>>>>>> MyService onBind......
    07-27 10:46:54.220: D/wangyanan(8876): >>>>>>>>>> hello service <<<<<<<<<<
    07-27 10:46:54.220: D/wangyanan(8876): >>>>>>>>>> hello client <<<<<<<<<<

代码地址

https://github.com/atopom/android_first_draft/tree/master/Code/IPC%E9%80%9A%E4%BF%A1

Object-C-DataType

发表于 2015-02-12 | 分类于 Object-C

数据类型,是每门开发语言的基本必修课,下面是为自己总结回顾使用的,分享给大家。如有错误请指正,交流学习,共同进步,谢谢~

基本数据类型

int类型

八进制 以0开头的整型
NSLog的格式符:
%o 显示的八进制不带前导0
%#o 显示的八进制带前导0

十六进制 以0x开头的整型
NSLog的格式符:
%x 显示的十六进制不带前导0x
%#x 显示的十六进制带签到0x
%X、%#X 显示的十六进制是大写

float类型

NSLog的格式符:%f
%e 科学技术法显示值
%g 指数的值小于-4 大 于5,采用%e,否则采用%f

十六进制的浮点常量包含前导 0x 或 0X,后面紧跟一个或多个十进制或十六进制数字,再后是 p 或 P,最后是可以带符号的二进制指数。
例:0x0.3p10 表示的值为 3/16*2^10
注:若无特殊说明,Object-c 将所有的浮点常量看做 double 值,要显示 double 值可使用和 float 一样的格式符。

char类型

NSLog的格式符:%c
long double 常量写成尾部带有字母 l 或者 L 的浮点常量。1.234e+7L

注:id类型可以通过类型转化符可以将一般的id类型的对象转换成特定的对象。

1
2
3
_Bool 处理 Boolean(即 0 或 1)
_Complex 处理复数
_Imaginary 处理抽象数字

实例变量的初始化值默认为 0
实例变量作用域的指令:
@private 实例变量可被定义在该类的方法直接访问,不能被子类定义的方法直接访问。
@package 对于 64 位图像,可以在实现该类的图像的任何地方访问这个实例变量。
@protected 实例变量可被该类及任何子类中定义的方法直接访问(默认的情况)。
@public 实例变量可被该类中定义的方法直接访问,也可被其他类或模块中定义的方法访问。使得其他方法或函数可以通过(->)来访问实例变量(不推荐用)。

在类中定义静态变量
说明符voaltile和const正好相反,明确告诉编译器,指定类型变量的值会改变。(I/O端口)
比如要将输出端口的地址存储在 outPort 的变量中。

1
2
3
volatile char *outPort;
*outPort = 'O';
*outPort = 'N';

这样就可以避免编译器将第一个赋值语句从程序中删除。

BOOL类型

1
2
3
typedef signed char BOOL;
#define YES (BOOL)1;
#define NO (BOOL)0;

nil空类型

也就是C中的NULL,也就是0;

NSNumber类型

可以使用对象来封装基本数值。
NSNumber 类来包装基本数据类型。

1
2
3
4
+ (NSNumber *)numberWithChar :(char)value;
+ (NSNumber *)numberWithInt :(int)value;
+ (NSNumber *)numberWithFloat :(float)value;
+ (NSNumber *)numberWithBool :(BOOL)value;

还有包括无符号版本和各种 long 型数据及 long long 整型数据
例如:

1
NSNumber *number = [NSNumber numberWithInt :42];

将一个基本类型封装到 NSNumber 后,可以使用下列方法重新获得:

1
2
3
4
5
- (char)charValue;
- (int)intValue;
- (float)floatValue;
- (BOOL)boolValue;
- (NSString *)stringValue;

NSValue类型

NSNumber 实际上是 NSValue的子类, NSValue可以封装任意值。可以用NSValue将结构放入 NSArray 和 NSDictionary 中。

创建新的 NSValue:

1
+(NSValue*)valueWithBytes:(const void *) value objCType:(const char *)type;

@encode 编译器指令可以接受数据类型的名称并为你生成合适的字符串。

1
2
3
NSRect rect = NSMakeRect(1,2,30,40);
NSValue *value;
value = [NSValuevalueWithBytes:&rect objCType:@encode(NSRect)];

使用getValue:来提取数值 (传递的是要存储这个数值的变量的地址)(先找地址再取值)

1
2
value = [array objectAtIndex : 0];
[value getValue:&rect];

注:Cocoa 提供了将常用的 struct 型数据转化成 NSValue 的便捷方法:

1
2
3
4
5
6
+ (NSValue*)valueWithPoint:(NSPoint)point;
+ (NSValue*)valueWithSize:(NSSize)size;
+ (NSValue*)valueWithRect:(NSRect)rect;
- (NSSize)sizeValue;
- (NSRect)rectValue;
- (NSPoint)pointValue;

NSNull类型

在关键字下如果属性是NSNull表明没有这个属性,没有数值的话表明不知道是否有这个属性。

1
2
[NSNull null] //总返回一样的值
+ (NSNull *)null;

例如:

1
[contast setObject:[NSNull null] forKey:@"home"];

访问它:

1
2
3
4
id home = [contast objectForKey:@"home"];
if (home==[NSNullnull]){
...
}

NSFileManager类型

允许对文件系统进行操作(创建目录、删除文件、移动文件或获取文件信息)
创建一个属于自己的 NSFileManager 对象

1
NSFileManager *manager = [NSFileManager defaultManager] ;

将代字符‘~’替换成主目录

1
2
3
NSString *home = [@"~" stringByExpandingTildeInPath];
//输出文件的扩展名
- (NSString*) pathExtension

示例:翻查主目录,查找.jpg 文件并输出找到的文件列表

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
#import <Foundation/Foundation.h>
int main (int argc, const char* argv[]) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSFileManager *manager;
manager = [NSFileManager defaultManager];
NSString *home;
home = [@"~" stringByExpandingTildeInPath];
NSDirectoryEnumerator *direnum;
direnum = [manager enumeratorAtPath: home];
NSMutableArray *files;
files = [NSMutableArray arrayWithCapacity:100];
NSString *filename;
while (filename = [direnum nextObject]) {
if ([[filename pathExtension] isEqualTo:@"jpg"]) {
[files addObject: filename];
}
}
NSEnumerator *fileenum;
fileenum = [files objectEnumerator];
while (filename = [fileenum nextObject]) {
NSLog (@"%@", filename);
}
[pool drain];
return 0;
}

Xcode常用快捷键

发表于 2015-02-07 | 分类于 Xcode

工欲善其事,必先利其器。
想要更畅快的开发,熟悉常用快捷键那时必不可少的。
内容会持续更新,如有错误,请指正,谢谢~。

Command组合

Command+R:表示Run Project
Command+B:表示Build Project
Command+]:表示缩紧
Command+[:表示反向缩紧
Command+Del:表示删除一行
Command+T:表示新建Tab
Command+0:表示隐藏/显示 工程左侧导航
Command+1:表示隐藏/显示 工程左侧导航tab1 文件导航器
Command+2:表示隐藏/显示 工程左侧导航tab2
Command+3:表示隐藏/显示 工程左侧导航tab3 搜索导航器
Command+~:表示隐藏/显示 工程底部导航

Command+Option组合

Command+Option+0:表示隐藏/显示 工程右侧导航
Command+Option+⬅:表示折叠代码块
Command+Option+[]:表示上下两行代码交换
Command+Option+F:表示查询替换当前文件的指定内容

Command+Shift组合

Command+Shift+0:表示打开文档
Command+Shift+K:表示Clean Project
Command+Shift+F:表示隐藏/显示 工程右侧导航tab3 搜索导航器
Command+Shift+O:表示打开包含指定方法或代码的文件
Command+Shift+[]:表示XcodeTab页的切换

Command+Control组合

Command+Control+⬆:表示oc或c++的.h和.m文件之间的切换
Command+Control+⬅:表示返回上次操作的历史位置
Command+Control+Y:debug模式全速执行代码
Command+Control+E:重命名选中的变量名

Option组合

Option+Left-Click-File:表示在辅助编辑器中打开文件
Option+Left-Click-方法名/类名:表示快速查看文档
Option+Del:表示删除一个单词

Control组合

Control+6:表示跳转到当前文件的某一代码行
Control+.:自动匹配
Control+/或Tab:表示参数切换

Command+Option+Shift组合

Command+Option+Shift+Left-Click-File:表示在指定位置打开文件

debug

fn+F6:表示下一行
fn+F7:表示进入方法内部
fn+F8:表示跳出方法

添加标记语法

#pragam mark - mark content

学习使用Markdown语法

发表于 2015-01-10 | 分类于 Markdown

Markdown语法在简书上分分钟实现效果,在github有一丁点错误都不行,整理了好长时间,总算正确显示出来了。

分割线

源码

1
***

效果


无序文本

源码

1
2
3
- 文本
- 文本
- 文本

效果

  • 文本
  • 文本
  • 文本

有序文本

源码

1
2
3
1. 文本
2. 文本
3. 文本

效果

  1. 文本
  2. 文本
  3. 文本

链接

源码

1
[简书](http://jianshu.io)

效果

简书

图片

源码

1
![](http://upload-images.jianshu.io/upload_images/1623602-de31fc2f960b360c.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

效果

单句引用

源码

1
> 一盏灯, 一片昏黄; 一简书, 一杯淡茶。 守着那一份淡定, 品读属于自己的寂寞。 保持淡定, 才能欣赏到最美丽的风景! 保持淡定, 人生从此不再寂寞。

效果

一盏灯, 一片昏黄; 一简书, 一杯淡茶。 守着那一份淡定, 品读属于自己的寂寞。 保持淡定, 才能欣赏到最美丽的风景! 保持淡定, 人生从此不再寂寞。

多句引用

源码

1
2
3
4
> 朝辞白帝彩云间
> 千里江陵一日还
> 两岸猿声啼不住
> 轻舟已过万重山

效果

朝辞白帝彩云间
千里江陵一日还
两岸猿声啼不住
轻舟已过万重山

粗体和斜体

源码

1
2
*斜体*
**粗体**

效果

斜体
粗体

代码块

源码

1
```
代码块
代码块
代码块
1
2
3
```
效果

代码块
代码块
代码块

1
2
3
4
目录级关系(在某些环境不起作用)
---------
源码

+ 个人收藏
    + AirDrop
    + iCloub Drive
    - 文档
        - 书籍
            + 程序员修炼之道:从小工到专家
            + 编程珠玑
            - 算法导论
        + 笔记
        + 稿件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
效果
+ 个人收藏
+ AirDrop
+ iCloub Drive
- 文档
- 书籍
+ 程序员修炼之道:从小工到专家
+ 编程珠玑
+ 算法导论
+ 笔记
+ 稿件
引用多层级关系
---------
源码

一级引用

二级引用

三级引用

1
2
3
4
5
6
7
8
9
10
11
12
效果
> 一级引用
>> 二级引用
>>> 三级引用
标题语法
---------
源码

1.标题

2.标题

3.标题

4.标题

5.标题

```

效果

1.标题

2.标题

3.标题

4.标题

5.标题

通过GitHub和Jekyll搭建属于自己的博客

发表于 2015-01-03 | 分类于 Jekyll

很早之前就想弄一个自己的博客玩一玩,不过由于各种原因,主要还是本人比较懒,一直拖到现在才开始整。以前写一些笔记和心得都是通过CSDN或Evernote,都是方便留着自己查看的,写的也不是特别细心。现在为了提升自己,强逼自己做出一些改变,用心总结并分享出去。言归正传,今天给大家分享的是如何搭建自己的博客!

  • content
    {:toc}

搭建博客的条件

  1. 注册GitHub的账号
  2. 安装Git环境、Jekyll环境
  3. 学会Markdown基础语法

Jekyll安装

1
$ gem install jekyll

注意:Jekyll环境在Mac上安装稍微遇到点问题(主要原因是Mac版本升级后对在其上面安装的程序的目录和权限有些变化,导致的一些乱七八糟的问题,随后百度、谷歌,也都解决了)。

手动搭建博客

目录结构

根据以下目录结构在你的username.github.io文件夹下建立以下目录结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
* _config.yml
- _drafts
* begin-with-the-crazy-ideas.textile
* on-simplicity-in-technology.markdown
- _includes
* footer.html
* header.html
- _layouts
* default.html
* post.html
- _posts
* 2015-1-3-jekyll-build-blog.md
* 2015-1-3-Markdown-Grammar.md
- _site
* index.html

可以一个个依次建立起来,然后再自己编写一个你想要的博客。

配置

项目需要配置的只有一个文件_config.yml,打开之后按照如下进行配置。

特别注意baseurl的配置。如果是***.github.io项目,不修改为空’’的话,会导致JS,CSS等静态资源无法找到的错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
name: 博客名称
email: 邮箱地址
author: 作者名
url: 个人网站
### baseurl修改为项目名,如果项目是'***.github.io',则设置为空''
baseurl: ""
resume_site: 个人简历网站
github: github地址
github_username: github用户名称
FB:
comments:
provider: duoshuo
duoshuo:
short_name: 多说账户
disqus:
short_name: Disqus账户

写文章

在./_posts目录下新建一个文件,可以创建文件夹并在文件夹中添加文件,方便维护。在新建文件中粘贴如下信息,并修改以下的titile,date,categories,tag的相关信息,添加* content {:toc}为目录相关信息,在进行正文书写前需要在目录和正文之间输入至少2行空行。然后按照正常的Markdown语法书写正文。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
---
layout: post
title: 标题
date: 2016-08-27 01:08:00 +0800
categories: Work
tag: Markdown
---
* content
{:toc}
正文......
正文......
正文......

查看效果

在终端执行如下命令:

1
$ jekyll server

打开浏览器并输入URLhttp://127.0.0.1:4000/,回车。

快速搭建博客

参考Jekyll-Now,可以快速搭建极简样式博客。
参考Jekyll主题,里面提供Jekyll的各种博客模版下载。

参考链接

  1. GitHub官网
  2. Jekyll-CN

Android-Component-Activity

发表于 2014-05-01 | 分类于 Android

Activity介绍

Android应用组件Activity是Android程序的呈现层,显示可视化的用户界面,并接收与用户交互所产生的界面事件。对于一个Android应用程序来说,可以包含一个或多个Activity,一般在程序启动后会呈现一个Activity,用于提示用户程序已经正常启动。当它不积极运行时,Activity可以被操作系统终止以节省内存。

Activity四种状态

  1. 活动状态,Activity在用户界面中处于最上层,完全能被用户看到,且能和用户进行交互。(前台进程)
  2. 暂停状态,Activity在界面上被部分遮挡,该Activity不再处于用户界面的最上层,且不能够与用户进行交互。(可见但非前台进程)
  3. 停止状态,Activity在界面上完全不能被用户看到,也就是说该Activity被其他Activity全部遮挡。(后台进程)
  4. 非活动状态,不在以上3种状态的Activity则处于非活动状态。

Activity生命周期

  1. onCreate: Activity被创建的时候调用(一般做初始化操作)
  2. onStart: Activity变成用户可见的时候调用
  3. onResume: 界面获取焦点的时候调用
  4. onPause: 界面失去焦点的时候调用
  5. onStop: Activity变成用户不可见的时候调用
  6. onDestroy: Activity被系统消毁的时候调用

Activity生命周期

如:同一个APP中一个Activity(A)跳转到另一个Activity(B)时:
A onPause (A先进入onPause状态,B才能创建)
B onCreate、onStart、onResume
A onStop
所以,为了防止界面间切换的卡顿,不要在onPause和onResume方法中些许耗时的操作。
注意:当界面A调用界面B时,如果界面B是以对话框形式来打开的话(也就是界面A还能看得见),界面A只会执行onPause方法,同时返回到界面A时,也只会执行onResume。

内存不足被销毁的生命周期

  1. onSaveInstanceState:Android系统因资源不足终止Activity前,调用此方法。用于保存Activity的状态信息,供onRestoreInstanceState()或onCreate()恢复时用。
  2. onRestoreInstanceState():恢复onSaveInstanceState()保存的Activity状态信息,在onStart()之后调用。

Activity异常状态的工作过程

configChanges配置

Activity在横竖屏切换的时候,会重走生命周期方法。
横竖屏切换activity不被消毁的设置:

android:configChanges=”orientation|keyboardHidden|screenSize” // 4.0以下只需前两个参数

注意:如果你的app一直是竖着显示的话,必须在所有Activity中设置:

android:screenOrientation=”portrait”

横竖屏切换时候activity的生命周期

  1. 不设置Activity的android:configChanges时,屏幕方向发生改变,会重新调用各个生命周期。
  2. 设置Activity的android:configChanges=“orientation|screenSize”时,屏幕方向发生改变,生命周期不会重新调用,只会执行onConfigurationChanged方法。(screenSize,API13新添加)

获取任务栈【代码】
ActivityService.getRunningTasks()

killProcess【代码】
android.os.Process.killProcess(android.os.Process.myPid()); // Process类在lang中也存在,所以加上包名调用
System.exit(0);

清单文件默认配置

1
2
3
4
<intent-filter>
<action android:name="android.intent.action.MAIN" /> // 启动程序优先启动
<category android:name="android.intent.category.LAUNCHER" /> // 桌面图标
</intent-filter>

两个配置同时存在才会在桌面创建图标且点击图标优先启动该Activity

Activity启动模式

  1. standard:标准模式

  2. singleTop:栈顶复用模式

    开启Activity时,会检查当前与之和匹配的Task的栈顶是否存在该Activity,如果存在,则复用,并切会回掉Activity.onNewIntent方法。

  3. singleTask:栈内复用模式

    开启Activity时,会检查当前与之匹配的Task的任务栈中是否已有该Activity,如果有,则将任务栈中的该Activity上面的所有Activity全部Remove,然后显示出该Activity,并且回掉onNewIntent方法。这种启动模式主要是为了让某些比较消耗资源且只需一个的Activity在任务栈中只存在一个,防止创建太多而造成内存不足等。

  4. singleInstance:单实例模式

    让同一个Activity在任务栈中只存在一个(换句话说就是整个手机内存中该Activity只会存在一个,如我的程序开了该Activity,别的程序再开启,也是同一个)底层实现是新建一个任务栈专门存放该Activity,在用户调用该Activity时,该任务栈就会被放到原任务栈的前面。

taskAffinity配置

TaskAffinity任务相关性。
主要和SingleTask启动模式或AllowTaskReparenting配置配对使用。

allowTaskReparenting配置

是否允许Activity从一个Task迁移到另一个Task。

Activity的Flags

  1. FLAT_ACTIVITY_SINGLE_TOP,和XML中singleTop启动模式相同。
  2. FLAG_ACTIVITY_NEW_TASK,和XML中singleTask启动模式相同。
  3. FLAT_ACTIVITY_CLEAR_TOP,
    在同一个任务栈中,所有位于它上面的Activity都会出栈。如果Activity使用standard启动模式,那么它连同它之上的Activity都要出栈,系统会创建新的Activity实例放入栈顶。
  4. FLAT_ACTIVITY_EXCLUDE_FROM_RECENTS,
    具有这个标记的Activity不会出现在历史Activity的列表中,当某些情况下我们不希望用户通过历史列表回到我们到Activity的时候,这个标记比较有用。它等同于XML中指定Activity的属性android:excludeFromRecents=“true”。

Activity常用方法

用于别的线程更新UI的操作:runOnUiThread(Runnable action),将更新ui的操作写到这个Runnable的run()中。该方法将会判断当前线程是否是创建UI的线程。如果是,则会直接运行。如果不是,将会跳到UI线程中执行。

Activity切换动画

overridePendingTransition(R.anim.tran_in, R.anim.tran_out);
其中overridePendingTransition是Activity的方法,传递两个动画的xml配置文件。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
tran_in.xml
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="500"
android:fromXDelta="100%p"
android:fromYDelta="0"
android:toXDelta="0"
android:toYDelta="0" >
</translate>
tran_out.xml
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="500"
android:fromXDelta="0"
android:fromYDelta="0"
android:toXDelta="-100%p"
android:toYDelta="0" >
</translate>

伪桌面(透明背景、无标题)

在清单文件中加入 android:theme=”@android:style/Theme.Translucent.NoTitleBar”

有关setContentView()方法的底层原理

首先每个控件都是不能自己设定宽高的,需要外面有一个ViewGroup包起来,设定宽高才有效。
而如果是这样的话,那样每个xml的最外层的ViewGroup的宽高都是无效的吗?其实不是,其实每个Activity默认就存在着一个FrameLayout。

Java-CountDownLatch

发表于 2014-04-05 | 分类于 Java

CountDownLatch是什么?

CountDownLatch 位于java.util.concurrent包下,是JDK5.0的新特性,根据包名便可知,它是用于处理多线程并发的工具类。

CountDownLatch能干什么?

CountDownLatch 能够使一个线程处于等待状态,直到它期望的其他线程达到预期,再恢复运行状态。

CountDownLatch的运行原理

CountDownLatch 是通过一个计数器来实现的,计数器的初始值为线程数。
每当一个线程完成任务后,计数器的值就会减1。当计数器的值为0时,它表示所线程已经完成任务,然后在闭锁上等待线程就恢复运行状态。

1
2
3
4
5
6
1. Main thread start
2. Create CountDownLatch for N threads
3. Create and start N threads
4. Main thread wait on latch
5. N threads completes there tasks are returns
6. Main thread resume execution

CountDownLatch原理

CountDownLatch主要API

  1. 构造函数

    1
    2
    //Constructs a CountDownLatch initialized with the given count.
    public void CountDownLatch(int count) {…}
  2. 让线程处于等待状态的函数

    1
    CountDownLatch.await()
  3. 让线程恢复执行状态的函数(cnt=0时,线程恢复运行状态)

    1
    CountDownLatch.countDown()

示例

主线程启动三个子线程,完成各自的验证工作后,主线程再继续运行,使用CountDownLatch进行实现。

主程序入口

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.atopom.jdk5.concurrent.countdownlatch;
public class _Main {
public static void main(String[] args) {
boolean result = false;
try {
result = ApplicationStartupUtil.checkExternalServices();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("External services validation completed !! Result was :: " + result);
}
}

运行结果

1
2
3
4
5
6
7
Checking Cache Service
Checking Database Service
Checking Network Service
Database Service is UP
Cache Service is UP
Network Service is UP
External services validation completed !! Result was :: true

代码地址

https://github.com/atopom/java_familiar_strange/tree/master/Code/CountDownLatchDemo

王亚楠

王亚楠

工作、分享、笔记

10 日志
8 分类
16 标签
GitHub
© 2017 王亚楠