糖葫芦


  • Startseite

  • Tags

  • Archiv

  • Suche

INSTALL_FAILED_TEST_ONLY

Veröffentlicht am 2018-12-17 |

莫名其妙更新了 studio的配置,然后打包签名,利用adb install xx.apk 安装就出现了 INSTALL_FAILED_TEST_ONLY,安装失败。心想也没干什么啊,查阅相关资料后,
原因是:Android Studio 3.0会在debug apk的manifest文件application标签里自动添加 android:testOnly="true"属性

网上有说:将这个android:testOnly="false" 添加到 Application 标签中,但是还是没有成功。

  • 解决办法一:
1
adb install -t xx.apk

需要加上-t. 问题是解决了,可以安装成功了,但是对于以前qq 发送文件还是不能安装成功。

  • 解决办法二:
    还是点击普通的绿色按钮安装,此时生成一个apk,我们再点击 Build---> Build Apk 此时拿出 output-->apk-->xx.apk 可以正常安装了。

Moved Permanently

Veröffentlicht am 2018-12-14 |

关于下载文件中包含重定向问题的解决方案。
之前下载文件没问题,但是今天遇到一个下载文件里包含重定向,还是用之前的下载文件的方法就不行了,默认浏览器打开下载,是自动重定向的。

这是我以前的下载文件的代码:

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
class DownloadTask extends AsyncTask<String, Integer, String> {

@Override
protected String doInBackground(String... params) {
String result = "";
OutputStream output = null;
try {
URL url = new URL(params[0]);
// 创建一个HttpURLConnection连接
URLConnection urlConn = (URLConnection ) url
.openConnection();

InputStream input = urlConn.getInputStream();

// 文件夹
File dir = new File(Constant.K0_VOICE_DIR);
if (!dir.exists()) {
dir.mkdirs();
}
// 本地文件
File file = new File(Constant.K0_VOICE_DIR + params[1]);
if (!file.exists()) {
file.createNewFile();
// 写入本地
output = new FileOutputStream(file);
result = file.getAbsolutePath();
byte buffer[] = new byte[4*1024];
int inputSize = -1;
while ((inputSize = input.read(buffer)) != -1) {
output.write(buffer, 0, inputSize);
}
output.flush();
}

} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
output.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return result;
}

@Override
protected void onPreExecute() {
super.onPreExecute();
}

@Override
protected void onPostExecute(String result) {

//下载完成.....

super.onPostExecute(result);
}
}

按照这个下载,你会发现下载的例如mp3 会无法播放,点开查看:

1
< a href="https:xxxx">Moved Permanently</ a>.

href 里包含一个新的链接。需要添加如下代码,获取新的链接地址下载:

1
2
3
4
5
6
7
 URLConnection urlConn = (URLConnection ) url
.openConnection();
//下载的文件中含有重定向链接
String redirect = urlConn.getHeaderField("Location");
if (redirect != null){
urlConn = new URL(redirect).openConnection();
}

奉上我在 stackoverflow 上找到的答案:
image.png

okhttp初探

Veröffentlicht am 2018-12-12 |

使用依赖最新的版本 implementation("com.squareup.okhttp3:okhttp:3.12.0")

网站:https://square.github.io/okhttp/

get 请求

1
2
3
4
5
6
7
8
9
10
11
public String getRequests() throws IOException {
OkHttpClient client = new OkHttpClient();

String url = "http://www.baidu.com";
Request request = new Request.Builder()
.url(url)
.build();

Response response = client.newCall(request).execute();
return response.body().string();
}

put 请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
final MediaType JSON = MediaType.parse("application/json; charset=utf-8");

String json = "http://www.roundsapp.com/post";
String url = bowlingJson("Jesse","Jake");//json字符串

OkHttpClient client = new OkHttpClient();
RequestBody body = RequestBody.create(JSON,json);

Request request = new Request.Builder()
.url(url)
.post(body)
.build();

Response response = client.newCall(request).execute();
return response.body().string();

总结

1.创建 OkHttpClient 对象

2.创建 Request

3.同步或者异步发出请求

4.得到 Response

同步执行:

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
@Override public Response execute() throws IOException {
//1.判断是否执行过,每个Call请求只能请求一次,如果需要重复执行可以使用clone 方法
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
timeout.enter();
eventListener.callStart(this);
try {
//2.获取分发器 dispatcher, 执行 execute
client.dispatcher().executed(this);
//3.获取网络请求的结果
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
e = timeoutExit(e);
eventListener.callFailed(this, e);
throw e;
} finally {
//4.最后通知 dispatcher 执行完毕
client.dispatcher().finished(this);
}
}

clone 方法:

1
2
3
@Override public RealCall clone() {
return RealCall.newRealCall(client, originalRequest, forWebSocket);
}

Dispatcher.java

1
2
//当异步线程执行时,会依照不同的策略执行
* Policy on when async requests are executed.

