当前位置: 首页 > 知识库问答 >
问题:

理解Android应用程序中复杂的多线程的困难

方昊
2023-03-14

我在理解应用程序中的多线程时遇到了一个大问题,因此我发现了一个bug。我已经检查了我认为所有的可能性,但我仍然得到各种(有时意想不到的)错误。

也许这里有人能给我建议,我应该做什么。

在我的项目中,我使用了两个外部库:

  • GraphView-为图形绘制提供视图
  • EventBus-为应用程序组件之间的轻松通信提供接口

至于应用程序,其结构如下:

           MainActivity
            /        \
           /          \
        Thread        Fragment
   (ProcessThread)   (GraphFragment)

其思想是,ProcessThread通过事件总线计算数据并向GraphFragment提供恒定的值流。在GraphFragment中,我有一个GraphView所需的系列。

为了根据示例实时更新图形,我需要创建一个新的可运行的

private class PlotsRun implements Runnable{

        @Override
        public void run() {
            mSeries1.appendData(new DataPoint(counter, getRandom()), true, 100);
            counter++;
            mHandler.post(this);
        }
}

当我从fragment的onResume()方法开始时,一切都像一个魔咒一样工作。

不幸的是,正如我所提到的,我正在使用来自另一个线程的外部数据。为了在GraphFraank中获取它,我正在使用(根据留档)onEventMainThread()方法。

在这里,无论我做什么,我都无法在PlotsRun对象中传递数据来更新我的图形。到目前为止,我已经尝试了:

  • 使用Queue-在onEventMainThread中添加值并进入PlotsRun。事实证明,runnable读取速度快于方法能够更新队列。
  • 创建各种缓冲区-结果与Queue完全相同。
  • 调用mSeries1.append数据(新DataPoint(计数器,getRandom()),true,100);直接从onEventMainThread-在某些时候它会被冻结。
  • 在我的runnable中创建onEvent()方法并从那里调用mHandler.post()-它阻塞了UI,更新看起来像快照。
  • 使用有或没有同步()块提到的所有内容。

对我来说,很难理解的是这个runnable(在某些情况下)工作正常。

正如Android官方博客上所说,你们不能从非用户界面线程更新用户界面。这就是为什么我不能在GraphFragment中使用另一个线程。但当我检查我的runnable时,它正在主线程(UI)上运行。这就是为什么我不能创建无限,而循环必须调用mHandler。张贴(此)。

它的行为仍然像另一个线程,因为它比onEventMainThread方法更快(调用更频繁)。

我可以做些什么来使用ProcessThread中的数据更新我的图表(或我应该查看的位置)?

编辑1:

在回答@Matt Wolfe的请求时,我包括了我认为对于这个问题最重要的代码部分,所有必需的变量都显示了它们是如何声明的。这是一个非常简单的示例:

主要活动:

private ProcessThread testThread = new ProcessThread();

 @Override
    protected void onResume() {
        super.onResume();
        testThread.start();
    }


    private class ProcessThread extends Thread{
        private float value = 0f;
        private ReadingsUpdateData updater = new ReadingsUpdateData(values);
        public void run() {
            while(true) {
                value = getRandom();
                updater.setData(value);
                EventBus.getDefault().post(updater);
            }
        }
    }

图形片段:

private LineGraphSeries<DataPoint> mSeries1;
    long counter = 0;
    private Queue<ReadingsUpdateData> queue;

    @Override
    public void onResume() {
        super.onResume();
        mTimer2.run();
    }

    public void onEventMainThread(ReadingsUpdateData data){
        synchronized(queue){
            queue.add(data);
        }
    }

    private class PlotsRun implements Runnable{

        @Override
        public void run() {
            if (queue.size()>0) {
                mSeries1.appendData(new DataPoint(counter, queue.poll()), true, 100);
                counter++;
            }
            mHandler.post(this);
        }
    }

由于快速读取问题,添加了if-in-runnable以提供保护。但它不应该在这里,因为应该总是有一些东西(至少我希望如此)。

还有一件事要补充——当我把simpleLog放进去时。它正在更新并正确显示它的值,但不幸的是,logcat不是主要的用户界面。

编辑2:

这主要是对@Mattwolve评论的回应

mHandler只是在GraphFragment中声明和创建的变量:

private final Handler mHandler = new Handler();
private Runnable mTimer2;

是的,没错,我正在使用mHandler.post()没有任何延迟。我会尝试使用一些延迟来查看是否有任何区别。

我之前没有提到的是,ProcessThread也向其他片段提供数据——不用担心,它们不会相互干扰或共享任何资源。这就是我使用EventBus的原因。

编辑3:

这是一段代码,我用另一个线程作为我的另一个想法,在GraphFragment和runOnMainThread方法中:

private MyThread thread = new MyThread();

    private class MyThread extends Thread {
        Queue<ReadingsUpdateData> inputList;
        ReadingsUpdateData msg;

        public MyThread() {
            inputList = new LinkedList<>();
        }

        public void run() {
            while(true) {
                try{
                    msg = inputList.poll();
                } catch(NoSuchElementException nse){
                    continue;
                }
                if (msg == null) {
                    continue;
                }
                getActivity().runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        mSeries1.appendData(new DataPoint(counter, getRandom()), true, 100);
                        counter++;
                    }
                });
            }
        }

        public void onEvent(ReadingsUpdateData data){
            inputList.add(data);
        }
    }

不幸的是,它也不起作用。


共有3个答案

章建木
2023-03-14

我会这样设置:

public class MainActivity extends Activity {

 private class ProcessThread extends Thread{
        private float value = 0f;
        private ReadingsUpdateData updater = new ReadingsUpdateData(values);
        public void run() {
            while(true) {
                value = getRandom();
                updater.setData(value);
                EventBus.getDefault().post(updater);
            }
        }
    }    

    @Override
    protected void onResume() {
        super.onResume();
        testThread.start();
    }

}



public class GraphFragment extends Fragment {

  private Handler mHandler;
  private Queue<ReadingsUpdateData> queue;

  @Override
  public void onActivityCreated(Bundle state) {
    super.onActivityCreated(state);
    mHandler = new Handler(Looper.getMainLooper());
  }

  public void onEvent(ReadingsUpdateData data){
    synchronized(queue){
        queue.add(data);
    }

    if (mHandler != null) {
       mHandler.post(processPlots);
    }
  }

  //implement pause/resume to register/unregister from event bus


 private Runnable processPlots = new Runnable {

        @Override
        public void run() {
            synchronized(queue) {
              if (queue.size()>0) {
                mSeries1.appendData(new DataPoint(counter, queue.poll()), true, 100);
                counter++;
              }
            }
        }
    }          

}
通和裕
2023-03-14

事实上,您的PlotsRun速度太快了:一旦完成执行,它就会通过调用mHandler重新调用主线程循环执行。post(处理图)

首先,您需要使数据缓冲区独立于数据采集器和数据可视化工具:创建一个可以接收(来自采集器)和交付(到可视化工具)数据的对象。因此,每个组件都可以独立工作。数据对象独立于任何线程。数据采集器可以在需要时将数据推送到数据对象,主线程可以基于常规计时器查询数据对象。

然后,在此缓冲区上设置一个锁,以便需要访问数据缓冲区的其他两个对象都不能同时执行此操作(这将导致崩溃)。该锁可以是方法声明中的简单同步锁。

这应该可以确保您的应用程序不会因为并发访问而崩溃(我认为这应该是您的主要问题)。

然后,如果主数据收集在新数据到达时已经在使用,您可以通过创建额外的缓冲区来存储临时数据来开始优化数据对象,或者制作一份实际数据的副本,使其始终可供主线程使用,即使主线程查询值时当前正在添加新数据。

尉迟华翰
2023-03-14
匿名用户

首先,

下面示例中的可运行部分只是为了为实时数据更新设置动画,您可以选择调用appendData(),而不创建新的可运行部分。不过,您需要从主线程调用appendData()。

其次,

您可以直接从您的onEventMainThread函数调用appendData()函数,但正如您指出的那样,这种方法有时会挂起UI,这种行为的一个可能原因是您可能发布事件太频繁,更新UI太频繁最终会在某个时候挂起UI。您可以执行以下操作来避免这种情况:

过于频繁地更新UI也可能会挂起UI,下面是一个解决方案:

ProcessThread中加入一些逻辑来保存上次发送的事件时间并在发送新事件之前进行比较,如果差异小于1秒,则将其保存以供以后发送,当下一次计算完成时,再次比较时间,如果现在大于1秒,则在数组中发送事件,或者可能只是发送最新事件,因为最新的计算可以代表图形的最新状态,对吗?

