宇哥带你飞之Android多线程与异步任务--第一篇

本人工作已经一年多了,具体点说已经一年多3个月了,实习的早,过早的受到了社会的蹂躏。今年6月多份毕业了,然后就到了一个比较大的公司,具体名字就不说了,就是妹子超级超级多。。。。在学校学的是游戏,cx之类的,但是鬼使神差的毕业后跟着同学就干上了应用,多亏了我的第一个老板--李金波,超级感谢~

好了,废话不多说了,接下来就开启Android多线程与异步任务的学习吧,由于内容有点多,分几篇博客来和大家扯淡~


学习Android当然就避免不了学习java,java中也有多线程还有线程之间的同步问题等等~设计到的知识也是相当庞大的,建议小伙伴们看看传智播客张孝祥的java多线程同步那几个视频,讲的是相当牛逼~,可惜祥哥已经走了,为啥干程序员的都活不长!时间充足的话,我后面也会分享线程同步的心得,当然得感谢祥哥,祝愿祥哥一切安好~


我们将分5部分来讲解Android的多线程与异步任务

1:java多线程基础

(1):ThreadPoolExecutor

(2):ScheduleExecutor

(3):线程同步(synchronized,lock,semaphore)

2:在Android中使用的线程

3:在用Handler异步时不可或缺的组件

4:使用AsyncTask快速实现异步任务

5:检测程序中是否有需要使用多线程的地方


其中第一部分,在最后面的篇章中我争取会给大家讲解java中的多线程的使用和线程之间的同步的问题,让各位大侠见笑了,因为吃的饭还没有你们吃的盐多~、


本篇博客我们着重讲解第二个部分----在Android中使用的线程

在引出这个话题的时候,我们要想到三个问题:

1:为何使用多线程?

2:在Android中如何使用多线程或者异步操作?

3:多线程和界面之间的交互

首先来看第一个问题--为何使用多线程?我们使用多线程往往是涉及到一些复杂的IO操作(文件操作,网络操作,数据库操作),复杂运算,定时操作。想必大家都碰到过应用程序弹出“无响应”的对话框,那是因为在Android主线程中进行了超时的操作,过多的阻塞了主线程就会导致应用出现ANR的Waring~

下面给大家举一个为什么要使用多线程的小例子:假如我们现在有一个需求,需要点击按钮的时候出现一个Dialog(对话框),这个对话框显示5秒钟后就自动消失,我们怎么实现呢?如果我们在按钮的点击事件中先调用Dialog.show();然后调用Thread.sleep(5000);最后调用Dialog.dismiss();是不是就可以了呢?答案是否定的!因为你会发现这样的现象,我们好像从始至终就没有看见Dialog~,这是为什么呢?

Android中的绘制都是线程不安全的,很多人就会疑问线程不安全的那还不乱了套?这个不用担心了,因为Android只有一个绘制线程,那就是UI线程(主线程)。只存在一个线程就不存在什么线程安全不安全的问题了~回到我们的问题,由于Android中控件的绘制是在UI线程中,当我们调用Dialog.show()这句代码的时候,距离Dialog绘制出来还有一定的时间间隔,而此时我们调用了Thread.sleep(5000);让主线程休眠了5秒钟,哥啊,主线程都休息了,还绘制个毛线啊!当然就看不见了~所以我们需要开启一个线程去让Dialog经过5秒钟去消失掉~(当然也有其他的方法~)

明白了为何使用多线程的问题,我们接下来看一下使用多线程或者异步操作的几种方式

1:Thread

2:Handler

3:AsyncTask

4:AsyncTaskLoader 这个类是API Level 11 中有的,也就是3.0(不过V4包中有~)

值得一提的是AsyncTaskLoader的子类CursorLoader这个用来关联数据库来进行操作也是不错的~具体的会再讲解

相信大家用的最多的就是Hander和AsyncTaskLoader了,本人平时用的最多的也就是这两个~,现在的项目中涉及到了聊天才用到了CursorLoader,不过感觉还凑活~

大家不要着急~,Handler和AsyncTask后面会重点剖析~第一篇就给大家说一个大概的轮廓,大家知道有这么几种方式就可以了,不要着急啊,么么哒~

最后说一下第三个小问题:多线程如何和界面交互呢?Android中提供的方式有:

1:Activity.runOnUIThread(Runnable);

2:View.post(Runnable);  View.postDelay(Runnable);

3:Handler

4:AsyncTask

线程和界面交互的时候我们要严格明确下面两个原则:

(1)不要阻塞UI线程

(2)不要在UI线程外更新界面

Tips:

1:对于耗时操作,我们应该放到非主线程中运行,从而避免阻塞主线程~

2:为了保证良好的用户体验,建议对超过50ms的操作,都使用线程处理~

为什么超过了50ms都要放在线程中处理呢?不是导致应用程序无响应是10秒钟吗?为大家算一下1秒钟=1000ms,1000ms/50ms=20 fps ,也就是1秒钟刷新的帧数为20帧,这是人眼感觉不到很卡顿的帧数,假如低于了20帧,就会感觉滑动页面之类的操作很卡顿~所以低于50ms对于用户体验还是很重要的~

下面给大家贴出一个小例子,方便大家的理解,上代码喽~

首先贴出布局文件的代码~

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="fill_parent"
              android:layout_height="fill_parent">
    <Button android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="从主线程加载"
            android:id="@+id/loadFromMainThread"/>
    <Button android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="从异步线程加载1"
            android:id="@+id/loadFromOtherThread1"/>
    <Button android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="从异步线程加载2"
            android:id="@+id/loadFromOtherThread2"/>
    <Button android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="利用Asyncthread加载图片"
            android:id="@+id/loadFromAsync"/>
    <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/image"/>
</LinearLayout>

父布局为LinearLayout,里面竖直方向摆放了4个按钮,最下面是一个ImageView,用来显示图片~

接下来就是主体代码部分

package com.example.XiaoYuAndroidThread1;

import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;

import java.io.IOException;
import java.net.URL;

public class MyActivity extends Activity {
    private ImageView image;
    public static final String url = "http://pic.sc.chinaz.com/files/pic/pic9/201501/apic9211.jpg";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        image = (ImageView) findViewById(R.id.image);
        //直接在主线程中下载图片
        findViewById(R.id.loadFromMainThread).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Drawable drawable = loadImageFromNework(url);
                image.setImageDrawable(drawable);
            }
        });
        //在开启的新线程中下载图片1
        findViewById(R.id.loadFromOtherThread1).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Drawable drawable = loadImageFromNework(url);
                        image.setImageDrawable(drawable);
                    }
                }).start();
            }
        });
        //在开启的新线程中下载图片2
        findViewById(R.id.loadFromOtherThread2).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        final Drawable drawable = loadImageFromNework(url);
                        image.post(new Runnable() {
                            @Override
                            public void run() {
                                image.setImageDrawable(drawable);
                            }
                        });
                    }
                }).start();
            }
        });
        //利用AsyncTask去下载图片
        findViewById(R.id.loadFromAsync).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new DownloadImageTask().execute(url);
            }
        });
    }

    private class DownloadImageTask extends AsyncTask<String,Void,Drawable>{

        @Override
        protected Drawable doInBackground(String... params) {
            return loadImageFromNework(params[0]);
        }

        @Override
        protected void onPostExecute(Drawable drawable) {
            image.setImageDrawable(drawable);
        }
    }

    //利用Drawable从网络上加载图片
    private Drawable loadImageFromNework(String imageUrl) {
        Drawable drawable = null;
        try {
            drawable = Drawable.createFromStream(new URL(imageUrl).openStream(), "meinv.jpg");
        } catch (IOException e) {
            Log.e("xiaoyu", e.getMessage());
        }
        if (drawable == null) {
            Log.e("xiaoyu", "drawable is null");
        } else {
            Log.e("xiaoyu", "drawable not null");
        }
        return drawable;
    }
}

这里要声明一下,点击第二个按钮的时候,程序会崩溃(这是正常的~),因为在UI线程外更新了ImageView,所以程序崩溃了,这是为了程序的演示效果~

第一个按钮为在主线程中加载图片,当然这张图片很小,假如图片很大的话,会造成主线程的阻塞,造成应用程序ANR~

第三个按钮的实现逻辑和第二个按钮是一样的,将加载网络数据的耗时操作放在了Thread中,但是和第二个按钮不同的是在得到drawable后,通过View的post方法将更新UI的操作放到了主线程中去更新,所以点击第三个按钮就不会导致程序的崩溃。

第四个按钮通过AsyncTask来更新UI,后面会详细剖析~

加载图片的方法也是极其的简单,通过Drawable中的createFromStream这个方法来实现,第一个参数传递一个IoStream,我们通过new Url(url).openStream();来实现,第二个参数没有必要传递,可以传递null,这里随便起了一个名字~

最后别忘记了在AndroidManifist文件中声明 访问网络的权限~

以上就是本篇的全部内容,接下来会为大家继续推出这一系列的博客~

就不给大家截图片了,我这里不是很方便~

测试代码地址:http://download.csdn.net/detail/ly985557461/8424333

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。