本章主要介绍Okhttp的使用和和源码分析

  1. 准备工作
  2. 常见用法
  3. OkHttp更好的封装
  4. OkHttp的源码分析

一. 准备工作

在gradle中添加依赖

    implementation 'com.squareup.okio:okio:1.7.0'implementation 'com.squareup.okhttp3:okhttp:3.2.0'

添加网络权限

   <uses-permission android:name="android.permission.INTERNET"/>

二. 常见用法

  1. get异步请求
private void getAsynHttp() {Request.Builder requestBuilder = new Request.Builder().url("http://www.baidu.com");requestBuilder.method("GET", null);Request request = requestBuilder.build();Call mcall = mOkHttpClient.newCall(request);mcall.enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {Toast.makeText(getApplicationContext(), "请求失败", Toast.LENGTH_SHORT).show();}@Overridepublic void onResponse(Call call, Response response) throws IOException {String str = response.body().string();Log.i(TAG, str);runOnUiThread(new Runnable() {@Overridepublic void run() {Toast.makeText(getApplicationContext(), "请求成功", Toast.LENGTH_SHORT).show();}});}});}
  1. post异步请求
    private void postAsynHttp() {RequestBody formBody = new FormBody.Builder().add("ip", "59.108.54.37").build();Request request = new Request.Builder().url("http://ip.taobao.com/service/getIpInfo.php").post(formBody).build();OkHttpClient mOkHttpClient = new OkHttpClient();Call call = mOkHttpClient.newCall(request);call.enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {Toast.makeText(getApplicationContext(), "请求失败", Toast.LENGTH_SHORT).show();}@Overridepublic void onResponse(Call call, Response response) throws IOException {String str = response.body().string();Log.d(TAG, str);runOnUiThread(new Runnable() {@Overridepublic void run() {Toast.makeText(getApplicationContext(), "请求成功", Toast.LENGTH_SHORT).show();}});}});}

只是添加了一个FormBody

  1. 异步上传文件
    private void postAsynFile() {String filepath = "";if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {filepath = Environment.getExternalStorageDirectory().getAbsolutePath();} else {filepath = getFilesDir().getAbsolutePath();}File file = new File(filepath, "wangshu.txt");Request request = new Request.Builder().url("https://api.github.com/markdown/raw").post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file)).build();mOkHttpClient.newCall(request).enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {}@Overridepublic void onResponse(Call call, Response response) throws IOException {Log.d(TAG, response.body().string());}});}

定义文件类型再调用RequestBody方法就ok

  1. 异步下载文件
    private void downAsynFile() {String url = "https://img-my.csdn.net/uploads/201603/26/1458988468_5804.jpg";Request request = new Request.Builder().url(url).build();mOkHttpClient.newCall(request).enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {Toast.makeText(getApplicationContext(), "文件下载失败", Toast.LENGTH_SHORT).show();}@Overridepublic void onResponse(Call call, Response response) {InputStream inputStream = response.body().byteStream();FileOutputStream fileOutputStream = null;String filepath = "";try {if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {filepath = Environment.getExternalStorageDirectory().getAbsolutePath();} else {filepath = getFilesDir().getAbsolutePath();}File file = new File(filepath, "wangshu.jpg");if (null != file) {fileOutputStream = new FileOutputStream(file);byte[] buffer = new byte[2048];int len = 0;while ((len = inputStream.read(buffer)) != -1) {fileOutputStream.write(buffer, 0, len);}fileOutputStream.flush();runOnUiThread(new Runnable() {@Overridepublic void run() {Toast.makeText(getApplicationContext(), "文件存储成功", Toast.LENGTH_SHORT).show();}});} else {runOnUiThread(new Runnable() {@Overridepublic void run() {Toast.makeText(getApplicationContext(), "文件存储失败", Toast.LENGTH_SHORT).show();}});}} catch (IOException e) {Log.e(TAG, "IOException");e.printStackTrace();}}});}

