这个标题大家不要奇怪,扯Http框架怎么扯到AsyncTask去了,有两个原因:首先是Http框架除了核心http理论外。其技术实现核心也是线程池 + 模板 + handler,而AsyncTask又正好也是这三者的完美结合。其次,也是自己在面试中发现大量的安卓开发人员全然不了解AsyncTask的原理和技术细节。而AsyncTask的思想在我们设计app框架和性能调优的时候是非常实用的。所以这里特地写一篇关于AsyncTask的博文。

老规矩,我的习惯还是通过写demo,把核心技术一点点剥离出来。一步步看完你就能深入理解其技术本质了。

第一个样例,先理解Java的线程池和FutureTask。

先说线程池。Java提供了一个非常重要的接口就是Executor。差点儿全部重要的线程池实现都继承自这个接口。只是这个不是我们今天的重点。详细请查看Java的API手冊,我们上代码看一下一般线程池是怎么实例化的。

private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE = 1; private static final BlockingQueue<Runnable> workQueue =
new LinkedBlockingQueue<Runnable>(10);
private static final ThreadFactory threadFactory = new ThreadFactory()
{
private final AtomicInteger count = new AtomicInteger(1); @Override
public Thread newThread(Runnable r)
{
return new Thread(r, "AsyncTask #" + count.getAndIncrement());
}
};
private static final ThreadPoolExecutor THREAD_POOL_EXECUTOR =
new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE,
KEEP_ALIVE,
TimeUnit.SECONDS,
workQueue,
threadFactory);

敏感的同学会发现。这个就是AsyncTask的线程池的源代码,的确。正常的需求,这段代码实例化出来的线程池基本都能够满足了。其它參数看命名都非常容易理解,这里主要讲一下workQueue。由于我们会不断提交任务给线程池运行,而线程池的线程数量是有限的,当全部核心线程都处于工作状态时。client再次提交的任务放在哪里呢?我这么一问你就懂了吧。

再讲一下java的FutureTask,我们知道正常情况下我们须要一个线程运行,提交的是一个Runnable。但有时候我们希望线程运行结束时带回一个处理完毕的数据。这个时候Runnable就无力了,这个时候就要看FutureTask了。大家有兴趣能够看一下它的源代码。事实上它也是继承自Runnable的,所以能够直接提交给线程来运行。

一般正常调用FutureTask的方法例如以下代码:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask; public class Test1
{
public static void main(String[] args)
{
Test1 test = new Test1();
test.test();
} public void test()
{
FutureTask<String> fTask = new FutureTask<String>(new Callable<String>()
{
@Override
public String call() throws Exception
{
System.out.println("calling");
return "hello";
}
})
{
@Override
protected void done()
{
try
{
System.out.println("done " + get());
}
catch (InterruptedException e)
{
e.printStackTrace();
}
catch (ExecutionException e)
{
e.printStackTrace();
} super.done();
}
}; Executor executor = Executors.newSingleThreadExecutor();
executor.execute(fTask);
} }

以上代码的运行结果为:

calling

done hello

所以,我们在线程结束时拿到了终于的线程处理结果。而AsyncTask在onPostExecute中给你结果的时候,就是这么干的。

第二个样例,我们来点干货。我们先写个AsyncTask的样例,跑起来并看下运行结果。先代码:

