安卓资源收集Android开发Android开发经验谈

Android中线程使用基础详解(一看就明白)

2017-12-19  本文已影响391人  AndroidDMW

前言

线程这个东东真的是一个很平常的工具了,在我们大Android包括Java中都是必不可少司空见惯的东西,最近正好有段难得的离职空闲,今天就来和大家探讨探讨。ps:友情提示,以下链接请童靴们使用vpn查看。

正文

无论何时第一次启动Android应用程序,都会自动创建一个名为“main”的线程。主线程(也称为UI线程)非常重要,因为它负责将事件分派给相应的widget,并且包含绘制事件。这也是童靴们与Android widget进行交互的线程。例如,如果触摸屏幕上的一个Button,则UI线程将触发(dispatch)事件(event)分派给widget,该widget依次设置其按下的状态并向事件队列发布无效请求。UI线程使请求出队,并通知widget重绘本身。

这种单线程模型在Android应用程序中可能会产生不好的性能,而这些应用程序并没有考虑到这种影响由于所有事情都发生在执行长操作的单个线程(如网络访问或数据库查询)上,因此此线程将阻塞整个用户界面。在漫长的操作过程中,不能派发任何事件,包括绘画事件。从用户的角度来看,应用程序显示为挂起。更糟糕的是,如果UI线程被阻塞超过几秒(当前大约5秒),用户将看到臭名昭着的“应用程序无响应”(ANR)对话框。(你将有50%的几率永久失去该用户,这可能是你们公司销售人员挂着大鼻涕在寒风中送出鼠标垫可乐等换回来的一个用户 :))

如果童靴们想看看这看起来有多操蛋,用一个Thread.sleep(2000)在OnClickListener中调用的按钮编写一个简单的应用程序(ps:对thread的调用必须包在try/catch块中,避免抛出InterruptedException)。在返回到正常状态之前,按钮将保持其按压状态约2秒钟。发生这种情况时,用户很容易感知应用程序缓慢。

既然童靴们知道必须避免在UI线程上进行冗长的操作,童靴们可能会使用额外的线程(后台线程或工作线程)来执行这些操作。我们以一个点击监听器为例,通过网络下载一个图像并将其显示在一个ImageView中

public void onClick(View v) {

    new Thread(new Runnable() {

        public void run() {

            Bitmap b = loadImageFromNetwork();

            mImageView.setImageBitmap(b);

        }

    }).start();

}

起初,这段代码似乎是一个很好的解决方案,因为它不会阻塞UI线程。不幸的是,这违反了单线程模型:Android UI工具包不是线程安全的,并且必须总是在UI线程上操作。在这段代码中,ImageView在一个工作线程上进行操作,这可能会导致非常奇怪的问题。追查和修复这些错误可能是困难和耗时的。

Android提供了几种从其他线程访问UI线程的方式。童靴们可能已经熟悉了其中的一些,但是这里有一个全面的列表:

Activity.runOnUiThread(Runnable)

View.post(Runnable)

View.postDelayed(Runnable, long)

Handler

任何这些类和方法都可以用来纠正我们以前的代码示例:

public void onClick(View v) {

    new Thread(new Runnable() {

        public void run() {

            final Bitmap b = loadImageFromNetwork();

            mImageView.post(new Runnable() {

                public void run() {

                    mImageView.setImageBitmap(b);

                }

            });

        }

    }).start();

}

可惜的是,这些类和方法往往会使你的代码更复杂,更难以阅读。当你的实现需要频繁的UI更新的复杂操作时,情况会变得更糟。为了解决这个问题,Android 1.5提供了一个名为AsyncTask的工具类,它简化了需要与用户界面进行通信的长时间运行任务的创建。

AsyncTask也可以用于名为UserTask的Android 1.0和1.1。它提供了完全相同的API,童靴们所要做的就是在应用程序中复制它的源代码。

目标AsyncTask是为您照顾线程管理。我们以前的例子可以很容易地改写为AsyncTask:

public void onClick(View v) {

    new DownloadImageTask().execute("http://example.com/image.png");

}

private class DownloadImageTask extends AsyncTask {

    protected Bitmap doInBackground(String... urls) {

        return loadImageFromNetwork(urls[0]);

    }

    protected void onPostExecute(Bitmap result) {

        mImageView.setImageBitmap(result);

    }

}

正如所看到的,AsyncTask必须通过子类化来使用它。记住一个AsyncTask实例必须在UI线程上创建并且只能执行一次也是非常重要的。大家可以阅读AsyncTask文档以全面了解如何使用此类,但下面简要介绍它的工作原理:

大家可以使用泛型指定参数的类型,进度值和任务的最终值

doInBackground()方法在工作线程上自动执行

onPreExecute()onPostExecute()onProgressUpdate()都在UI线程上调用

doInBackground()返回的值被发送到onPostExecute()

童靴们可以随时在doInBackground()中调用publishProgress()以在UI线程上执行onProgressUpdate()

童靴们可以随时从任何线程取消任务

除了官方的文档,童靴们可以读取Shelves(源代码的几个复杂的例子ShelvesActivity.javaAddBookActivity.java)和照片流(LoginActivity.javaPhotostreamActivity.javaViewPhotoActivity.java)。强烈建议阅读Shelves的源代码,以了解如何在配置更改中保留任务,以及在活动被破坏时如何正确取消它们。

无论是否使用AsyncTask,请始终记住关于单线程模型的以下两条规则:请勿阻止UI线程,并确保仅在UI线程上访问Android UI工具包。AsyncTask只是使这两件事情更容易。

后记

这个后记纯属扯淡,不看也罢!

北京离职半个月了,走亲访友,看看风景,真的是不亦乐乎!奈何双亲担忧我成无业游民,于是往下个目的地互联网之都选投了几份简历,HR们真的很热情,家长里短一聊半小时,很是暖心。互联网之都就是不一样,关爱程序猿,从你我做起!

上一篇下一篇

猜你喜欢

热点阅读