调用response.body().byteStream();得到输入流然后就是文件操作了

  1. 初始化okClient
    private void initOkHttpClient() {File sdcache = getExternalCacheDir();int cacheSize = 10 * 1024 * 1024;OkHttpClient.Builder builder = new OkHttpClient.Builder().connectTimeout(15, TimeUnit.SECONDS).writeTimeout(20, TimeUnit.SECONDS).readTimeout(20, TimeUnit.SECONDS).cache(new Cache(sdcache.getAbsoluteFile(), cacheSize));mOkHttpClient = builder.build();}

在okHttp中,所有所有对于连接的初始化操作都在OkHttpClient.Builder进行,这里设置了超市时间和缓存。

  1. 取消请求

当用户离开应用程序或者跳转到其他页面的时候,我们可以取消任务节省网络资源。

简单的方法就是在Request.Builder.tag中分配一个标签,然后我们就能用OkHtppClient.cancel(Object tag)来取消任务

三. OkHttp更好的封装

public class OkHttpEngine {private static volatile  OkHttpEngine mInstance;private OkHttpClient mOkHttpClient;private Handler mHandler;// 双重检验锁创建OkHttpEngine单例public static OkHttpEngine getInstance(Context context) {if (mInstance == null) {synchronized (OkHttpEngine.class) {if (mInstance == null) {mInstance = new OkHttpEngine(context);}}}return mInstance;}// 在构造函数中初始化Client和handlerprivate OkHttpEngine(Context context) {File sdcache = context.getExternalCacheDir();int cacheSize = 10 * 1024 * 1024;OkHttpClient.Builder builder = new OkHttpClient.Builder().connectTimeout(15, TimeUnit.SECONDS).writeTimeout(20, TimeUnit.SECONDS).readTimeout(20, TimeUnit.SECONDS).cache(new Cache(sdcache.getAbsoluteFile(), cacheSize));mOkHttpClient=builder.build();mHandler = new Handler();}/*** 异步get请求* @param url* @param callback*/public void getAsynHttp(String url, ResultCallback callback) {final Request request = new Request.Builder().url(url).build();Call call = mOkHttpClient.newCall(request);dealResult(call, callback);}private void dealResult(Call call, final ResultCallback callback) {call.enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {sendFailedCallback(call.request(), e, callback);}@Overridepublic void onResponse(Call call, Response response) throws IOException {sendSuccessCallback(response.body().string(), callback);}private void sendSuccessCallback(final String str, final ResultCallback callback) {mHandler.post(new Runnable() {@Overridepublic void run() {if (callback != null) {try {callback.onResponse(str);} catch (IOException e) {e.printStackTrace();}}}});}private void sendFailedCallback(final Request request, final Exception e, final ResultCallback callback) {mHandler.post(new Runnable() {@Overridepublic void run() {if (callback != null)callback.onError(request, e);}});}});}public abstract class ResultCallback{public abstract void onError(Request request, Exception e);public abstract void onResponse(String str) throws IOException;}
}

请求网络的时候是用Handler将请求结果回调给UI线程,所以我们想要请求网络的时候只需要调用OkHttpEngine的getAsynHttp方法并写一个ResultCallback回调就可以了。

四. OkHttp的源码分析

我们从使用开始,一步步剖析OkClient的源码实现

//下面是一段Kotlin代码
override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)val request = Request.Builder().url("https://www.baidu.com/").method("get", null).build()OkHttpClient().newCall(request).enqueue(object : Callback {override fun onFailure(call: Call?, e: IOException?) {Log.d("onClient", "isBad")}override fun onResponse(call: Call?, response: Response?) {Log.e("onClient", "isOk")}})}

我们把重点关注在 OkHttpClient().newCall(request).enqueue() 这个方法中

newCall方法:

  // OkHttpClint中的newCall方法@Override public Call newCall(Request request) {return new RealCall(this, request);}