拦截器

OkHttpClinet 拿到返回结果,是通过 getResponseWithInterceptorChain() 方法,从名字上我们可以大概猜测方法的语义,通过一系列的拦截器链得到结果,我们可以看下这个方法:

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
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.一个集合存储所有的拦截器啊
List<Interceptor> interceptors = new ArrayList<>();
//用户自定义的拦截器,能拦截所有的请求
interceptors.addAll(client.interceptors());
//猜测是重试机制
interceptors.add(retryAndFollowUpInterceptor);
//请求的头信息,cookie,gzip
interceptors.add(new BridgeInterceptor(client.cookieJar()));
//猜测是缓存
interceptors.add(new CacheInterceptor(client.internalCache()));
//开始与目标服务器建立连接,获得RealConnection
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
//用户自定义的 interceptor, 仅在生产网络请求时生效
interceptors.addAll(client.networkInterceptors());//猜测是网络请求
}
//向服务器发出一次网络请求的地方
interceptors.add(new CallServerInterceptor(forWebSocket));

Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());

return chain.proceed(originalRequest);
}

Interceptor 是 OkHttp 最核心的一个东西,采用了责任链的设计模式,除了日常负责拦截请求进行一些额外处理,例如 cookie, 实际上它把网络请求,缓存,透明压缩等功能都统一在一起,每个功能都是一个 Interceptor , 它们再连接称为 Intercepteor.Chain 拦截链条,环环相扣,最终完成一次网络请求。

RealInterceptorChain.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
/**
* A concrete interceptor chain that carries the entire interceptor chain: all application
* interceptors, the OkHttp core, all network interceptors, and finally the network caller.
*/
public final class RealInterceptorChain implements Interceptor.Chain {
private final List<Interceptor> interceptors;//所有的拦截器集合
private final StreamAllocation streamAllocation;
private final HttpCodec httpCodec;
private final RealConnection connection;
private final int index;
private final Request request;
private final Call call;
private final EventListener eventListener;
private final int connectTimeout;
private final int readTimeout;
private final int writeTimeout;
private int calls;

public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
HttpCodec httpCodec, RealConnection connection, int index, Request request, Call call,
EventListener eventListener, int connectTimeout, int readTimeout, int writeTimeout) {
this.interceptors = interceptors;
this.connection = connection;
this.streamAllocation = streamAllocation;
this.httpCodec = httpCodec;
this.index = index;
this.request = request;
this.call = call;
this.eventListener = eventListener;
this.connectTimeout = connectTimeout;
this.readTimeout = readTimeout;
this.writeTimeout = writeTimeout;
}

@Override public Connection connection() {
return connection;
}

@Override public int connectTimeoutMillis() {
return connectTimeout;
}

@Override public Interceptor.Chain withConnectTimeout(int timeout, TimeUnit unit) {
int millis = checkDuration("timeout", timeout, unit);
return new RealInterceptorChain(interceptors, streamAllocation, httpCodec, connection, index,
request, call, eventListener, millis, readTimeout, writeTimeout);
}

@Override public int readTimeoutMillis() {
return readTimeout;
}

@Override public Interceptor.Chain withReadTimeout(int timeout, TimeUnit unit) {
int millis = checkDuration("timeout", timeout, unit);
return new RealInterceptorChain(interceptors, streamAllocation, httpCodec, connection, index,
request, call, eventListener, connectTimeout, millis, writeTimeout);
}

@Override public int writeTimeoutMillis() {
return writeTimeout;
}

@Override public Interceptor.Chain withWriteTimeout(int timeout, TimeUnit unit) {
int millis = checkDuration("timeout", timeout, unit);
return new RealInterceptorChain(interceptors, streamAllocation, httpCodec, connection, index,
request, call, eventListener, connectTimeout, readTimeout, millis);
}

public StreamAllocation streamAllocation() {
return streamAllocation;
}

public HttpCodec httpStream() {
return httpCodec;
}

@Override public Call call() {
return call;
}

public EventListener eventListener() {
return eventListener;
}

@Override public Request request() {
return request;
}

@Override public Response proceed(Request request) throws IOException {
return proceed(request, streamAllocation, httpCodec, connection);
}

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();

calls++;

// If we already have a stream, confirm that the incoming request will use it.
if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must retain the same host and port");
}

// If we already have a stream, confirm that this is the only call to chain.proceed().
if (this.httpCodec != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}

// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);

// Confirm that the next interceptor made its required call to chain.proceed().
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}

// Confirm that the intercepted response isn't null.
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}

if (response.body() == null) {
throw new IllegalStateException(
"interceptor " + interceptor + " returned a response with no body");
}

return response;
}
}

这个方法我没看懂,网上说是传递参数和执行拦截器,并且设定条件,每个拦截器都会执行 proceed 方法。

