AsyncTask源码分析及仿AsyncTask异步任务举例
来源:互联网 发布:休斯网络系统 编辑:程序博客网 时间:2024/06/05 11:58
参考:
Android 从零开始打造异步处理框架
详解Android中AsyncTask的使用
Android AsyncTask 源码解析
AsyncTask源码分析及仿AsyncTask异步任务举例
最近研究研究网络框架,不打算研究那些虚浮的新技术。
第一部分:AsyncTask简单使用方式:
一、AsyncTask的使用方式(Demo来自鸿洋的博客)
public class Main2Activity extends Activity { private static final String TAG = "Main2Activity"; private ProgressDialog mDialog; private TextView mTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main2); mTextView = (TextView) findViewById(R.id.id_tv); mDialog = new ProgressDialog(this); mDialog.setMax(100); mDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); mDialog.setCancelable(false); new MyAsyncTask().execute(); } private class MyAsyncTask extends AsyncTask<Void, Integer, Void> { @Override protected void onPreExecute() { mDialog.show(); Log.e(TAG, Thread.currentThread().getName() + " onPreExecute "); } @Override protected Void doInBackground(Void... params) { // 模拟数据的加载,耗时的任务 for (int i = 0; i < 100; i++) { try { Thread.sleep(80); } catch (InterruptedException e) { e.printStackTrace(); } publishProgress(i); } Log.e(TAG, Thread.currentThread().getName() + " doInBackground "); return null; } @Override protected void onProgressUpdate(Integer... values) { mDialog.setProgress(values[0]); Log.e(TAG, Thread.currentThread().getName() + " onProgressUpdate "); } @Override protected void onPostExecute(Void result) { // 进行数据加载完成后的UI操作 mDialog.dismiss(); mTextView.setText("LOAD DATA SUCCESS "); Log.e(TAG, Thread.currentThread().getName() + " onPostExecute "); } }}
二、效果图如下:
三、Log日志打印如下:
12-22 10:29:07.287 12897-12897/com.simple.simpledemo E/Main2Activity: main onPreExecute 12-22 10:29:07.397 12897-12897/com.simple.simpledemo E/Main2Activity: main onProgressUpdate ...此处省略(共100个onProgressUpdate)...12-22 10:29:15.297 12897-12897/com.simple.simpledemo E/Main2Activity: main onProgressUpdate 12-22 10:29:15.377 12897-28721/com.simple.simpledemo E/Main2Activity: AsyncTask #5 doInBackground 12-22 10:29:15.377 12897-12897/com.simple.simpledemo E/Main2Activity: main onProgressUpdate 12-22 10:29:15.387 12897-12897/com.simple.simpledemo E/Main2Activity: main onPostExecute
第二部分、AsyncTask源码分析:
知道如何使用后,看看使用AsyncTask的代码;
private class MyAsyncTask extends AsyncTask<Void, Integer, Void> { @Override protected void onPreExecute() { ......之前 } @Override protected Void doInBackground(Void... params) { ......处理 return null; } @Override protected void onProgressUpdate(Integer... values) { ......进度 } @Override protected void onPostExecute(Void result) { ......之后 } }复写了四个方法。一个个来。我们反向推导AsyncTask的源码。
1.先看调用方法:new MyAsyncTask().execute(),再看哪个地方调用了这四个复写的方法。
(一)AsyncTask初始化
——————————初始化开始——————————
先看构造方法吧:
public AsyncTask() { mWorker = new WorkerRunnable<Params, Result>() { public Result call() throws Exception { mTaskInvoked.set(true); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); //noinspection unchecked Result result = doInBackground(mParams); Binder.flushPendingCommands(); return postResult(result); } }; mFuture = new FutureTask<Result>(mWorker) { @Override protected void done() { try { 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); } } }; }
构造了两个对象mWorker、mFuture。
mWorker、mFuture两个对象是什么?
——————————扫盲分割线开始——————————
一个比较尴尬的事,一开始不知道Callable和FutrueTask。因此一开始一直也没看懂。
OK,知道哪不懂,才能去懂。简单讲讲我的这个盲区:Callable:
是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其他线程执行的任务。
Runnable和Callable的区别是:
(1)Callable规定的方法是call(),Runnable规定的方法是run()。
(2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
(3)call方法可以抛出异常,run方法不可以。
(4)运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
简单理解为runnable类似就好。
FutureTask:
FutureTask实际上是一个任务的操作类,它并不启动新线程,只是在自己所在线程上操作,任务的具体实现是构造FutureTask时提供的。
FutureTask执行Callable任务(类似Thread执行runnable任务),执行完会调用done方法。
——————————扫盲分割线结束——————————
mWorker:
mWorker = new WorkerRunnable<Params, Result>() { public Result call() throws Exception { mTaskInvoked.set(true); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); //noinspection unchecked Result result = doInBackground(mParams); Binder.flushPendingCommands(); return postResult(result); } };
doInBackground:方法在任务内执行,类似runnalbe的run方法。执行完call方法,会调用postResult方法。
private Result postResult(Result result) { @SuppressWarnings("unchecked") Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult<Result>(this, result)); message.sendToTarget(); return result; }
先暂时简单描述为:发送消息通知给主UI线程。执行handler的主UI操作。
暂时先不讲handler调用,放到后面讲。这儿先把初始化讲完。
mFuture:
复写了done方法,意味着执行完任务mWorker后,会调用done方法。
mFuture = new FutureTask<Result>(mWorker) { @Override protected void done() { try { 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); } } };postResultIfNotInvoked(get()):
private void postResultIfNotInvoked(Result result) { final boolean wasTaskInvoked = mTaskInvoked.get(); if (!wasTaskInvoked) { postResult(result); } }
第2行final boolean wasTaskInvoked = mTaskInvoked.get();mTaskInvoked对象取值,这个值好像在哪看到被赋值了。
原来在mworker对象初始化的时候设置为true了:mTaskInvoked.set(true);
也就是说如果mWorker成功初始化,则接下来的代码if(!wasTaskInvoked){... }根本不会执行括号内操作。
MyAsyncTask().execute()构造方法初始化讲完了。然后讲讲执行。
——————————初始化结束——————————
(二)AsyncTask执行
——————————执行开始———————————
MyAsyncTask().execute():
public final AsyncTask<Params, Progress, Result> execute(Params... params) { return executeOnExecutor(sDefaultExecutor, params); }
executeOnExecutor:
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) { if (mStatus != Status.PENDING) { switch (mStatus) { case RUNNING: throw new IllegalStateException("Cannot execute task:" + " the task is already running."); case FINISHED: throw new IllegalStateException("Cannot execute task:" + " the task has already been executed " + "(a task can be executed only once)"); } } mStatus = Status.RUNNING; onPreExecute(); mWorker.mParams = params; exec.execute(mFuture); return this; }异常检查:根据几种状态判断。默认状态是Pending,待定状态。如下。
public enum Status { /** * Indicates that the task has not been executed yet. */ PENDING, /** * Indicates that the task is running. */ RUNNING, /** * Indicates that {@link AsyncTask#onPostExecute} has finished. */ FINISHED, }
判断不是Running,Finished状态后,
1.执行mStatus = Status.RUNNING:改变状态为Running。
2.onPreExecute():这个方法眼熟么?最开始的Demo中使Dialog显示的方法就在这儿使用。
3.mWorker.mParams = params:这个params是什么?new MyAsyncTask().execute();我们并没有传递任何参数过来。over,下一个。
4.exec.execute(mFuture):exec对象是什么?
executeOnExecutor(sDefaultExecutor, params)
是这个sDefaultExecutor,这个又是什么?
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;这个初始化的时候就是另一个对象:
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();知道这个对象,然后看看exec.execute(mFuture)调用的方法execute。
public synchronized void execute(final Runnable r) { mTasks.offer(new Runnable() { public void run() { try { r.run(); } finally { scheduleNext(); } } }); if (mActive == null) { scheduleNext(); } }
向mTasks队列中插入一条Runnable对象(mTask对象final ArrayDeque<Runnable> mTasks)。并复写了runnable对象的run方法。成功插入后下面有一个判断if (mActive == null) 执行一个scheduleNext()方法。如下图:
分几种情况:
1)首次插入数据时,mActive默认值为空,必然立即执行scheduleNext()(代码如下);
2)再插入数据时,mActive则可能不为空,可能不会立即scheduleNext();
3)过程中:有某一条runnable任务被执行的时候,调用其自身的run方法。尝试执行mFuture的run方法r.run()后,会立刻执行scheduleNext();同理,就是说最后一条runnable任务执行的时候,会调用scheduleNext()中的方法,尝试从mTasks队列取出runnable任务,如果mTasks队列中还有就会被取出,否则,则结束。
注意:mFuture还记得么?初始化的两个对象中的第二个FutureTask对象。插入后到上面的第10行代码为止,这个被插入队列的runnable对象还被调用。因此一开始并不会执行run()方法里面的东西。
protected synchronized void scheduleNext() { if ((mActive = mTasks.poll()) != null) { THREAD_POOL_EXECUTOR.execute(mActive); } }
mActive从队列mTasks中取出一条runnable对象(FutureTask对象mFuture),通过线程池执行。
注意:线程池有约束最大的大小,而mTasks队列没有大小约束,因此,可以无限制插入数据,但是同一时间最大能执行的线程数是固定的。见源码:
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 ThreadFactory sThreadFactory = new ThreadFactory() { private final AtomicInteger mCount = new AtomicInteger(1); public Thread newThread(Runnable r) { return new Thread(r, "AsyncTask #" + mCount.getAndIncrement()); } }; 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 = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
——————————执行结束———————————
(三)与主UI线程交互
执行结束之后,postResult发送消息MESSAGE_POST_RESULT给getHandler()方法的Handler。
private Result postResult(Result result) { @SuppressWarnings("unchecked") Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult<Result>(this, result)); message.sendToTarget(); return result; }很明显是一个单例模式的Handler写法,看看。
private static InternalHandler sHandler;
InternalHandler
这个Handler看看是什么?
private static Handler getHandler() { synchronized (AsyncTask.class) { if (sHandler == null) { sHandler = new InternalHandler(); } return sHandler; } }看看InternalHandler构造方法:
private static class InternalHandler extends Handler { public InternalHandler() { super(Looper.getMainLooper()); } @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"}) @Override public void handleMessage(Message msg) { AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj; switch (msg.what) { case MESSAGE_POST_RESULT: // There is only one result result.mTask.finish(result.mData[0]); break; case MESSAGE_POST_PROGRESS: result.mTask.onProgressUpdate(result.mData); break; } } }
Looper.getMainLooper()获取了主UI线程的handler对象!非常好。当获得消息MESSAGE_POST_RESULT之后,执行result.mTask.finish(result.mData[0])方法。这个result对象是什么?回到发消息的postresult方法里面看下,发现getHandler().obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult<Result>(this, result)),这个result对象就是new AsyncTaskResult<Result>(this, result),result原来是AsyncTask的静态内部类AsyncTaskResult的mTask(AsyncTask自身)的finish方法。说白了就是AsyncTask自己的finish方法:(后面的Demo中会解释为什么这么传递数据)
private void finish(Result result) { if (isCancelled()) { onCancelled(result); } else { onPostExecute(result); } mStatus = Status.FINISHED; }正常的话执行onPostExecute方法。这个方法就是执行完成后的对主UI线程的修改方法。
貌似漏了两个方法:publishProgress(i)这个方法在子线程中发送消息MESSAGE_POST_PROGRESS给主UI线程。
getHandler().obtainMessage(MESSAGE_POST_PROGRESS, new AsyncTaskResult<Progress>(this, values)).sendToTarget();onProgressUpdate在handler的构造方法中执行如下
case MESSAGE_POST_PROGRESS: result.mTask.onProgressUpdate(result.mData); break;
第三部分、仿AsyncTask异步任务举例
(一)一个简单的异步任务
分析:既然是异步任务,那么开启新线程是有必要的。要求要有任务开始前onStart(),子线程onOoInBackground(),任务开始后onResult(T t)。
先写一个类继承自Thread类。复写其中的run方法。写三个抽象方法。
public abstract class ThreadTask<T> extends Thread{ private Handler handler; public ThreadTask(){ handler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); onResult((T)msg.obj); } }; } @Override public void run() { super.run(); Message message = Message.obtain(); message.obj = onOoInBackground(); handler.sendMessage(message); } /** * 任务开始之前 * */ public abstract void onStart(); /** * 子线程中调用,运行在子线程 * */ public abstract T onOoInBackground(); /** * 子线程返回的结果,运行在主线程 * */ public abstract void onResult(T t); public void execute(){ onStart(); start(); }}该Thread继承类执行通过execute方法,先执行onStart()方法。再执行start()方法,执行内部run()方法。
在run方法内部,先执行耗时操作onOoInBackground,执行完之后再通过handler执行通知UI线程已经执行完了,handler接收到消息,执行onResult方法。
使用举例代码,在主方法里面通过如下方式使用:
new ThreadTask<String>(){ @Override public void onStart() { Log.d("ThreadTask","我是加载动画"); } @Override public String onOoInBackground() { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } return "结果返回"; } @Override public void onResult(String s) { Log.d("ThreadTask","结果"+s); my_text.setText("结果"+s); } }.execute();打个Log日志吧,不发效果图了:
12-22 18:33:44.088 6407-6407/com.simple.simpledemo D/ThreadTask: 我是加载动画12-22 18:33:47.088 6407-6951/com.simple.simpledemo D/ThreadTask: 我是执行过程12-22 18:33:47.088 6407-6407/com.simple.simpledemo D/ThreadTask: 结果结果返回
逻辑上是否感受到和AsyncTask相似?比如onPreExecute和我们的onStart,doInBackground和我们的doInBackground,onPostExecute和我们的onResult。当然,看上去还有不小的差异。有差异证明我们的异步还有很大的进步空间。大的方向已经ok了。
(二)单例优化handler后的异步任务
分析:回到AsyncTask方法,发现它用了单例。我的第一版异步也应该用上单例才对,否则,每次都要new一个handler对象。
为了后续的区别,验证一下先:把handler打印出来:
public ThreadTask(){ handler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); onResult((T)msg.obj); } }; Log.d("InternalHandler",handler.toString()); }查看一下Log日志:点击两次,发现两个handler对象不同。
12-22 19:06:11.958 5397-5397/com.simple.simpledemo D/InternalHandler: Handler (com.simple.simpledemo.ThreadTask$1) {42456248}12-22 19:06:11.968 5397-5397/com.simple.simpledemo D/ThreadTask: 我是加载动画12-22 19:06:15.008 5397-5397/com.simple.simpledemo D/ThreadTask: 结果结果返回12-22 19:06:16.878 5397-5397/com.simple.simpledemo D/InternalHandler: Handler (com.simple.simpledemo.ThreadTask$1) {42456e98}12-22 19:06:16.878 5397-5397/com.simple.simpledemo D/ThreadTask: 我是加载动画12-22 19:06:19.918 5397-5397/com.simple.simpledemo D/ThreadTask: 结果结果返回
先写一个最简单的单例模式:
private static class InternalHandler extends Handler { private static InternalHandler handler; private InternalHandler() { super(Looper.getMainLooper()); } public static InternalHandler getHandler(){ if (sHandler == null) { handler = new InternalHandler(); } return handler; } }因为我们这是一个高并发的异步网络框架,当然需要加锁。否则,单例模式就失效了。那就用最有名的双重锁定吧。
private static class InternalHandler extends Handler { private static InternalHandler handler; private InternalHandler() { super(Looper.getMainLooper()); } public static InternalHandler getHandler(){ if (handler==null){ synchronized (InternalHandler.class){ if (handler==null){ handler = new InternalHandler(); } } } Log.d("InternalHandler",handler.toString()); return handler; } }
然后,为了使用这个Handler那我只能把发送消息的handleMessage方法复写了。但是这个Message对象之前是个什么呢?是一个泛型T。这就让人比较困扰了。我们之前做的是将一个数据对象msg.obj提供给ThreadTask方法的onResult作为参数。执行result当中的方法。此处对应上面源码分析中为什么我发数据给主UI线程的时候需要把AsyncTask自己,及要发送的数据都发送过去这个写法上的疑问。
public abstract class ThreadTask2<T> extends Thread{ @Override public void run() { super.run(); ... } ... /** * 子线程返回的结果,运行在主线程 * */ public abstract void onResult(T t); ... private static class InternalHandler extends Handler { private static InternalHandler handler; private InternalHandler() { super(Looper.getMainLooper()); } public static InternalHandler getHandler(){ if (handler==null){ synchronized (InternalHandler.class){ if (handler==null){ handler = new InternalHandler(); } } } Log.d("InternalHandler",handler.toString()); return handler; } @Override public void handleMessage(Message msg) { super.handleMessage(msg); ---此处处理msg-- } } ...}现在在handleMessage中如何调用外部类ThreadTask的onResult方法呢?那么必须获取外部类的对象。参考一下AsyncTask对象中的方法:
private static class AsyncTaskResult<Data> { final AsyncTask mTask; final Data[] mData; AsyncTaskResult(AsyncTask task, Data... data) { mTask = task; mData = data; } }AsyncTask对象postResult方法:
private Result postResult(Result result) { @SuppressWarnings("unchecked") Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult<Result>(this, result)); message.sendToTarget(); return result; }仿造后得到我们的代码:run方法中发送一个ResultData对象包含自身对象和一个耗时操作的返回值结果给handler,handler处理的时候先解析这个ResultData,再通过ThreadTask对象的onresult方法中处理数据。
public abstract class ThreadTask2<T> extends Thread{ @Override public void run() { super.run(); Message message = Message.obtain(); message.obj = new ResultData<>(this,onOoInBackground()); handler.sendMessage(message); } ... private static class InternalHandler extends Handler { private static InternalHandler handler; private InternalHandler() { super(Looper.getMainLooper()); } public static InternalHandler getHandler(){ if (handler==null){ synchronized (InternalHandler.class){ if (handler==null){ handler = new InternalHandler(); } } } Log.d("InternalHandler",handler.toString()); return handler; } @Override public void handleMessage(Message msg) { super.handleMessage(msg); ResultData resultData = (ResultData) msg.obj; resultData.task2.onResult(resultData.data); } } ... private static class ResultData<T>{ ThreadTask2 task2; T data; public ResultData(ThreadTask2 task2, T data) { this.task2 = task2; this.data = data; } }}打印日志看看是否成功。
Log日志如下:发现一直都是同一个Handler对象。成功。
12-22 19:08:41.438 8805-8805/com.simple.simpledemo D/InternalHandler: Handler (com.simple.simpledemo.ThreadTask2$InternalHandler) {424498a8}12-22 19:08:41.448 8805-8805/com.simple.simpledemo D/ThreadTask: 我是加载动画12-22 19:08:44.448 8805-9061/com.simple.simpledemo D/ThreadTask: 我是执行过程12-22 19:08:44.448 8805-8805/com.simple.simpledemo D/ThreadTask: 结果结果返回12-22 19:08:47.238 8805-8805/com.simple.simpledemo D/InternalHandler: Handler (com.simple.simpledemo.ThreadTask2$InternalHandler) {424498a8}12-22 19:08:47.248 8805-8805/com.simple.simpledemo D/ThreadTask: 我是加载动画12-22 19:08:50.248 8805-9155/com.simple.simpledemo D/ThreadTask: 我是执行过程12-22 19:08:50.248 8805-8805/com.simple.simpledemo D/ThreadTask: 结果结果返回
现在这一版本的ThreadTask已经非常接近AsyncTask类的实现了。就差一个线程池的距离。
(三)线程池实现的进阶版举例
为什么要使用线程池,因为之前的该方案不断的创建线程。既然不断创建新线程那么就有新建和销毁的大量开销。关于线程池
那就简单写个线程池吧:写一个定长线程池:
//定义一个定长线程池 private static ExecutorService executorService = Executors.newFixedThreadPool(3);再把复写的run方法修改一下:
public void run() { executorService.execute(new Runnable() { @Override public void run() { Message message = Message.obtain(); message.obj = new ResultData<>(ThreadTask3.this,onOoInBackground()); handler.sendMessage(message); } }); }然后让代码不再继承自Thread方法,将execute方法改了:
public void execute(){ onStart(); run(); }运行代码,查看结果:貌似肉眼看不出区别。写个循环验证一下:
for (int i = 0; i < 30; i++) { final int index = i; new ThreadTask3<String>() { @Override public void onStart() { Log.d("ThreadTask", "我是加载动画"); } @Override public String onOoInBackground() { Log.e("ThreadTask", "测试日志" + index); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } Log.d("ThreadTask", "我是执行过程"); return "结果返回"; } @Override public void onResult(String s) { Log.d("ThreadTask", "结果" + s); my_text.setText("结果" + s); } }.execute(); }外面加了一个循环执行线程的任务,里面通过Log.e("ThreadTask", "测试日志" + index)打印日志。发现如下效果:
因为我将线程池大小设置为3,每次打印3条数据,一直到结束。因为我们执行过程有3秒。每次打印之后都有3秒等待。
效果我们已经知道了,再回想一下AsyncTask的代码,发现它用的就是线程池,不过是一个自定义线程池罢了。当然还是有少量优化上的差异。但,通过这个Demo是否更加了解了AsyncTask源码的意义及优缺点?
到此就讲完了。感谢阅读。over。
- AsyncTask源码分析及仿AsyncTask异步任务举例
- AsyncTask异步任务总结以及源码分析
- AsyncTask异步任务 源码解读
- Android实现异步任务机制AsyncTask 的使用及源码分析
- Android异步任务处理框架AsyncTask源码分析
- Android从源码分析二:AsyncTask异步任务
- Android异步任务处理框架AsyncTask源码分析
- AsyncTask异步任务机制源码分析和总结笔记
- 《android framework常用api源码分析》之AsyncTask异步任务
- Android异步任务之AsyncTask源码解析
- Android AsyncTask 异步任务之源码解析
- AsyncTask使用及源码分析
- AsyncTask详解及源码分析
- 异步任务AsyncTask及JSON解析
- 异步任务AsyncTask及JSON解析
- 08、异步任务AsyncTask及JSON解析
- AsyncTask异步任务及异常处理
- 异步任务AsyncTask及JSON解析
- jQuery+Ajax+PHP+Mysql实现分页显示数据
- 苹果强制使用HTTPS传输后APP开发者必须知道的事
- Error:warning: Ignoring InnerClasses attribute for an anonymous inner class
- Java 中volitale,static
- launchMode四种启动模式先介绍前三种
- AsyncTask源码分析及仿AsyncTask异步任务举例
- Java时间和时间戳的相互转换
- 自己定个一个小项目,探究web应用期间,动态编译java脚本的影响
- Feescale K60开发笔记7: RTCS中sendto函数的使用
- STM32 VCP PC端安装驱动失败的问题解决
- androidstudio新建html
- 关于DevOps,你不知道的那些事儿
- mybatis逆向生成实体类,接口类,映射xml文件
- 2016年年度总结