当前位置:网站首页>Volley source code analysis
Volley source code analysis
2022-07-03 20:50:00 【xiaopangcame】
summary
This article is based on Volley 1.1.1 Version of the source .
Volley yes Google An official set of small and ingenious asynchronous request library , The framework is very extensible , Support HttpClient、HttpUrlConnection, Even support OkHttp.Volley It is not suitable for downloading a large amount of content or streaming operation , Because in the process of parsing ,Volley All responses will be stored in memory , therefore Volley Not suitable for uploading and downloading large files .
Add dependency :
1 | implementation 'com.android.volley:volley:1.1.1' |
Use :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | RequestQueue queue = Volley.newRequestQueue(context); String url ="http://www.google.com"; StringRequest stringRequest = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() { @Override public void onResponse(String response) { // in UI Thread } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { // in UI Thread } }); stringRequest.setTag(TAG); // Add the request to the RequestQueue. queue.add(stringRequest); // onStop queue.cancelAll(TAG); |
Create request queue
Volley Class has multiple overloaded newRequestQueue Static methods :
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 | public class Volley { private static final String DEFAULT_CACHE_DIR = "volley"; public static RequestQueue newRequestQueue(Context context) { return newRequestQueue(context, (BaseHttpStack) null); } public static RequestQueue newRequestQueue(Context context, BaseHttpStack stack) { BasicNetwork network; if (stack == null) { if (Build.VERSION.SDK_INT >= 9) { network = new BasicNetwork(new HurlStack()); } else { // ... } } else { network = new BasicNetwork(stack); } return newRequestQueue(context, network); } private static RequestQueue newRequestQueue(Context context, Network network) { final Context appContext = context.getApplicationContext(); DiskBasedCache.FileSupplier cacheSupplier = new DiskBasedCache.FileSupplier() { private File cacheDir = null; @Override public File get() { if (cacheDir == null) { cacheDir = new File(appContext.getCacheDir(), DEFAULT_CACHE_DIR); } return cacheDir; } }; RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheSupplier), network); queue.start(); return queue; } } |
RequestQueue Class construction method is as follows :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public class RequestQueue { private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4; private final Cache mCache; private final Network mNetwork; private final ResponseDelivery mDelivery; private final NetworkDispatcher[] mDispatchers; private CacheDispatcher mCacheDispatcher; public RequestQueue( Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery) { mCache = cache; mNetwork = network; mDispatchers = new NetworkDispatcher[threadPoolSize]; mDelivery = delivery; } public RequestQueue(Cache cache, Network network, int threadPoolSize) { this(cache, network, threadPoolSize, new ExecutorDelivery(new Handler(Looper.getMainLooper()))); } public RequestQueue(Cache cache, Network network) { this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE); } } |
RequestQueue#start()
The method is as follows , It can be seen that a Cache Dispatcher And a specified number Network Dispatcher:
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 | /** Starts the dispatchers in this queue. */ public void start() { // Make sure any currently running dispatchers are stopped. stop(); // 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 < mDispatchers.length; i++) { NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery); mDispatchers[i] = networkDispatcher; networkDispatcher.start(); } } /** Stops the cache and network dispatchers. */ public void stop() { if (mCacheDispatcher != null) { mCacheDispatcher.quit(); } for (final NetworkDispatcher mDispatcher : mDispatchers) { if (mDispatcher != null) { mDispatcher.quit(); } } } |
RequestQueue event
stay RequestQueue Class defines some events :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public @interface RequestEvent { /** The request was added to the queue. */ public static final int REQUEST_QUEUED = 0; /** Cache lookup started for the request. */ public static final int REQUEST_CACHE_LOOKUP_STARTED = 1; /** Cache lookup finished for the request and cached response is delivered or request is queued for network dispatching. */ public static final int REQUEST_CACHE_LOOKUP_FINISHED = 2; /** Network dispatch started for the request. */ public static final int REQUEST_NETWORK_DISPATCH_STARTED = 3; /** The network dispatch finished for the request and response (if any) is delivered. */ public static final int REQUEST_NETWORK_DISPATCH_FINISHED = 4; /** All the work associated with the request is finished and request is removed from all the queues. */ public static final int REQUEST_FINISHED = 5; } public interface RequestEventListener { void onRequestEvent(Request<?> request, @RequestEvent int event); } private final List<RequestEventListener> mEventListeners = new ArrayList<>(); |
These events can be manipulated in the following ways :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | void sendRequestEvent(Request<?> request, @RequestEvent int event) { synchronized (mEventListeners) { for (RequestEventListener listener : mEventListeners) { listener.onRequestEvent(request, event); } } } public void addRequestEventListener(RequestEventListener listener) { synchronized (mEventListeners) { mEventListeners.add(listener); } } public void removeRequestEventListener(RequestEventListener listener) { synchronized (mEventListeners) { mEventListeners.remove(listener); } } |
Send a request
RequestQueue#add()
The method is as follows :
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 | private final AtomicInteger mSequenceGenerator = new AtomicInteger(); private final Set<Request<?>> mCurrentRequests = new HashSet<>(); private final PriorityBlockingQueue<Request<?>> mCacheQueue = new PriorityBlockingQueue<>(); private final PriorityBlockingQueue<Request<?>> mNetworkQueue = new PriorityBlockingQueue<>(); 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); } // Process requests in the order they are added. request.setSequence(getSequenceNumber()); request.addMarker("add-to-queue"); sendRequestEvent(request, RequestEvent.REQUEST_QUEUED); // If the request does not need caching , Directly give it to the network request queue to execute , The default is to cache if (!request.shouldCache()) { mNetworkQueue.add(request); return request; } mCacheQueue.add(request); return request; } |
In this method, the request is handed over to the cache queue or network queue for processing .
CacheDispatcher
CacheDispatcher Inherited from Thread, stay RequestQueue#start()
Method has been started , So look directly run Method :
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 137 138 | public class CacheDispatcher extends Thread { private final BlockingQueue<Request<?>> mCacheQueue; private final BlockingQueue<Request<?>> mNetworkQueue; private final Cache mCache; private final ResponseDelivery mDelivery; private volatile boolean mQuit = false; /** Manage list of waiting requests and de-duplicate requests with same cache key. */ private final WaitingRequestManager mWaitingRequestManager; public CacheDispatcher( BlockingQueue<Request<?>> cacheQueue, BlockingQueue<Request<?>> networkQueue, Cache cache, ResponseDelivery delivery) { mCacheQueue = cacheQueue; mNetworkQueue = networkQueue; mCache = cache; mDelivery = delivery; mWaitingRequestManager = new WaitingRequestManager(this); } public void quit() { mQuit = true; interrupt(); } @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 { processRequest(); } catch (InterruptedException e) { // We may have been interrupted because it was time to quit. if (mQuit) { Thread.currentThread().interrupt(); return; } VolleyLog.e("Ignoring spurious interrupt of CacheDispatcher thread; " + "use quit() to terminate it"); } } } private void processRequest() throws InterruptedException { // Blocking queues final Request<?> request = mCacheQueue.take(); processRequest(request); } void processRequest(final Request<?> request) throws InterruptedException { request.addMarker("cache-queue-take"); request.sendEvent(RequestQueue.RequestEvent.REQUEST_CACHE_LOOKUP_STARTED); try { // If the request has been canceled, don't bother dispatching it. if (request.isCanceled()) { request.finish("cache-discard-canceled"); return; } // Attempt to retrieve this item from cache. 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); } return; } // 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); } return; } // We have a cache hit; parse its data for delivery back to the request. request.addMarker("cache-hit"); Response<?> response = request.parseNetworkResponse(new NetworkResponse(entry.data, entry.responseHeaders)); request.addMarker("cache-hit-parsed"); if (!response.isSuccess()) { request.addMarker("cache-parsing-failed"); mCache.invalidate(request.getCacheKey(), true); request.setCacheEntry(null); if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) { mNetworkQueue.put(request); } return; } if (!entry.refreshNeeded()) { // Cache does not need to be refreshed , Give it directly to ResponseDelivery To deal with mDelivery.postResponse(request, response); } else { // Soft expired cache hits , You can distribute cached response, But we also need to send the request to the network to refresh . request.addMarker("cache-hit-refresh-needed"); request.setCacheEntry(entry); // Mark the response as intermediate. response.intermediate = true; if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) { // take intermediate The response is sent back to the user , Then forward the request to the network . mDelivery.postResponse( request, response, new Runnable() { @Override public void run() { try { // If the cache needs to be refreshed , This place needs to re request the network mNetworkQueue.put(request); } catch (InterruptedException e) { // Restore the interrupted status Thread.currentThread().interrupt(); } } }); } else { // The request has been added to the list of waiting requests , To receive the network response from the first request when the first request returns . mDelivery.postResponse(request, response); } } } finally { request.sendEvent(RequestQueue.RequestEvent.REQUEST_CACHE_LOOKUP_FINISHED); } } } |
If you have a cache , Take it out and deal with it , No or expired , Join the network request queue to request , There are several places that need network requests :
- Requests do not require caching
- Request not found in cache
- The requested cache has expired
- The requested cache needs to be refreshed
NetworkDispatcher
NetworkDispatcher
NetworkDispatcher Also inherit from Thread, They are RequestQueue#start()
Method has been started , So look directly run Method :
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 | public class NetworkDispatcher extends Thread { private final BlockingQueue<Request<?>> mQueue; private final Network mNetwork; private final Cache mCache; private final ResponseDelivery mDelivery; private volatile boolean mQuit = false; public NetworkDispatcher( BlockingQueue<Request<?>> queue, Network network, Cache cache, ResponseDelivery delivery) { mQueue = queue; mNetwork = network; mCache = cache; mDelivery = delivery; } public void quit() { mQuit = true; interrupt(); } @Override public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); while (true) { try { processRequest(); } catch (InterruptedException e) { // We may have been interrupted because it was time to quit. if (mQuit) { Thread.currentThread().interrupt(); return; } VolleyLog.e("Ignoring spurious interrupt of NetworkDispatcher thread; " + "use quit() to terminate it"); } } } private void processRequest() throws InterruptedException { Request<?> request = mQueue.take(); processRequest(request); } void processRequest(Request<?> request) { long startTimeMs = SystemClock.elapsedRealtime(); request.sendEvent(RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_STARTED); try { request.addMarker("network-queue-take"); if (request.isCanceled()) { request.finish("network-discard-cancelled"); request.notifyListenerResponseNotUsable(); return; } addTrafficStatsTag(request); // Perform network requests 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(); return; } // 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(); } finally { request.sendEvent(RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_FINISHED); } } } |
You can see , stay NetworkDispatcher in , The most important thing is to call mNetwork.performRequest(request)
Perform network requests , And in the Network It's an interface , The concrete implementation class is BasicNetwork.
BasicNetwork
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 | public NetworkResponse performRequest(Request<?> request) throws VolleyError { long requestStart = SystemClock.elapsedRealtime(); while (true) { // ... try { // ... httpResponse = mBaseHttpStack.executeRequest(request, additionalRequestHeaders); // return NetworkResponse } catch (SocketTimeoutException e) { attemptRetryOnException("socket", request, new TimeoutError()); } catch (MalformedURLException e) { throw new RuntimeException("Bad URL " + request.getUrl(), e); } catch (IOException e) { int statusCode; if (httpResponse != null) { statusCode = httpResponse.getStatusCode(); } else { throw new NoConnectionError(e); } VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl()); NetworkResponse networkResponse; if (responseContents != null) { networkResponse = new NetworkResponse( statusCode, responseContents, /* notModified= */ false, SystemClock.elapsedRealtime() - requestStart, responseHeaders); if (statusCode == HttpURLConnection.HTTP_UNAUTHORIZED || statusCode == HttpURLConnection.HTTP_FORBIDDEN) { attemptRetryOnException( "auth", request, new AuthFailureError(networkResponse)); } else if (statusCode >= 400 && statusCode <= 499) { // Don't retry other client errors. throw new ClientError(networkResponse); } else if (statusCode >= 500 && statusCode <= 599) { if (request.shouldRetryServerErrors()) { attemptRetryOnException( "server", request, new ServerError(networkResponse)); } else { throw new ServerError(networkResponse); } } else { // 3xx? No reason to retry. throw new ServerError(networkResponse); } } else { attemptRetryOnException("network", request, new NetworkError()); } } } } // Request to retry private static void attemptRetryOnException( String logPrefix, Request<?> request, VolleyError exception) throws VolleyError { RetryPolicy retryPolicy = request.getRetryPolicy(); int oldTimeout = request.getTimeoutMs(); try { retryPolicy.retry(exception); } catch (VolleyError e) { request.addMarker(String.format("%s-timeout-giveup [timeout=%s]", logPrefix, oldTimeout)); throw e; } request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout)); } public class DefaultRetryPolicy implements RetryPolicy { @Override public void retry(VolleyError error) throws VolleyError { mCurrentRetryCount++; mCurrentTimeoutMs += (int) (mCurrentTimeoutMs * mBackoffMultiplier); if (!hasAttemptRemaining()) { throw error; } } /** Returns true if this policy has attempts remaining, false otherwise. */ protected boolean hasAttemptRemaining() { return mCurrentRetryCount <= mMaxNumRetries; } } |
stay BasicNetwork#performRequest() Called in httpResponse = mBaseHttpStack.executeRequest(request, additionalRequestHeaders);
, there mBaseHttpStack That's what we created earlier RequestQueue It's new HurlStack and HttpClientStack(@Deprecated, above RequestQueue There is also a new one in the constructor of HttpClientStack Object method , But it has been abandoned , No parsing ). So in HttpStack Of executeRequest() Is the specific network request .
HurlStack
1 2 3 4 5 6 7 | public class HurlStack extends BaseHttpStack { @Override public HttpResponse executeRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError { // This class uses HttpURLConnection To realize the real network request , You can customize } } |
ResponseDelivery
ResponseDelivery It's an interface :
1 2 3 4 5 6 7 8 9 10 11 12 13 | public interface ResponseDelivery { /** Parses a response from the network or cache and delivers it. */ void postResponse(Request<?> request, Response<?> response); /** * Parses a response from the network or cache and delivers it. The provided Runnable will be * executed after delivery. */ void postResponse(Request<?> request, Response<?> response, Runnable runnable); /** Posts an error for the given request. */ void postError(Request<?> request, VolleyError error); } |
The concrete implementation class is ExecutorDelivery, It ensures that the callback called by the client runs in UI Threads :
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 | public class ExecutorDelivery implements ResponseDelivery { private final Executor mResponsePoster; // From the above analysis, we can see that handle = new Handler(Looper.getMainLooper()) public ExecutorDelivery(final Handler handler) { mResponsePoster = new Executor() { @Override public void execute(Runnable command) { handler.post(command); } }; } public ExecutorDelivery(Executor executor) { mResponsePoster = executor; } @Override public void postResponse(Request<?> request, Response<?> response) { postResponse(request, response, null); } @Override public void postResponse(Request<?> request, Response<?> response, Runnable runnable) { request.markDelivered(); request.addMarker("post-response"); mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable)); } @Override public void postError(Request<?> request, VolleyError error) { request.addMarker("post-error"); Response<?> response = Response.error(error); mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, null)); } private static class ResponseDeliveryRunnable implements Runnable { private final Request mRequest; private final Response mResponse; private final Runnable mRunnable; public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) { mRequest = request; mResponse = response; mRunnable = runnable; } @SuppressWarnings("unchecked") @Override public void run() { if (mRequest.isCanceled()) { mRequest.finish("canceled-at-delivery"); return; } // Here we will call create Request Callback passed in when if (mResponse.isSuccess()) { mRequest.deliverResponse(mResponse.result); } else { mRequest.deliverError(mResponse.error); } if (mResponse.intermediate) { mRequest.addMarker("intermediate-response"); } else { mRequest.finish("done"); } // If there is a task to be performed , For example, the previous cache needs to be refreshed , It will be called in this place if (mRunnable != null) { mRunnable.run(); } } } } |
summary
Volley These are the main classes in :
- Volley: Provides build RequestQueue A unified approach , We can also not pass Volley Class, but build it yourself RequestQueue;
- RequestQueue: Responsible for distributing requests to different request queues ;
- CacheDispatcher: Cache request processing ;
- NetworkDispatcher: Handle network requests ;
- ResponseDelivery: Get the request and process it , The specific implementation class is ExecutorDelivery;
- Cache: Cache interface , The specific implementation classes are DiskBasedCache;
- Network: Network interface , The specific implementation classes are BasicNetwork;
- HttpStack: Actually execute the request , The specific implementation classes are HttpClientStack( obsolete ),BaseHttpStack( Subclasses have HurlStack,AdaptedHttpStack etc. );
- Request: Encapsulate the request information and process the callback , The specific implementation classes are StringRequest,JsonRequest,ImageRequest etc. ;
- NetworkResponse: Returned when requesting the network response;
- Response: Some callback interfaces are defined , Used to encapsulate the data returned to the client .
Volley The frame is highly removable , Many of our custom classes can be used to extend its functions :
- Request: By default, the framework provides StringRequest,JsonRequest and ImageRequest etc. , We can inherit Request And rewrite deliverResponse() and parseNetworkResponse() Customize request .
- Cache: By default, the framework provides DiskBasedCache Class to handle caching , We can inherit Cache Interface to customize cache policy .
- HttpStack: The framework uses by default HurlStack Class is through HttpUrlConnection and HttpClient To implement network requests , We can replace it with others, such as OkHttp, Just inherit HttpStack And rewrite performRequest() Just process the request .
To sum up Volley Network request process :
- First create a request queue RequestQueue, You can customize related classes , Here will start a Cache Dispatcher And a specified number Network Dispatcher;
- And then in CacheDispatcher Processing requests in , If cache is available , Then directly return to cache , Otherwise request the network ;
- And then NetworkDispatcher Process network requests in , What really works is HttpStack Implementation class ;
- Finally through ResponseDelivery The implementation class returns the request result .
边栏推荐
- In 2021, the global general crop protection revenue was about $52750 million, and it is expected to reach $64730 million in 2028
- 2022 high voltage electrician examination and high voltage electrician reexamination examination
- Go learning notes (4) basic types and statements (3)
- 2.2 integer
- String and+
- 设计电商秒杀系统
- MySQL learning notes - single table query
- 19、 MySQL -- SQL statements and queries
- 淺析 Ref-NeRF
- [gd32l233c-start] 5. FLASH read / write - use internal flash to store data
猜你喜欢
JS three families
"Designer universe" APEC safety and health +: environmental protection Panda "xiaobaobao" Happy Valentine's Day 2022 | ChinaBrand | Asia Pacific Economic media
AI enhanced safety monitoring project [with detailed code]
Such as the visual appeal of the live broadcast of NBA Finals, can you still see it like this?
Machine learning support vector machine SVM
An old programmer gave it to college students
Qt6 QML Book/Qt Quick 3D/基础知识
The global industrial design revenue in 2021 was about $44360 million, and it is expected to reach $62720 million in 2028. From 2022 to 2028, the CAGR was 5.5%
Measurement fitting based on Halcon learning -- Practice [1]
Etcd 基于Raft的一致性保证
随机推荐
Sort out several network request methods of JS -- get rid of callback hell
Battle drag method 1: moderately optimistic, build self-confidence (1)
Transformation between yaml, Jason and Dict
MySQL dump - exclude some table data - MySQL dump - exclude some table data
"Actbert" Baidu & Sydney University of technology proposed actbert to learn the global and local video text representation, which is effective in five video text tasks
Qtablewidget control of QT
From the behind the scenes arena of the ice and snow event, see how digital builders can ensure large-scale events
Producer consumer mode (multithreading, use of shared resources)
SQL injection - Fundamentals of SQL database operation
Set, weakset, map, weakmap in ES6
Global and Chinese market of rubidium standard 2022-2028: Research Report on technology, participants, trends, market size and share
C 10 new feature [caller parameter expression] solves my confusion seven years ago
Test access criteria
University of Electronic Science and technology | playback of clustering experience effectively used in reinforcement learning
《ActBERT》百度&悉尼科技大学提出ActBERT,学习全局局部视频文本表示,在五个视频-文本任务中有效!...
Etcd 基于Raft的一致性保证
阻塞非阻塞和同步异步的区分 参考一些书籍
Design e-commerce seckill system
【leetcode】1027. Longest arithmetic sequence (dynamic programming)
In 2021, the global revenue of syphilis rapid detection kits was about US $608.1 million, and it is expected to reach US $712.9 million in 2028