第十二课:Running in the Background(基于AndroidStudio3.2)

现在我们对UI元素和屏幕有了一些了解,我们需要让它们具有响应性。响应力并不仅仅与速度有关 - 你可以在一段时间内完成多少工作。更重要的是应用程序的速度有多快。当人们说应用程序响应时,通常他们的意思是应用程序不会阻止他们做他们正在尝试的事情去做。它不会妨碍他们。如果你曾经使用过一个只是冻结的应用程序点击某个按钮,你可以欣赏我们正在谈论的内容。它不会阻止。
想象阻止就像打电话给某人一样。拨号时,您会听到铃声和声音你等着对方接。除非对方接听,否则电话不能继续。我们可以说电话是一种阻止操作,因为事情必须发生按顺序。你拨打电话,打电话,另一个人拿起电话,然后你说话。都不是事情可能同时发生。所有步骤都涉及某种形式的“等待” - 或者计算术语,阻塞。

长期运行的任务
用户可能能够容忍日常生活中的阻塞,例如排队续订许可证或杂货,或等待某人拿起电话,等等。但在使用您的应用时,他们可能不那么宽容。即便是Android平台也不会容忍你的应用程序,如果花费太多时间做它做的事情:WindowManager和ActivityManager是响应性的警察。用户点击时一个按钮,或与任何触发事件的视图交互,您的应用程序没有很多时间来完成应该做的事情;事实上,它最多只有5秒钟被运行时杀死。到那时,你会看到臭名昭着的ANR错误(申请没有响应)。把它想象成Android的BSOD(蓝屏死机)。
根据Android指南,应用程序可以在100毫秒到200毫秒之间在事件处理程序中完成一项任务 - 这不是很多时间,所以我们确实需要确保我们不会在事件处理程序中做任何太疯狂的事情。但说起来容易做起来难,而且有几种情况我们不会完全控制我们在里面做的事情事件处理程序。我们可以在这里列出其中几个。

  • 当我们read a file时 - 我们的程序需要保存数据或读取他们在某个时间点。 文件IO操作可能是出了名的有时不可预测; 你只是不知道该文件有多大。如果它太大,完成任务可能需要200多秒
  • 当我们interact with a database时 - 我们与数据库进行交互为其提供读取,更新,创建和删除数据的命令。与文件一样,有时候,我们可能会发出一个返回大量文件的命令数据; 处理这些记录可能需要一段时间
  • 当我们 interact with the network时 - 当我们获取数据时网络套接字,我们受网络条件的支配。 如果它是没有拥挤或失望,这对我们有好处。 但它并不总是上升而且它是并不总是很快; 如果你编写处理网络内部的代码事件处理程序,您冒着ANR的风险
  • 当我们use other people’s code时 - 我们越来越依赖API构建我们的应用程序,并且有充分的理由:它们节省了我们的时间。 但我们不能总是知道这些API是如何构建的以及它们是什么类型的他们在引擎盖下的操作(你真的总是读它您使用的所有API的源代码?)

那么,我们应该怎么做才能使我们的应用程序不会遇到ANR? 我们当然无法避免前面列出的东西,因为大多数现代(和有用)应用程序将需要做一件或多件(或全部)这些事。 事实证明,答案是运行中的东西背景。 有几种方法可以做到这一点,但在本节中,我们将看看运行情况我们在AsyncTask中的代码。

一、模拟一个长时间运行的任务

当用户点击时“长时间运行任务”,它将模拟一个长时间运行的任务,但我们所做的只是从1开始计算到15; 计数的每个刻度需要2秒。 我们实际上是将用户作为人质至少30秒,在此期间他不能在应用程序中做太多其他事情。

1、新建项目Async

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="317dp"
        android:gravity="center"
        android:text="Long running task"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView"
        />

    <TextView
        android:id="@+id/textView"
        android:layout_width="184dp"
        android:layout_height="0dp"
        android:layout_marginBottom="55dp"
        android:layout_marginTop="34dp"
        android:gravity="center"
        android:text="TextView"
        android:textSize="18sp"
        app:layout_constraintBottom_toTopOf="@+id/button"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Click"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>