拦截器:RetryAndFollowUpInterceptor.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
/**
* This interceptor recovers from failures and follows redirects as necessary. It may throw an
* {@link IOException} if the call was canceled.
这个拦截器必要时会重试和重定向,可能会抛出异常在call被取消的时候
*/
public final class RetryAndFollowUpInterceptor implements Interceptor {
....

@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Call call = realChain.call();
EventListener eventListener = realChain.eventListener();

StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;

int followUpCount = 0;
Response priorResponse = null;
while (true) {
if (canceled) {//被取消
streamAllocation.release();
throw new IOException("Canceled");
}
}
  • BridgeInterceptor.java

    基本上请求的头信息,Cookie 压缩: gzip

    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
    /**
    * Bridges from application code to network code. First it builds a network request from a user
    * request. Then it proceeds to call the network. Finally it builds a user response from the network
    * response.
    */
    public final class BridgeInterceptor implements Interceptor {
    private final CookieJar cookieJar;
    @Override public Response intercept(Chain chain) throws IOException {
    Request userRequest = chain.request();
    Request.Builder requestBuilder = userRequest.newBuilder();

    RequestBody body = userRequest.body();
    if (body != null) {
    MediaType contentType = body.contentType();
    if (contentType != null) {
    requestBuilder.header("Content-Type", contentType.toString());
    }

    long contentLength = body.contentLength();
    if (contentLength != -1) {
    requestBuilder.header("Content-Length", Long.toString(contentLength));
    requestBuilder.removeHeader("Transfer-Encoding");
    } else {
    requestBuilder.header("Transfer-Encoding", "chunked");
    requestBuilder.removeHeader("Content-Length");
    }
    }

    if (userRequest.header("Host") == null) {
    requestBuilder.header("Host", hostHeader(userRequest.url(), false));
    }

    if (userRequest.header("Connection") == null) {
    requestBuilder.header("Connection", "Keep-Alive");
    }

    // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
    // the transfer stream.
    boolean transparentGzip = false;
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
    transparentGzip = true;
    requestBuilder.header("Accept-Encoding", "gzip");
    }

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

    if (userRequest.header("User-Agent") == null) {
    requestBuilder.header("User-Agent", Version.userAgent());
    }

    Response networkResponse = chain.proceed(requestBuilder.build());

    HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());

    Response.Builder responseBuilder = networkResponse.newBuilder()
    .request(userRequest);

    if (transparentGzip
    && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
    && HttpHeaders.hasBody(networkResponse)) {
    GzipSource responseBody = new GzipSource(networkResponse.body().source());
    Headers strippedHeaders = networkResponse.headers().newBuilder()
    .removeAll("Content-Encoding")
    .removeAll("Content-Length")
    .build();
    responseBuilder.headers(strippedHeaders);
    String contentType = networkResponse.header("Content-Type");
    responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
    }

    return responseBuilder.build();
    }

    CacheInterceptor.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
    /** Serves requests from the cache and writes responses to the cache. */
    public final class CacheInterceptor implements Interceptor {
    final InternalCache cache;

    public CacheInterceptor(InternalCache cache) {
    this.cache = cache;
    }

    @Override public Response intercept(Chain chain) throws IOException {
    Response cacheCandidate = cache != null
    ? cache.get(chain.request())
    : null;

    long now = System.currentTimeMillis();

    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;
    //从缓存中获取 response
    Response cacheResponse = strategy.cacheResponse;

    if (cache != null) {
    cache.trackResponse(strategy);
    }

    if (cacheCandidate != null && cacheResponse == null) {
    closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }

    // If we're forbidden from using the network and the cache is insufficient, fail.
    if (networkRequest == null && cacheResponse == null) {
    return new Response.Builder()
    .request(chain.request())
    .protocol(Protocol.HTTP_1_1)
    .code(504)
    .message("Unsatisfiable Request (only-if-cached)")
    .body(Util.EMPTY_RESPONSE)
    .sentRequestAtMillis(-1L)
    .receivedResponseAtMillis(System.currentTimeMillis())
    .build();
    }

    // If we don't need the network, we're done.
    if (networkRequest == null) {
    return cacheResponse.newBuilder()
    .cacheResponse(stripBody(cacheResponse))
    .build();
    }

    Response networkResponse = null;
    try {
    networkResponse = chain.proceed(networkRequest);
    } finally {
    // If we're crashing on I/O or otherwise, don't leak the cache body.
    if (networkResponse == null && cacheCandidate != null) {
    closeQuietly(cacheCandidate.body());
    }
    }

    // If we have a cache response too, then we're doing a conditional get.
    if (cacheResponse != null) {
    if (networkResponse.code() == HTTP_NOT_MODIFIED) {
    Response response = cacheResponse.newBuilder()
    .headers(combine(cacheResponse.headers(), networkResponse.headers()))
    .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
    .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
    .cacheResponse(stripBody(cacheResponse))
    .networkResponse(stripBody(networkResponse))
    .build();
    networkResponse.body().close();

    // Update the cache after combining headers but before stripping the
    // Content-Encoding header (as performed by initContentStream()).
    cache.trackConditionalCacheHit();
    cache.update(cacheResponse, response);
    return response;
    } else {
    closeQuietly(cacheResponse.body());
    }
    }

    Response response = networkResponse.newBuilder()
    .cacheResponse(stripBody(cacheResponse))
    .networkResponse(stripBody(networkResponse))
    .build();

    if (cache != null) {
    if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
    // Offer this request to the cache.
    CacheRequest cacheRequest = cache.put(response);
    return cacheWritingResponse(cacheRequest, response);
    }

    if (HttpMethod.invalidatesCache(networkRequest.method())) {
    try {
    cache.remove(networkRequest);
    } catch (IOException ignored) {
    // The cache cannot be written.
    }
    }
    }

    return response;
    }
    .....

    连接池

