AsyncTask 官方介绍:

在UI线程使用AsyncTask是适当和简单的。
这个类允许你在UI线程中不使用多线程或者Handers的情况下,就能执行后台操作和发布结果。AsyncTask是围绕Thread和Handler来设计的帮助类,不构成通用线程框架。
AsyncTasks通常理想情况下用来执行简短的操作(最多就是几秒钟)。
如果你需要保持线程跑很长时间,就推荐你使用java.util.concurrent包下面的Executor、ThreadPoolExecutor、FutureTask各种api。一个异步任务是需要通过计算来定义,也就是任务跑在后台线程而结果会在UI线程发布。
异步任务定义有3个泛型类型,Prarams、Progress、Result;并且有4个步骤,叫做
onPreExecute、doInBackground、onProgressUpdate、onPostExecuteAsyncTask必须子类化才可以使用。子类需要至少重写1个方法doInBackground,并且通常需要重
写第二个方法onPostExecute异步任务的三个泛型参数,Params这个参数是当执行任务是发送给task的参数;Progress是在后台计算
的进度回调返回的类型;Result是后台线程计算的返回值。当一个异步任务执行的时候,task会经历4步:
1. onPreExecute,task执行之前,在UI线程调用。这步通常用来设置task,例如用来在用户的界面中展示一个进度条。
2. doInBackground,onPreExecute执行之后,立即在后台线程调用。
这步是用来调用后台计算,计算会花费较长时间。在这步中,异步任务的参数会传递进来。
计算的结果一定会在这步中返回回来,并且会传递到最后一步。
这步也会使用publishProgress用来发送一次或多次进度值。这些值会在主线程中通过onProgressUpdate这一步发布。
3. onProgressUpdate,在调用publishProgress之后在UI线程中调用onProgressUpdate。
执行的时机是不确定的。这个方法被用来在用户界面展示任意来自于执行中的内容。
例如被用来驱动一个进度条或者使用文本来展示日志。
4. onPostExecute,当后台线程执行完后在UI线程调用。后台计算的结果会在这一步中通过参数形式传递。取消task
在任何时候都可以通过调用cancel(boolean)来取消task。
调用这个方法会造成,后续调用isCancelled()时返回true。
调用这个方法后,在执行完doInBackground(Object[])后,onCancelled(Object)会代替onPostExecute(Object)被调用。
确保任务尽可能快的被取消,比如在包含循环的情况下你应该经常定期在doInBackground(Object[])中检查isCancelled()的返回值。Threading rules
在这个类中为了能确保正常工作,需要遵循一些线程规则:异步任务类必须要UI线程中进行加载。在android.os.Build.VERSION_CODES JELLY_BEAN 版本中自动完成了。task实例必须在UI线程中创建。execute方法一定要在UI线程中调用。不要手动调用onPreExecute、onPostExecute、doInBackground、onProgressUpdate。每一个task只能执行一次(如果当再次调用execution时,会抛出异常)。Memory observability
AsyncTask通过该方法,下面操作在没有明确同步操作下是安全的,来保证所有的callback调用是同步的。在构造方法或者onPreExecute设置成员字段,并在doInBackground中进行引用。通过doInBackground设置成员变量,在onProgressUpdate和onPostExecute中进行引用。Order of execution
当第一次对外公布时,AsyncTasks只会在一个后台线程中串行执行。
自从android.os.Build.VERSION_CODES DONUT 1.6后,变为使用一种允许并行操作多个任务的线程池执行。
在android.os.Build.VERSION_CODES HONEYCOMB 3.0.x后,任务被执行在单一线程中来避免一些因为并行引起的程序错误。
如果你想真正的并行执行,那么你需要使用结合THREAD_POOL_EXECUTOR去调用executeOnExecutor(java.util.concurrent.Executor, Object[])。
复制代码

上面介绍了AsyncTask的使用方法,注意事项等。 下面是原文:

