Android从零开始:后台操作

除了最基本的Android应用程序之外,您构建的所有内容都将至少需要使用一些后台线程来执行操作。 这是因为,Android有一些被称为一个ANR(A pplicationÑOTřesponsive)超时,其中当操作需要五个秒内造成或更长的UI线程上,防止用户输入并导致显示的内容对用户是一个悬应用程式。

为了避免这种情况,您必须将运行时间较长的操作(例如网络请求或缓慢的数据库查询)移至其他线程,以防止用户继续使用您的应用程序。 尽管对线程的全面介绍是计算机科学中的一个大而复杂的主题,但是本教程将向您介绍Android中线程的核心概念,以及一些可用的工具来帮助您通过使用后台进程来构建性能更好的应用程序。

了解线程

启动应用程序时,将启动具有单个执行线程的新Linux进程。 这是可以访问Android UI工具包,侦听用户输入并处理绘制到Android设备屏幕的线程。 因此,它通常也称为UI线程

默认情况下,应用程序的所有组件都在同一线程和进程中运行,尽管可以创建其他线程来将任务移出UI线程并阻止ANR。 对于Android中的线程处理,要记住两个简单的规则,以确保应用程序按预期运行:

  1. 不要阻塞UI线程。
  2. 不要尝试从UI线程外部访问Android UI组件。

尽管您可以通过简单地创建新的ThreadRunnable来遵守第一条规则,但是处理第二条规则会有些棘手。 考虑以下代码片段:

new Thread(new Runnable() {
    public void run() {
        try {
            Thread.sleep(6000);
        } catch( InterruptedException e ) {

        }

        mTextView.setText("test");
    }
}).start();

虽然这段代码不会使UI线程在ANR超时之后进入Hibernate状态,但尝试设置TextView文本将导致应用抛出以下错误:

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

幸运的是,有一些简单的方法可以解决此问题。 您可以使用Android的runOnUiThread(Runnable)方法在应用程序的主线程上执行代码。

mTextView = (TextView) findViewById(R.id.text);
new Thread(new Runnable() {
    public void run() {
        try {
            Thread.sleep(6000);
        } catch( InterruptedException e ) {

        }

        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mTextView.setText("test");
            }
        });
    }
}).start();

或者你也可以采取一个标准的View对象,并post一个Runnable它。

new Thread(new Runnable() {
    public void run() {
        try {
            Thread.sleep(6000);
        } catch( InterruptedException e ) {

        }

        mTextView.post(new Runnable() {
            @Override
            public void run() {
                mTextView.setText("test");
            }
        });
    }
}).start();

虽然这两个技巧都将有助于使操作线程安全,但是随着应用程序变得越来越复杂,这将变得难以维护。

异步任务

Android提供的可帮助管理后台线程复杂性的工具之一是AsyncTaskAsyncTask提供了一个用于阻止操作的工作线程,然后使用预先创建的回调方法将结果发布回UI线程,使您可以轻松完成任务,而不必费解线程和处理程序。

AsyncTask生命周期

在开始使用AsyncTask类之前,您需要了解与在主线程上运行操作相比的生命周期。

AsyncTask生命周期

AsyncTask调用的第一个方法是onPreExecute() 。 此方法在UI线程上运行,用于设置需要让用户知道发生了什么的任何接口组件。

onPreExecute()完成之后,将doInBackground(T) 。 此处的通用参数是您需要传递给方法以执行其任务的任何信息。 例如,如果要编写一个从URL检索JSON的任务,则可以将该URL作为String传递给此方法。 当操作在doInBackground()取得进展时,您可以调用onProgressUpdate(T)来更新您的UI(例如屏幕上的进度条)。 在这里,泛型是代表进度的值,例如Integer

一旦doInBackground()方法完成,它就可以返回传递到onPostExecute(T)的对象,例如从初始URL下载的JSONObjectonPostExecute(T)在UI线程上运行。