希望这有帮助!

编辑:(回应评论1

我不确定你尝试了什么,也许张贴你的更新代码会给你一个更好的主意。但我认为您试图在onEventMainThread或PlotsRun中实现时间检查功能,对吗?如果是的话,恐怕对你没有多大帮助。相反,您需要做的是在ProcessThread内实现此时间检查,并且只有在达到阈值时间时才发布新事件。出于以下原因:

1-后端的EventBus会自动创建一个新的可运行对象并在其中调用onEventMainThread。因此,在ProcessThread中处理时间检查会在内存中产生更少的不需要的运行对象,从而减少内存消耗。

2-也不需要维护队列和生成新的可运行文件,只需更新onEventMainThread中的数据。

以下是仅提供概念证明的最低代码,您需要根据需要进行更新:

ProcessThread类:

private class ProcessThread extends Thread{
    private static final long TIME_THRESHOLD = 100; //100 MS but can change as desired
    private long lastSentTime = 0;
    private float value = 0f;
    private ReadingsUpdateData updater = new ReadingsUpdateData(values);
    public void run() {
        while(true) {
            if (System.currentTimeMillis() - lastSentTime < TIME_THRESHOLD) {
                try {
                    Thread.sleep(TIME_THRESHOLD - (System.currentTimeMillis() - lastSentTime));
                } catch (InterruptedException e) {}
            }

            value = getRandom();
            updater.setData(value);
            EventBus.getDefault().post(updater);
            lastSentTime = System.currentTimeMillis();
        }
    }
}

onEventMainThread方法:

public void onEventMainThread(ReadingsUpdateData data){
    mSeries1.appendData(new DataPoint(counter, data), true, 100);
    counter++;
}

 类似资料:
  • 问题内容: 自两年以来,我一直在使用java(Servlets,JSPs)进行Web应用程序开发。在那两年中,我从不需要在任何项目中使用(明确地- 众所周知,servlet容器使用线程为不同的请求提供相同的servlet)。 但是,每当我参加Web开发人员职位(java)的面试时,就会有几个与java中的线程相关的问题。我知道Java线程的基础知识,因此回答问题不是问题。但是有时我会感到困惑,是否

  • 问题内容: 在多线程应用程序中如何使用Hibernate(例如,每个客户端连接在服务器上启动它自己的线程)。 EntityManager应该仅由EntityManagerFactory创建一次,例如: 还是我必须为每个线程以及关闭EM的每个事务重新创建实体? 我的CRUD方法如下所示: 我要不要每次都跑?还是因为每个人都使用自己的缓存创建自己的EntityManager实例而使我陷入麻烦了? 问题

  • 那么,这种架构的瓶颈在哪里?也许推送每条带有互斥体的消息是个坏主意?

  • 我在一个用C语言编写的多线程服务器应用程序上工作,并在嵌入式Linux上执行。一个线程(我称之为通信线程)应该处理所有套接字I/O(发送和接收消息)。依赖于接收到的消息,通信线程将消息发送到另一个线程(例如Thread)。Controller-Thread)处理所需的序列。控制器线程在序列的末尾创建返回消息。此消息被写回通信线程,该线程应该将它们传输到客户端。 这两个线程之间的通信是通过队列实现的

  • 问题内容: 现在完成XML SAX解析之后,我将在应用程序中进行JSON解析。我在这里为您提供杰森 现在,我提到了一个Json解析的示例,在该示例中,他们创建了一个jString对象,我对此特定行有疑问,如下所示: 任何人都可以让我澄清一下。完整示例的链接如下: http://www.androidcompetencycenter.com/2009/10/json-parsing-in- andr

  • 我有一个多线程应用程序,它有一个生产者线程和几个消费者线程。数据存储在一个共享的线程安全集合中,当缓冲区中有足够的数据时,就刷新到数据库中。 来自爪哇文档 - 一个队列,它还支持在检索元素时等待队列变为非空,并在存储元素时等待队列中的空间变为可用。 检索并删除此队列的头部,如有必要,请等待元素可用。 我的问题是- < li >是否有另一个集合具有E[] take(int n)方法?即阻塞队列等待,