/*** <p>AsyncTask enables proper and easy use of the UI thread. This class allows you* to perform background operations and publish results on the UI thread without* having to manipulate threads and/or handlers.</p>** <p>AsyncTask is designed to be a helper class around {@link Thread} and {@link Handler}* and does not constitute a generic threading framework. AsyncTasks should ideally be* used for short operations (a few seconds at the most.) If you need to keep threads* running for long periods of time, it is highly recommended you use the various APIs* provided by the <code>java.util.concurrent</code> package such as {@link Executor},* {@link ThreadPoolExecutor} and {@link FutureTask}.</p>** <p>An asynchronous task is defined by a computation that runs on a background thread and* whose result is published on the UI thread. An asynchronous task is defined by 3 generic* types, called <code>Params</code>, <code>Progress</code> and <code>Result</code>,* and 4 steps, called <code>onPreExecute</code>, <code>doInBackground</code>,* <code>onProgressUpdate</code> and <code>onPostExecute</code>.</p>** <div class="special reference">* <h3>Developer Guides</h3>* <p>For more information about using tasks and threads, read the* <a href="{@docRoot}guide/components/processes-and-threads.html">Processes and* Threads</a> developer guide.</p>* </div>** <h2>Usage</h2>* <p>AsyncTask must be subclassed to be used. The subclass will override at least* one method ({@link #doInBackground}), and most often will override a* second one ({@link #onPostExecute}.)</p>** <p>Here is an example of subclassing:</p>* <pre class="prettyprint">* private class DownloadFilesTask extends AsyncTask&lt;URL, Integer, Long&gt; {*     protected Long doInBackground(URL... urls) {*         int count = urls.length;*         long totalSize = 0;*         for (int i = 0; i < count; i++) {*             totalSize += Downloader.downloadFile(urls[i]);*             publishProgress((int) ((i / (float) count) * 100));*             // Escape early if cancel() is called*             if (isCancelled()) break;*         }*         return totalSize;*     }**     protected void onProgressUpdate(Integer... progress) {*         setProgressPercent(progress[0]);*     }**     protected void onPostExecute(Long result) {*         showDialog("Downloaded " + result + " bytes");*     }* }* </pre>** <p>Once created, a task is executed very simply:</p>* <pre class="prettyprint">* new DownloadFilesTask().execute(url1, url2, url3);* </pre>** <h2>AsyncTask's generic types</h2>* <p>The three types used by an asynchronous task are the following:</p>* <ol>*     <li><code>Params</code>, the type of the parameters sent to the task upon*     execution.</li>*     <li><code>Progress</code>, the type of the progress units published during*     the background computation.</li>*     <li><code>Result</code>, the type of the result of the background*     computation.</li>* </ol>* <p>Not all types are always used by an asynchronous task. To mark a type as unused,* simply use the type {@link Void}:</p>* <pre>* private class MyTask extends AsyncTask&lt;Void, Void, Void&gt; { ... }* </pre>** <h2>The 4 steps</h2>* <p>When an asynchronous task is executed, the task goes through 4 steps:</p>* <ol>*     <li>{@link #onPreExecute()}, invoked on the UI thread before the task*     is executed. This step is normally used to setup the task, for instance by*     showing a progress bar in the user interface.</li>*     <li>{@link #doInBackground}, invoked on the background thread*     immediately after {@link #onPreExecute()} finishes executing. This step is used*     to perform background computation that can take a long time. The parameters*     of the asynchronous task are passed to this step. The result of the computation must*     be returned by this step and will be passed back to the last step. This step*     can also use {@link #publishProgress} to publish one or more units*     of progress. These values are published on the UI thread, in the*     {@link #onProgressUpdate} step.</li>*     <li>{@link #onProgressUpdate}, invoked on the UI thread after a*     call to {@link #publishProgress}. The timing of the execution is*     undefined. This method is used to display any form of progress in the user*     interface while the background computation is still executing. For instance,*     it can be used to animate a progress bar or show logs in a text field.</li>*     <li>{@link #onPostExecute}, invoked on the UI thread after the background*     computation finishes. The result of the background computation is passed to*     this step as a parameter.</li>* </ol>* * <h2>Cancelling a task</h2>* <p>A task can be cancelled at any time by invoking {@link #cancel(boolean)}. Invoking* this method will cause subsequent calls to {@link #isCancelled()} to return true.* After invoking this method, {@link #onCancelled(Object)}, instead of* {@link #onPostExecute(Object)} will be invoked after {@link #doInBackground(Object[])}* returns. To ensure that a task is cancelled as quickly as possible, you should always* check the return value of {@link #isCancelled()} periodically from* {@link #doInBackground(Object[])}, if possible (inside a loop for instance.)</p>** <h2>Threading rules</h2>* <p>There are a few threading rules that must be followed for this class to* work properly:</p>* <ul>*     <li>The AsyncTask class must be loaded on the UI thread. This is done*     automatically as of {@link android.os.Build.VERSION_CODES#JELLY_BEAN}.</li>*     <li>The task instance must be created on the UI thread.</li>*     <li>{@link #execute} must be invoked on the UI thread.</li>*     <li>Do not call {@link #onPreExecute()}, {@link #onPostExecute},*     {@link #doInBackground}, {@link #onProgressUpdate} manually.</li>*     <li>The task can be executed only once (an exception will be thrown if*     a second execution is attempted.)</li>* </ul>** <h2>Memory observability</h2>* <p>AsyncTask guarantees that all callback calls are synchronized in such a way that the following* operations are safe without explicit synchronizations.</p>* <ul>*     <li>Set member fields in the constructor or {@link #onPreExecute}, and refer to them*     in {@link #doInBackground}.*     <li>Set member fields in {@link #doInBackground}, and refer to them in*     {@link #onProgressUpdate} and {@link #onPostExecute}.* </ul>** <h2>Order of execution</h2>* <p>When first introduced, AsyncTasks were executed serially on a single background* thread. Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed* to a pool of threads allowing multiple tasks to operate in parallel. Starting with* {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are executed on a single* thread to avoid common application errors caused by parallel execution.</p>* <p>If you truly want parallel execution, you can invoke* {@link #executeOnExecutor(java.util.concurrent.Executor, Object[])} with* {@link #THREAD_POOL_EXECUTOR}.</p>*/
复制代码

