Akioss Blog


  • 首页

  • 归档

Android Tips_Notification icons color

发表于 2016-06-02

将状态栏通知图标或者常驻图标改成深色的方法

5.0之后

Not since Lollipop. Starting with Android 5.0, the guidelines say:

Notification icons must be entirely white.

Even if they’re not, the system will only consider the alpha channel of your icon, rendering them white.

6.0之后

Viewpager切换及点击过渡动画

发表于 2016-05-17

前几天从dribble上看到一个动画,关于ViewPager的切换及点击动画,于是花时间写了个demo。

原图

最终效果

查看Github源码

对RecycleView-Adapter的简单封装

发表于 2016-03-16

对分页展示而言,RecyclerView已经可以完全替代ListView。这里只进行一下对其Adapter的简单封装。至于RecyclerView的特性,会重开几篇博客来学习

分析

  • 目的:目的是为了减少Adapter具体实现的代码,使代码整洁,易于维护。
  • 分析:与listview面向view的adapter不同,recyclerview面向的是viewholder。所以BaseRecyclerAdapter需要两个泛型声明,一个是数据源,一个是viewholder。
    onCreateViewHoler()方法中需要生成具体的viewhoder实例,所以暂时不去对其做封装。对onBindViewHoler()方法的操作分为两步,一步是bindItemData()将数据填充至itemview,下一步是setupOnItemClick()设置每个item的点击监听。其它方法都是对便利性的扩展,可以根据不同情况添加。
  • 并没有对viewhoder动刀子,考虑到目前情况下在AS中利用ButterKnife可以高效的生成viewholder,对开发效率影响不大。不过ButterKnife毕竟是通过运行时反射对view进行绑定的,对效率是有影响,但是AS2.0之后发现Google有扶正DataBindding的趋势,所以综合考虑下,还是没有对viewholder动刀子。但是会在RecyclerView深入研究时思考这个问题。

BaseRecyclerViewAdapter

话不多说,上代码

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
public abstract class BaseRecyclerAdapter<T, VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> {

protected List<T> mDatas = new ArrayList<>();
protected Context mContext;
protected LayoutInflater inflater;
AdapterView.OnItemClickListener mItemClickListener;

public BaseRecyclerAdapter(List<T> mDatas, Context mContext) {
this.mDatas = mDatas;
this.mContext = mContext;
inflater = LayoutInflater.from(mContext);
}

@Override
public void onBindViewHolder(VH holder, int position) {
final T item = getItem(position);
bindItemData(holder, item, position);
setupOnItemClick(holder, position);
}

protected abstract void bindItemData(VH viewHolder, T data, int position);

protected void setupOnItemClick(final VH viewHolder, final int position) {
if (mItemClickListener != null) {
viewHolder.itemView.setOnClickListener(new View.OnClickListener() {

@Override
public void onClick(View v) {
mItemClickListener.onItemClick(null, viewHolder.itemView, position, position);
}
});
}
}

@Override
public int getItemCount() {
return mDatas.size();
}

public T getItem(int position) {
position = Math.max(0, position);
return mDatas.get(position);
}

public List<T> getDataSource() {
return mDatas;
}

public void addData(List<T> newItems) {
if (newItems != null) {
mDatas.addAll(newItems);
notifyDataSetChanged();
}
}

public void updateListViewData(List<T> lists) {
mDatas.clear();
if (lists != null) {
mDatas.addAll(lists);
notifyDataSetChanged();
}
}

public void setOnItemClickListener(AdapterView.OnItemClickListener listener) {
this.mItemClickListener = listener;
}

}

使用

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
public class NotesAdapter extends BaseRecyclerAdapter<NoteItem, NotesAdapter.NotesViewHolder> {

public NotesAdapter(List<NoteItem> mDatas, Context mContext) {
super(mDatas, mContext);
}

@Override
public NotesViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View contentView = LayoutInflater.from(mContext)
.inflate(R.layout.recycler_item_note, parent, false);
NotesViewHolder vh = new NotesViewHolder(contentView);
return vh;
}

