热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

流行框架源码分析(7)Volley源码解析

主目录见:Android高级进阶知识(这是总目录索引) 这是我们第一篇讲解网络请求相关的框架,前面我们的所有讲解都是跟网络请求没有关系,如果大家对Http协议的原理等还不熟悉,希望

主目录见:Android高级进阶知识(这是总目录索引)
 这是我们第一篇讲解网络请求相关的框架,前面我们的所有讲解都是跟网络请求没有关系,如果大家对Http协议的原理等还不熟悉,希望大家自己复习一下,毕竟还是要知其然知其所以然,首先为了大家对[Volley]有个整体的认识,我们先贴个图:

《流行框架源码分析(7)-Volley源码解析》 Volley原理图

这是官网的一个原理图,大家看着这个应该能对整体的原理有个把握,思路还是非常清晰的,
看图可以看出蓝色的是在主线程,绿色的是缓存调度线程,粉红的是网络调度线程,首先我们会调用RequestQueue的add方法把一个Request对象添加进缓存队列中,如果在缓存队列中发现有对应的请求结果,则直接解析然后传递给主线程,如果没有的话那就放进网络请求的队列中,然后循环取出请求进行http请求,解析网络返回的结果,写入缓存中,最后把解析的结果传递给主线程 。

一.目标

 今天我们是讲解网络请求框架的第一篇,希望大家有个好的开始,接下来有时间会相应地讲解OkHttp和retrofit的源码,敬请期待,今天目标如下:
1.从源码角度深刻了解Volley的源码;
2.知道怎么自定义Request。

二.源码解析

我们分析源码从最基础的用法入手,我们回顾下Volley的最基本用法,这里以StringRequest为例:
1.创建一个RequestQueue对象

RequestQueue mQueue = Volley.newRequestQueue(context);

2.创建一个StringRequest对象