package com.amuro.activity;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View; import com.amuro.R; public class MainActivity extends Activity
{ @Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_layout); findViewById(R.id.bt).setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
testAsync();
} });
} private void testAsync()
{
for(int i = 0; i < 10; i++)
{
final int j = i;
AsyncTask<String, Integer, String> aTask =
new AsyncTask<String, Integer, String>()
{
@Override
protected void onProgressUpdate(Integer... values)
{
super.onProgressUpdate(values);
} @Override
protected String doInBackground(String... params)
{
Log.e("amuro", Thread.currentThread().getName());
try
{
Thread.sleep(3000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
return params[0] + "done";
} @Override
protected void onPostExecute(String s)
{
Log.e("amuro", "result: " + s + " " + j);
}
};
aTask.execute("DoubleX");
}
}
}

看下运行结果:

03-13 11:23:47.950 22777-23081/com.amuro E/amuro: AsyncTask #1

03-13 11:23:50.955 22777-22777/com.amuro E/amuro: result: DoubleXdone 0

03-13 11:23:50.955 22777-23120/com.amuro E/amuro: AsyncTask #2

03-13 11:23:53.960 22777-22777/com.amuro E/amuro: result: DoubleXdone 1

03-13 11:23:53.960 22777-23195/com.amuro E/amuro: AsyncTask #3

03-13 11:23:56.965 22777-22777/com.amuro E/amuro: result: DoubleXdone 2

03-13 11:23:56.965 22777-23236/com.amuro E/amuro: AsyncTask #4

03-13 11:23:59.960 22777-22777/com.amuro E/amuro: result: DoubleXdone 3

03-13 11:23:59.965 22777-23277/com.amuro E/amuro: AsyncTask #5

03-13 11:24:02.965 22777-22777/com.amuro E/amuro: result: DoubleXdone 4

03-13 11:24:02.965 22777-23277/com.amuro E/amuro: AsyncTask #5

03-13 11:24:05.965 22777-22777/com.amuro E/amuro: result: DoubleXdone 5

03-13 11:24:05.970 22777-23277/com.amuro E/amuro: AsyncTask #5

03-13 11:24:08.975 22777-22777/com.amuro E/amuro: result: DoubleXdone 6

03-13 11:24:08.975 22777-23277/com.amuro E/amuro: AsyncTask #5

03-13 11:24:11.975 22777-22777/com.amuro E/amuro: result: DoubleXdone 7

03-13 11:24:11.975 22777-23277/com.amuro E/amuro: AsyncTask #5

03-13 11:24:14.980 22777-22777/com.amuro E/amuro: result: DoubleXdone 8

03-13 11:24:14.980 22777-23081/com.amuro E/amuro: AsyncTask #1

03-13 11:24:17.985 22777-22777/com.amuro E/amuro: result: DoubleXdone 9

能够看到,10个任务是顺序运行的,而且仅仅有5个线程在工作,好,

我们把AsyncTask刚才那个线程池和FutureTask结合起来,写一个简单的样例实现和它一模一样的功能。代码:

package com.amuro;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.FutureTask;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; public class Test2
{ public static void main(String[] args)
{
Test2 test = new Test2();
test.test();
} private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE = 1; private static final BlockingQueue<Runnable> workQueue =
new LinkedBlockingQueue<Runnable>(10);
private static final ThreadFactory threadFactory = new ThreadFactory()
{
private final AtomicInteger count = new AtomicInteger(1); @Override
public Thread newThread(Runnable r)
{
return new Thread(r, "AsyncTask #" + count.getAndIncrement());
}
};
private static final ThreadPoolExecutor THREAD_POOL_EXECUTOR =
new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE,
KEEP_ALIVE,
TimeUnit.SECONDS,
workQueue,
threadFactory); private static volatile Executor defaultExecutor = new Executor()
{
final ArrayDeque<Runnable> tasks = new ArrayDeque<Runnable>();
Runnable activeRunnable; @Override
public void execute(final Runnable r)
{
tasks.offer(new Runnable()
{ @Override
public void run()
{
try
{
System.out.println(Thread.currentThread().getName());
r.run();
}
finally
{
scheduleNext();
}
}
}); if(activeRunnable == null)
{
scheduleNext();
}
} protected synchronized void scheduleNext()
{
if((activeRunnable = tasks.poll()) != null)
{
THREAD_POOL_EXECUTOR.execute(activeRunnable);
}
}
}; public void test()
{
List<FutureTask<String>> fList = new ArrayList<FutureTask<String>>();
for(int i = 0; i < 10; i++)
{
final int j = i; fList.add(new FutureTask<String>(new Callable<String>()
{ @Override
public String call() throws Exception
{
Thread.sleep(3000);
return "I'm callable " + j;
}
}){
@Override
protected void done()
{
try
{
System.out.println(get() + " done");
}
catch (InterruptedException e)
{
e.printStackTrace();
}
catch (ExecutionException e)
{
e.printStackTrace();
}
}
} );
} for(FutureTask<String> fTask : fList)
{
defaultExecutor.execute(fTask);
}
} }

先看运行结果:

AsyncTask #1

I’m callable 0 done

AsyncTask #2

I’m callable 1 done

AsyncTask #3

I’m callable 2 done

AsyncTask #4

I’m callable 3 done

AsyncTask #5

I’m callable 4 done

AsyncTask #5

I’m callable 5 done

AsyncTask #5

I’m callable 6 done

AsyncTask #5

I’m callable 7 done

AsyncTask #5

I’m callable 8 done

AsyncTask #5

I’m callable 9 done