@Override
protected void bindItemData(NotesViewHolder viewHolder, NoteItem data, int position) {
viewHolder.titleTxt.setText(data.getTitle());
viewHolder.timeTxt.setText(data.getCreatedTime());
viewHolder.tagTxt.setText(data.getTags().size() != 0 ? data.getTags().get(0) : "other");
}

static class NotesViewHolder extends RecyclerView.ViewHolder {
@Bind(R.id.title_txt)
TextView titleTxt;
@Bind(R.id.time_txt)
TextView timeTxt;
@Bind(R.id.tag_txt)
TextView tagTxt;

NotesViewHolder(View view) {
super(view);
ButterKnife.bind(this, view);
}

}
}

一个有趣的问题(java静态字段)

发表于 2016-03-08

问题

在jake wharton的Twitter上看到他提得这样一个问题:
ques
于是测试了一下,发现很有趣,于是记录下来。

测试类

两个测试类如下

1
2
3
class TestMethodA {
static String name = "akioss";
}

1
2
3
class TestMethodB {
static final String name = "akioss";
}

查看字节码

通过javap指令查看两个类的字节码

classA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class com.akioss.TestMethodA {
static java.lang.String name;

com.akioss.TestMethodA();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return

static {};
Code:
0: ldc #2 // String akioss
2: putstatic #3 // Field name:Ljava/lang/String;
5: return
}

可以看到除了默认的构造方法之外,还有一个静态代码块,执行了putstatic指令

classB

1
2
3
4
5
6
7
8
9
class com.akioss.TestMethodB {
static final java.lang.String name;

com.akioss.TestMethodB();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
}

Tips

之所以会产生这样的情况,就是在于final修饰符(好吧,这是废话 - -!),在讲明为何会出现上述情况之前,先来温习一下static和final两个的区别吧。

  • static:static修饰成员变量时,并不会随着类的实例化再去分配空间,从上边putstatic指令也可以看出来,但有一点它是变量。
  • final+static:final和static一同使用,那么被其修饰的值便成了全局常量,即不可变了。
    所以大胆猜测final+static修饰的值一开始就会被jvm分配到常量池,不会在类中去作这一步操作。而static修饰的成员变量,实际是在类实例化的时候执行了一段static代码块,将成员变量putstatic,为了验证,改写了一下TestMethodA.java

    TestMethodA

    1
    2
    3
    4
    5
    6
    7
    class TestMethodA {
    static String name = "akioss";

    static{
    name = "jake";
    }
    }

其字节码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class com.akioss.TestMethodA {
static java.lang.String name;

com.akioss.TestMethodA();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return

static {};
Code:
0: ldc #2 // String akioss
2: putstatic #3 // Field name:Ljava/lang/String;
5: ldc #4 // String jake
7: putstatic #3 // Field name:Ljava/lang/String;
10: return
}

可以发现,同一个类中,在static代码块中对静态成员变量name赋值时,与声明name成员变量走的同一代码块。

okHttp3之Cookies管理及持久化

发表于 2016-03-08

okHttp3正式版刚发布了没几天,正好重构之前的代码,于是第一时间入坑了。对okHttp3的一些改变,会陆续写下来,这是第一篇Cookies管理及持久化。

Cookies管理

OkHttp的源码过于复杂,感兴趣的同学可以自行阅读,这里只针对HttpEngineer类进行分析,从字面意思即可看出这个类负责http请求的request、response等等操作的处理,而cookies管理也是随着http请求的request、response来处理。

3.0之前

先看networkRequest方法,在里面通过client.getCookieHandler()函数获得了CookieHandler对象,通过该对象拿到cookie并设置到请求头里,请求结束后取得响应后通过networkResponse.headers()函数将请求头获得传入receiveHeaders函数,并将取得的cookie存入getCookieHandler得到的一个CookieHandler对象中去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private Request networkRequest(Request request) throws IOException {
Request.Builder result = request.newBuilder();

//例行省略.....

CookieHandler cookieHandler = client.getCookieHandler();
if (cookieHandler != null) {
// Capture the request headers added so far so that they can be offered to the CookieHandler.
// This is mostly to stay close to the RI; it is unlikely any of the headers above would
// affect cookie choice besides "Host".
Map<String, List<String>> headers = OkHeaders.toMultimap(result.build().headers(), null);

Map<String, List<String>> cookies = cookieHandler.get(request.uri(), headers);

// Add any new cookies to the request.
OkHeaders.addCookies(result, cookies);
}

//例行省略....

return result.build();
}