okHttp 利用连接池来复用连接,避免反复握手建立连接,并且具备在合适的时候挥手连接的能力。

CAS机制

Veröffentlicht am 2018-12-11 |

背景介绍:CAS 完整名称为 : “Compare and Swap” 比较并替换。

里面涉及三个基本操作数,内存地址值:V;旧的预期值:A;新的预期值:B。

规则:当更新变量时,只有当内存地址里的值V = 旧的预期值A,才能被成功的更新为B。

举个例子:

  • 在内存地址V中,存在一个变量值为10的值:

    image.png

  • 此时线程一,线程二 ,同时去操作这个变量,例如都想+1;

    image.png

    对于线程一来说: A:10; B: 11; V: 10

    但是由于线程二比线程一执行的更快,此时线程二已经把内存地址V中的10更新为11.

​ 那么当线程一再次执行更新操作,发现 V = 11,A = 10, V≠A,更新失败。

​ 此时线程一需要重新读取内存中的值,并重新计算要更新的新值,计算后得出:

​ A= 11,B= 12. 重新提交请求,这个过程称为自旋。

image.png

​ 首先Compare V== A, 然后进行swap , V = 12,被更新为12.

从思想上来说,syncronized 属于悲观锁,认为并发情况非常严重,CAS 机制属于乐观锁,认为并发情况并不是一种特别严重的情况,发生问题,不断尝试更新即可。

使用场景:Atomic系列类,以及Lock系列类的底层实现;不适合高并发的场景,高并发还是 syncronized

缺点

  • CPU消耗大

    如果在多个线程并发去更新一个变量,多个线程同时去重复尝试更新某一个变量,却又一直不成功,循环往复就会给CPU造成很大的压力。

  • 不能保证代码块的原子性

    CAS 可以保证一个变量的原子性,却无法保证整个代码块的原子性。

  • ABA问题—–最大的问题

ABA 问题

举例说明:

假设有三个线程都要对内存地址V 中的 A 进行操作,

image.png

线程一:旧的预期值:A;新的预期值:B

线程二:旧的预期值:A;新的预期值:B

线程三:还未开始工作

  1. 线程一首先进行了操作,顺利将A更新为B:
    image.png

此时线程三上来了,它是:旧的预期值:B;新的预期值:A,此时发现:

image.png

​ V(B) == B(B),更新成功,变为图下:

image.png

此时线程二开始工作,线程二的目标是:旧的预期值:A;新的预期值:B,同样一比对,相同,于是也更新成功,变为如下:

image.png

虽然从结果上来看,A 成功的变为 B,然后在实际涉及钱的时候,问题就大了。

再次举例:

小明银行卡里有200块钱,想取出100块钱,但是由于设备故障,提交了两次扣款申请。

扣款申请1:旧的预估值:200,新的预估值:100

扣款申请2:旧的预估值:200,新的预估值:100

此时,扣款申请1提前进行了操作,扣款成功,小明银行卡里为100块钱;

扣款申请2假设还在等待。//block

此时小明的妈妈给小明存储100元,ok, 现在小明银行卡里就是200块钱。

扣款申请2恢复正常,开始必对,旧的预估值:200,卡里余额200,成功,替换更新为100. 更新成功,那么此时小明银行卡里剩余金额:100块。

哈哈!!!这显然是不对的,虽然值相同,但是中间操作改变了最终的结果。那如何解决这个问题呢?可以利用版本号,每修改成功一次V,都要设置一个版本号。每次更新的时候,不但要比对值还要比对版本号,就可以避免这种错误。

好,就上面的例子说明下:

当扣款申请1完成更新操作时,我们设置内存地址V中的值的版本号为::01

image.png

第二次操作:妈妈成功向卡里存入了100块

image.png

扣款申请2开始工作:

旧的预估值:200,新的预估值:100;版本号为00

而此时:V(200)= 旧的预估值(200)成立

​ 版本号(00) ≠ version(02) 不成立

