Android:在getView()中执行异步操作的最佳实践

时间:2015-02-13 16:45:59

标签: android multithreading asynchronous concurrency

请不要关闭它,恕我直言,这是一个体面的,可能有用的编程问题。


请我阅读很多内容,因为我阅读了不同的意见和不同的方法,我感到很困惑。

问题如下:

<{1>} getView() Adapter我需要执行一些异步操作,比如检查网络上的格式,然后根据它更新视图。

我使用了以下方法:

每次调用getView()时,我都会开始Thread

但我的做法为我赢得了很多批评:

https://stackoverflow.com/a/28484345/1815311

https://stackoverflow.com/a/28484335/1815311

https://stackoverflow.com/a/28484351/1815311

public View getView(int position, View convertView, ViewGroup parent) { 
    ViewHolder holder;            
    if (convertView == null) {                
        //...         
    } 
    else {                
        //...
    }     

    Thread th= new Thread(new Runnable() {

           @Override
           public void run() {
                mActivity.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {

                        CheckSomeInfoOverTheInternet(url, new myCallback {


                            @Override
                            public void onSuccess() {
                            holder.textview.setText("OK");
                            }

                            @Override
                            public void onFailre() {
                            holder.textview.setText("NOT OK!!!!");
                            }    

                        });
                    }
                });


           }
       });

    th.start();

    return convertView;
}  

请做出这样的事情的最佳做法是什么?


请注意,我不是在寻找在getView()中执行网络请求的解决方案,而是如何根据异步调用的结果更新视图。

7 个答案:

答案 0 :(得分:5)

这绝对不是更新ListView中信息的好方法。 getView方法应该只根据您已有的数据创建视图。当然不应该运行任何东西来获取更多信息。

我能给你的最好建议是事先获取数据。拉取数据,更新ArrayList所连接的Adapter,然后拨打adapter.notifyDataSetChanged()。这将重绘您的所有信息。

一次性拉出数据 - 不是小部件。这是执行此操作的最佳和最合理的方法。

答案 1 :(得分:4)

修改

  

我认为这是一个有趣的问题,值得某种“规范”的解决方案

Google I/O 2013 :P我建议你从2013年开始观看这个Google I / O.他们已经清楚地解释了很多这些东西。你的所有问题都将在那里得到解答。这是佳能。

我在这里使用过Volley库。如果您阅读文档,那么您将看到Volley在后台线程上运行。因此无需实现异步任务。由于其他人已经涵盖了使用线程的问题,我不会谈论这些问题。让我直接进入代码:)

每当我的列表视图或网格视图或任何其他视图依赖于来自网络的信息时,以下内容对我有用:

  1. 创建一个界面:WebInfoUpdateReceiver.java

    public interface WebInfoUpdateReceiver {
    
        public void receive(Foo [] fooItems);
    
    }
    
  2. 创建一个下载内容的类:Downloader.java

    public class Downloader {
        private Context mContext;
        private WebInfoUpdateReceiver mReceiver;
    
        public Downloader(WebInfoUpdateReceiver receiver) {
           mReceiver = receiver;
        }
    
        public void downloadStuff() {
        MyStringRequest request = new MyStringRequest(Request.Method.GET,requestUrl, new Response.Listener<String>() {
        @Override
        public void onResponse(String response) {
           // parse the response into Foo[]
    
            mReceiver.update(foo);
                }
            }
        }
    }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
    
        }
    });
    RequestQueue queue = Volley.newRequestQueue(mContext);
    queue.add(request);
        }
    
    }
    
  3. 现在让您的活动实现界面:

    public class Activity extends Activity implements WebInfoUpdateReceiver {
    
    public void receive(Foo [] fooItems) {
         // convert the array  into arrayList
        adapter.insert(arraylist);
        adapter.notifyDataSetChanged();
    }
      }
    

答案 2 :(得分:2)

这有几个appraoches。 虽然你所做的事情真的不合适。

  1. AsyncTask

    • 此处的线程池是在内部完成的,因此您无需为此烦恼
    • 它可以更清晰地解决您的问题,而不是产生单个线程。
    • 如果您的用户在API通话期间更改了屏幕,您还可以cancel the call
    • 您必须启用notifyDatasetChanged()
    • 您需要覆盖很少的功能才能实现所需的功能。
  2. <强> AsyncTaskLoader

    • 它为您提供了更多控制权,但您失去了几个隐含定义的功能
    • 您需要更多知识才能使用此功能,并且应该精通LoaderManagerLoader等课程。
    • 改变是自我激励 假设您要更改基础数据集,更改将自动触发并为您的UI提供更改。
  3. Handlers和主题

    • 这是当前appraoch之上的一个,但提供了更多的好处
    • 您可以抽象线程创建并提供处理所有ID的调用的处理程序。
    • 您可以对线程和传递的消息进行排队。
    • 如果屏幕发生变化,您可以remove callbacks and messages
  4. 总之,您当前方法的主要缺点是:      - 需要进行更改时丢失上下文。      - 显式创建多个线程

    虽然后者是一个主要问题,但第一个问题会出现“用户更明显”的问题。

    基于您需要的控件以及您在Android回调和线程管理方面的专业知识,可能会有其他几种方法。但这三种方法(根据我而言)最合适。

    PS :所有这些方法的共同点是,

    public View getView(int position, View convertView, ViewGroup     parent) { 
        ViewHolder holder;            
        if (convertView == null) {                
            //...         
        } 
        else {                
            //...
        }     
    
        //execute a task for your given id where task could be:
        //1. AsyncTask
        //2. AsyncTaskLoader
        //3. Handlers and thread
        //call notifyDataSetChanged() in all the cases,
    
    
    
    
        return convertView;
    }
    
     @Override
     public void notifyDataSetChanged() {
            super.notifyDataSetChanged();
            //do any tasks which you feel are required
     } 
    

    PPS :您还可以查看DataSetObserver以再次自动化您的要求。