实例化一个RealCall类,RealCall实现了Call接口,看一下Call是干什么的
,这里保留了源码的英文注释

// A call is a request that has been prepared for execution.
public interface Call {//  Invokes the request immediately, and blocks until the response can be processed or is in  error.Response execute() throws IOException;// Schedules the request to be executed at some point in the future.void enqueue(Callback responseCallback);void cancel();boolean isExecuted();boolean isCanceled();interface Factory {Call newCall(Request request);}
}

然后我们继续看OkHttpClient().newCall(request).enqueue()
的enqueue方法

  // RealCall中的enqueue方法void enqueue(Callback responseCallback, boolean forWebSocket) {synchronized (this) {if (executed) throw new IllegalStateException("Already Executed");executed = true;}client.dispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket));}

其中这个参数responseCallback就是源码介绍开头中OkHttpClient().newCall(request).enqueue()传入的callback

我们这里可以看到,真正处理这个Callback的并不是RealCall,而是dispatcher(),这个就是调度器,看看他的类声明 public final class Dispatcher 是一个不变类,也就是说不能被重写。

我们看一下调度器Dispatcher中的enqueue方法:

 // Dispatcher类的enqueue方法synchronized void enqueue(AsyncCall call) {if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {runningAsyncCalls.add(call);executorService().execute(call);} else {readyAsyncCalls.add(call);}}

这个方法用了synchronized声明。看到这么多全局的变量,感觉有点烦,没事,其实很简单,我们看一下Dispatcher的声明:

 // Dispatcher类的成员变量private int maxRequests = 64;private int maxRequestsPerHost = 5;/** Executes calls. Created lazily. */private ExecutorService executorService;/** Ready async calls in the order they'll be run. */private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();/** Running synchronous calls. Includes canceled calls that haven't finished yet. */private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

是不是感觉很清晰,很简单

我们回头看这段代码

 // Dispatcher类的enqueue方法synchronized void enqueue(AsyncCall call) {if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {runningAsyncCalls.add(call);executorService().execute(call);} else {readyAsyncCalls.add(call);}}

很简单,如果可以这个符合可以运行的条件,就把它加到runningAsyncCalls队列中,然后调用execute执行,否则加到readyAsyncCalls准备队列中

ok,我们把关注点放在这个方法中
executorService().execute(call);

// Dispatcher的executorService方法
public synchronized ExecutorService executorService() {if (executorService == null) {executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));}return executorService;}// 对比一下newCachedThreadPool,简直就是一毛一样好吧public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());}

我们知道CachedThreadPool实际上就是一个无限容量,但是队列中只有一个线程的线程池。这种线程池比较适合任务比较多,但是任务比较小的情况。

execute(Runnable run)调度方法最后会调用调用这个runnable的run方法,因为这个call是AsyncCall类,我们看看这个类的run方法:

final class AsyncCall extends NamedRunnable {private final Callback responseCallback;private final boolean forWebSocket;private AsyncCall(Callback responseCallback, boolean forWebSocket) {super("OkHttp %s", originalRequest.url().toString());this.responseCallback = responseCallback;this.forWebSocket = forWebSocket;}@Override protected void execute() {boolean signalledCallback = false;try {Response response = getResponseWithInterceptorChain(forWebSocket);if (canceled) {signalledCallback = true;responseCallback.onFailure(RealCall.this, new IOException("Canceled"));} else {signalledCallback = true;responseCallback.onResponse(RealCall.this, response);}} catch (IOException e) {if (signalledCallback) {// Do not signal the callback twice!logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e);} else {responseCallback.onFailure(RealCall.this, e);}} finally {client.dispatcher().finished(this);}}}

没有run方法呀,怎么肥事?看看它继承的类NamedRunnable搞了什么。

public abstract class NamedRunnable implements Runnable {protected final String name;public NamedRunnable(String format, Object... args) {this.name = String.format(format, args);}@Override public final void run() {String oldName = Thread.currentThread().getName();Thread.currentThread().setName(name);try {execute();} finally {Thread.currentThread().setName(oldName);}}protected abstract void execute();
}

原来是它在run方法执行了execute方法,行吧,其实一样,线程池最后执行的是AsyncCall的execute方法。ojbk,来看一下吧!