HashMap 高并发导致的死循环

Veröffentlicht am 2018-12-07 |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
for (int j = 0; j < src.length; ++j) {

Entry<K,V> e = src[j];//src 表示原table
while(null != e) {
//此处表示取出下一个位置的Entry(键值对)
Entry<K<V> next = e.next;
//计算得出在扩容后新数组table中的下标,因为扩容后要重新计算下标
//未扩容前下标:index = hash(key) & (length-1)
int i= indexFor(e.hash,newCapacity);
//根据下标取出在新表newTable中的Entry<K,V>
e.next = newTable[i];
//将当前e 赋值给newTable[i](Entry<K,V>)
newTable[i] = e;
//将下一个next.Entry 赋值给当前的e
e = next;
}
}

举例说明下:

原

table的信息排列是:image.png

再次添加新元素的时候就需要扩容了,此时我们来类比下上述的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
for (int j = 0; j < src.length; ++j) {

Entry<K,V> e = src[j];//src 表示原table
while(null != e) {
//next = Entry2
Entry<K<V> next = e.next;
//假设计算结果为3;循环第二次:假设Entry2计算的位置也是3
int i= indexFor(e.hash,newCapacity);
//第一次Entry1.next = newTable[3],此时newTable[3]还为null;所以是Entry1.next地址指向null
//循环第二次:Entry2.next = Entry1
e.next = newTable[i];
//newTable[3] = Entry1, 此时e是 Entry1;
//循环第二次: newTable[3] = Entry2
newTable[i] = e;
//Entry1 = Entry2,e 指向了Entry2,好再次循环;
//循环第二次:e = null,跳出循环
e = next;
}
}

变成如下:

image.png

上述是属于正常情况的那种,假设我们现在有两个线程同时去 put 元素,二者均发现需要做扩容处理,那么又会出现什么情况呢?

参考阅读网上的博客+自己理解记录

当线程一 和 线程二 同时进行插入的时候刚好达到扩容的条件,然后同时开始进行 Resize 操作。

1
2
3
4
5
6
7
8
9
10
11
for (int j = 0; j < src.length; ++j) {
//src 表示原table
Entry<K,V> e = src[j];
while(null != e) {
Entry<K<V> next = e.next;//假设线程一执行到这里就被调度挂起了
int i= indexFor(e.hash,newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}

线程一挂起,线程二继续操作,变成如下:

image.png

线程二是 rehash 之后的样子,如上图:

当前线程一来说:

e 是: key(3)

next 是: key(7)

线程一来了,被调度回来了,该人家执行了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

while(null != e) {//再次循环,e:key(7) next:key(3)
//假设线程一执行到这里就被调度挂起了,开始继续执行
//第二次循环:next = key(3),next 指向key(3)
//e:key(3) next:null
Entry<K<V> next = e.next;
int i= indexFor(e.hash,newCapacity);//根据线程二的计算不难推出线程一计算的i = 3
//key(7).next 指向 key(3)
//key(3).next 指向 key(7)
e.next = newTable[i];
//first : newTable[3] = key(3)
//second : newTable[3] = key(7)
//third : newTable[3] = key(3)
newTable[i] = e;
//e 指向key(7)
//e = key(3)
//e = null//退出循环
e = next

这样就产生死循环了。

1
2
key(7).next 指向 key(3)
key(3).next 指向 key(7)

当我们取出一个key(5) 的一个值时,恰巧也是 int i = 3 , 这样去取,就陷入key(3),key(7) 的死循环中去了。

为了更清晰的展示每一次循环,我决定分开来展示:

第一次:要从这个线程一恢复运行开始说起吧: e: key(3) ; next: key(7)

1
2
3
4
5
6
7
8
9
10
11
12
while(null != e) {
Entry<K<V> next = e.next;//第一次的时候,线程一被卡住在这里,当时状态是:e:key(3),next:key(7) 这应该都没什么问题

//根据线程二的计算,这个值也应该是int i = 3
int i= indexFor(e.hash,newCapacity);
//key(3).next = newTable[3], 此时newTable[3]为null, key(3).next指向null
e.next = newTable[i];
//newTable[3] = key(3)
newTable[i] = e;
//e = key(7),e指向key(7)
e = next
}

OK, 由于e = key(7) 不为null ,循环继续:

第二次循环:经过上述循环,此时 e: key(7),next: key(3)

image.png

由于线程二已经改变了这个整体table 的结构,当遍历到key(7) 时, next: key(3)

1
2
3
4
5
6
7
8
9
10
11
12
while(null != e) {//e:key(7) 
//next = key(3)
Entry<K<V> next = e.next;
//int i = 3
int i= indexFor(e.hash,newCapacity);
//key(7).next = key(3) , key(7).next指向key(3)
e.next = newTable[i];
//newTable[3] = key(7)
newTable[i] = e;
//e = key(3) e指向key(3),不为null,循环继续
e = next
}

第三次循环:e : key(3) next:null

1
2
3
4
5
6
7
8
9
10
11
12
while(null != e) {//e:key(3) 
//next = null next指向null
Entry<K<V> next = e.next;
//int i = 3
int i= indexFor(e.hash,newCapacity);
//key(3).next = key(7) key(3).next指向key(7)
e.next = newTable[i];
//newTable[3] = key(3)
newTable[i] = e;
//e = null, 循环结束
e = next
}

此时 e : null, next:null.

1
2
//key(7).next指向key(3)
//key(3).next指向key(7) 这里产生死循环

图就是这样的:最后newTable[3] = key(3)

image.png

over.

HashMap基本知识

Veröffentlicht am 2018-12-06 |

自我总结

什么是 HashMap?

首先看下类继承关系:

image.png

HashMap 是存储键值对的集合,一个 key–value , 这样的键值对也称为 Entry ,这些键值对分散存储于数组当中,组成 HashMap 的主干。

  • 特点

    其中 key 可为 null ;非线程安全;

其中 put 方法 和 get 方法比较重要。

  • put 方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
    }

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
    boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
    n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null)
    tab[i] = newNode(hash, key, value, null);
    .....
    }