StringRequest stringRequest = new StringRequest("http://www.baidu.com",
new Response.Listener() {
@Override
public void onResponse(String response) {
Log.d("TAG", response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e("TAG", error.getMessage(), error);
}
});

3.将StringRequest对象添加到RequestQueue里面。

mQueue.add(stringRequest);

这样三步,最基本的使用就已经完成了,那么我们现在遵循这个步骤来进行源码解析。

1.Volley newRequestQueue

首先我们看到Volley这个类里面的这个方法:

public static RequestQueue newRequestQueue(Context context) {
return newRequestQueue(context, (BaseHttpStack) null);
}

我们看到这个方法调用了两个参数的同个方法,第二个参数传的是null,我们就来看看两个参数的这个方法:

public static RequestQueue newRequestQueue(Context context, BaseHttpStack stack) {
BasicNetwork network;
if (stack == null) {
//这个方法是public的,以为这我们可以自己传一个stack进来,如果我们没有传进来则需要进行下面的操作
if (Build.VERSION.SDK_INT >= 9) {
network = new BasicNetwork(new HurlStack());
} else {
// Prior to Gingerbread, HttpUrlConnection was unreliable.
// See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
// At some point in the future we'll move our minSdkVersion past Froyo and can
// delete this fallback (along with all Apache HTTP code).
String userAgent = "volley/0";
try {
String packageName = context.getPackageName();
PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
userAgent = packageName + "/" + info.versionCode;
} catch (NameNotFoundException e) {
}
network = new BasicNetwork(
new HttpClientStack(AndroidHttpClient.newInstance(userAgent)));
}
} else {
//如果有传stack这个参数我们就使用他构造一个BasicNetwork对象
network = new BasicNetwork(stack);
}
return newRequestQueue(context, network);
}

我们看到我们这里会进行版本的判断,如果android的版本大于等于9则使用HurlStack作为BasicNetwork的参数,如果小于9呢则让HttpClientStack作为BasicNetwork作为参数,其中HurlStack用的是HttpURLConnection访问的网络,HttpClientStack使用HttpClient问网络。最后又调用了另外一个newRequestQueue方法,我们一起来看看:

private static RequestQueue newRequestQueue(Context context, Network network) {
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();
return queue;
}

我们看到这里得到一个File对象,这个File对象主要是指定缓存的目录,然后传给DiskBasedCache这个缓存类的(public class DiskBasedCache implements Cache),记住这个类,后面从缓存中取出数据和放入数据都是放的这里面。然后将这个缓存类和BasicNetwork传给RequestQueue来构建这个对象。我们看下RequestQueue这个对象是怎么构建的:

public RequestQueue(Cache cache, Network network) {
this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
}

DEFAULT_NETWORK_THREAD_POOL_SIZE这里的值默认是4,我们看到这边又调用了RequestQueue的三个参数的构造函数:

public RequestQueue(Cache cache, Network network, int threadPoolSize) {
this(cache, network, threadPoolSize,
new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}

我们看到这里又出现了一个新的类ExecutorDelivery,这个类里面传进了一个在主线中的Handler,其实这个类就是后面负责用来把解析完的网络的结果返回给主线程的。同样,这个类又调用了四个参数的构造函数:

public RequestQueue(Cache cache, Network network, int threadPoolSize,
ResponseDelivery delivery) {
mCache = cache;
mNetwork = network;
mDispatchers = new NetworkDispatcher[threadPoolSize];
mDelivery = delivery;
}

我们看到了这里面就是将缓存类DiskBasedCache,网络访问类BasicNetWork,还有利用threadPoolSize=4创建一个NetworkDispatcher数组,发送到主线程类ExecutorDelivery赋值给RequestQueue的变量中,后面都会用到,望大家注意!!!!

2.RequestQueue start

我们看到在获取到RequestQueue对象完之后会调用RequestQueue的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);
mCacheDispatcher.start();
// Create network dispatchers (and corresponding threads) up to the pool size.
for (int i = 0; i NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}

我们看到程序会new出一个CacheDispatcher对象,然后调用它的start()方法,CacheDispatcher是一个Thread子类,就是上面图片说的这是个缓存调度线程。然后我们看到会遍历mDispatchers数组,这就是我们刚才用threadPoolSize=4创建的,我们这里遍历创建四个NetworkDispatcher 对象,然后调用start()方法,同样的NetworkDispatcher同时也是一个Thread的子类,这就是前面图片说的网络调度线程。然后我们根据基本用法知道我们这时候还需要一个StringRequest请求对象。我们看下这个到底是什么。

3.StringRequest

这个类其实是继承Request的类:

public class StringRequest extends Request {
/** Lock to guard mListener as it is cleared on cancel() and read on delivery. */
private final Object mLock = new Object();
// @GuardedBy("mLock")
private Listener mListener;
/**
* Creates a new request with the given method.
*
* @param method the request {@link Method} to use
* @param url URL to fetch the string at
* @param listener Listener to receive the String response
* @param errorListener Error listener, or null to ignore errors
*/
public StringRequest(int method, String url, Listener listener,
ErrorListener errorListener) {
super(method, url, errorListener);
mListener = listener;
}
/**
* Creates a new GET request.
*
* @param url URL to fetch the string at
* @param listener Listener to receive the String response
* @param errorListener Error listener, or null to ignore errors
*/
public StringRequest(String url, Listener listener, ErrorListener errorListener) {
this(Method.GET, url, listener, errorListener);
}
@Override
public void cancel() {
super.cancel();
synchronized (mLock) {
mListener = null;
}
}
@Override
protected void deliverResponse(String response) {
Response.Listener listener;
synchronized (mLock) {
listener = mListener;
}
if (listener != null) {
listener.onResponse(response);
}
}
@Override
protected Response parseNetworkResponse(NetworkResponse response) {
String parsed;
try {
parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
} catch (UnsupportedEncodingException e) {
parsed = new String(response.data);
}
return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
}
}

首先StringRequest是继承自Request类的,Request可以指定一个泛型类,这里指定的当然就是String了,接下来StringRequest中提供了两个有参的构造函数,参数包括请求类型,请求地址,以及响应回调等。但需要注意的是,在构造函数中一定要调用super()方法将这几个参数传给父类,因为HTTP的请求和响应都是在父类中自动处理的。

另外,由于Request类中的deliverResponse()parseNetworkResponse()是两个抽象方法,因此StringRequest中需要对这两个方法进行实现。deliverResponse()方法中的实现很简单,仅仅是调用了mListener中的onResponse()方法,并将response内容传入即可,这样就可以将服务器响应的数据进行回调了。parseNetworkResponse()方法中则应该对服务器响应的数据进行解析,其中数据是以字节的形式存放在NetworkResponse的data变量中的,这里将数据取出然后组装成一个String,并传入Response的success()方法中即可。

4.RequestQueue add

得到RequestQueue 的对象以及Request对象之后,我们需要将这个Request对象添加进这个Queue里面去。我们看看这个add做了什么:

public Request add(Request 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);
}
// Process requests in the order they are added.
request.setSequence(getSequenceNumber());
request.addMarker("add-to-queue");
// If the request is uncacheable, skip the cache queue and go straight to the network.
if (!request.shouldCache()) {
mNetworkQueue.add(request);
return request;
}
mCacheQueue.add(request);
return request;
}