答案 3 :(得分:1)

您可以使用以下内容:

   public View getView(int position, View convertView,
        ViewGroup parent) {
    ViewHolder holder;

    ...

    holder.position = position;

    new ThumbnailTask(position, holder)
            .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null);

    return convertView;
}

private static class ThumbnailTask extends AsyncTask {
    private int mPosition;
    private ViewHolder mHolder;

    public ThumbnailTask(int position, ViewHolder holder) {
        mPosition = position;
        mHolder = holder;
    }

    @Override
    protected Cursor doInBackground(Void... arg0) {
        // Download bitmap here
    }

    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (mHolder.position == mPosition) {
            mHolder.thumbnail.setImageBitmap(bitmap);
        }
    }
}

private static class ViewHolder {
    public ImageView thumbnail;
    public int position;
}

此外,为了获得更好的性能,您需要向ListView适配器添加交互感知,以便在ListView上的一个fling手势之后,它不会触发每行的任何异步操作 - 这意味着滚动速度太快,甚至无法启动任何异步操作。一旦滚动停止或即将停止,就是当你想要开始实际显示每行的重量级内容时。

可以在这里找到一个很好的例子:https://code.google.com/p/shelves/

答案 4 :(得分:1)

Volley图书馆似乎已经抽出了一些常用的图片......

为了更好地控制网络请求和缓存,您可以查看:http://developer.android.com/training/volley/simple.html#send

就像在架构图中一样,在发出请求时,它会查找缓存,如果未命中,则尝试网络请求并将其添加到缓存供以后使用, 此外,您似乎可以根据需要提供自定义退出重试请求,并在需要时处理/使缓存无效。

如需深入参考,您可以查看 - https://android.googlesource.com/platform/frameworks/volley/+/master/src/main/java/com/android/volley

答案 5 :(得分:1)

恕我直言,最佳做法是不在getView()中执行异步操作。您的Adapter类只应将对象转换为视图,您应该尽可能简单。

如果在视图不在屏幕上之后任务完成,或者由于滚动而被适配器回收,则启动异步任务(或线程)引用新创建的View时,可能会遇到麻烦。

您应该考虑在适配器之外执行异步操作。完成操作并在完成后更新列表。您可以在异步操作完成时更新整个列表或某些元素。

操作本身可以在您的活动的AsyncTask中或在专用服务中完成。避免创建新线程,因为创建线程是昂贵的,如果由于某种原因你经常调用它,你可能会耗尽内存。 AsyncTasks使用线程池,因此您不会遇到此问题。

答案 6 :(得分:1)

所以,退后一步,完全抽象地看待这个问题,你问的问题似乎是如何管理一些可能影响列表外观的长期运行行为(网络或其他)查看项目。

鉴于这种假设,你面临两个主要问题:

  1. 线程起步比较昂贵
  2. getView()返回的视图已被回收,可以更改&#34; item&#34;当用户滚动列表时
  3. 问题1可以通过使用ThreadPoolExecutor(或您感觉的其他任何机制)来解决。这个想法是线程也被回收,所以他们没有花太多时间来旋转。

    问题2有点复杂。您可以在视图即将被回收时取消线程操作(有几种方法可以执行此操作)但您需要确定是否可以接受丢失的工作(用户可能会在屏幕上滚动您的视图,而您必须重新开始)。 您可以将长期运行的任务的结果存储在列表项(或与列表项关联的某个数据持有者)中,但是您需要警惕内存不足(最近最少使用的缓存或lru缓存有时在这里很有用) )。这样您就可以继续努力,只有在列表视图仍在屏幕上时才会更新。您的商品数据中的标记可用于表示您已拥有该数据而不会再次加载该数据。

    抱歉,目前我还没有足够的时间详细介绍。我的就寝时间: - )

    祝你好运。如果我有时间的话,我会写些更多的东西。 亲切的问候, CJ