首先根据 key 计算 hash 值 , 如何算出在 Node 数组中具体的下标呢?从上面代码我们可以大概看出:

1
i = (n-1) & hash

就是数组的长度 -1 & key 的hash 值。

得到数组下标后,再把这个键值对放入进去,完成了 put 操作。

假设我们: put(“qq”,”糖葫芦”) hash("qq") = 2, 下标为2,那么结果如下:

image.png

Entry1====> [key = "qq", value= "糖葫芦"]

但是总会有那么一种情况,就是不同 key 计算的 hash 之后是相同的,假如我们新 put("mi",value="milo") index = hash("mi") = 2 也是数组下标为2,那么此时在下标为2的位置就会通过 链表 的形式存储相应的键值对。

image.png

HashMap 数组的每一个元素不止是一个Entry 对象,也是一个链表的头节点。每一个Entry对象通过 Next 指针指向它的下个 Entry 节点。当再有新的 Entry 映射到这个冲突位置时,也就是计算出 hash 值也是2,只需要插入到对应的链表当中去即可,注意:新插入的会在之前的前面,称为 头插入, 被新 put 进入的新元素可能被用到的概率大一些。

  • get 方法

    了解了 put 方法,get 好理解些

    1
    2
    3
    4
    public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
    }

    第一步:同样 根据 key 算出 hash 值;

    第二步:根据 hash值和数组的长度 计算数组的下标 index

    第三步:假设我们取出我们 get("qq") 对应的值,我们知道下标是2,且是一个链表,我们需要循环遍历整个链表,找到 key = "qq" 的Entry。

  • HashMap的默认长度是多少?为什么要那么定义?

HashMap 默认长度是16,也可以自定义长度必须是2幂次方。之所以选择16,是为了方便于 key 映射 index 的算法。

为了实现一个均匀的 HashMap ,数组的长度必须是 2 的幂次方,将 index = hash(key) & (length-1) 这样做的效率也更高,通过位运算的方式。

举例:book 的 hashcode,结果为十进制的 3029737,二进制:1011100011101011101001;假定 HashMap 长度是默认的16, length-1 = 15,二进制: 1111;

index = hash(key) & (length-1)

计算结果如下图:

image.png

二进制:1001;十进制:就是index= 9。

