【问题标题】:How to access and modify the Action Bar content from within another thread如何从另一个线程中访问和修改操作栏内容
【发布时间】:2018-11-07 13:25:40
【问题描述】:

我正在做一些实验,以在 Windows 10 Pro x64 上使用 Android Studio 最新版本来体验 Android 开发中的线程和任务。我编写了一个带有两个按钮的非常简单的应用程序的两个版本,以说明使用分离的线程来处理非常繁重的工作是多么重要,这样应用程序就不会挂起:使用一个按钮我可以更改布局背景颜色,使用另一个按钮我启动一个从 0 计数到 10000000 的计数器,显示操作栏上的增量值。此计数器可以将应用程序的响应性卡住几秒钟,因此当它启动时,直到它结束时,都无法更改背景颜色。在这种情况下,为这项繁重的工作创建一个单独的任务是个好主意。

这两个版本的应用程序不同,因为第一个版本没有单独的计数器任务(因此可以体验到应用程序在计数期间没有响应),而第二个版本有。

这是第一个版本的源代码:

package com.example.testasync;

import android.graphics.Color;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;

public class MainActivity extends AppCompatActivity {

    ActionBar actionBar;
    int flagBackgroundColor = 0;
    LinearLayout linearLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        actionBar = getSupportActionBar();
        actionBar.setTitle("Counter: 0");
        linearLayout = new LinearLayout(this);
        linearLayout.setOrientation(LinearLayout.VERTICAL);
        Button btnStartCounter = new Button(this);
        Button btnChangeBackgroundColor = new Button(this);
        btnStartCounter.setText("Start counter");
        btnChangeBackgroundColor.setText("Change background color");

        btnChangeBackgroundColor.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                switch (flagBackgroundColor) {
                    case 0:
                        linearLayout.setBackgroundColor(Color.BLACK);
                        flagBackgroundColor++;
                        break;
                    case 1:
                        linearLayout.setBackgroundColor(Color.RED);
                        flagBackgroundColor++;
                        break;
                    case 2:
                        linearLayout.setBackgroundColor(Color.GREEN);
                        flagBackgroundColor++;
                        break;
                    case 3:
                        linearLayout.setBackgroundColor(Color.GRAY);
                        flagBackgroundColor++;
                        break;
                    case 4:
                        linearLayout.setBackgroundColor(Color.WHITE);
                        flagBackgroundColor = 0;
                        break;
                }
            }
        });

        btnStartCounter.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int i;
                actionBar.setTitle("Counter: 0");
                for(i=0; i<10_000_000; i++) {
                    actionBar.setTitle("Counter: " + i);
                }
            }
        });

        linearLayout.addView(btnStartCounter);
        linearLayout.addView(btnChangeBackgroundColor);
        setContentView(linearLayout);
    }
}

我对增量 for 循环使用单独任务的第二个源代码是这样的:

package com.example.testasync;

import android.graphics.Color;
import android.os.AsyncTask;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;

public class MainActivity extends AppCompatActivity {

    ActionBar actionBar;
    int flagBackgroundColor = 0;
    LinearLayout linearLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        actionBar = getSupportActionBar();
        actionBar.setTitle("Counter: 0");
        linearLayout = new LinearLayout(this);
        linearLayout.setOrientation(LinearLayout.VERTICAL);
        Button btnStartCounter = new Button(this);
        Button btnChangeBackgroundColor = new Button(this);
        btnStartCounter.setText("Start counter");
        btnChangeBackgroundColor.setText("Change background color");

        btnChangeBackgroundColor.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                switch (flagBackgroundColor) {
                    case 0:
                        linearLayout.setBackgroundColor(Color.BLACK);
                        flagBackgroundColor++;
                        break;
                    case 1:
                        linearLayout.setBackgroundColor(Color.RED);
                        flagBackgroundColor++;
                        break;
                    case 2:
                        linearLayout.setBackgroundColor(Color.GREEN);
                        flagBackgroundColor++;
                        break;
                    case 3:
                        linearLayout.setBackgroundColor(Color.GRAY);
                        flagBackgroundColor++;
                        break;
                    case 4:
                        linearLayout.setBackgroundColor(Color.WHITE);
                        flagBackgroundColor = 0;
                        break;
                }
            }
        });

        btnStartCounter.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MyTask myTask = new MyTask();
                myTask.execute();
            }
        });

        linearLayout.addView(btnStartCounter);
        linearLayout.addView(btnChangeBackgroundColor);
        setContentView(linearLayout);
    }

    class MyTask extends AsyncTask<Void, Void, Void> {

        @Override
        protected Void doInBackground(Void... voids) {
            int i;
            actionBar.setTitle("Counter: 0");
            for(i=0; i<10_000_000; i++) {
                actionBar.setTitle("Counter: " + i);
            }
            return null;
        }
    }
}