1
2
3
4
5
6
7
public void readResponse() throws IOException {
//例行省略....

receiveHeaders(networkResponse.headers());

//例行省略....
}
1
2
3
4
5
6
public void receiveHeaders(Headers headers) throws IOException {
CookieHandler cookieHandler = client.getCookieHandler();
if (cookieHandler != null) {
cookieHandler.put(userRequest.uri(), OkHeaders.toMultimap(headers, null));
}
}

CookieHandler对象是OkHttpClient类中的一个属性,传入了这个对象,那么OkHttp就会对cookie进行自动管理

1
2
3
4
5
6
7
8
9
private CookieHandler cookieHandler;
public OkHttpClient setCookieHandler(CookieHandler cookieHandler) {
this.cookieHandler = cookieHandler;
return this;
}

public CookieHandler getCookieHandler() {
return cookieHandler;
}

1
2
OkHttpClient client = new OkHttpClient();
client.setCookieHandler(CookieHandler cookieHanlder);

3.0之后

而在OkHttp3中,对cookie而言,新增了两个类Cookiejar、Cookie两个类,在了解这两个类之前,先去看一下HttpEngine关于cookie管理的变化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private Request networkRequest(Request request) throws IOException {
Request.Builder result = request.newBuilder();

//例行省略....

List<Cookie> cookies = client.cookieJar().loadForRequest(request.url());
if (!cookies.isEmpty()) {
result.header("Cookie", cookieHeader(cookies));
}

//例行省略....

return result.build();
}

1
2
3
4
5
6
7
8
9
10
11
private String cookieHeader(List<Cookie> cookies) {
StringBuilder cookieHeader = new StringBuilder();
for (int i = 0, size = cookies.size(); i < size; i++) {
if (i > 0) {
cookieHeader.append("; ");
}
Cookie cookie = cookies.get(i);
cookieHeader.append(cookie.name()).append('=').append(cookie.value());
}
return cookieHeader.toString();
}
1
2
3
4
5
6
7
8
public void receiveHeaders(Headers headers) throws IOException {
if (client.cookieJar() == CookieJar.NO_COOKIES) return;

List<Cookie> cookies = Cookie.parseAll(userRequest.url(), headers);
if (cookies.isEmpty()) return;

client.cookieJar().saveFromResponse(userRequest.url(), cookies);
}

通过以上几个关键方法,可以很明显的感觉到作者的意图了,为了更加自由定制化的cookie管理。其中loadForRequest()、saveFromResponse()这两个方法最为关键,分别是在发送时向request header中加入cookie,在接收时,读取response header中的cookie。现在再去看Cookiejar这个类,就很好理解了

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
public interface CookieJar {
/** A cookie jar that never accepts any cookies. */
CookieJar NO_COOKIES = new CookieJar() {
@Override public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
}

@Override public List<Cookie> loadForRequest(HttpUrl url) {
return Collections.emptyList();
}
};

/**
* Saves {@code cookies} from an HTTP response to this store according to this jar's policy.
*
* <p>Note that this method may be called a second time for a single HTTP response if the response
* includes a trailer. For this obscure HTTP feature, {@code cookies} contains only the trailer's
* cookies.
*/

void saveFromResponse(HttpUrl url, List<Cookie> cookies);

/**
* Load cookies from the jar for an HTTP request to {@code url}. This method returns a possibly
* empty list of cookies for the network request.
*
* <p>Simple implementations will return the accepted cookies that have not yet expired and that
* {@linkplain Cookie#matches match} {@code url}.
*/

List<Cookie> loadForRequest(HttpUrl url);
}

so!在OkHttpClient创建时,传入这个CookieJar的实现,就能完成对Cookie的自动管理了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
OkHttpClient client = new OkHttpClient.Builder()
.cookieJar(new CookieJar() {
private final HashMap<HttpUrl, List<Cookie>> cookieStore = new HashMap<>();

@Override
public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
cookieStore.put(url, cookies);
}

@Override
public List<Cookie> loadForRequest(HttpUrl url) {
List<Cookie> cookies = cookieStore.get(url);
return cookies != null ? cookies : new ArrayList<Cookie>();
}
})
.build();