那么假设我们自定义长度设置为:10. 那length-1=9, 二进制就是:1001,我们重新计算下index`, 如下图:

image.png

二进制为:1001 ;十进制就是: index = 9

然后我们再尝试一个新的 HashCode:

image.png

结果还是:1001;

然后我们继续来一个新的 HashCode:

image.png

结果还是:1001,index= 9.

也就是说,当 HashMap 长度为 10 的时候,多个 不同的key生成的 index 可能是相同的,相同的概率比较大,这样,显然不符合 Hash 算法均匀分布的原则。

反观长度为16或者2的幂次方,length-1 的值是所有二进制全为1,这种情况下,index 的结果等同于HashCode 的后几位值,只有输入的 HashCode 本身分布均匀,Hash 算法的结果也是均匀的。

RecycleView更清晰地观察缓存

Veröffentlicht am 2018-11-28 |

为了更清晰的观察 recycleView 缓存复用,在看到有一篇文章是打印缓存相关信息后,觉着确实是一种办法,比打断点更合适查看缓存的变化情况,就尝试自己写了写。

这里我主要利用反射观察的是:mCachedViews 和 recycleViewPool 里面的变化情况。代码如下:

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
public void getInfo(){
try{
Class<?> class1 = Class.forName("android.support.v7.widget.RecyclerView");
Field field = class1.getDeclaredField("mRecycler");
field.setAccessible(true);

RecyclerView.Recycler recycler = (RecyclerView.Recycler) field.get(recyclerView);
Field field1 = recycler.getClass().getDeclaredField("mCachedViews");
field1.setAccessible(true);
ArrayList<RecyclerView.ViewHolder> mCachedViews = (ArrayList<RecyclerView.ViewHolder>) field1.get(recycler);

for(int i=0;i<mCachedViews.size();i++){

Log.i("xx","mCachedViews::"+mCachedViews.get(i));
}


RecyclerView.RecycledViewPool viewPool = recyclerView.getRecycledViewPool();
//这里之所以能获取到 RecycledViewPool中的mScrpa,需要新建一个android.support.v7.widget包,将该类放入该包下即可访问到。
SparseArray<RecyclerView.RecycledViewPool.ScrapData> list = viewPool.mScrap;
//这里传入的类型type 是我在adapter中定义的,如下:
//@Override
//public int getItemViewType(int position) {
//return 666;
//}
RecyclerView.RecycledViewPool.ScrapData spra = list.get(666);

Field field2 = spra.getClass().getDeclaredField("mScrapHeap");
ArrayList<RecyclerView.ViewHolder> mScrapHeap= (ArrayList<RecyclerView.ViewHolder>) field2.get(spra);
for(int i=0;i<mScrapHeap.size();i++){
Log.i("xx","mScrapHeapInfo::"+mScrapHeap.get(i));
}
Log.i("xx","=================================================");
}catch (Exception e) {
Log.i("xx", "error");
e.printStackTrace();
}
}

需要注意的是:反射获取

然后在 recycleView 滚动的时候,显示信息:

1
2
3
4
5
6
7
8
9
10
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
// int recycleCount = recyclerView.getRecycledViewPool().getRecycledViewCount(666);
// Log.i("xx","recyclePoolCount::"+recycleCount);

getInfo();
}
});

还有可以在 adapter 重写方法: onViewRecycled(ViewHOlder holder) 方法,可以查看最新被回收的位置ViewHolder :

1
2
3
4
5
@Override
public void onViewRecycled(ViewHolder holder) {
Log.i("xx","holder被回收了"+holder);//可以拿到被回收holder的相关信息
super.onViewRecycled(holder);
}

这样我们就可以通过日志的方式来近距离查看 recyclerView 复用回收。

我们假设这样一个场景:总共假设有20个item ,使用 GrideLayoutManager 每行显示5个,一个屏幕差不多显示两行,如下图所示:
image.png

我们在 adapter 的 onCreateViewHolder 和 onBindViewHolder 方法的地方都打印日志,并且重写 onViewRecycled 方法,查看 View 的回收情况。

当我们向下滑动,将第一行的数据滑出屏幕后,我们发现打印日志如下:
image.png

我们发现 2,3,4卡位的 ViewHolder 被回收了。这里的被回收是指添加到 RecycleViewPool 当中了。
此时 mCachedViews 中的缓存信息如下:

image.png

从日志上来看,它缓存了:1,0,15,16,17,18,19共7个卡位的数据。

这里补充一下:mCachedViews 的大小在源码定义中默认是:
static final int DEFAULT_CACHE_SIZE = 2;(当然也可以自己设置,通过 RecyclerView #setItemViewCacheSize,一般不设置)经过我的多次实验观察,当每行设置为5列时,mCachedViews.size == 2+ 5 = 7,当每行是4时,mCachedViews.size == 2+ 4 = 6,所以此时就是mCachedView.size 是7。

问题1:为什么mCachedViews 缓存的卡位是1,0,15,16,17,18,19?
要回答这个问题,首先应该明白当我们在滑动第三行展示的时候,此时 RecyclerView 创建了第四行的 ViewHolder,即 15,16,17,18,19因为它们未曾展示到屏幕上,所以被 mCachedViews 缓存,继续滑动,第一行数据移出屏幕之外,也要回收了,0,1,2,3,4,5 这5个是将要被缓存,但是mCachedVIews 已经缓存了5个,势必只能再添加两个,两外三个卡位的 ViewHolder 将要被回收,GrideLayoutManager 默认从右往左回收:

第一次回收4,3卡位如图:
image.png

第二次回收2卡位如图:

因为此时缓存数量已到最大值7了,所以再次添加时,会移除第一个。代码如下:

1
2
3
4
5
int cachedViewSize = mCachedViews.size();
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
recycleCachedViewAt(0);
cachedViewSize--;
}

日志:

image.png

新缓存的2卡位被插入index = 1 的位置。
如此,我们就可以知道当1卡位会回收时,mCachedViews 中缓存的信息应该是,先移除第0个,也就是position= 3 的 ViewHolder,插入到 index = 1 的位置,顺序应该是:2,1,15,16,17,18,19,好我们看下日志:
image.png

是的,不出我们所料,至此到最后我们就知道了,当0卡位被回收时,mCachedViews 的缓存信息就是:1,0,15,16,17,18,19. 被移除的ViewHolder 被添加到了 RecycleViewPool 当中了,从 RecycleViewPool 当中取出的 ViewHolder 类似于全新的,但是不会重新调用 onCreateViewHolder,会重新调用 onBindViewHolder 重新绑定数据。

ConcurrentHashMap 原理

Veröffentlicht am 2018-11-22 |

背景知识:我们知道 HashMap 是线程不安全的,多线程情况下,进行put 操作会引起死循环,导致CPU 利用率比较高,所以在并发情况下不能使用 HashMap ;

而 Hashtable 是线程安全的,其实就是在 put 操作的时候加了 syncronized, 但是当一个线程在进行put 操作时,其它的线程连 get 操作也不能进行,导致阻塞或者轮询状态,所以竞争越来越激烈,效率低下。

目录结构:

1
2
3
4
public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
implements ConcurrentMap<K,V>, Serializable {
.....
}
  • 锁分段技术

    Hashtable 在多并发的情况下效率低下,是因为很多线程争夺同一把锁,而假如容器内有多把锁,每一把锁用于锁容器内的一部分数据,当多线程访问容器内不同段的数据时,线程间就不会存在锁竞争,从而有效的提高并发访问效率。

Segment

英文翻译:” 段“

内部结构是:二级的哈希表,如下图:

image.png

多个Segment 保存在一个名为segments 数组当中,每个 Segment 高度自治,各个 Segment 之前读写互不影响。

读写操作

  • get

    想要获取里面的元素,按照结构,我们大概也能猜测出如何获取。

      1. 首先根据 key 计算的 hash 值;
      2. 计算得出 在 segments 数组中的下标,根据下标得出相对应的 Segment;
      3. 再根据key 的 hash 值,找到Segment 数组中的位置
  • put

    put 元素

    • 1.首先根据 key 计算hash 值
    • 2.通过 hash 值,定位到具体的 Segment 对象
    • 3.获取可重入锁
    • 4.再次通过 hash 值,定位到 Segment 数组中的具体位置
    • 5.插入或覆盖原 HashEntry 对象
    • 6.释放锁

统计Size大小

jdk 1.7:

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
public int size() {
// Try a few times to get accurate count. On failure due to
// continuous async changes in table, resort to locking.
final Segment<K,V>[] segments = this.segments;
int size;
boolean overflow; // true if size overflows 32 bits
long sum; // sum of modCounts
long last = 0L; // previous sum
int retries = -1; // first iteration isn't retry
try {
for (;;) {
if (retries++ == RETRIES_BEFORE_LOCK) {
for (int j = 0; j < segments.length; ++j)
ensureSegment(j).lock(); // force creation
}
sum = 0L;
size = 0;
overflow = false;
for (int j = 0; j < segments.length; ++j) {
Segment<K,V> seg = segmentAt(segments, j);
if (seg != null) {
sum += seg.modCount;
int c = seg.count;
if (c < 0 || (size += c) < 0)
overflow = true;
}
}
if (sum == last)
break;
last = sum;
}
} finally {
if (retries > RETRIES_BEFORE_LOCK) {
for (int j = 0; j < segments.length; ++j)
segmentAt(segments, j).unlock();
}
}
return overflow ? Integer.MAX_VALUE : size;
}
    1. 遍历所有的 Segment
    2. 把 Segment 元素数量累加起来
    3. 把 Segment 修改次数累加起来
    4. 判断所有的Segment 的总修改次数是否大于上一次的总修改次数。如果大于,说明统计过程有修改,尝试统计,统计次数+1;反之,统计结束。
    5. 如果统计次数超过阀值,则对每个 Segment 加锁,重新统计
    6. 再次判断总修改次数是否大于上一次的总修改次数,
    7. 释放锁,统计结束。

集合随笔

Veröffentlicht am 2018-11-21 |

1. ArrayList 和 HashMap 是否线程安全?

Collection ArrayList HashMap HashSet 都是非同步的,线程是不安全的

集合中:Vector 和 HashTable 是线程安全的

Collection —-> 线程安全:Collections.synchronizedCollection()
ArrayList —–> Collections.synchronizedList()
HashMap ——> Collections.synchronizedMap()
HashSet ——-> Collections.synchronizedSet()

Handler 相关总结

Veröffentlicht am 2018-11-21 |
    1. 首先一个线程中是可以创建多个 Handler 的,互不影响,默认在 ActivityThread 当中也创建了一个 Handler H ,我们还是可以在 MainActivity 中创建我们自己的 Handler, 互不影响;
    1. 一个线程 对应一个 Looper ,一个 Looper 对应一个 MessageQueue ;
1…345…8

QQabby

79 Artikel
63 Tags
© 2020 QQabby
Erstellt mit Hexo
|
Theme — NexT.Pisces v5.1.3