我们看到首先将request对象添加进mCurrentRequests里面,记录当前的request请求。然后我们看到会判断shouldCache()是否返回要缓存,一般情况下是true的,但是我们可以自己修改,调用setShouldCache()来改变这个值。如果要缓存则直接调用mCacheQueue#add()方法将这个request添加进缓存队列中。如果不需要缓存则直接添加进网络请求队列中去。

5.CacheDispatcher run

我们知道前面将request将请求添加进队列里面,那么网络请求线程和缓存请求线程的run方法肯定会从队列里面取出任务来执行,我们先来看下缓存请求线程做了什么工作:

@Override
public void run() {
if (DEBUG) VolleyLog.v("start new dispatcher");
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// 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();
request.addMarker("cache-queue-take");
// If the request has been canceled, don't bother dispatching it.
if (request.isCanceled()) {
//判断这个request是不是已经取消了,取消了就直接结束即可
request.finish("cache-discard-canceled");
continue;
}
// Attempt to retrieve this item from cache.
//从缓存中取出对应key的request是不是已经有缓存网络返回结果了,如果有的话就直接返回结果
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
request.addMarker("cache-miss");
// Cache miss; send off to the network dispatcher.
if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
//如果缓存没有命中,则直接放进网络请求队列中,在网络请求线程中会请求网络
mNetworkQueue.put(request);
}
continue;
}
// If it is completely expired, just send it to the network.
if (entry.isExpired()) {
//判断缓存的数据是否已经过期,如果过期了则放进网络请求队列中重新请求
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
mNetworkQueue.put(request);
}
continue;
}
// We have a cache hit; parse its data for delivery back to the request.
request.addMarker("cache-hit");
//调用request的parseNetworkResponse来解析网络返回结果,因为网络返回可能是xml,string,json等等数据,这个地方可以自定义一个request来解析
Response respOnse= request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
if (!entry.refreshNeeded()) {
//缓存数据是否需要重新刷新,如果不需要则直接返回给主线程
// Completely unexpired cache hit. Just deliver the response.
mDelivery.postResponse(request, response);
} else {
// 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;
if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
// Post the intermediate response back to the user and have
// the delivery then forward the request along to the network.
//如果是需要刷新的则我们先返回结果,然后再把request返回网络请求队列中去刷新数据
mDelivery.postResponse(request, response, new Runnable() {
@Override
public void run() {
try {
mNetworkQueue.put(request);
} catch (InterruptedException e) {
// Restore the interrupted status
Thread.currentThread().interrupt();
}
}
});
} else {
// request has been added to list of waiting requests
// to receive the network response from the first request once it returns.
mDelivery.postResponse(request, response);
}
}
} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.
if (mQuit) {
return;
}
}
}
}

我们看到上面的代码就是说明的我们图片里面的意思,程序会循环运行,首先会尝试从缓存DiskBasedCache中取出请求的request,如果缓存DiskBasedCache中已经有这个缓存的结果,则先判断返回结果有没有过期或者需要重新刷新,如果都不需要则直接解析调用ExecutorDelivery返回给主线程即可。

6.NetworkDispatcher run

我们上面已经看了缓存的调度线程,我们现在来看网络的调度线程里面做了些啥,我们可以预想,这里面肯定就是实际地访问网络的代码了,这里面就是我们上面创建来的BasicNetWork来访问网络了:

@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
while (true) {
long startTimeMs = SystemClock.elapsedRealtime();
Request request;
try {
// Take a request from the queue.
//首先从网络请求队列中取出request对象我们这里是以StringRequest为例
request = mQueue.take();
} 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.
//判断下request是不是是取消状态
if (request.isCanceled()) {
request.finish("network-discard-cancelled");
request.notifyListenerResponseNotUsable();
continue;
}
addTrafficStatsTag(request);
// Perform the network request.
//这个地方的mNetwork就是BasicNetWork,调用BasicNetwork的performRequest方法来请求网络
NetworkResponse networkRespOnse= mNetwork.performRequest(request);
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");
request.notifyListenerResponseNotUsable();
continue;
}
// Parse the response here on the worker thread.
//解析网络返回数据
Response respOnse= request.parseNetworkResponse(networkResponse);
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) {
//返回数据需要缓存且返回数据有数据则放进缓存中
mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}
// Post the response back.
request.markDelivered();
//往主线程发送返回的网络结果
mDelivery.postResponse(request, response);
request.notifyListenerResponseReceived(response);
} catch (VolleyError volleyError) {
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
parseAndDeliverNetworkError(request, volleyError);
request.notifyListenerResponseNotUsable();
} 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);
request.notifyListenerResponseNotUsable();
}
}
}

这个方法跟上面的方法有点类似,这里主要有一个不同的地方是要调用mNetwork对象的performRequest方法,这个mNetwork就是BasicNetwork对象,所以我们看下BasicNetwork对象的performRequest()方法:

@Override
public NetworkResponse performRequest(Request request) throws VolleyError {
long requestStart = SystemClock.elapsedRealtime();
while (true) {
HttpResponse httpRespOnse= null;
byte[] respOnseContents= null;
List respOnseHeaders= Collections.emptyList();
try {
// Gather headers.
//得到一些另外的请求头具体可以看getCacheHeaders方法
Map additiOnalRequestHeaders=
getCacheHeaders(request.getCacheEntry());
//这里就是调用的AdaptedHttpStack的executeRequest来请求网络(这里又包装了HttpURLConnection和HttpClient访问网络)
httpRespOnse= mBaseHttpStack.executeRequest(request, additionalRequestHeaders);
int statusCode = httpResponse.getStatusCode();
//获得到返回数据的头部
respOnseHeaders= httpResponse.getHeaders();
// Handle cache validation.
if (statusCode == HttpURLConnection.HTTP_NOT_MODIFIED) {
//如果返回的状态码是不修改的,那么则获取缓存给NetworkResponse
Entry entry = request.getCacheEntry();
if (entry == null) {
return new NetworkResponse(HttpURLConnection.HTTP_NOT_MODIFIED, null, true,
SystemClock.elapsedRealtime() - requestStart, responseHeaders);
}
// Combine cached and response headers so the response will be complete.
List combinedHeaders = combineHeaders(responseHeaders, entry);
return new NetworkResponse(HttpURLConnection.HTTP_NOT_MODIFIED, entry.data,
true, SystemClock.elapsedRealtime() - requestStart, combinedHeaders);
}
// Some responses such as 204s do not have content. We must check.
InputStream inputStream = httpResponse.getContent();
if (inputStream != null) {
respOnseContents=
inputStreamToBytes(inputStream, httpResponse.getContentLength());
} 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, statusCode);
if (statusCode <200 || statusCode > 299) {
throw new IOException();
}
//不然就从网络返回response取出返回的结果数据给NetworkResponse
return new NetworkResponse(statusCode, responseContents, false,
SystemClock.elapsedRealtime() - requestStart, responseHeaders);
} catch (SocketTimeoutException e) {
attemptRetryOnException("socket", request, new TimeoutError());
} catch (MalformedURLException e) {
throw new RuntimeException("Bad URL " + request.getUrl(), e);
} catch (IOException e) {
...........
}
}
}

我们看到这个地方主要就是调用mBaseHttpStack的executeRequest方法,这个mBaseHttpStack在构造BasicNetwork的时候会进行包装:

@Deprecated
public BasicNetwork(HttpStack httpStack, ByteArrayPool pool) {
mHttpStack = httpStack;
mBaseHttpStack = new AdaptedHttpStack(httpStack);
mPool = pool;
}

可以看到这里的mBaseHttpStack就是AdaptedHttpStack对象,所以我们可以看到AdaptedHttpStack的executeRequest方法:

@Override
public HttpResponse executeRequest(
Request request, Map additionalHeaders)
throws IOException, AuthFailureError {
org.apache.http.HttpResponse apacheResp;
try {
//我们看到这个地方调用了mHttpStack的performRequest用来请求网络
apacheResp = mHttpStack.performRequest(request, additionalHeaders);
} catch (ConnectTimeoutException e) {
// BasicNetwork won't know that this exception should be retried like a timeout, since
// it's an Apache-specific error, so wrap it in a standard timeout exception.
throw new SocketTimeoutException(e.getMessage());
}
//得到网络返回的返回码
int statusCode = apacheResp.getStatusLine().getStatusCode();
//返回所有的消息报头
org.apache.http.Header[] headers = apacheResp.getAllHeaders();
List headerList = new ArrayList<>(headers.length);
for (org.apache.http.Header header : headers) {
headerList.add(new Header(header.getName(), header.getValue()));
}
if (apacheResp.getEntity() == null) {
//返回的数据为空则直接就返回状态码和头部列表
return new HttpResponse(statusCode, headerList);
}
long cOntentLength= apacheResp.getEntity().getContentLength();
if ((int) contentLength != contentLength) {
throw new IOException("Response too large: " + contentLength);
}
//将所有的信息封装成HttpResponse对象
return new HttpResponse(
statusCode,
headerList,
(int) apacheResp.getEntity().getContentLength(),
apacheResp.getEntity().getContent());
}

我们看到这里的主要代码也就是调用mHttpStack的performRequest方法来请求网络,这里的mHttpStack就是前面在构造newRequestQueue时候的HurlStack和HttpClientStack,这里面就是具体的HttpClient与HttpURLConnection访问网络的内容了,具体的这两个怎么使用我们不细究。到这里我们网络请求已经返回结果了。那么我们来看看我们是怎么发送我们返回结果到主线程的。

7.mDelivery postResponse(request, response)

我们看到前面已经网络请求数据已经返回了,我们最后会解析完发送给主线程,我们马上就来看看怎么发送的吧:

@Override
public void postResponse(Request request, Response response, Runnable runnable) {
request.markDelivered();
request.addMarker("post-response");
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}

这里就是我们发送数据到主线程的代码,我们看到这里会执行ResponseDeliveryRunnable线程,我们干脆就直接来看这个线程干了什么:

public void run() {
// NOTE: If cancel() is called off the thread that we're currently running in (by
// default, the main thread), we cannot guarantee that deliverResponse()/deliverError()
// won't be called, since it may be canceled after we check isCanceled() but before we
// deliver the response. Apps concerned about this guarantee must either call cancel()
// from the same thread or implement their own guarantee about not invoking their
// listener after cancel() has been called.
// If this request has canceled, finish it and don't deliver.
if (mRequest.isCanceled()) {
mRequest.finish("canceled-at-delivery");
return;
}
// Deliver a normal response or error, depending.
if (mResponse.isSuccess()) {
//返回数据成功则调用mRequest的deliverResponse返回结果,不然返回错误
mRequest.deliverResponse(mResponse.result);
} else {
mRequest.deliverError(mResponse.error);
}
// If this is an intermediate response, add a marker, otherwise we're done
// and the request can be finished.
if (mResponse.intermediate) {
mRequest.addMarker("intermediate-response");
} else {
mRequest.finish("done");
}
// If we have been provided a post-delivery runnable, run it.
if (mRunnable != null) {
mRunnable.run();
}
}

我看看到这个方法主要调用mRequest的deliverResponse来返回数据,这个mRequest我们是以StringRequest为例的,所以我们看StringRequest的deliverResponse方法:

@Override
protected void deliverResponse(String response) {
Response.Listener listener;
synchronized (mLock) {
listener = mListener;
}
if (listener != null) {
listener.onResponse(response);
}
}