Cookies持久化

对Cookies持久化的方案,与之前版本并无很大区别,还是参考android-async-http这个库,主要参考其中两个类:

  • PersistentCookieStore
  • SerializableHttpCookie
    与之前版本的区别是要将对java.net.HttpCookie这个类的缓存处理换成对okhttp3.Cookie的处理,其他方面几乎一样。
    废话不多说了,直接上代码

    SerializableOkHttpCookies

    主要做两件事:
  • 将Cookie对象输出为ObjectStream
  • 将ObjectStream序列化成Cookie对象
    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
    public class SerializableOkHttpCookies implements Serializable {

    private transient final Cookie cookies;
    private transient Cookie clientCookies;

    public SerializableOkHttpCookies(Cookie cookies) {
    this.cookies = cookies;
    }

    public Cookie getCookies() {
    Cookie bestCookies = cookies;
    if (clientCookies != null) {
    bestCookies = clientCookies;
    }
    return bestCookies;
    }

    private void writeObject(ObjectOutputStream out) throws IOException {
    out.writeObject(cookies.name());
    out.writeObject(cookies.value());
    out.writeLong(cookies.expiresAt());
    out.writeObject(cookies.domain());
    out.writeObject(cookies.path());
    out.writeBoolean(cookies.secure());
    out.writeBoolean(cookies.httpOnly());
    out.writeBoolean(cookies.hostOnly());
    out.writeBoolean(cookies.persistent());
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
    String name = (String) in.readObject();
    String value = (String) in.readObject();
    long expiresAt = in.readLong();
    String domain = (String) in.readObject();
    String path = (String) in.readObject();
    boolean secure = in.readBoolean();
    boolean httpOnly = in.readBoolean();
    boolean hostOnly = in.readBoolean();
    boolean persistent = in.readBoolean();
    Cookie.Builder builder = new Cookie.Builder();
    builder = builder.name(name);
    builder = builder.value(value);
    builder = builder.expiresAt(expiresAt);
    builder = hostOnly ? builder.hostOnlyDomain(domain) : builder.domain(domain);
    builder = builder.path(path);
    builder = secure ? builder.secure() : builder;
    builder = httpOnly ? builder.httpOnly() : builder;
    clientCookies =builder.build();
    }
    }

PersistentCookieStore