</android.support.constraint.ConstraintLayout>
package com.example.administrator.async;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity
        implements View.OnClickListener{

    private String TAG;
    TextView tv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button b = (Button) findViewById(R.id.button);
        Button b2 = (Button) findViewById(R.id.button2);
        tv = (TextView) findViewById(R.id.textView);
        TAG = getClass().getSimpleName();
        b.setOnClickListener(this);
        b2.setOnClickListener(new View.OnClickListener(){
            public void onClick(View v) {
                Log.i(TAG, "Clicked");
            }
        });
    }
    //This whole code block is designed to simulate a time-consuming activity inside an event handler
    public void onClick(View v) {
        int i = 0;
        while (i < 15) {
            try {
                //This will halt execution for 10 seconds
                Thread.sleep(2000);
                //Every 10 seconds, we write the value of i to the UI
                tv.setText(String.format("Value of i = %d", i));
                Log.i(TAG, String.format("value of i = %d", i++ ));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

2、测试

这段代码不会走得太远。 如果单击,它很快就会遇到ANR错误“长时间运行任务”按钮,然后单击其他按钮。 你会注意到你不会
能够点击它,因为UI线程正在等待“长时间运行的任务完成” - 用户界面是不再敏感。

二、AsyncTask

在上面我们遇到的问题是当一个事件处理程序时做一些冗长的事情,整个用户界面冻结,用户无法做太多其他事情 - 用户被封锁了。 AsyncTask旨在解决这些问题。 它旨在即使在执行需要相当长时间的操作时,也能使UI响应。

AsyncTask,即异步任务,是Android给我们提供的一个处理异步任务的类.通过此类,可以实现UI线程和后台线程进行通讯,后台线程执行异步任务,并把结果返回给UI线程.

.为什么需要使用异步任务?

我们知道,Android中只有UI线程,也就是主线程才能进行对UI的更新操作,而其他线程是不能直接操作UI的.这样的好处是保证了UI的稳定性和准确性,避免多个线程同时对UI进行操作而造成UI的混乱.但Android是一个多线程的操作系统,我们总不能把所有的任务都放在主线程中进行实现,比如网络操作,文件读取等耗时操作,如果全部放到主线程去执行,就可能会造成后面任务的阻塞.Android会去检测这种阻塞,当阻塞时间太长的时候,就会抛出Application Not Responsed(ANR)错误.所以我们需要将这些耗时操作放在非主线程中去执行.这样既避免了Android的单线程模型,又避免了ANR.

.AsyncTask为何而生?

提到异步任务,我们能想到用线程,线程池去实现.确实,Android给我们提供了主线程与其他线程通讯的机制.但同时,Android也给我们提供了一个封装好的组件--AsyncTask.利用AsyncTask,我们可以很方便的实现异步任务处理.AsyncTask可以在子线程中更新UI,也封装简化了异步操作.使用线程,线程池处理异步任务涉及到了线程的同步,管理等问题.而且当线程结束的时候还需要使用Handler去通知主线程来更新UI.而AsyncTask封装了这一切,使得我们可以很方便的在子线程中更新UI.

----构建AsyncTask子类的泛型参数

AsyncTask<Params,Progress,Result>是一个抽象类,通常用于被继承.继承AsyncTask需要指定如下三个泛型参数:

Params:启动任务时输入的参数类型.您想将哪些信息传递给后台线程? 这是通常是您要更新的UI元素。 当你从中调用execute时
MainActivity,您需要将此参数传递给AsyncTask。 这个参数自动进入doInBackground方法。 在我们的例子中,这个是一个文本视图对象。 我们希望后台线程可以访问此UI元素

Progress:后台任务执行中返回进度值的类型.您希望后台线程返回什么类型的信息到onProgressUpdate方法,这样你就可以指定长时间运行的状态对用户的操作? 在我们的例子中,我们想要更新的text属性文本视图,所以这是一个String对象

Result:后台任务执行完成后返回结果的类型.您希望使用哪种数据来指定doInBackground的状态什么时候完成任务? 在我们的例子中,我只是想让它返回真实的一切顺利,所以第三个参数是布尔值

-----构建AsyncTask子类的回调方法,AsyncTask主要有如下几个方法:

doInBackground:必须重写,异步执行后台线程要完成的任务,耗时操作将在此方法中完成.

onPreExecute:执行后台耗时操作前被调用,通常用于进行初始化操作.

onPostExecute:当doInBackground方法完成后,系统将自动调用此方法,并将doInBackground方法返回的值传入此方法.通过此方法进行UI的更新.

onProgressUpdate:当在doInBackground方法中调用publishProgress方法更新任务执行进度后,将调用此方法.通过此方法我们可以知晓任务的完成进度.

下图描述了AsyncTask在此解决上例中的作用。

  • MainActivity创建一个AsyncTask对象(我们基本上创建一个扩展AsyncTask的类)
  • 调用AsyncTask的execute方法;在这个方法中,我们将传递给我们想要更新的UI元素的AsyncTask对象
  • AsyncTask有各种生命周期方法,但是唯一的方法要覆盖的强制回调是doInBackground() - 我们将写入要实现的操作
  • 时,AsyncTask将创建一个后台线程,但是这样线程对我们来说是透明的; 我们不关心它,因为AsyncTask将是管理它的人

在doInBackground()方法中,我们可以定期调用publishProgress()。 每一次我们这样做,运行时将调用AsyncTask的onProgressUpdate()方法,它将是以线程安全的方式完成。 在这个方法中,我们可以做一些UI更新

注意:线程是一系列指令,就像我们在方法中编写的指令序列一样。 然而,线程以特殊方式执行:它在后台执行,因此它不会阻止前台运行的任何内容(UI线程)。 这就是为什么我们需要编写需要很长时间才能完成线程的指令的原因

1、我们来修改AsyncTask项目。 首先,我们需要创建一个从AsyncTask扩展的新类。

File ➤ New ➤ Java class,新建类为Worker,扩展AsyncTask

package com.example.administrator.async;

import android.os.AsyncTask;
import android.util.Log;
import android.widget.TextView;

//The AsyncTask is parameterized; it’s a generic type, so we need to pass arguments to it.
//These parameters are <Params,Progress, Result>;

public class Worker extends AsyncTask<TextView, String, Boolean> {
    private String TAG;
/*
The text view is declared at the top of the class so can we access it from onProgressUpdate;
we can’t define it yet because we will only get object reference to this text view when
doInBackground gets called
*/

    private TextView tv;

/*
This is the only method we are obliged to override.
Inside this is where we should put the program logic, which may take some time to complete
*/
    @Override
    protected Boolean doInBackground(TextView... textViews) {
 /*
 Now we can define the text view; it was already passed to us when MainActivitycalled the
execute()method. The parameter of this method is an array, but we know that we only passed
one UI object (the text view), so we get only the first element of the array. We can now store that
reference to TextView(tv) variable that we hoisted up in
*/
        tv = textViews[0];
        TAG = getClass().getSimpleName();
        int i = 0;
        while (i++ < 15) {
            try {
                Thread.sleep(2000);
                //On each tick, we will call publishProgress, so it can update the UI
                publishProgress(String.format("Value of i = %d", i));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return true;
    }

    //Use this method to communicate progress to the user
/*
This method will catch whatever values we passed to the publishProgressmethod. The
parameter of this method is, again, an array. And since we passed only one string to it, we’ll
only get the first element and set its value as the text attribute of the text view object.
*/

    @Override
    protected void onProgressUpdate(String... values) {
        tv.setText(values[0]);
        Log.i(TAG, String.format(values[0]));

    }
}

2、我们基本上重新定位了MainActivity中耗时的任务,并将它放在Worker类中。 下一步是更新MainActivity中的代码。

package com.example.administrator.async;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;



public class MainActivity extends AppCompatActivity
        implements View.OnClickListener{

    private String TAG;
    TextView tv;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button b = (Button) findViewById(R.id.button);
        Button b2 = (Button) findViewById(R.id.button2);
        tv = (TextView) findViewById(R.id.textView);
        TAG = getClass().getSimpleName();
        b.setOnClickListener(this);
        b2.setOnClickListener(new View.OnClickListener(){
            public void onClick(View v) {
                Log.i(TAG, "Clicked");
            }
        });
    }

    /*
    //This whole code block is designed to simulate a time-consuming activity inside an event handler
    public void onClick(View v) {
        int i = 0;
        while (i < 15) {
            try {
                //This will halt execution for 10 seconds
                Thread.sleep(2000);
                //Every 10 seconds, we write the value of i to the UI
                tv.setText(String.format("Value of i = %d", i));
                Log.i(TAG, String.format("value of i = %d", i++ ));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    */

    public void onClick(View v) {
 /*
 Create an instance of AsyncTask Worker class. Note that the background execution of the
AsyncTask isn’t started by merely creating an instance of it
*/
        Worker worker = new Worker();
/*
The execute method starts the background operation. In this method, we pass whatever we
want to update to the AsyncTask. Note that you can pass more than one UI element to the
execute method, since it will be passed as an array in the doInBackground method of the
AsyncTask
*/
        worker.execute(tv);
    }

}

注意:AsyncTask并不意味着运行非常冗长的操作,大约几分钟。通常,AsyncTaskis仅用于持续几秒钟的操作。 任何超过那个和WindowManager / ActivityManager可能仍会杀死该应用程序。 对于长时间运行的操作,你需要使用服务,但这超出了本课的范围

3、运行ok

猜你喜欢

转载自blog.csdn.net/gumufuyun/article/details/83276727