创建AsyncTask类时,必须在类声明和上述方法中都覆盖这些泛型。 可以在下面看到一个示例AsyncTask ,它每秒更新一个ProgressBar

protected class DemoAsyncTask extends AsyncTask<Integer, Void, String> {
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        mProgress.setProgress(0);
        mProgress.setVisibility(View.Visible);
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
        mProgress.setProgress(values[0]);
    }

    @Override
    protected Void doInBackground(Void... params) {

        for( int i = 0; i < 100; i++ ) {
            try {
                Thread.sleep(1000);
            } catch( InterruptedException e ) {}
            publishProgress(i);
        }

       return "All done!";

    }

    @Override
    protected void onPostExecute(String result) {
        super.onPostExecute(aVoid);
        if( isCancelled() ) {
            return;
        }

        mProgress.setVisibility(View.GONE);
        Toast.makeText(context, result, Toast.LENGTH_SHORT).show();
    }
}

您可能已经注意到onPostExecute(T)检查isCancelled() 。 这是因为AsyncTasks有一个大问题:即使在Context被销毁之后,它们仍维护对Context的引用。

启动AsyncTask然后旋转屏幕时,最容易看到这种情况。 如果您在原始Context被销毁后尝试引用Context项目(例如ViewActivity ),则将引发Exception 。 解决此问题的最简单方法是在ActivityFragmentonDestroy()方法中的AsyncTask上调用cancel(true) ,然后验证onPostExecute(T)尚未取消任务。

与编程中的任何内容一样,何时应该使用AsyncTask的答案是:它取决于。 尽管AsyncTasks易于使用,但它们并不是线程解决方案的最终解决方案,并且最适合用于最多持续几秒钟的简短操作。 如果您有可能会持续较长时间的操作,我建议你使用调查ThreadPoolExecutorService ,或GcmNetworkManager (中的向后兼容版本JobScheduler )。

服务

当您需要在后台执行长时间运行的操作(例如播放音乐,执行网络交易或与内容提供商进行交互)时,您可能需要考虑使用Service 。 基本Service可以以两种状态存在:启动状态和有界状态。

启动的Service由应用程序中的某个组件启动,即使原始组件被销毁,该服务仍在设备的后台处于活动状态。 当启动的Service正在执行的任务完成时, Service将自行停止。 标准启动的Service通常用于长时间运行的后台任务,不需要与应用程序的其余部分进行通信。

绑定的Service类似于启动的Service ,它还为可以绑定到它的各种应用程序组件提供了回调。 当所有绑定的组件都与Service解除绑定后,它将停止运行。 这两种方式来运行,应该注意的重要Service并不是相互排斥的,您可以启动一个Service ,将无限期地运行,并且可以有组件绑定到它。

意图服务

标准Service的最大问题之一是它不能一次处理多个请求,因为这将是多线程的噩梦。 解决此问题的一种方法是扩展IntentService ,它扩展了标准ServiceIntentService创建一个默认工作线程来执行onStartCommand()中接收的所有意图,因此所有操作都可以在主线程之外进行。 然后,它创建一个工作队列,一次将每个意图发送到onHandleIntent() ,这样您就不必担心多线程问题。

除了处理线程之外,一旦处理了所有启动请求, IntentService也会自动停止自身。 由于所有实现细节都在IntentService中处理, IntentService作为开发人员的工作非常简单。

public class ExampleIntentService extends IntentService {

  //required constructor with a name for the service
  public ExampleIntentService() {
      super("ExampleIntentService");
  }

  @Override
  protected void onHandleIntent(Intent intent) {
    //Perform your tasks here
      try {
          Thread.sleep(5000);
      } catch (InterruptedException e) {}
  }
}

结论

在本教程中,您已经学到了很多有关Android中的线程和多线程解决方案的知识。 整本书都是关于Android中的线程编写的,但是您现在应该有足够的基础来编写常规任务,并深入了解针对更复杂的Android应用程序的更深入的文档。

翻译自: https://code.tutsplus.com/tutorials/android-from-scratch-background-operations--cms-26810

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值