根据一定的规则去缓存或者获取Cookie:

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
public class PersistentCookieStore {
private static final String LOG_TAG = "PersistentCookieStore";
private static final String COOKIE_PREFS = "Cookies_Prefs";

private final Map<String, ConcurrentHashMap<String, Cookie>> cookies;
private final SharedPreferences cookiePrefs;


public PersistentCookieStore(Context context) {
cookiePrefs = context.getSharedPreferences(COOKIE_PREFS, 0);
cookies = new HashMap<>();

//将持久化的cookies缓存到内存中 即map cookies
Map<String, ?> prefsMap = cookiePrefs.getAll();
for (Map.Entry<String, ?> entry : prefsMap.entrySet()) {
String[] cookieNames = TextUtils.split((String) entry.getValue(), ",");
for (String name : cookieNames) {
String encodedCookie = cookiePrefs.getString(name, null);
if (encodedCookie != null) {
Cookie decodedCookie = decodeCookie(encodedCookie);
if (decodedCookie != null) {
if (!cookies.containsKey(entry.getKey())) {
cookies.put(entry.getKey(), new ConcurrentHashMap<String, Cookie>());
}
cookies.get(entry.getKey()).put(name, decodedCookie);
}
}
}
}
}

protected String getCookieToken(Cookie cookie) {
return cookie.name() + "@" + cookie.domain();
}

public void add(HttpUrl url, Cookie cookie) {
String name = getCookieToken(cookie);

//将cookies缓存到内存中 如果缓存过期 就重置此cookie
if (!cookie.persistent()) {
if (!cookies.containsKey(url.host())) {
cookies.put(url.host(), new ConcurrentHashMap<String, Cookie>());
}
cookies.get(url.host()).put(name, cookie);
} else {
if (cookies.containsKey(url.host())) {
cookies.get(url.host()).remove(name);
}
}

//讲cookies持久化到本地
SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
prefsWriter.putString(url.host(), TextUtils.join(",", cookies.get(url.host()).keySet()));
prefsWriter.putString(name, encodeCookie(new SerializableOkHttpCookies(cookie)));
prefsWriter.apply();
}

public List<Cookie> get(HttpUrl url) {
ArrayList<Cookie> ret = new ArrayList<>();
if (cookies.containsKey(url.host()))
ret.addAll(cookies.get(url.host()).values());
return ret;
}

public boolean removeAll() {
SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
prefsWriter.clear();
prefsWriter.apply();
cookies.clear();
return true;
}

public boolean remove(HttpUrl url, Cookie cookie) {
String name = getCookieToken(cookie);

if (cookies.containsKey(url.host()) && cookies.get(url.host()).containsKey(name)) {
cookies.get(url.host()).remove(name);

SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
if (cookiePrefs.contains(name)) {
prefsWriter.remove(name);
}
prefsWriter.putString(url.host(), TextUtils.join(",", cookies.get(url.host()).keySet()));
prefsWriter.apply();

return true;
} else {
return false;
}
}

public List<Cookie> getCookies() {
ArrayList<Cookie> ret = new ArrayList<>();
for (String key : cookies.keySet())
ret.addAll(cookies.get(key).values());

return ret;
}

/**
* cookies 序列化成 string
*
* @param cookie 要序列化的cookie
* @return 序列化之后的string
*/

protected String encodeCookie(SerializableOkHttpCookies cookie) {
if (cookie == null)
return null;
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
ObjectOutputStream outputStream = new ObjectOutputStream(os);
outputStream.writeObject(cookie);
} catch (IOException e) {
Log.d(LOG_TAG, "IOException in encodeCookie", e);
return null;
}

return byteArrayToHexString(os.toByteArray());
}

/**
* 将字符串反序列化成cookies
*
* @param cookieString cookies string
* @return cookie object
*/

protected Cookie decodeCookie(String cookieString) {
byte[] bytes = hexStringToByteArray(cookieString);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
Cookie cookie = null;
try {
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
cookie = ((SerializableOkHttpCookies) objectInputStream.readObject()).getCookies();
} catch (IOException e) {
Log.d(LOG_TAG, "IOException in decodeCookie", e);
} catch (ClassNotFoundException e) {
Log.d(LOG_TAG, "ClassNotFoundException in decodeCookie", e);
}

return cookie;
}

/**
* 二进制数组转十六进制字符串
*
* @param bytes byte array to be converted
* @return string containing hex values
*/

protected String byteArrayToHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder(bytes.length * 2);
for (byte element : bytes) {
int v = element & 0xff;
if (v < 16) {
sb.append('0');
}
sb.append(Integer.toHexString(v));
}
return sb.toString().toUpperCase(Locale.US);
}

/**
* 十六进制字符串转二进制数组
*
* @param hexString string of hex-encoded values
* @return decoded byte array
*/

protected byte[] hexStringToByteArray(String hexString) {
int len = hexString.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character.digit(hexString.charAt(i + 1), 16));
}
return data;
}
}

最终效果

完成对Cookie持久化之后,就可以对Cookiejar进行进一步修改了,最终效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 自动管理Cookies
*/

private class CookiesManager implements CookieJar {
private final PersistentCookieStore cookieStore = new PersistentCookieStore(getApplicationContext());

@Override
public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
if (cookies != null && cookies.size() > 0) {
for (Cookie item : cookies) {
cookieStore.add(url, item);
}
}
}

@Override
public List<Cookie> loadForRequest(HttpUrl url) {
List<Cookie> cookies = cookieStore.get(url);
return cookies;
}
}

Tips

在这样做之前,尝试了使用Interceptor和NetWorkInterceptor在Http请求request和response时,拦截响应链,加入对Cookie的管理。so!接下来可能会详细介绍下Interceptor这个非常酷的实现。

Akioss

Akioss

如果不再写代码 也许是个好厨子

5 日志
2 标签
© 2016 Akioss
由 Hexo 强力驱动
主题 - NexT.Mist