当我单击启动计数器的按钮时,出现以下错误:

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

在互联网上搜索我发现对于这样的问题,解决方案是使用 runOnUiThread() 但这样做我得到了与第一个源代码相同的结果:应用程序卡住等待计数器结束。 如何在不冻结 UI 的情况下在操作栏上显示计数器的增量值?有没有办法从另一个线程访问和修改操作栏内容?还有另一种我不知道的方法吗?

【问题讨论】:

  • 你为什么不使用处理程序? developer.android.com/reference/android/os/Handler
  • 感谢您的快速回复。我是 Andoird 开发的新手,我在网上关注一些视频教程,当谈到任务和线程时,他们都没有谈到 Handler。现在我要看看你的链接。再见。

标签: java android multithreading task


【解决方案1】:

您可以直接访问用户界面

new Handler(Looper.getMainLooper()).post(new Runnable()
{
    @Override
    public void run()
    {
        //Access UI code from here
    }
});

或者你可以使用回调

从界面开始

public interface MyCallback {
    void count(int i);
}

如何启动任务并接收回调

MyTask myTask = new MyTask(new MyCallback()
{
    @Override
    public void count(int i)
    {
        actionBar.setTitle("Counter: " + i);
    }
});
myTask.execute();

扩展您的 MyTask 以包含回调

static class MyTask extends AsyncTask<Void, Void, Void>
{
    private MyCallback _callback;

    MyTask(MyCallback callback) {
        _callback = callback;
    }

    @Override
    protected Void doInBackground(Void... voids) {
        for(int i = 0; i < 100; i++) {
            _callback.count(i);
        }
        return null;
    }
}

【讨论】:

  • 感谢您的帮助。第一个 sn-p(使用 Handler 的那个)产生与我的第一个代码相同的效果(应用程序卡住直到计数结束),第二个建议不启动计数器。 (如果 MainActivity 实现了我必须在 MainActivity 主体的 count 方法中写入的接口?)
  • 你是对的,对不起,有点累,我没有在编译器中编写。没错,在activity中实现接口是没有必要的。
  • @filograndipad2 我编辑了答案,你可以试试那个例子吗?
  • 非常感谢您的帮助 Ezzy。我完全遵循了新的建议,但现在我收到错误“原因:android.view.ViewRootImpl$CalledFromWrongThreadException:只有创建视图层次结构的原始线程才能触及其视图。”当我单击启动计数器的按钮时。
  • @filograndipad2 您可以将actionBar.setTitle("Counter: " + i); 包装在处理程序中,但我很确定 UI 可能仍然会冻结,因为它每隔几个滴答就会被调用一次。您可以尝试一下,至少应该可以消除错误。
【解决方案2】:

您可以使用 onPostExecute 与 UI 进行交互。请阅读ref

注意: onPreExecute()onProgressUpdate() 也可以与 UI 线程通信。 但doInBackground 只能从工作线程调用。

【讨论】:

  • 您好,感谢您的回复。你能告诉我我该怎么做吗?我不明白阅读参考。非常感谢
【解决方案3】:

您不应修改“doInBackground”方法中的数字。 AsyncTask 类有一个名为onProgressUpdate 的方法。只需覆盖它并从这里更新您的用户界面。 从您的doInBackground 方法调用onProgressUpdate(yourvalue),然后更新UI。另请注意,我将您的 asyncTask 类的第二个类型参数从 Void 更改为 Int。

(我没有在IDE中实现解决方案,可能有语法错误,但概念是这样的)

class MyTask extends AsyncTask<Void, Int, Void> {

        @Override
        protected Void doInBackground(Void... voids) {
            int i;
            actionBar.setTitle("Counter: 0");
            for(i=0; i<10_000_000; i++) {
                publishUpdate(i)
            }
            return null;
        }

        @Overrdie
        protected void onProgressUpdate(Int.. ints){
        actionBar.setTitle("Counter: " + ints[0]);
        }

    }

【讨论】:

  • 感谢 sn-p,但我继续收到“android.view.ViewRootImpl$CalledFromWrongThreadException:只有创建视图层次结构的原始线程才能触摸其视图。”错误
  • 哦,抱歉,当您从 doInBackground 调用时,请使用 publishProgress(i) 方法而不是 onProgressUpdate()。我更新了 sn-p
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-15
  • 2012-03-21
  • 1970-01-01
  • 2012-11-05
相关资源
最近更新 更多