Handler线程中如何做横向数据

Posted

技术标签:

【中文标题】Handler线程中如何做横向数据【英文标题】:How to do transverse data in Handler thread 【发布时间】:2018-03-22 19:36:06 【问题描述】:

我有一个activity,它包含2个fragment,每个fragment有2个list views,每个都有一个适配器。 所以我从服务器获得了json 中相当长的数据,我使用AsyncTaskpost execute,我解析数据。问题是它会将加载动画冻结 4-5 秒。

我曾尝试使用Handler 线程,但问题仍然存在,我一定是在这里做错了什么。

public class DataService extends AsyncTask<String, String, Void> 

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

        url = new URL(DATA_URL);    
        //Connection stuff

        String jsonString = stringBuilder.toString();
        jsonObject = new JSONObject(jsonString);

        //Adding data in Java Objects

    


    @Override
    protected void onPostExecute(Void aVoid) 
            super.onPostExecute(aVoid);


             //Upcoming List in Fragment
             allDataFragment.setUpcomingDataList(UpcomingAllDataList);
             awayFragment.setUpcomingDataList(tUpcomingAwayList);
             homeFragment.setUpcomingDataList(tUpcomingHomeList);


             //Past List in Fragment            
             allDataFragment.setPastDataList(pastAllDataList);
             awayFragment.setPastDataList(pastAwayList);
             homeFragment.setPastDataList(pastHomeList);    

    

我在适配器中添加了日志消息,我可以看到在解析行时它会冻结,所以它会在处理程序线程中执行所有后期执行的东西

       Handler handler= new Handler();

        handler.post(new Runnable() 
            @Override
            public void run() 


                    //Upcoming List in Fragment
                     allDataFragment.setUpcomingDataList(UpcomingAllDataList);
                     awayFragment.setUpcomingDataList(tUpcomingAwayList);
                     homeFragment.setUpcomingDataList(tUpcomingHomeList);


                     //Past List in Fragment            
                     allDataFragment.setPastDataList(pastAllDataList);
                     awayFragment.setPastDataList(pastAwayList);
                     homeFragment.setPastDataList(pastHomeList);    


                               
            );

我也尝试在Adapter 中添加一个处理程序,但它无法从那里加载UI 组件。

这是我的片段代码中的一个 sn-p。

if (pastListView != null) 
        PastListAdapter allGamesAdapter = new PastListAdapter(getContext(), pastAllDataList);
        pastListView.setAdapter(allGamesAdapter);
        

并且在适配器中我正在执行以下操作。

  @Override
    public View getView(int position, View convertView, ViewGroup parent) 

            TextView vScore = (TextView) v.findViewById(R.id.v_score);
            TextView date = (TextView) v.findViewById(R.id.date);
            ImageView image = (ImageView) v.findViewById(R.id.iv_image);
            //7 Other views


            vScore.setText(dataList.get(position).getScore);
            date.setText(dataList.get(position).date);

             Log.d(TAG, "getView: Row");

            return v;
    

加载动画在开始时工作正常,但在开始解析适配器中的数据时,它会挂起 UI 线程。

【问题讨论】:

所以它会在处理程序线程中执行所有后期执行的东西 ...处理程序是在 UI 线程上创建的,所以“处理程序线程”== UI 线程...所以它很明显无济于事...您应该确定程序的哪个部分需要很长时间,然后修复它...getView() 的问题一个明显的原因:您没有使用回收...一个“prolly”原因(但没有代码只是盲目猜测)您正在 UI 线程上加载图像... @Selvin 我没有使用RecyclerView 我看到的是getView() 需要时间,但我无法理解如何修复它,getView() 应该在 UI 线程中,我我无法找到任何方法将该部分放在其他线程中 您正在使用 ListView 作为 RecyclerView 的适配器,但没有 getView 方法...您还必须找出 getView 中究竟是什么需要这么多时间。我打赌加载应该在后台线程上完成的图像。 而且,是的,ListView 支持视图回收...... convertView 是回收视图 - 当您需要创建新对象时它为空,如果视图可以重复使用则不为空 @Selvin :我已经注释掉了图像加载部分,它仍然挂了几秒钟,我在渲染rows 时可以看到它挂了几秒钟。我添加了日志消息,打印在每一行 getView 我如何将 Base Adapter - getView 移动到其他线程? 【参考方案1】:

使用AsyncTask 处理持续时间短于 5 毫秒(5 毫秒)的工作项。由于您从服务器获取了相当长的 JSON 数据,因此您必须考虑其他替代方案(ThreadHandlerThreadThreadPoolExecutor

即使您创建了Handler,它也仅在 UI 线程上运行。代替Handler,使用HandlerThread

建议的解决方案:

    使用HandlerThread 从服务器获取 JSON 数据。 UI 线程没有被阻塞来获取数据。

    将数据从 HandlerThread 发布到 UI 线程的 Handler。 UI Handler 将使用在handleMessage(Message msg) 上收到的数据更新 UI

有用的帖子:

Asynctask vs Thread vs Services vs Loader

Handler vs AsyncTask vs Thread

Why to use Handlers while runOnUiThread does the same? (这篇文章包含上述解决方案的代码示例)

【讨论】:

【参考方案2】:

使用 Volley 代替 AsyncTask

【讨论】:

【参考方案3】:

如果它阻塞了 ui 线程,那么不要在 ui 线程中这样做

你已经有了一个 asyncTask,所以只需使用它并从那里调用你的解析 json 函数

然后您可以从该任务中返回 void 并从那里更新您的采用者

换句话说,将你的逻辑从 onPostExecute 移到 doInBackground

【讨论】:

阵列适配器 getView() 导致问题,如果我将 fragment.setDataListpostExecute 移动到 doInBackground 然后后台线程无法访问 UI 对象 那么写一个函数并从任务中调用它应该这样做 我不明白你对这些数据的使用,但看起来应该是简单的或分割的,并在几个api调用中查询你的数据有多大?也许考虑分页【参考方案4】:

一开始,我劝你,不要偷懒,看看图书馆

Retrofit - RESTfull client library

OKhttp - http client library

您无需使用这些库编写 AsyncTask,这将使您的生活更轻松、更轻松。

但是,如果你不这样做,好吧。让我们谈谈 AsyncTask 的问题和您关心的决定。

AsyncTask 在后台计算线程上运行,其结果在 UI 线程上发布。它们由 3 种通用类型定义,称为 Params、Progress 和 Result,以及 4 个步骤,称为开始、doInBackground、进度更新和计算结束更新 UI。 因此,当它被执行时,任务会经过 4 个步骤:

1 步。 onPreExecute(),在任务执行后立即在 UI 线程上调用。此步骤主要用于配置任务,例如显示进度条、进度对话框或 UI 中的任何其他更改。

2 步。 doInBackground(Params...),在 onPreExecute() 完成执行后立即在后台线程上调用。此步骤用于执行可能需要很长时间的后台计算。异步任务的参数传递到这一步。计算的结果必须由这一步返回,并将传递回最后一步。此步骤还可以使用 publishProgress(Progress...) 来发布一个或多个进度单位。这些值在 UI 线程的 onProgressUpdate(Progress...) 步骤中发布。

3 步。 onProgressUpdate(Progress...),在调用 publishProgress(Progress... )。执行的时间是不确定的。此方法用于在后台计算仍在执行时在用户界面中显示任何形式的进度。例如,它可用于动画进度条或在文本字段中显示日志。

4 步。 onPostExecute(Result),在后台计算完成后在 UI 线程上调用。后台计算的结果作为参数传递给这一步。

因此,就像您现在一样,如果您不想捕获性能冻结,则必须在 2 步中返回计算结果。

public class DataService extends AsyncTask<String, String, List<UpcomingAllDataList>> 

    @Override
    protected List<UpcomingAllDataList> doInBackground(String... params) 

        // making HTTP request

        return UpcomingAllDataList; // return results of Http request
    

    @Override
    protected void onPostExecute(List<UpcomingAllDataList> upcomingAllDataList) 
        super.onPostExecute(upcomingAllDataList);


        //Upcoming List in Fragment
        allDataFragment.setUpcomingDataList(upcomingAllDataList);

        // all another work

    

希望对你有用。

【讨论】:

【参考方案5】:

你真的应该使用 Retrofit 来进行服务器调用 + 解析。Retrofit Github

Retrofit Site

我认为真正的问题是您在 setUpcomingDataList (以及所有其他)方法中解析更多数据。

无论如何,HandlerThread 的示例实际上发生在 UI 线程中,因为 Handler 的默认构造函数采用您在其上构造 Handler 的当前线程(在您的情况下是 UI 线程)并发布Runnable 到该线程。

无论如何 2 - 看看这些方法是否真的可以在不同的线程中工作(我猜你会得到一个 android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. )你可以尝试用普通线程包装你的代码:

Thread thread = new Thread(new Runnable() 
                @Override
                public void run() 
                    //Upcoming List in Fragment
                    allDataFragment.setUpcomingDataList(UpcomingAllDataList);
                    awayFragment.setUpcomingDataList(tUpcomingAwayList);
                    homeFragment.setUpcomingDataList(tUpcomingHomeList);


                    //Past List in Fragment            
                    allDataFragment.setPastDataList(pastAllDataList);
                    awayFragment.setPastDataList(pastAwayList);
                    homeFragment.setPastDataList(pastHomeList);
                
            );
            thread.start();

这是一个非常糟糕的做法 - 正如我之前所说,使用 Retrofit。

【讨论】:

我认为,这不是一个好的决定...对于更新 UI 线程,最好使用 getActivity().runOnUiThread(() -> )

以上是关于Handler线程中如何做横向数据的主要内容,如果未能解决你的问题,请参考以下文章

Handler

异步数据处理Handler

Android中Handler原理

qt中handler的定义

Android可以让主线程在其他子线程执行完后再执行吗?如果可以,该怎么做?

Android 面试中常问到的那些 Handler 面试题