   // AsyncCall的execute方法@Override protected void execute() {boolean signalledCallback = false;try {Response response = getResponseWithInterceptorChain(forWebSocket);if (canceled) {signalledCallback = true;responseCallback.onFailure(RealCall.this, new IOException("Canceled"));} else {signalledCallback = true;responseCallback.onResponse(RealCall.this, response);}} catch (IOException e) {if (signalledCallback) {// Do not signal the callback twice!logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e);} else {responseCallback.onFailure(RealCall.this, e);}} finally {client.dispatcher().finished(this);}}

别急,我们一个个往下面看,先看这个方法:getResponseWithInterceptorChain

  private Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException {Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest, forWebSocket);return chain.proceed(originalRequest);}

ApplicationInterceptorChain 从名字可以猜测到,他是一个拦截链,看一下吧!

 // ApplicationInterceptorChain类的proceed方法@Override public Response proceed(Request request) throws IOException {// If there's another interceptor in the chain, call that.if (index < client.interceptors().size()) {Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);// 得到了当前拦截器Interceptor interceptor = client.interceptors().get(index);// 拦截操作,所有拦截器(除了最后一个)都会阻塞到这里Response interceptedResponse = interceptor.intercept(chain);if (interceptedResponse == null) {throw new NullPointerException("application interceptor " + interceptor+ " returned null");}return interceptedResponse;}return getResponse(request, forWebSocket);}}

看到这段,有点发蒙

Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);

为什么还要创建一个chain呢,原来index代表当前拦截器所在的位置,比如一共有五个拦截器,拦截器“G”在第二个,那你要做的是将“G”拦截器拦截在第一个拦截器之后。get到了吗?

Response interceptedResponse = interceptor.intercept(chain);
这个方法为什么会拦住所有的拦截器,大家可能不明白,我就随便创建一个拦截器(拦截发出的请求和响应的日志)出来,大家就知道了。

class LoggingInterceptor implements Interceptor {@Override public Response intercept(Interceptor.Chain chain) throws IOException {Request request = chain.request();long t1 = System.nanoTime();logger.info(String.format("Sending request %s on %s%n%s",request.url(), chain.connection(), request.headers()));Response response = chain.proceed(request);long t2 = System.nanoTime();logger.info(String.format("Received response for %s in %.1fms%n%s",response.request().url(), (t2 - t1) / 1e6d, response.headers()));return response;}
}

大家能够明白吗,这里我画一张图:

这个有点类似Spirng中的拦截器,其实是一个道理。

ok,我们讨论完了拦截器。回到AsyncCall的execute方法。

