prefácio
No processo de desenvolvimento e programação do Android, é inevitável envolver programação de rede, o que exige que nossos desenvolvedores sejam mais proficientes em programação de rede. Haha, vamos começar analisando o Volley hoje!
1.0 O que é Voleibol
Volley é uma biblioteca HTTP lançada na conferência Googel I/O em 2013, que pode ajudar aplicativos Android a realizar solicitações de rede com mais facilidade. Ele pode não apenas acessar a rede para obter dados, mas também acessar a rede para obter fotos.
Vantagens e desvantagens do vôlei 2.0
O vôlei tem as seguintes vantagens:
- Despachar solicitações de rede automaticamente
- Alta conexão de rede simultânea
- Respostas transparentes à memória armazenadas em cache no disco via coerência de cache HTTP padrão
- Suporte para especificar a prioridade das solicitações
- API de solicitação de cancelamento ou especifique uma região na fila de solicitação de cancelamento
- As estruturas são facilmente personalizáveis. Por exemplo, funções personalizadas de repetição ou retorno de chamada
- A ordenação forte pode facilitar o carregamento assíncrono de dados de rede e exibi-los corretamente na IU
- Inclui ferramentas de depuração e rastreamento
Desvantagens do Volley:
- Não é adequado para baixar arquivos de dados grandes
3.0 Fila de solicitação de rede do Volley
O processo de uso do Volley é criar um RequestQueue
objeto (fila de solicitações) e, em seguida, Request
enviar (solicitações) a ele.
// 代码[1]
final TextView textView = (TextView) MainActivity.this.findViewById(R.id.tv);
//[1.0]创建 RequestQueue 的实例
RequestQueue requestQueue = Volley.newRequestQueue(MainActivity.this); // 1
String url = "http://gank.io/api/data/Android/10/1";
//[2.0]构造一个 request(请求)
StringRequest request = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() {
@Override
public void onResponse(String response) {
textView.setText("Response is: " + response.toString());
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
textView.setText("Error is happenning");
}
});
//[3.0]把 request 添加进请求队列 RequestQueue 里面
requestQueue.add(request);
A lógica do código acima é principalmente construir uma StringRequest
instância e, em seguida, adicionar essa instância à fila de solicitações RequestQueue
. Vejamos Volley.newRequestQueue(Context)
o código-fonte do método na Nota 1:
// 源码[2]
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR); // 1
String userAgent = "volley/0";
try {
String packageName = context.getPackageName();
PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
userAgent = packageName + "/" + info.versionCode;
} catch (NameNotFoundException e) {
}
if (stack == null) {
if (Build.VERSION.SDK_INT >= 9) { // 2
stack = new HurlStack();
} else {
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}
Network network = new BasicNetwork(stack);
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network); // 3
queue.start(); // 4
return queue;
}
public static RequestQueue newRequestQueue(Context context) {
return newRequestQueue(context, null);
}
Como pode ser visto no código-fonte acima, newRequestQueue(Context)
é newRequestQueue(Context, HttpStack)
o método sobrecarregado de . Em seguida, examinamos principalmente newRequestQueue(Context, HttpStack)
o método. Na Nota 1, new File(File, String)
um cache (este é um substantivo) é construído por meio da inicialização cacheDir
. Na Nota 3, new DiskBasedCache(cacheDir)
5M é alocado para este cache. Espaço de armazenamento;
de volta à Nota 2, quando a versão do SDK for maior ou igual a 9, ou seja, o número da versão do Android for maior ou igual a 2,3, crie com base em , caso contrário, crie com base na solicitação HttpURLConnection
de HurlStack
execução HttpClient
, HttpClientStack
e então na Nota 3, Ao new RequestQueue(Cache, Network)
criar uma fila de solicitações, vejamos new RequestQueue(Cache, Network)
o código-fonte de:
// 源码[3]
...
private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;
...
public RequestQueue(Cache cache, Network network) {
this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
}
...
Método de construção new RequestQueue(Cache, Network)
Aloca 4 threads para solicitações de rede. Para RequestQueue
esta classe, a função principal é servir como pool de threads para despachar requisições na fila: ao chamar, a add(Request)
requisição recebida (Request) será analisada na fila de cache (cache) ou na fila de rede (rede), e então passada de volta ao thread principal, ou seja, a função de callback no código [1] onResponse(String)
e onErrorResponse(VolleyError)
obtém o conteúdo da solicitação do callback;
no comentário 4 do código [1], add(Request)
o método é chamado:
// 源码[4]
public <T> Request<T> add(Request<T> request) {
request.setRequestQueue(this);
//同步代码块,保证 mCurrentRequests.add(request) 在一个进程里面的所有线程里面,有且只能在
//一个线程里面执行
synchronized (mCurrentRequests) {
mCurrentRequests.add(request);
}
request.setSequence(getSequenceNumber());
request.addMarker("add-to-queue");
//如果不可以存储,就把请求(request)添加进网络调度队列里面
if (!request.shouldCache()) {
mNetworkQueue.add(request);
return request;
}
//如果可以存储:
//同步代码块
synchronized (mWaitingRequests) {
String cacheKey = request.getCacheKey();
//如果之前有相同的请求并且还没有返回结果的,就把此请求加入到 mWaitingRequests 里面
if (mWaitingRequests.containsKey(cacheKey)) {
Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
//如果没有请求在进行中,重新初始化 Queue<Request<?>> 的实例
if (stagedRequests == null) {
stagedRequests = new LinkedList<Request<?>>();
}
stagedRequests.add(request);
mWaitingRequests.put(cacheKey, stagedRequests);
if (VolleyLog.DEBUG) {
VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
}
} else {
//如果没有的话,就把请求添加到 mCacheQueue 队列里面
mWaitingRequests.put(cacheKey, null);
mCacheQueue.add(request);
}
return request;
}
}
}
Como pode ser visto no código-fonte do add(Request)
método acima, a lógica principal é: se a requisição (pedido) não puder ser armazenada em cache, ela será adicionada diretamente à fila de cache, caso contrário, será adicionada à fila de cache; após obter e , retorne a requisição e mNetworkQueue
chame mCacheQueue
o start()
método (como Nota 4 do código fonte [2]), ao visualizar start()
o código fonte do método:
// 源码[5]
public void start() {
stop(); // 确保当前的缓存调度和网络调度都已经停止
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
mCacheDispatcher.start();
for (int i = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}
Ele cria principalmente CacheDispatcher(BlockingQueue<Request<?>>, BlockingQueue<Request<?>>, Cache, ResponseDelivery)
uma instância e mCacheDispatcher.start()
inicia o encadeamento de agendamento de cache por meio de , e cria NetworkDispatcher(BlockingQueue<Request<?>>,Network, Cache,ResponseDelivery)
uma instância de e networkDispatcher.start()
inicia o encadeamento de agendamento de rede por meio de .
4.0 Encadeamento do despachante de rede NetworkDispatcher
O encadeamento de agendamento de rede NetworkDispatcher
é um Thread
encadeamento herdado de , visualizando run()
o código-fonte de seu método de tarefa:
// 源码[6]
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
Request<?> request;
while (true) {
try {
// 从网络队列中取出请求
request = mQueue.take();
} catch (InterruptedException e) {
if (mQuit) {
return;
}
continue;
}
try {
request.addMarker("network-queue-take");
// 如果请求被取消
if (request.isCanceled()) {
request.finish("network-discard-cancelled");
continue;
}
addTrafficStatsTag(request);
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete");
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
request.finish("not-modified");
continue;
}
Response<?> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");
// 把网络请求到的实体缓存到 源码[2] 的注释 1 处的本地缓存文件里面
if (request.shouldCache() && response.cacheEntry != null) {
mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}
request.markDelivered();
// 回调请求到的响应给主线程
mDelivery.postResponse(request, response);
} catch (VolleyError volleyError) {
...
} catch (Exception e) {
...
}
}
}
A lógica principal do escalonamento de rede é primeiro julgar se a solicitação de rede foi cancelada. Caso contrário, mNetwork.performRequest(request)
execute a resposta da rede de solicitação. Depois de obter a resposta, primeiro armazene-a localmente e, em seguida, chame de volta para o thread principal.
Encadeamento de agendamento de cache 5.0 CacheDispatcher
O código-fonte do encadeamento de agendamento de cache CacheDispatcher
é o seguinte:
@Override
public void run() {
if (DEBUG) VolleyLog.v("start new dispatcher");
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
mCache.initialize();
while (true) {
try {
final Request<?> request = mCacheQueue.take();
request.addMarker("cache-queue-take");
//如果请求被取消
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
continue;
}
// 尝试在本地缓存中取回数据
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
//本地缓存丢失或者没有
request.addMarker("cache-miss");
mNetworkQueue.put(request);
continue;
}
// 本地缓存过期
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
mNetworkQueue.put(request);
continue;
}
// 命中缓存
request.addMarker("cache-hit");
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
if (!entry.refreshNeeded()) {
// 回调相应给主线程
mDelivery.postResponse(request, response);
} else {
request.addMarker("cache-hit-refresh-needed");
request.setCacheEntry(entry);
response.intermediate = true;
mDelivery.postResponse(request, response, new Runnable() {
@Override
public void run() {
try {
mNetworkQueue.put(request);
} catch (InterruptedException e) {
...
}
}
});
}
} catch (InterruptedException e) {
...
}
}
}
Da mesma forma, se a solicitação não for cancelada, o cache local é recuperado. Quando o cache local não existe, é perdido ou expirou, a solicitação é adicionada à fila de solicitações de rede. Quando o cache local é atingido, a resposta em cache é chamado de volta para o thread principal;
6.0 Análise do princípio do Volley
Para enviar uma solicitação, você só precisa construir uma solicitação e add()
adicioná-la por meio do método RequestQueue
. Depois que a solicitação é adicionada, ela passa pela fila, por uma série de despachos e, em seguida, recupera os dados brutos da resposta.
Quando o método é executado add()
, o Volley aciona a execução de um thread do manipulador de cache e uma série de threads do manipulador de rede. Ao adicionar uma solicitação à fila, ela será capturada e acionada pelo thread do cache: Se a solicitação puder ser processada pelo cache, a análise dos dados da resposta será realizada no thread do cache e retornada ao thread principal. Se a solicitação não puder ser tratada pelo cache, ela será colocada em uma fila de rede. O primeiro thread de rede disponível no pool de threads de rede obterá a solicitação da fila e executará a operação HTTP, analisará os dados de resposta do thread de trabalho, gravará os dados no cache e retornará os dados analisados ao thread principal.
O ciclo de vida de uma solicitação
resumo
Neste ponto, a análise da biblioteca Volley está temporariamente encerrada. Desta vez, o autor analisa principalmente o processo desde quando uma solicitação é adicionada à fila de solicitações até quando ela retorna uma resposta e entende como o Volley agenda uma solicitação e, em seguida, obtém uma resposta e a retorna. haha obrigada por ler...
Download do código-fonte TestVolley