下面进行源码分析: 还是老规矩,通过成员变量入手,

//cpu可用数量
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
// We want at least 2 threads and at most 4 threads in the core pool,
// preferring to have 1 less than the CPU count to avoid saturating
// the CPU with background work
// 我们想要在线程池中至少有2个线程和最多4个线程,宁愿比cpu数量少1,以此来避免因为后台工作引起的饱和。//保存在池中的线程数
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
//池中允许的最大线程数
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
//当线程数大于cpu核数的时候,KEEP_ALIVE_SECONDS该时间为空余线程在终止之前等待新任务的最大时间。
private static final int KEEP_ALIVE_SECONDS = 30;//用来创建新线程
private static final ThreadFactory sThreadFactory = new ThreadFactory() {private final AtomicInteger mCount = new AtomicInteger(1);public Thread newThread(Runnable r) {return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());}
};//在task被执行之前,存储在sPoolWorkQueue 队列中。该队列进存放通过execute方法添加的Runnable任务。
private static final BlockingQueue<Runnable> sPoolWorkQueue =new LinkedBlockingQueue<Runnable>(128);/*** An {@link Executor} that can be used to execute tasks in parallel.*/
public static final Executor THREAD_POOL_EXECUTOR;static {//上面都是准备工作,准备构造ThreadPoolExecutor。ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,sPoolWorkQueue, sThreadFactory);//线程池数量最后销毁到0个threadPoolExecutor.allowCoreThreadTimeOut(true);THREAD_POOL_EXECUTOR = threadPoolExecutor;
}/*** An {@link Executor} that executes tasks one at a time in serial* order.  This serialization is global to a particular process.*/
//SERIAL_EXECUTOR 是顺序执行task的。在指定进程中是全局的。
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();private static final int MESSAGE_POST_RESULT = 0x1;
private static final int MESSAGE_POST_PROGRESS = 0x2;//执行task的Executor,默认为SerialExecutor,后面介绍SerialExecutor类
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;//通过sHandler将后台线程返回的结果转到主线程
private static InternalHandler sHandler;//mWorker在AsyncTask的构造方法中创建
private final WorkerRunnable<Params, Result> mWorker;
private final FutureTask<Result> mFuture;private volatile Status mStatus = Status.PENDING;//用来同步线程是否被取消,或者执行失败
private final AtomicBoolean mCancelled = new AtomicBoolean();
//用来同步task是否被调用
private final AtomicBoolean mTaskInvoked = new AtomicBoolean();//Executor, 默认的Executor,按顺序一个个执行。在制定进程中是全局的。
private static class SerialExecutor implements Executor {final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();Runnable mActive;public synchronized void execute(final Runnable r) {mTasks.offer(new Runnable() {public void run() {try {r.run();} finally {//执行完一个,顺序执行下一个。scheduleNext();}}});if (mActive == null) {//如果mTasks 中没有任务执行那么将任务通过THREAD_POOL_EXECUTOR执行。scheduleNext();}}protected synchronized void scheduleNext() {if ((mActive = mTasks.poll()) != null) {//通过THREAD_POOL_EXECUTOR执行THREAD_POOL_EXECUTOR.execute(mActive);}}
}/*** Indicates the current status of the task. Each status will be set only once* during the lifetime of a task.*///表示当前任务状态。在task的生命周期内每一个状态仅能被设置一次。
public enum Status {/*** Indicates that the task has not been executed yet.*///标示task还没有被执行PENDING,/*** Indicates that the task is running.*///task正在执行RUNNING,/*** Indicates that {@link AsyncTask#onPostExecute} has finished.*///onPostExecute已经执行完成。FINISHED,
}/** @hide */
//更换sDefaultExecutor ,为hide方法,默认不对开发者提供,默认使用SerialExecutor
public static void setDefaultExecutor(Executor exec) {sDefaultExecutor = exec;
}/*** Creates a new asynchronous task. This constructor must be invoked on the UI thread.*///创建一个新的异步任务。该构造方法一定要在UI线程调用。
public AsyncTask() {mWorker = new WorkerRunnable<Params, Result>() {public Result call() throws Exception {//标志该任务已经被调用mTaskInvoked.set(true);Result result = null;try {//设置当前线程优先级,Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);//noinspection unchecked//传递mParams,并执行doInBackground()result = doInBackground(mParams);//官方解释:flush在当前线程任何等待的Binder命令到内核驱动程序。调用这个函数是有益的//在执行一个可能阻塞很长时间的操作。为了确保任何等待的object引用会被释放,//为了防止进程对对象持有超过它需要的时间//翻译的不是很好, 查阅了一些资料//Binder.flushPendingCommands()方法被调用说明后面的代码可能会引起线程阻塞Binder.flushPendingCommands();} catch (Throwable tr) {//当有异常时,设置mCancelled为true,说明被取消mCancelled.set(true);throw tr;} finally {//通过主线程返回结果postResult(result);}return result;}};//mWorker 会放在FutureTask中执行。mFuture = new FutureTask<Result>(mWorker) {@Overrideprotected void done() {//如果task没有调用,通过postResultIfNotInvoked,返回结果try {//get()会引起阻塞,等待计算结果postResultIfNotInvoked(get());} catch (InterruptedException e) {android.util.Log.w(LOG_TAG, e);} catch (ExecutionException e) {throw new RuntimeException("An error occurred while executing doInBackground()",e.getCause());} catch (CancellationException e) {postResultIfNotInvoked(null);}}};
}
private void postResultIfNotInvoked(Result result) {final boolean wasTaskInvoked = mTaskInvoked.get();//如果task没有调用,通过postResultIfNotInvoked,返回结果if (!wasTaskInvoked) {postResult(result);}
}
private Result postResult(Result result) {//最终通过该方法返回结果,无论task是否执行。//getHandler会在主线中执行MESSAGE_POST_RESULT case 返回result@SuppressWarnings("unchecked")Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,new AsyncTaskResult<Result>(this, result));message.sendToTarget();return result;
}private static class InternalHandler extends Handler {public InternalHandler() {//绑定到主线程的Loopersuper(Looper.getMainLooper());}@SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})@Overridepublic void handleMessage(Message msg) {AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;switch (msg.what) {case MESSAGE_POST_RESULT:// There is only one result// 在主线程执行finish()result.mTask.finish(result.mData[0]);break;case MESSAGE_POST_PROGRESS:// 在主线程回调onProgressUpdateresult.mTask.onProgressUpdate(result.mData);break;}}
}private void finish(Result result) {if (isCancelled()) {//如果task被取消,调用onCancelled(),不会调用onPostExecuteonCancelled(result);} else {onPostExecute(result);}//更新task状态mStatus = Status.FINISHED;
}
复制代码

下面介绍一下几个子类需要复写的方法

/*** Override this method to perform a computation on a background thread. The* specified parameters are the parameters passed to {@link #execute}* by the caller of this task.** This method can call {@link #publishProgress} to publish updates* on the UI thread.** @param params The parameters of the task.** @return A result, defined by the subclass of this task.** @see #onPreExecute()* @see #onPostExecute* @see #publishProgress*/// 复写该方法,在后台线程中执行计算。通过调用execute传递的指定的参数 params// 该方法可以在主线程调用publishProgress来更新进度// 返回一个result。类型由task子类定义的类型。
@WorkerThread
protected abstract Result doInBackground(Params... params);/*** Runs on the UI thread before {@link #doInBackground}.** @see #onPostExecute* @see #doInBackground*/// 在调用doInBackground之前,首先在主线程调用onPreExecute// 在子线程计算之前,需要在主线程准备一些工作。就复写该方法在这里面完成
@MainThread
protected void onPreExecute() {
}/*** <p>Runs on the UI thread after {@link #doInBackground}. The* specified result is the value returned by {@link #doInBackground}.</p>* * <p>This method won't be invoked if the task was cancelled.</p>** @param result The result of the operation computed by {@link #doInBackground}.** @see #onPreExecute* @see #doInBackground* @see #onCancelled(Object) */// 当doInBackground执行完成后,会在主线程中调用onPostExecute方法。参数result是// doInBackground返回的。// 如果task被取消了,那么就不会调用onPostExecute返回结果。而是通过onCancelled
@SuppressWarnings({"UnusedDeclaration"})
@MainThread
protected void onPostExecute(Result result) {
}/*** Runs on the UI thread after {@link #publishProgress} is invoked.* The specified values are the values passed to {@link #publishProgress}.** @param values The values indicating progress.** @see #publishProgress* @see #doInBackground*/// 调用publishProgress之后,在主线程调用onProgressUpdate。// values参数是publishProgress传递过来的。
@SuppressWarnings({"UnusedDeclaration"})
@MainThread
protected void onProgressUpdate(Progress... values) {
}/*** <p>Runs on the UI thread after {@link #cancel(boolean)} is invoked and* {@link #doInBackground(Object[])} has finished.</p>* * <p>The default implementation simply invokes {@link #onCancelled()} and* ignores the result. If you write your own implementation, do not call* <code>super.onCancelled(result)</code>.</p>** @param result The result, if any, computed in*               {@link #doInBackground(Object[])}, can be null* * @see #cancel(boolean)* @see #isCancelled()*/// cancel调用之后,会在主线程调用onCancelled方法。并且doInBackground已经执行完成。// 默认实现简单的调用调用了onCancelled(),并且忽略result。// 如果写了自己的实现,那么就不需要调用super.onCancelled(result)。// result 为doInBackground计算的结果,可能为null
@SuppressWarnings({"UnusedParameters"})
@MainThread
protected void onCancelled(Result result) {onCancelled();
}    /*** <p>Applications should preferably override {@link #onCancelled(Object)}.* This method is invoked by the default implementation of* {@link #onCancelled(Object)}.</p>* * <p>Runs on the UI thread after {@link #cancel(boolean)} is invoked and* {@link #doInBackground(Object[])} has finished.</p>** @see #onCancelled(Object) * @see #cancel(boolean)* @see #isCancelled()*/// 最好复写onCancelled(object)方法。这个方法被默认实现的onCancelled(Object)方法所调用。// 在cancel(boolean)运行之后并且doInBackground(Object[])也调用完成之后,在主线程调用onCancelled()
@MainThread
protected void onCancelled() {
}
复制代码

大概分析到这。

总结一下:

  1. 对外提供的方法,需要复写的方法除了doInBackground外都是在主线程回调。
  2. 在线程池中线程最多可存在数量为CPU_COUNT * 2 + 1, CPU_COUNT至少为2最多为4
  3. 使用get或者计算结果,会星辰阻塞。还有可能有异常返回
  4. 一个task只能执行一次

Thanks.

转载于:https://juejin.im/post/5ae841a36fb9a07ac36341d6

小葵花妈妈课堂开课了:《AsyncTask源码分析》相关推荐

  1. ITextPDF填写模板,部分中文丢失,如“小葵花妈妈课堂”,剩余“小葵花堂”

    ITextPDF填写模板,部分中文中文,如"小葵花妈妈课堂",剩余"小葵花堂" 问题描述 问题描述 一开始以为是linux字体的问题,重新安装两次无效无效.后怀 ...

  2. 你真的了解AsyncTask吗?AsyncTask源码分析

    转载请注明出处:http://blog.csdn.net/yianemail/article/details/51611326 1,概述 Android UI是线程不安全的,如果想要在子线程很好的访问 ...

  3. 线程中task取消_Rust Async: async-task源码分析

    async-std是rust异步生态中的基础运行时库之一,核心理念是合理的性能 + 用户友好的api体验.经过几个月密集的开发,前些天已经发布1.0稳定版本.因此是时候来一次深入的底层源码分析.asy ...

  4. Android之AsyncTask源码分析(第五篇:execute方法只能执行一次的原因)

    (注意:本文基于API 28的源码分析,API 29上或其他平台的源码略有不同) 前言 当你调用AsyncTask对象的execute()方法时,突然发生崩溃--内心充满不解:java.lang.Il ...

  5. 【Android 异步操作】AsyncTask 异步任务 ( 参数简介 | 方法简介 | 使用方法 | AsyncTask 源码分析 )

    文章目录 一.AsyncTask 参数简介 二.AsyncTask 方法简介 三.AsyncTask 基本用法 四.AsyncTask 构造函数源码解析 五.AsyncTask 构造函数相关源码注释 ...

  6. Asynctask源码分析

    ​ 首先我们使用AsyncTask时,一般是: new AsyncTask(...).execute() 复制代码 我们看new AsyncTask(),它走的是: public AsyncTask( ...

  7. android asynctask源码分析,Android通过Handler与AsyncTask两种方式动态更新ListView(附源码)...

    本文实例讲述了Android通过Handler与AsyncTask两种方式动态更新ListView的方法.分享给大家供大家参考,具体如下: 有时候我们需要修改已经生成的列表,添加或者修改数据,noti ...

  8. JavaFX源码分析实战:如何设置窗体标题小图标和任务栏图标

    JavaFX实战系列 JavaFX源码分析和实战:javaFX线程结构分析 JavaFX源码分析和实战之launcher启动器:两种启动javaFX的方式及launch(args[])参数设置和获取 ...

  9. AsyncTask使用以及源码分析

    综述 在Android中,我们需要进行一些耗时的操作,会将这个操作放在子线程中进行.在子线程操作完成以后我们可以通过Handler进行发送消息,通知UI进行一些更新操作(具体使用及其原理可以查看And ...

  10. Redis学习之intset整数集合源码分析

    1.整数集合:整数的集合,升序排序,无重复元素 2.整数集合intset是集合键的底层实现之一,当一个集合只包含整数值的元素,并且这个集合的元素数量不多时,redis会使用整数集合作为集合键的底层实现 ...

最新文章

  1. 重学《动手学深度学习》转
  2. python center函数_python pandas移动窗口函数rolling的用法
  3. CSS属性选择器和部分伪类
  4. Git 学习第二天(一)
  5. 一些对制作网站有帮助的站点
  6. 【CMAC小脑】CMAC逼近sin(t)函数的训练和测试
  7. 台积电和三星成半导体代工厂恶战主角 受谁影响?
  8. Digit v3.0.0 – 响应式WHMCS模板
  9. Spring Annotation知识梳理
  10. python学习笔记之property
  11. java 环境配置(详细教程)
  12. 解决 Winfrom richtextbox不显示RTF文档 图片问题
  13. word设置表格文字紧贴下框线
  14. mysql 10张表左关联查询_mysql left join 左连接查询关联n多张表
  15. gmail smtp 535 5.7.8无法登陆解决
  16. repo报错:SyntaxError: invalid syntax
  17. Android打字机动画,Android自定义View实现打字机效果
  18. 路由器、交换机、网桥、集线器的区别和联系
  19. 深圳大学数学文化赏析MOOC答案
  20. GD32F3x0 USB CDC应用案例

热门文章

  1. 傲腾服务器硬盘,Intel傲腾Optane硬盘实测:“高价U盘”,加速性能不错
  2. Python初学者(零基础学习Python入门)书籍、视频、资料分享
  3. mysql order优化2019_MySQL 效率提高N倍的19条MySQL优化秘籍
  4. 计划bom表 java_ERP总结系列(BOM浅谈)
  5. 格力如失去经销商支持,或将进一步落后于美的,董明珠慌了么?
  6. 操作系统:动态分区存储(首次适应算法、最佳适应算法)
  7. vue 判断页面是首次加载还是刷新
  8. yolov3.weight
  9. selenium结合sikuliX操作Flash网页
  10. SAP之FPM卷一:FPM是什么