   // AsyncCall的execute方法@Override protected void execute() {boolean signalledCallback = false;try {Response response = getResponseWithInterceptorChain(forWebSocket);if (canceled) {signalledCallback = true;responseCallback.onFailure(RealCall.this, new IOException("Canceled"));} else {signalledCallback = true;responseCallback.onResponse(RealCall.this, response);}} catch (IOException e) {if (signalledCallback) {// Do not signal the callback twice!logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e);} else {responseCallback.onFailure(RealCall.this, e);}} finally {client.dispatcher().finished(this);}}
  responseCallback.onFailure(RealCall.this, responseCallback.onResponse(RealCall.this, response);

这里正是我们重写的Callback的两个方法。

好了,做了这么久的前戏,也该开始网络请求了吧?看这个方法
client.dispatcher().finished(this); 是调度器的finish方法。

   // Dispatcher类的finished方法synchronized void finished(AsyncCall call) {if (!runningAsyncCalls.remove(call)) throw new AssertionError("AsyncCall wasn't running!");promoteCalls();}

这里简单地把call从运行队列中移走了。看一下PromoteCalls方法:

 // Dispatcher类中的promoteCalls方法private void promoteCalls() {if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {AsyncCall call = i.next();if (runningCallsForHost(call) < maxRequestsPerHost) {i.remove();runningAsyncCalls.add(call);executorService().execute(call);}if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.}}

很简单,running的一个call被消化了,这个队列也就有了空位置,这时候ready队列有call乘机上位。等等,说好的网络请求呢,在哪?是不是哪里疏忽了?

我们分析到AsyncCall的execute方法的时候,还记得它做了什么吗,它用一大堆拦截链拦截他,然后在最后 client.dispatcher().finished(this);对调度器Dispactcher的两个队列进行收尾对吧?

于是我们大概分析到网络请求操作在它们中间!果然在拦截器中间ApplicationInterceptorChain类的proceed方法找到啦!

 // ApplicationInterceptorChain类的proceed方法@Override public Response proceed(Request request) throws IOException {// If there's another interceptor in the chain, call that.if (index < client.interceptors().size()) {Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);// 得到了当前拦截器Interceptor interceptor = client.interceptors().get(index);// 拦截操作,所有拦截器(除了最后一个)都会阻塞到这里Response interceptedResponse = interceptor.intercept(chain);if (interceptedResponse == null) {throw new NullPointerException("application interceptor " + interceptor+ " returned null");}return interceptedResponse;}return getResponse(request, forWebSocket);}}

网络请求在这里:getResponse(request, forWebSocket);

这个方法又臭又长,这里我把重要的贴出来。

Response getResponse(Request request, boolean forWebSocket) throws IOException {engine.sendRequest();engine.readResponse();
}

喵喵喵!就这么短

一个个看啰!
HttpEngine的sendRequest方法也很复杂,主要解决的是缓存问题,我们就不展开了,不然要要要讲到明年!

// HttpEngine的sendRequest方法public void sendRequest() throws RequestException, RouteException, IOException {if (cacheStrategy != null) return; if (httpStream != null) throw new IllegalStateException();Request request = networkRequest(userRequest);//获取Client中的Cache,同时Cache在初始化的时候读取缓存目录中曾经请求过的所有信息。InternalCache responseCache = Internal.instance.internalCache(client);Response cacheCandidate = responseCache != null? responseCache.get(request): null;long now = System.currentTimeMillis();cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get();//网络请求networkRequest = cacheStrategy.networkRequest;//缓存的响应cacheResponse = cacheStrategy.cacheResponse;if (responseCache != null) {//记录当前请求是网络发起的还是缓存发起的responseCache.trackResponse(cacheStrategy);}if (cacheCandidate != null && cacheResponse == null) {closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.}// 不进行网络请求并且缓存不存在或者过期,返回504错误if (networkRequest == null && cacheResponse == null) {userResponse = new Response.Builder().request(userRequest).priorResponse(stripBody(priorResponse)).protocol(Protocol.HTTP_1_1).code(504).message("Unsatisfiable Request (only-if-cached)").body(EMPTY_BODY).build();return;}// 不进行网络请求而且缓存可以使用,则直接返回缓存if (networkRequest == null) {userResponse = cacheResponse.newBuilder().request(userRequest).priorResponse(stripBody(priorResponse)).cacheResponse(stripBody(cacheResponse)).build();userResponse = unzip(userResponse);return;}// 需要访问网络时boolean success = false;try {httpStream = connect();httpStream.setHttpEngine(this);

cacheCandidate是上次与服务器交互时缓存的Response,这里的缓存均基于Map。key是请求中url的md5,value是在文件中查到的缓存,页面置换算法基于LRU。

通过cacheStrategy我们可以得到networkRequest和cacheResponse,代表网络请求和缓存是否存在。如果networkRequest和cacheResponse都为null的时候,返回504错误,当networkRequest为null时,也就是不进行网络请求时,就可以直接返回缓存,其他情况就请求网络。

我们粗略看一下readResponse()方法:

 public void readResponse() throws IOException {Response networkResponse;if (forWebSocket) {httpStream.writeRequestHeaders(networkRequest);// 读取网络响应networkResponse = readNetworkResponse();} else if (!callerWritesRequestBody) {// 检查缓存是否可用,如果可用则使用当前缓存的Response,关闭网络连接,释放连接networkResponse = new NetworkInterceptorChain(0, networkRequest).proceed(networkRequest);} else {// Emit the request body's buffer so that everything is in requestBodyOut.if (bufferedRequestBody != null && bufferedRequestBody.buffer().size() > 0) {bufferedRequestBody.emit();}// Emit the request headers if we haven't yet. We might have just learned the Content-Length.if (sentRequestMillis == -1) {if (OkHeaders.contentLength(networkRequest) == -1&& requestBodyOut instanceof RetryableSink) {long contentLength = ((RetryableSink) requestBodyOut).contentLength();networkRequest = networkRequest.newBuilder().header("Content-Length", Long.toString(contentLength)).build();}httpStream.writeRequestHeaders(networkRequest);}// Write the request body to the socket.if (requestBodyOut != null) {if (bufferedRequestBody != null) {// This also closes the wrapped requestBodyOut.bufferedRequestBody.close();} else {requestBodyOut.close();}if (requestBodyOut instanceof RetryableSink) {httpStream.writeRequestBody((RetryableSink) requestBodyOut);}}networkResponse = readNetworkResponse();}receiveHeaders(networkResponse.headers());// If we have a cache response too, then we're doing a conditional get.if (cacheResponse != null) {if (validate(cacheResponse, networkResponse)) {userResponse = cacheResponse.newBuilder().request(userRequest).priorResponse(stripBody(priorResponse)).headers(combine(cacheResponse.headers(), networkResponse.headers())).cacheResponse(stripBody(cacheResponse)).networkResponse(stripBody(networkResponse)).build();networkResponse.body().close();releaseStreamAllocation();// Update the cache after combining headers but before stripping the// Content-Encoding header (as performed by initContentStream()).InternalCache responseCache = Internal.instance.internalCache(client);responseCache.trackConditionalCacheHit();responseCache.update(cacheResponse, stripBody(userResponse));userResponse = unzip(userResponse);return;} else {closeQuietly(cacheResponse.body());}}userResponse = networkResponse.newBuilder().request(userRequest).priorResponse(stripBody(priorResponse)).cacheResponse(stripBody(cacheResponse)).networkResponse(stripBody(networkResponse)).build();if (hasBody(userResponse)) {maybeCache();userResponse = unzip(cacheWritingResponse(storeRequest, userResponse));}}

这个方法做的是解析HTTP响应报头。如果有缓存而且可用,则用缓存的数据并更新缓存,否则则用网络请求返回的数据。

总结完了,大家应该会觉得很乱,这里粗略画个图帮助大家理解。

安卓总结 之 OkHttp使用及源码分析(三)相关推荐

  1. Nouveau源码分析(三):NVIDIA设备初始化之nouveau_drm_probe

    Nouveau源码分析(三) 向DRM注册了Nouveau驱动之后,内核中的PCI模块就会扫描所有没有对应驱动的设备,然后和nouveau_drm_pci_table对照. 对于匹配的设备,PCI模块 ...

  2. 【投屏】Scrcpy源码分析三(Client篇-投屏阶段)

    Scrcpy源码分析系列 [投屏]Scrcpy源码分析一(编译篇) [投屏]Scrcpy源码分析二(Client篇-连接阶段) [投屏]Scrcpy源码分析三(Client篇-投屏阶段) [投屏]Sc ...

  3. Spring源码分析(三)

    Spring源码分析 第三章 手写Ioc和Aop 文章目录 Spring源码分析 前言 一.模拟业务场景 (一) 功能介绍 (二) 关键功能代码 (三) 问题分析 二.使用ioc和aop重构 (一) ...

  4. Okhttp同步请求源码分析

    进阶android,OKhttp源码分析--同步请求的源码分析 OKhttp是我们经常用到的框架,作为开发者们,我们不单单要学会灵活使用,还要知道他的源码是如何设计的. 今天我们来分析一下OKhttp ...

  5. 安卓PopupWindow使用详解与源码分析(附项目实例)

    基本用法 首先定义弹窗的Layout文件 res/layout/popup_window.xml <?xml version="1.0" encoding="utf ...

  6. 【安卓学习积累】IntentService的源码分析

    今天主要总结一下IntentService的源码,里面是如何实现的,为什么IntentService在执行完耗时操作后会自动停止. 1.the service is started as needed ...

  7. Journey源码分析三:模板编译

    2019独角兽企业重金招聘Python工程师标准>>> 在Journey源码分析二:整体启动流程中提到了模板编译,这里详细说下启动流程 看下templates.Generate()源 ...

  8. ABP源码分析三十四:ABP.Web.Mvc

    ABP.Web.Mvc模块主要完成两个任务: 第一,通过自定义的AbpController抽象基类封装ABP核心模块中的功能,以便利的方式提供给我们创建controller使用. 第二,一些常见的基础 ...

  9. ABP源码分析三十:ABP.RedisCache

    ABP 通过StackExchange.Redis类库来操作Redis数据库. AbpRedisCacheModule:完成ABP.RedisCache模块的初始化(完成常规的依赖注入) AbpRed ...

最新文章

  1. onsrcoll和scrollTop兼容与实现
  2. JAX-RS客户端WebClient的使用教程
  3. 月入过万的副业你要不要?不需要编程知识,不限男女,不限学历
  4. demo16 webpack 处理字体
  5. 测试原理_耐压测试仪绝缘电阻测试仪基本原理与选用
  6. Spring Boot 概述、初始化器、spring-boot-maven-plugin 插件简化部署、starter 自动配置原理
  7. Unsatisfied forward or external declaration 错误分析
  8. 中控考勤机二次开发 java_SDK二次开发,读取中控考勤机打卡记录测试。
  9. 网站权重大有用处,枫树seo教你一键进行网站权重查询
  10. android lame wav 转 mp3,Wav文件转mp3(LAME)
  11. thinkpad T480安装WIN7
  12. JavaScript 常见鼠标事件
  13. HAL库学习之串口通信
  14. python——plt.figure()画子图(双轴图)双Y轴实例
  15. 正则,grep命令详解
  16. 因果信号的傅里叶变换_信号与系统实验报告3实验3 傅里叶变换及其性质
  17. 金蝶eas服务器文件更新端口,金蝶eas更换服务器地址
  18. 阿里P8半年来每天加班到凌晨,只为这份Spring Boot进阶宝典,从理论到实战全面起飞
  19. 什么是AOP?AOP面向切面编程
  20. python调用c/c++代码以及解决ctypes.ArgumentError: argument 1: class 'TypeError': Don't know how to convert

热门文章

  1. nginx系列之六:cache服务
  2. 服务器性能优化之网络性能优化
  3. []int 能转换为 []interface 吗?
  4. 编写与优化 Go 代码(一)
  5. C语言实现封装、继承、多态
  6. Shell的基本语法结构
  7. ECCV 2020 亮点摘要(上)
  8. “疫”外爆发:没那么简单的视频会议
  9. Nebula:Slack 的覆盖全球性的开源网络
  10. 多媒体技术创新与难点探索(内附讲师资料下载)