这个方法主要是调用的listener的onResponse进行回调的。所以就会调用到我们给StringRequest设置的listener了。到这里我们整个流程也就讲完了,非常的通畅,跟前面的图片完全符合呀。
总结:Volley的代码非常清晰,思路也非常清晰,只要照着图就能了解个大概,这篇是网络框架的第一篇,希望我们接下来能把相应的网络框架拿出来说说,大家一起努力。。。。


推荐阅读
  • 毕业设计做的项目,答辩完了,就共享出来。波尼音乐是一款开源Android在线音乐播放器。 ... [详细]
  • 本文分享了一位Android开发者多年来对于Android开发所需掌握的技能的笔记,包括架构师基础、高级UI开源框架、Android Framework开发、性能优化、音视频精编源码解析、Flutter学习进阶、微信小程序开发以及百大框架源码解读等方面的知识。文章强调了技术栈和布局的重要性,鼓励开发者做好学习规划和技术布局,以提升自己的竞争力和市场价值。 ... [详细]
  • Android程序员面试宝典自定义控件一分钟实现贴纸功能一分钟实现TextView高亮一分钟实现新手引导页一分钟实现ViewPager卡片一分钟实现加载对话框一分钟实现轮播图一分钟 ... [详细]
  • 目前正在做毕业设计,一个关于校园服务的app,我会抽取已完成的相关代码写到文章里。一是为了造福这个曾经帮助过我的社区,二是写文章的同时更能巩固相关知识的记忆。一、前言在爬取教务系统 ... [详细]
  • 美团Android 岗3次挂了,这次终于成功拿下!
    美团Android岗3次挂了,这次终于成功拿下!-面试流程自我介绍回答问题————(详情看下面的攻略)前面会问你很多技术问题,从简单到难,直到问到你打不出来就会又问其他部分的,也是 ... [详细]
  • android之OkHttpClient通信OkHttpClient用法1:自定义缓存OkHttpClienthttpclientnewOkHttpClient.Builder() ... [详细]
  • 联邦学习: 联邦场景下的时空数据挖掘
    不论你望得多远,仍然有无限的空间在外边,不论你数多久,仍然有无限的时间数不清。——惠特曼《自己之歌》1.导引时空数据挖掘做为智慧城市的重要组成部分,和我们的日常生活息息相关。如我 ... [详细]
  • php怎么设置多个脚本(php怎么设置多个脚本编辑)
    导读:很多朋友问到关于php怎么设置多个脚本的相关问题,本文编程笔记就来为大家做个详细解答,供大家参考,希望对大家有所帮助!一起来看看吧!本文目录一览: ... [详细]
  • 调用scanner.close()报错Java.io.Exception: Stream closed; 的原因分析
    引言:想对比用于控制台输入的Scanner和BufferedReader,因此放在同一个方法中,之前学习或者使用时没有特别注意过要不要在使 ... [详细]
  • 经典Java开发教程!迄今为止讲解的最详细的一篇!太牛了!
    前言原来,一瞬间,一句话,真的可以改变一个人的命运。说一个前几年一个热门话题:“是否应该跳出舒适圈。”一时间, ... [详细]
  • 注意:以下分析都是基于Retrofit2转载请注明出处:http:blog.csdn.netevan_manarticledetails51320637本节是《Retrofit的使 ... [详细]
  • 对okhttp网络请求的简单介绍publicclassAppextendsApplication{OkHttpClient实例是唯一的,所有的请求都会通过 ... [详细]
  • 欢迎Follow我的GitHub,关注我的CSDN.应用本质上是一个apk包,即一个zip包,可以直接显示其中的内容.通过apk文件,我们可以获知apk的版本信息,so库和第三方 ... [详细]
  • 09 性能优化网络优化
    如何优化一个网络请求呢?相信大家在面试的时候可能会被问到这个问题。今天我其实就是讲述下我知道的一些简单的优化方式,可以帮助大家在面试的过程中得到点基础分数。 ... [详细]
  • 本篇文章给大家分享的是有关静态方法如何在Kotlin项目中实现,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话 ... [详细]
author-avatar
诸葛二蛋
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有