参考:

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    {        @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    {        @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() {            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(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() {            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(this, result));        message.sendToTarget();        return result;    }

先暂时简单描述为:发送消息通知给主UI线程。执行handler的主UI操作。

暂时先不讲handler调用,放到后面讲。这儿先把初始化讲完。

mFuture:

复写了done方法,意味着执行完任务mWorker后,会调用done方法。

mFuture = new FutureTask(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 execute(Params... params) {        return executeOnExecutor(sDefaultExecutor, params);    }

executeOnExecutor:

public final AsyncTask 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 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 sPoolWorkQueue =            new LinkedBlockingQueue(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(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(this, result)),这个result对象就是new AsyncTaskResult(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(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 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(){            @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 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 {        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(this, result));        message.sendToTarget();        return result;    }
仿造后得到我们的代码:run方法中发送一个ResultData对象包含自身对象和一个耗时操作的返回值结果给handler,handler处理的时候先解析这个ResultData,再通过ThreadTask对象的onresult方法中处理数据。
public abstract class ThreadTask2 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{        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() {                @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。


























更多相关文章

  1. Android(安卓)截取手机屏幕两种实现方法
  2. Android(安卓)H5 js webView初体验
  3. android中onMeasure初看,深入理解布局之一!
  4. Android中notifyDataSetInvalidated()和notifyDataSetChanged()
  5. 第八章、理解Window和WindowManager
  6. Android进阶 - 消息处理机制探索
  7. Android文本输入框EditText方法说明和属性
  8. Java集合 && Android提供的集合
  9. Unable to execute dex: java.nio.BufferOverflowException解决

随机推荐

  1. [gitbook] Android框架分析系列之Android
  2. Android电话秀(三)
  3. 安卓开发之去标题栏
  4. AndroidStudio3.6.3新版本遇到的坑
  5. android ndk 入门2 - 基本方法实现
  6. 安卓报错:java.lang.RuntimeException: Un
  7. android webview 截图快照
  8. Android 隐藏显示键盘
  9. Android JB 4.2 中InputManager 启动过程
  10. Android(安卓)应用图标库icons与icons PS