是不是一模一样~没错事实上我们正常调用AsyncTask的execute方法的时候,就是调用了这个defaultExecutor。它的作用就是维持了一个双向的任务队列,当AsyncTask的execute方法运行的时候,它就把client提交的任务塞到了这个队列里,假设这时候没有任务在运行。activeRunnable就为null,则scheduleNext方法直接调用,这个刚被提交的任务就会从队列中被取出交给线程池区运行,运行完毕后又会继续调用scheduleNext方法,有任务就会继续运行下一个任务。所以你看到的结果就是这样一个顺序运行。而且线程池仅仅使用了5个线程,充分利用了资源。补充一点,AsyncTask的源代码中,假设你想把全部任务改为并行运行,是能够传一个自己的Executor进来的。可是这种方法被hide了。看来是官方不建议大家这么做。

理解了上面两个样例的话。第三个样例写起来就so easy了,没错,理解轮子的最好试金石就是自己写个轮子。所以以下我们就是要简单地写一个自己的AsyncTask。和java直接run最大的差别就是安卓的非UI线程不能操作UI线程的实例,这个时候,把handler君请过来就好了嘛~ 还是先看代码。我们自己定义一个MyAsyncTask:

package com.amuro.thread;

import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message; import java.util.ArrayDeque;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.FutureTask;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.LogRecord; /**
* Created by Echo on 2016/3/12.
*/
public abstract class MyAsyncTask<Params, Result>
{
/*************线程池核心代码*******************/
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE = 1; private static final BlockingQueue<Runnable> workQueue =
new LinkedBlockingQueue<Runnable>(10);
private static final ThreadFactory threadFactory = new ThreadFactory()
{
private final AtomicInteger count = new AtomicInteger(1); @Override
public Thread newThread(Runnable r)
{
return new Thread(r, "MyAsyncTask #" + count.getAndIncrement());
}
};
private static final ThreadPoolExecutor THREAD_POOL_EXECUTOR =
new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE,
KEEP_ALIVE,
TimeUnit.SECONDS,
workQueue,
threadFactory); private static volatile Executor defaultExecutor = new Executor()
{
final ArrayDeque<Runnable> tasks = new ArrayDeque<Runnable>();
Runnable activeRunnable; @Override
public void execute(final Runnable r)
{
tasks.offer(new Runnable()
{ @Override
public void run()
{
try
{
r.run();
}
finally
{
scheduleNext();
}
}
}); if(activeRunnable == null)
{
scheduleNext();
}
} protected synchronized void scheduleNext()
{
if((activeRunnable = tasks.poll()) != null)
{
THREAD_POOL_EXECUTOR.execute(activeRunnable);
}
}
}; /****************消息处理核心代码************************/
private static final int MESSAGE_POST_RESULT = 0x01; private static class AsyncTaskResult<Data>
{
final MyAsyncTask mTask;
final Data[] mData; AsyncTaskResult(MyAsyncTask task, Data... data)
{
mTask = task;
mData = data;
}
} private static abstract class WorkerRunnable<Params, Result>
implements Callable<Result> {
Params[] mParams;
} private static final Handler handler = new Handler()
{
@Override
public void handleMessage(Message msg)
{
AsyncTaskResult result = (AsyncTaskResult) msg.obj;
switch (msg.what)
{
case MESSAGE_POST_RESULT:
result.mTask.finish(result.mData[0]);
break;
}
}
};
private final WorkerRunnable<Params, Result> workerRunnable;
private final FutureTask<Result> futureTask; public MyAsyncTask()
{
workerRunnable = new WorkerRunnable<Params, Result>()
{
@Override
public Result call() throws Exception
{
return postResult(doInBackground(mParams));
}
}; futureTask = new FutureTask<Result>(workerRunnable);
} private Result postResult(Result result)
{
Message message = handler.obtainMessage(
MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
} private void finish(Result result)
{
onPostExecute(result);
} protected void onPreExecute(){} protected abstract Result doInBackground(Params... params); protected void onPostExecute(Result result){} public final MyAsyncTask<Params, Result> execute(Params... params)
{
return executeOnExecutor(defaultExecutor, params);
} public final MyAsyncTask<Params, Result> executeOnExecutor(Executor executor, Params... params)
{
onPreExecute(); workerRunnable.mParams = params;
executor.execute(futureTask);
return this;
}
}

然后再看一下调用的代码:

package com.amuro.activity;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View; import com.amuro.R;
import com.amuro.thread.MyAsyncTask; public class MainActivity extends Activity
{ @Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_layout); findViewById(R.id.bt).setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
testAsync();
} }); findViewById(R.id.bt1).setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
testMyAsync();
}
});
} private void testAsync()
{
for(int i = 0; i < 10; i++)
{
final int j = i;
AsyncTask<String, Integer, String> aTask =
new AsyncTask<String, Integer, String>()
{
@Override
protected void onProgressUpdate(Integer... values)
{
super.onProgressUpdate(values);
} @Override
protected String doInBackground(String... params)
{
Log.e("amuro", Thread.currentThread().getName());
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
return params[0] + "done";
} @Override
protected void onPostExecute(String s)
{
Log.e("amuro", "result: " + s + " " + j);
}
};
aTask.execute("DoubleX");
}
} private void testMyAsync()
{
for(int i = 0; i < 10; i++)
{
final int j = i; MyAsyncTask<String, String> myTask = new MyAsyncTask<String, String>()
{
@Override
protected String doInBackground(String... params)
{
Log.e("amuro", Thread.currentThread().getName());
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
return params[0] + "done";
} @Override
protected void onPostExecute(String s)
{
Log.e("amuro", "result: " + s + " " + j);
}
};
myTask.execute("outSideParam ");
}
}
}

再看一下运行结果:

03-13 13:15:55.065 20514-20732/com.amuro E/amuro: MyAsyncTask #1

03-13 13:15:56.070 20514-20514/com.amuro E/amuro: result: outSideParam done 0

03-13 13:15:56.070 20514-20747/com.amuro E/amuro: MyAsyncTask #2

03-13 13:15:57.075 20514-20514/com.amuro E/amuro: result: outSideParam done 1

03-13 13:15:57.075 20514-20758/com.amuro E/amuro: MyAsyncTask #3

03-13 13:15:58.075 20514-20514/com.amuro E/amuro: result: outSideParam done 2

03-13 13:15:58.075 20514-20758/com.amuro E/amuro: MyAsyncTask #3

03-13 13:15:59.075 20514-20514/com.amuro E/amuro: result: outSideParam done 3

03-13 13:15:59.075 20514-20758/com.amuro E/amuro: MyAsyncTask #3

03-13 13:16:00.080 20514-20514/com.amuro E/amuro: result: outSideParam done 4

03-13 13:16:00.080 20514-20758/com.amuro E/amuro: MyAsyncTask #3

03-13 13:16:01.080 20514-20514/com.amuro E/amuro: result: outSideParam done 5

03-13 13:16:01.080 20514-20758/com.amuro E/amuro: MyAsyncTask #3

03-13 13:16:02.080 20514-20514/com.amuro E/amuro: result: outSideParam done 6

03-13 13:16:02.080 20514-20758/com.amuro E/amuro: MyAsyncTask #3

03-13 13:16:03.085 20514-20514/com.amuro E/amuro: result: outSideParam done 7

03-13 13:16:03.085 20514-20758/com.amuro E/amuro: MyAsyncTask #3

03-13 13:16:04.085 20514-20514/com.amuro E/amuro: result: outSideParam done 8

03-13 13:16:04.090 20514-20732/com.amuro E/amuro: MyAsyncTask #1

03-13 13:16:05.095 20514-20514/com.amuro E/amuro: result: outSideParam done 9

暴露了我的測试机弱爆了。Orz。

为了简单起见这里就不处理onProgressUpdate了,有兴趣的同学能够在这个基础上自己去实现。

我在这里总结一下execute方法运行的整个流程。

1. 先回调了onPreExecute方法,这个是在UI线程里的。然后把外面传入的params赋值给了workerRunnable,事实上就是FutureTask须要的Callable对象。

2. 然后就把这个FutureTask丢给了我们的defaultExecutor去运行。这个流程和上面的样例二是一样一样的。

3. 运行成功后子线程完毕了结果的生成,这个时候就能够通过handler把结果丢给UI线程了。这里封装了一个AsyncTaskResult类来传递结果,原因非常easy。handler是静态对象。没法直接拿到当前MyAsyncTask的引用。而我们要把task和result对象同一时候丢给handler。所以要进行一下封装。

4. OK。handler拿到result之后就会把task拿出来并回调finish方法。

5. finish方法。这个时候已经在UI线程中了,所以能够回调终于的onPostExecute方法把结果丢给client去处理了。

不管多么复杂的技术或实现,仅仅要我们抓到其本质,耐心地把它涉及到的知识一点点的吃透,并多写代码多做測试。

终于你会发现,再复杂,只是也是小知识的层叠和扩展罢了。这和一个互联网公司须要深厚的技术积累,道理也是一样的。

就酱。谢谢欣赏~

