Android Volley框架使用源碼分享
過去在Android上網(wǎng)絡(luò)通信都是使用的Xutils 因?yàn)橛盟梢皂樀捞幚砹藞D片和網(wǎng)絡(luò)這兩個(gè)方面,后來發(fā)覺Xutils里面使用的是HttpClient 而Google在6.0的版本上已經(jīng)把HttpClient廢除了,所以開始尋找新的網(wǎng)絡(luò)框架,okhttp也用過,但是它是在作用在UI線程,使用起來還需要用handler 所以就先用著Volley框架了。 這里我先分析下Volley框架的簡單網(wǎng)絡(luò)請求的源碼。
使用Volley請求網(wǎng)絡(luò)數(shù)據(jù)的簡單過程:
RequestQueue queue = Volley.newRequestQueue(this); //實(shí)例化一個(gè)請求隊(duì)列 Google推薦寫一個(gè)單例類 獲取唯一一個(gè)隊(duì)列
StringRequest request = new StringRequest(Request.Method.POST, url1, new Response.Listener<String>() {
@Override
public void onResponse(String response) {
Toast.makeText(MainActivity.this, "success"+response, Toast.LENGTH_SHORT).show();
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Toast.makeText(MainActivity.this, "失敗了"+error.getMessage(), Toast.LENGTH_SHORT).show();
}
}){
@Override
protected Map<String, String> getParams() throws AuthFailureError { //重寫這個(gè)函數(shù)提交參數(shù) 也可以重寫一個(gè)Request實(shí)現(xiàn)這個(gè)方法
Map<String,String> params = new HashMap<>();
params.put(aaa+"name","1233555"); //參數(shù)
return params;
}
};
queue.add(request);
請求的處理在newRequestQueue的時(shí)候就開始執(zhí)行了 只不過那時(shí)候請求隊(duì)列中還沒有請求 所以阻塞了 當(dāng) add的方法執(zhí)行時(shí) 才開始真正請求網(wǎng)絡(luò)
所以我們先來看 queue.add(request) 方法
public <T> Request<T> add(Request<T> request) {
// Tag the request as belonging to this queue and add it to the set of current requests.
request.setRequestQueue(this);
synchronized (mCurrentRequests) {
mCurrentRequests.add(request); //在當(dāng)前隊(duì)列中加入
}
// Process requests in the order they are added.
request.setSequence(getSequenceNumber());
request.addMarker("add-to-queue"); //設(shè)置標(biāo)志
// If the request is uncacheable, skip the cache queue and go straight to the network.
if (!request.shouldCache()) { //根據(jù)是否需要緩存 如果不需要緩存 就直接加入網(wǎng)絡(luò)任務(wù)隊(duì)列中 然后返回 如果需要緩存 那么在下面代碼中加入緩存隊(duì)列 默認(rèn)是需要緩存的
mNetworkQueue.add(request);
return request;
}
// Insert request into stage if there's already a request with the same cache key in flight.
synchronized (mWaitingRequests) {
String cacheKey = request.getCacheKey();
if (mWaitingRequests.containsKey(cacheKey)) { //判斷當(dāng)前正在被處理并可以緩存的請求中是否包含該請求的key 如果包含說明已經(jīng)有一個(gè)相同的請求 那么就加入到其中
// There is already a request in flight. Queue up.
Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
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 { //如果不包含 加入一個(gè)空的請求到 暫存隊(duì)列中 然后加入到緩存隊(duì)列中
// Insert 'null' queue for this cacheKey, indicating there is now a request in
// flight.
mWaitingRequests.put(cacheKey, null);
mCacheQueue.add(request);
}
return request;
}
}
分析add方法 首先加入到mCurrentRequests集合中 這個(gè)集合存放所有這個(gè)隊(duì)列所處理的請求 然后判斷這個(gè)請求是否需要緩存,如果不需要緩存,那么直接加入mNetworkQueue隊(duì)列中等待處理即可,如果需要那么最終加入到mCacheQueue隊(duì)列中,因?yàn)镽equestQueue在處理請求時(shí)總會(huì)先處理緩存的任務(wù),在處理緩存時(shí)如果第一次處理沒有緩存還是會(huì)加入mNetworkQueue隊(duì)列中處理,如果有緩存那么就直接獲取緩存了,之后判斷當(dāng)前的請求中是否有相同的請求,如果有的話那么就把這個(gè)請求加入到暫存集合中,如果沒有那么就加入一個(gè)空的到請求到暫存隊(duì)列中,用來以后判斷是否有和這個(gè)請求相同的請求,然后加入緩存隊(duì)列中即可。
然后我們來看RequstQueue的創(chuàng)建過程
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR); //創(chuàng)建一個(gè)文件用于緩存
String userAgent = "volley/0"; //用戶代理初始化
try {
String packageName = context.getPackageName();
PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
userAgent = packageName + "/" + info.versionCode; //用戶代理為app包名+版本號(hào)
} catch (NameNotFoundException e) {
}
if (stack == null) { //如果沒傳入HttpStack 那么采用下述默認(rèn)的 這里可以自行重寫擴(kuò)展HttpStack 體現(xiàn)了該框架的高擴(kuò)展性
if (Build.VERSION.SDK_INT >= 9) { //如果sdk版本高于2.3 采用HurlStack 內(nèi)部是httpUrlConnection實(shí)現(xiàn)
stack = new HurlStack();
} else { //如果版本低于2.3 采用httpClientStack
// Prior to Gingerbread, HttpUrlConnection was unreliable.
// See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}
Network network = new BasicNetwork(stack); //創(chuàng)建一個(gè)網(wǎng)絡(luò)工作 僅僅作用于請求網(wǎng)絡(luò)
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network); //實(shí)例化一個(gè)請求隊(duì)列 傳入?yún)?shù)
queue.start();
return queue;
}
</pre><pre code_snippet_id="1680121" snippet_file_name="blog_20160512_5_2241745" name="code" class="java">public RequestQueue(Cache cache, Network network, int threadPoolSize) { //構(gòu)造函數(shù) 會(huì)創(chuàng)建默認(rèn)的ExecutorDelivery 用于回調(diào)
this(cache, network, threadPoolSize,
new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}
RequestQueue的創(chuàng)建過程也比較簡單 根據(jù)sdk版本號(hào)判斷使用HttpURLConnection還是HttpClient 因?yàn)樵?.3之前 httpUrlConnection有一個(gè)重大的bug 所以使用HttpClient代替,而httpUrlConnection體積小 支持gzip壓縮和緩存,并且速度相對httpClient快 并逐漸優(yōu)化 所以選擇httpUrlConnection 之后根據(jù)創(chuàng)建的NetWork 創(chuàng)建RequestQueue隊(duì)列 然后開啟即可
之后我們查看 queue的start方法
public void start() {
stop(); // Make sure any currently running dispatchers are stopped.
// Create the cache dispatcher and start it.
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery); //創(chuàng)建一個(gè)緩存調(diào)度器 是一個(gè)線程 start后執(zhí)行run方法
mCacheDispatcher.start();
// Create network dispatchers (and corresponding threads) up to the pool size.
for (int i = 0; i < mDispatchers.length; i++) { //默認(rèn)會(huì)有4個(gè)NetworkDispatcher 為了提高效率 執(zhí)行netWorkQueue里的request
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}
這個(gè)方法 先執(zhí)行緩存調(diào)度器線程然后執(zhí)行4個(gè)網(wǎng)絡(luò)工作調(diào)度器線程,因?yàn)樵诰彺嬲{(diào)度器中 會(huì)判斷是否緩存過,如果緩存過并且沒過期,就直接復(fù)用緩存的,不把任務(wù)加入netWordQueue中 所以下面的NetWork調(diào)度器線程就會(huì)取不到請求而阻塞,不會(huì)執(zhí)行,而如果沒有緩存,緩存調(diào)度器線程中就會(huì)把請求加入NetWork隊(duì)列中,下面的netWork調(diào)度器就會(huì)取到該請求并執(zhí)行了
我們仔細(xì)看一下CacheDispatcher線程的源碼:
run方法的代碼比較長 我們分開來看 先看第一部分:
@Override
public void run() {
if (DEBUG) VolleyLog.v("start new dispatcher");
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); //設(shè)置線程的優(yōu)先級(jí) 值為10
// Make a blocking call to initialize the cache.
mCache.initialize(); //初始化一下緩存
while (true) {
try {
// Get a request from the cache triage queue, blocking until
// at least one is available.
final Request<?> request = mCacheQueue.take(); //從緩存隊(duì)列取出一個(gè)請求 如果沒有則會(huì)阻塞
request.addMarker("cache-queue-take"); //添加一個(gè)標(biāo)記
// If the request has been canceled, don't bother dispatching it.
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
continue;
}
// Attempt to retrieve this item from cache.
Cache.Entry entry = mCache.get(request.getCacheKey()); //從緩存中讀取緩存
if (entry == null) { //如果沒讀取到緩存
request.addMarker("cache-miss"); //添加緩存miss標(biāo)記
// Cache miss; send off to the network dispatcher.
mNetworkQueue.put(request); //換區(qū)緩存失敗 添加到netWork中等待請求
continue;
}
// If it is completely expired, just send it to the network.
if (entry.isExpired()) { //判斷緩存是否過期了 如果過期了 那么就添加到netWork中等待請求
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
mNetworkQueue.put(request);
continue;
}
第二部分 :
// We have a cache hit; parse its data for delivery back to the request.
request.addMarker("cache-hit"); //執(zhí)行到了這里說明緩存沒有過期 并且可以使用
Response<?> response = request.parseNetworkResponse( //把讀取到的緩存內(nèi)容解析成Response對象
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed"); //添加標(biāo)記
if (!entry.refreshNeeded()) { //如果緩存不需要刷新 直接調(diào)用 mDelivery.postResponse方法 在其中會(huì)回調(diào)request的listener接口
// Completely unexpired cache hit. Just deliver the response.
mDelivery.postResponse(request, response);
} else { //如果需要刷新 把請求加入mNetworkQueue中 等待請求
// Soft-expired cache hit. We can deliver the cached response,
// but we need to also send the request to the network for
// refreshing.
request.addMarker("cache-hit-refresh-needed");
request.setCacheEntry(entry);
// Mark the response as intermediate.
response.intermediate = true;
// Post the intermediate response back to the user and have
// the delivery then forward the request along to the network.
mDelivery.postResponse(request, response, new Runnable() {
@Override
public void run() {
try {
mNetworkQueue.put(request);
} catch (InterruptedException e) {
// Not much we can do about this.
}
}
});
}
} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.
if (mQuit) {
return;
}
continue;
}
}
}
上面代碼的具體過程也很簡單 首先從緩存請求隊(duì)列取出一個(gè)請求,在緩存中看看有沒有該請求的緩存,如果沒有 那么 請求放入NetWork調(diào)度器中 等待調(diào)用 如果有 也分幾種情況 如果獲取到的是空,放入NetWOrk 如果過期 放入 NetWork 如果不需要刷新 就直接從緩存獲取響應(yīng)信息并解析 然后用mDelivery回調(diào)接口即可 如果需要刷新 放入NetWOrd隊(duì)列等待調(diào)用。。。
我們再來看看NetworkDispatcher 線程的代碼就可以了 類似于CacheDispatcher的代碼:
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); //設(shè)置優(yōu)先級(jí) 10
while (true) {
long startTimeMs = SystemClock.elapsedRealtime(); //獲取請求執(zhí)行開始時(shí)間
Request<?> request;
try {
// Take a request from the queue.
request = mQueue.take(); //從隊(duì)列獲取一個(gè)請求 沒有則阻塞
} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.
if (mQuit) {
return;
}
continue;
}
try {
request.addMarker("network-queue-take");
// If the request was cancelled already, do not perform the
// network request.
if (request.isCanceled()) {
request.finish("network-discard-cancelled");
continue;
}
addTrafficStatsTag(request);
// Perform the network request.
NetworkResponse networkResponse = mNetwork.performRequest(request); //真正執(zhí)行請求的函數(shù) 并返回響應(yīng)
request.addMarker("network-http-complete");
// If the server returned 304 AND we delivered a response already,
// we're done -- don't deliver a second identical response.
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
request.finish("not-modified");
continue;
}
// Parse the response here on the worker thread.
Response<?> response = request.parseNetworkResponse(networkResponse); //解析響應(yīng)
request.addMarker("network-parse-complete");
// Write to cache if applicable.
// TODO: Only update cache metadata instead of entire record for 304s.
if (request.shouldCache() && response.cacheEntry != null) { //如果需要緩存 那么把響應(yīng)的信息存入緩存中
mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}
// Post the response back.
request.markDelivered();
mDelivery.postResponse(request, response); //之后回調(diào)一些方法
} catch (VolleyError volleyError) {
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
parseAndDeliverNetworkError(request, volleyError); //回調(diào)錯(cuò)誤接口
} catch (Exception e) {
VolleyLog.e(e, "Unhandled exception %s", e.toString());
VolleyError volleyError = new VolleyError(e);
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
mDelivery.postError(request, volleyError); //回調(diào)錯(cuò)誤接口
}
}
}
NetworkDispatcher 線程的執(zhí)行過程 先從 networkDispatch中獲取一個(gè)請求 然后判斷 是否取消了 如果沒有 那么就執(zhí)行NetWOrk的performRequest方法 執(zhí)行http請求,這個(gè)函數(shù)內(nèi)部才是真正的請求數(shù)據(jù) ,請求后 根據(jù)設(shè)置的shouldCache標(biāo)志 判斷是否放入緩存中 之后回調(diào)一些接口方法 即可 這樣就完成了一個(gè)請求
最后我們看一看NetWork類mNetwork.performRequest(request)方法是如何提交請求的吧 代碼比較長 但是不難:
@Override
public NetworkResponse performRequest(Request<?> request) throws VolleyError {
long requestStart = SystemClock.elapsedRealtime(); //記錄開始時(shí)間
while (true) {
HttpResponse httpResponse = null;
byte[] responseContents = null;
Map<String, String> responseHeaders = Collections.emptyMap(); //初始化響應(yīng)頭為空
try {
// Gather headers.
Map<String, String> headers = new HashMap<String, String>(); //請求頭
addCacheHeaders(headers, request.getCacheEntry()); //根據(jù)緩存添加請求頭
httpResponse = mHttpStack.performRequest(request, headers); //調(diào)用HttpStack的方法請求網(wǎng)絡(luò)
StatusLine statusLine = httpResponse.getStatusLine();
int statusCode = statusLine.getStatusCode();
responseHeaders = convertHeaders(httpResponse.getAllHeaders()); //獲取響應(yīng)頭
// Handle cache validation.
if (statusCode == HttpStatus.SC_NOT_MODIFIED) { //如果為304 讀取的緩存
Entry entry = request.getCacheEntry(); //查看以前是否緩存過
if (entry == null) { //如果以前緩存的為空 那么 說明上次緩存的請求也為空 直接返回response
return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null,
responseHeaders, true,
SystemClock.elapsedRealtime() - requestStart);
}
// A HTTP 304 response does not have all header fields. We
// have to use the header fields from the cache entry plus
// the new ones from the response.
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
entry.responseHeaders.putAll(responseHeaders); //如果不空 那么就添加頭 然后返回 數(shù)據(jù)了
return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data,
entry.responseHeaders, true,
SystemClock.elapsedRealtime() - requestStart);
}
// Some responses such as 204s do not have content. We must check.
if (httpResponse.getEntity() != null) { //不是304的情況
responseContents = entityToBytes(httpResponse.getEntity()); //獲取響應(yīng)的內(nèi)容 下面返回響應(yīng)即可
} else {
// Add 0 byte response as a way of honestly representing a
// no-content request.
responseContents = new byte[0];
}
// if the request is slow, log it.
long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
logSlowRequests(requestLifetime, request, responseContents, statusLine);
if (statusCode < 200 || statusCode > 299) {
throw new IOException();
}
return new NetworkResponse(statusCode, responseContents, responseHeaders, false,
SystemClock.elapsedRealtime() - requestStart);
} catch (SocketTimeoutException e) {
attemptRetryOnException("socket", request, new TimeoutError());
} catch (ConnectTimeoutException e) {
attemptRetryOnException("connection", request, new TimeoutError());
} catch (MalformedURLException e) {
throw new RuntimeException("Bad URL " + request.getUrl(), e);
} catch (IOException e) {
int statusCode = 0;
NetworkResponse networkResponse = null;
if (httpResponse != null) {
statusCode = httpResponse.getStatusLine().getStatusCode();
} else {
throw new NoConnectionError(e);
}
VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
if (responseContents != null) {
networkResponse = new NetworkResponse(statusCode, responseContents,
responseHeaders, false, SystemClock.elapsedRealtime() - requestStart);
if (statusCode == HttpStatus.SC_UNAUTHORIZED ||
statusCode == HttpStatus.SC_FORBIDDEN) {
attemptRetryOnException("auth",
request, new AuthFailureError(networkResponse));
} else {
// TODO: Only throw ServerError for 5xx status codes.
throw new ServerError(networkResponse);
}
} else {
throw new NetworkError(networkResponse);
}
}
}
然后看 HttpStack的 請求代碼:
@Override
public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
throws IOException, AuthFailureError {
String url = request.getUrl();
HashMap<String, String> map = new HashMap<String, String>();
map.putAll(request.getHeaders()); //添加請求頭
map.putAll(additionalHeaders);
if (mUrlRewriter != null) {
String rewritten = mUrlRewriter.rewriteUrl(url);
if (rewritten == null) {
throw new IOException("URL blocked by rewriter: " + url);
}
url = rewritten;
}
URL parsedUrl = new URL(url);
HttpURLConnection connection = openConnection(parsedUrl, request); //打開連接
for (String headerName : map.keySet()) { //設(shè)置頭
connection.addRequestProperty(headerName, map.get(headerName));
}
setConnectionParametersForRequest(connection, request); //在這個(gè)函數(shù)里添加請求的參數(shù) 和一些基本的信息配置
// Initialize HttpResponse with data from the HttpURLConnection.
ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);
int responseCode = connection.getResponseCode(); //下面就是些獲取響應(yīng)信息后的處理了
if (responseCode == -1) {
// -1 is returned by getResponseCode() if the response code could not be retrieved.
// Signal to the caller that something was wrong with the connection.
throw new IOException("Could not retrieve response code from HttpUrlConnection.");
}
StatusLine responseStatus = new BasicStatusLine(protocolVersion,
connection.getResponseCode(), connection.getResponseMessage());
BasicHttpResponse response = new BasicHttpResponse(responseStatus);
response.setEntity(entityFromConnection(connection));
for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
if (header.getKey() != null) {
Header h = new BasicHeader(header.getKey(), header.getValue().get(0));
response.addHeader(h);
}
}
return response;
}
這個(gè)函數(shù)中主要是HttpUrlConnection的使用 添加頭在 connection.addRequestProperty方法中 添加參數(shù)需要獲取流 然后寫入?yún)?shù) 下面這個(gè)函數(shù)中有介紹 假設(shè)是post方式:
case Method.POST:
connection.setRequestMethod("POST");
addBodyIfExists(connection, request);
break;
private static void addBodyIfExists(HttpURLConnection connection, Request<?> request)
throws IOException, AuthFailureError {
byte[] body = request.getBody();
if (body != null) {
connection.setDoOutput(true);
connection.addRequestProperty(HEADER_CONTENT_TYPE, request.getBodyContentType());
DataOutputStream out = new DataOutputStream(connection.getOutputStream());
out.write(body);
out.close();
}
}
將body寫入到流中 就可以 參數(shù)的封裝在 body中
public byte[] getBody() throws AuthFailureError {
Map<String, String> params = getParams();
if (params != null && params.size() > 0) {
return encodeParameters(params, getParamsEncoding());
}
return null;
}
getParams方法 是Request需要重寫的一個(gè)方法 返回值就是參數(shù)的Map集合
[java] view plain copy 在CODE上查看代碼片派生到我的代碼片
private byte[] encodeParameters(Map<String, String> params, String paramsEncoding) {
StringBuilder encodedParams = new StringBuilder();
try {
for (Map.Entry<String, String> entry : params.entrySet()) {
encodedParams.append(URLEncoder.encode(entry.getKey(), paramsEncoding));
encodedParams.append('=');
encodedParams.append(URLEncoder.encode(entry.getValue(), paramsEncoding));
encodedParams.append('&');
}
return encodedParams.toString().getBytes(paramsEncoding);
} catch (UnsupportedEncodingException uee) {
throw new RuntimeException("Encoding not supported: " + paramsEncoding, uee);
}
}
這個(gè)函數(shù)就是按照一定規(guī)則拼接字符串參數(shù)即可 然后 就可以提交參數(shù)了
最后介紹下這個(gè)框架主要的幾個(gè)類、成員及他們作用:
RequestQueue 用來處理請求的隊(duì)列,請求都放在這個(gè)類中 調(diào)用start方法 開始處理請求
mCache 請求的緩存,當(dāng)提交了一個(gè)請求 并且此請求需要緩存時(shí),會(huì)放入這個(gè)緩存中
mNetwork 單純用于提交網(wǎng)絡(luò)請求的接口 只有一個(gè)提交請求的方法 需要傳入一個(gè)HttpStack來完成請求的提交
mDelivery 用于請求響應(yīng)后的 接口回調(diào)等功能
mDispatchers NetWork調(diào)度器線程數(shù)組 包含4個(gè)對象處理請求 目的是為了提高效率 當(dāng)沒有緩存可以獲取或者已經(jīng)過期 需要刷新時(shí) 會(huì)調(diào)用這個(gè)線程的run方法 如果沒有 則阻塞
mCacheDispatcher 緩存調(diào)度器線程 處理已經(jīng)緩存了的請求 如果沒有緩存 則將請求放入 NetWorkQueue 等待調(diào)用
以上就是本文的全部內(nèi)容,希望對大家學(xué)習(xí)Android Volley框架有所幫助。
- Android 中Volley二次封裝并實(shí)現(xiàn)網(wǎng)絡(luò)請求緩存
- Android中volley封裝實(shí)踐記錄
- Android Volley框架全面解析
- Android Volley框架使用方法詳解
- Android的HTTP類庫Volley入門學(xué)習(xí)教程
- Android中Volley框架下保持會(huì)話方法
- Android 開發(fā)中Volley詳解及實(shí)例
- android 網(wǎng)絡(luò)請求庫volley方法詳解
- Android 網(wǎng)絡(luò)請求框架Volley實(shí)例詳解
- Android中volley封裝實(shí)踐記錄(二)
相關(guān)文章
Android中的Handler與多線程應(yīng)用實(shí)例
這篇文章主要介紹了Android中的Handler與多線程應(yīng)用實(shí)例,本文首先解釋一下handler是用來干嘛的,然后通過例子介紹其在多線程中的應(yīng)用,需要的朋友可以參考下2015-03-03
詳解Android如何實(shí)現(xiàn)自定義的動(dòng)畫曲線
最近在寫動(dòng)畫相關(guān)的篇章,經(jīng)常會(huì)用到 Curve 這個(gè)動(dòng)畫曲線類,那這個(gè)類到底怎么實(shí)現(xiàn)的?如果想自己來一個(gè)自定義的動(dòng)畫曲線該怎么弄?本文將為大家詳細(xì)解答2022-04-04
Android設(shè)備上非root的抓包實(shí)現(xiàn)方法(Tcpdump方法)
通常我們在Android應(yīng)用中執(zhí)行某個(gè)命令時(shí)會(huì)使用“Runtime.getRuntime().exec("命令路徑")”這種方式,但是當(dāng)我們執(zhí)行抓包操作時(shí),使用這條命令無論如何都不行,通過下面代碼打印結(jié)果發(fā)現(xiàn),該命令一定要在root權(quán)限下才能執(zhí)行,具體實(shí)現(xiàn)思路,請參考本教程2016-11-11
Android Studio preview 不固定及常見問題的解決辦法
preview 可以幫助您預(yù)覽您的布局文件將如何在用戶的設(shè)備上呈現(xiàn)。這篇文章主要介紹了Android Studio preview 不固定及常見問題的解決辦法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-05-05
Android中如何指定SnackBar在屏幕的位置及小問題解決
這篇文章主要給大家介紹了關(guān)于Android中如何指定SnackBar在屏幕的位置,以及一個(gè)小問題解決的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-03-03
解決Android SearchView不顯示搜索icon的問題
這篇文章主要介紹了解決Android SearchView不顯示搜索icon問題,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-05-05
Android RecyclerView詳解及簡單實(shí)例
這篇文章主要介紹了Android RecyclerView詳解及簡單實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-01-01
Android實(shí)現(xiàn)調(diào)用系統(tǒng)分享功能示例的總結(jié)
這篇文章主要介紹了通過Android調(diào)用系統(tǒng)分享文本信息、單張圖片、多個(gè)文件和指定分享到微信、QQ,同時(shí)分享圖片和文字的功能示例,小編覺得挺不錯(cuò),一起跟隨小編過來看看吧2018-05-05