教你写Http框架(二)——三个样例带你深入理解AsyncTask的更多相关文章

  1. Android开发之手把手教你写ButterKnife框架(三)

    欢迎转载,转载请标明出处: http://blog.csdn.net/johnny901114/article/details/52672188 本文出自:[余志强的博客] 一.概述 上一篇博客讲了, ...

  2. Android开发之手把手教你写ButterKnife框架(二)

    欢迎转载,转载请标明出处: http://blog.csdn.net/johnny901114/article/details/52664112 本文出自:[余志强的博客] 上一篇博客Android开 ...

  3. spring事务详解(二)简单样例

    系列目录 spring事务详解(一)初探事务 spring事务详解(二)简单样例 spring事务详解(三)源码详解 spring事务详解(四)测试验证 spring事务详解(五)总结提高 一.引子 ...

  4. 手写DAO框架(三)-数据库连接

    -------前篇:手写DAO框架(二)-开发前的最后准备--------- 前言 上一篇主要是温习了一下基础知识,然后将整个项目按照模块进行了划分.因为是个人项目,一个人开发,本人采用了自底向上的开 ...

  5. 手写MQ框架(三)-客户端实现

    一.背景 书接手写MQ框架(二)-服务端实现  ,前面介绍了服务端的实现.但是具体使用框架过程中,用户肯定是以客户端的形式跟服务端打交道的.客户端的好坏直接影响了框架使用的便利性. 虽然框架目前是通过 ...

  6. 手写SpringMVC框架(三)-------具体方法的实现

    续接前文 手写SpringMVC框架(二)结构开发设计 本节我们来开始具体方法的代码实现. doLoadConfig()方法的开发 思路:我们需要将contextConfigLocation路径读取过 ...

  7. 手把手教你写DI_1_DI框架有什么?

    DI框架有什么? 在上一节:手把手教你写DI_0_DI是什么? 我们已经理解DI是什么 接下来我们就徒手撸一撸,玩个支持构造函数注入的DI出来 首先我们回顾一下 构造函数注入 的代码形式, 大概长这模 ...

  8. Tiny并行计算框架之复杂演示样例

    问题来源  很感谢@doctorwho的问题: 假如职业介绍所来了一批生产汽车的工作,如果生产一辆汽车任务是这种:搭好底盘.拧4个轮胎.安装发动机.安装4个座椅.再装4个车门.最后安装顶棚. 之间有的 ...

  9. solr特点三: 排序样例汇总

    目的是提供solrj 实现 查询的样例参考 单维度排序 //查询条件 query.setQuery(queryString); // add 是添加 query.addSortField(field_ ...

随机推荐

  1. Easy mock - 安装配置和基本使用

    Easy-mock easy-mock是一款比较好用的接口模拟工具, 使用之前我们需要安装和配置 需要下载的内容有以下 Node Redis MongoDB Node和Redis一路点下一步就行, M ...

  2. 【DRF分页】

    目录 第一种 PageNumberPagination 查第n页,每页显示n条数据 第二种 LimitOffsetPagination 在第n个位置,向后查n条数据 第三种 CursorPaginat ...

  3. ssh-keygen && ssh-copy-id 生成管理传输秘钥

  4. JNDI学习总结(2)——JNDI数据源的配置

    一.数据源的由来 在Java开发中,使用JDBC操作数据库的四个步骤如下:   ①加载数据库驱动程序(Class.forName("数据库驱动类");)    ②连接数据库(Con ...

  5. POJ 4047 Garden

    Garden Time Limit: 1000ms Memory Limit: 65536KB This problem will be judged on PKU. Original ID: 404 ...

  6. mahout历史(二)

    mahout历史 Apache Mahout起源于2008年,经过两年的发展,2010年4月ApacheMahout最终成为了Apache的顶级项目.Mahout 项目是由 ApacheLucene( ...

  7. Vue Cli 打包之后静态资源路径不对的解决方法

    cli2版本: 将 config/index.js 里的 assetsPublicPath 的值改为 './' . build: { ... assetsPublicPath: './', ... } ...

  8. SpringCloud核心教程 | 第一篇: 使用Intellij中的Spring Initializr来快速构建Spring Cloud工程

    spring cloud简介 spring cloud 为开发人员提供了快速构建分布式系统的一些工具,包括配置管理.服务发现.断路器.路由.微代理.事件总线.全局锁.决策竞选.分布式会话等等.它运行环 ...

  9. 自绘listCtrl控件选中该行高亮(模拟windows)

    CListCtrl的派生类CMyListCtrl的DrawItem()函数里添加代码 CDC *pDC = CDC::FromHandle(lpDrawItemStruct->hDC); if ...

  10. StartCoroutine的使用

    StartCoroutine在unity3d的帮助中叫做协程,意思就是启动一个辅助的线程. 在C#中直接有Thread这个线程,可是在unity中有些元素是不能操作的.这个时候能够使用协程来完毕. 使 ...