如何在Android中使Volley Request活动独立? [英] How to make a Volley Request activity independent in android?

查看:48
本文介绍了如何在Android中使Volley Request活动独立?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我阅读了文档,该文档通过以下方式使网络请求活动独立单例类,并将应用程序上下文传递给它.我以类似的方式实现了它,但是我仍然发现在旋转时,应用程序会再次等待调用完成,然后再显示任何数据.因此,我在做什么错了,以及如何正确设置它,以便该调用可以持续应用程序的生命周期,从而使每次文档说明中的方向更改时都不会调用它.我知道可以使用装载机,改造或okhttp来完成,但是我想知道如何使用凌空抽空来实现它

MainActivity.java

package com.example.imnobody.photosearch;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private ImageGridAdapter imageGridAdapter;
    private List<String> imageList;

    public static final String URL = "API_HERE";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        imageList = new ArrayList<>();
        //imageList = QueryUtils.extractImages(SAMPLE_JSON_RESPONSE);


        GridView gridView = (GridView) findViewById(R.id.gridview);
        final TextView emptyTextView = (TextView)findViewById(android.R.id.empty);
        gridView.setEmptyView(emptyTextView);

        imageGridAdapter = new ImageGridAdapter(MainActivity.this,imageList);

        gridView.setAdapter(imageGridAdapter);

        gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {

                Intent intent = new Intent(MainActivity.this,ImageActivity.class);
                intent.putExtra("imageuri",imageList.get(position));
                startActivity(intent);

            }
        });

        StringRequest stringRequest = new StringRequest(Request.Method.GET, URL,
                new Response.Listener<String>() {
                    @Override
                    public void onResponse(String response) {

                        imageList = QueryUtils.extractImages(response); //extract needed things from json
                        imageGridAdapter.clear();
                        imageGridAdapter.addAll(imageList);

                    }
                }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {

                emptyTextView.setText("Unknown error occured");
            }
        });

        VolleySingleton.getInstance(this.getApplicationContext()).addToRequestQueue(stringRequest);


    }
}

VolleySingleton.java

package com.example.imnobody.photosearch;

import android.content.Context;
import android.graphics.Bitmap;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.Volley;

import android.support.v4.util.LruCache;

/**
 * Created by imnobody on 7/8/17.
 */

public class VolleySingleton {

    private static VolleySingleton mInstance;
    private RequestQueue mRequestQueue;
    private ImageLoader mImageLoader;
    private static Context mCtx;

    private VolleySingleton(Context context) {
        mCtx = context;
        mRequestQueue = getRequestQueue();

        mImageLoader = new ImageLoader(mRequestQueue,
                new ImageLoader.ImageCache() {
                    private final LruCache<String, Bitmap>
                            cache = new LruCache<String, Bitmap>(20);

                    @Override
                    public Bitmap getBitmap(String url) {
                        return cache.get(url);
                    }

                    @Override
                    public void putBitmap(String url, Bitmap bitmap) {
                        cache.put(url, bitmap);
                    }
                });
    }

    public static synchronized VolleySingleton getInstance(Context context) {
        if (mInstance == null) {
            mInstance = new VolleySingleton(context);
        }
        return mInstance;
    }

    public RequestQueue getRequestQueue() {
        if (mRequestQueue == null) {

            mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext());
        }
        return mRequestQueue;
    }

    public <T> void addToRequestQueue(Request<T> req) {
        getRequestQueue().add(req);
    }

    public ImageLoader getImageLoader() {
        return mImageLoader;
    }
}

解决方案

好几点:

每次重新创建活动时,您都会在onCreate中发出请求.从理论上讲,如果您在活动打开时确实真的需要刷新数据,这并不一定不好,因为Volley似乎在创建带有RequestQueue的内容时自动为自己设置了DiskBasedCache. newRequestQueue(请参见 https://stackoverflow.com/a/23654407/2220337

但是,默认缓存仍然只是Disk缓存,它比内存中的缓存慢.如果需要更快的缓存,可以通过实现stringRequest是一个匿名内部类实例,该实例将隐式引用MainActivity.

为避免这种情况,我在过去的Volley请求中取得了不错的成绩,响应由Android服务管理,并且响应通过粘性广播转发到UI.事件总线也可以用于此目的.

广播必须保持粘性,以便在重新创建活动时完成响应后不会丢失响应,因为在重新创建活动时未将其注册为广播接收器.通过发送粘性广播,它们可以保留并允许我在Android完成重新创建我的Activity后读取数据.

  • 如果您的响应字符串不是很大的JSON(指向一些稍后将下载的在线图像),则我提到的第二种方法应该很好.但是,如果它包含BASE64编码映像,则Volley的默认DiskBasedCache可能更适合于缓存其数据.

  • 希望这会有所帮助!

    I read the documentation about making an network request activity independent by making a singleton class and passing the application context to it. I implemented it similarly, however I still find that on rotation the app waits for the call again to complete before displaying any data. So what am I doing wrong and how to set it up properly so that the call lasts the lifetime of the application so that it doesn't call every time on orientation change as per the documentation. I know it can be done using loaders or retrofit or okhttp but I wanna know how to achieve it using volley

    MainActivity.java

    package com.example.imnobody.photosearch;
    
    import android.content.Intent;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.view.View;
    import android.widget.AdapterView;
    import android.widget.GridView;
    import android.widget.ImageView;
    import android.widget.TextView;
    import android.widget.Toast;
    
    import com.android.volley.Request;
    import com.android.volley.RequestQueue;
    import com.android.volley.Response;
    import com.android.volley.VolleyError;
    import com.android.volley.toolbox.StringRequest;
    import com.android.volley.toolbox.Volley;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class MainActivity extends AppCompatActivity {
    
        private ImageGridAdapter imageGridAdapter;
        private List<String> imageList;
    
        public static final String URL = "API_HERE";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            imageList = new ArrayList<>();
            //imageList = QueryUtils.extractImages(SAMPLE_JSON_RESPONSE);
    
    
            GridView gridView = (GridView) findViewById(R.id.gridview);
            final TextView emptyTextView = (TextView)findViewById(android.R.id.empty);
            gridView.setEmptyView(emptyTextView);
    
            imageGridAdapter = new ImageGridAdapter(MainActivity.this,imageList);
    
            gridView.setAdapter(imageGridAdapter);
    
            gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
    
                    Intent intent = new Intent(MainActivity.this,ImageActivity.class);
                    intent.putExtra("imageuri",imageList.get(position));
                    startActivity(intent);
    
                }
            });
    
            StringRequest stringRequest = new StringRequest(Request.Method.GET, URL,
                    new Response.Listener<String>() {
                        @Override
                        public void onResponse(String response) {
    
                            imageList = QueryUtils.extractImages(response); //extract needed things from json
                            imageGridAdapter.clear();
                            imageGridAdapter.addAll(imageList);
    
                        }
                    }, new Response.ErrorListener() {
                @Override
                public void onErrorResponse(VolleyError error) {
    
                    emptyTextView.setText("Unknown error occured");
                }
            });
    
            VolleySingleton.getInstance(this.getApplicationContext()).addToRequestQueue(stringRequest);
    
    
        }
    }
    

    VolleySingleton.java

    package com.example.imnobody.photosearch;
    
    import android.content.Context;
    import android.graphics.Bitmap;
    
    import com.android.volley.Request;
    import com.android.volley.RequestQueue;
    import com.android.volley.toolbox.ImageLoader;
    import com.android.volley.toolbox.Volley;
    
    import android.support.v4.util.LruCache;
    
    /**
     * Created by imnobody on 7/8/17.
     */
    
    public class VolleySingleton {
    
        private static VolleySingleton mInstance;
        private RequestQueue mRequestQueue;
        private ImageLoader mImageLoader;
        private static Context mCtx;
    
        private VolleySingleton(Context context) {
            mCtx = context;
            mRequestQueue = getRequestQueue();
    
            mImageLoader = new ImageLoader(mRequestQueue,
                    new ImageLoader.ImageCache() {
                        private final LruCache<String, Bitmap>
                                cache = new LruCache<String, Bitmap>(20);
    
                        @Override
                        public Bitmap getBitmap(String url) {
                            return cache.get(url);
                        }
    
                        @Override
                        public void putBitmap(String url, Bitmap bitmap) {
                            cache.put(url, bitmap);
                        }
                    });
        }
    
        public static synchronized VolleySingleton getInstance(Context context) {
            if (mInstance == null) {
                mInstance = new VolleySingleton(context);
            }
            return mInstance;
        }
    
        public RequestQueue getRequestQueue() {
            if (mRequestQueue == null) {
    
                mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext());
            }
            return mRequestQueue;
        }
    
        public <T> void addToRequestQueue(Request<T> req) {
            getRequestQueue().add(req);
        }
    
        public ImageLoader getImageLoader() {
            return mImageLoader;
        }
    }
    

    解决方案

    Well, a couple of points:

    You're making a request every time your activity is recreated, in onCreate. In theory, this isn't necessarily bad if you really need to refresh your data whenever the activity is opened, since it seems that Volley automatically sets a DiskBasedCache for itself when creating a RequestQueue with newRequestQueue (see here).

    This means that even though you're making a new request after each orientation change, Volley would fetch you a cached response, instead of hitting the network. By enabling verbose logging you should see when Volley uses the network or the cache to serve a request.

    To enable verbose logging, see here: https://stackoverflow.com/a/23654407/2220337

    However, the default cache is still just a Disk cache, which is slower than an in-memory cache. If you need your cache to be faster, you can implement your own in-memory cache by implementing the Cache interface, and then by creating your RequestQueue as described here, and providing your own custom, in-memory cache in the constructor.

    An even better way would be to not make a request at all after an orientation change, and instead rely on onSaveInstanceState/onRestoreInstanceState to persist and then restore your data. This way, if a request was already completed, you won't fire a new one when the activity gets recreated.

    Instead, you just display the data you saved in onSaveInstanceState.

    public class MainActivity extends AppCompatActivity {
    
    public static final String URL = "API_HERE";
    private static final String SAVED_RESPONSE = "SAVED_RESPONSE";
    private ImageGridAdapter imageGridAdapter;
    private List<String> imageList;
    private GridView gridView;
    private String savedResponse;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        imageList = new ArrayList<>();
        gridView = (GridView) findViewById(R.id.gridview);
        final TextView emptyTextView = (TextView) findViewById(android.R.id.empty);
        gridView.setEmptyView(emptyTextView);
        imageGridAdapter = new ImageGridAdapter(MainActivity.this, imageList);
        gridView.setAdapter(imageGridAdapter);
    
        gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
    
                Intent intent = new Intent(MainActivity.this, ImageActivity.class);
                intent.putExtra("imageuri", imageList.get(position));
                startActivity(intent);
            }
        });
    
        if (savedInstanceState == null) {
            //activity was created for the first time, fetch images
            getImages();
        } else {
            //everything in this else branch can be moved in onRestoreInstanceState, this is just a matter of preference
            savedResponse = savedInstanceState.getString(SAVED_RESPONSE);
            if (savedResponse != null) {
                refreshImages(savedResponse);
            } else {
                //an error occurred when the request was fired previously
                ((TextView) gridView.getEmptyView()).setText("Unknown error occured");
            }
        }
    }
    
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString(SAVED_RESPONSE, savedResponse);
    }
    
    private void getImages() {
        StringRequest stringRequest = new StringRequest(Request.Method.GET, URL,
                new Response.Listener<String>() {
                    @Override
                    public void onResponse(String response) {
                        savedResponse = response;
                        refreshImages(response);
                    }
                }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                savedResponse = null;
                ((TextView) gridView.getEmptyView()).setText("Unknown error occured");
            }
        });
    
        VolleySingleton.getInstance(this.getApplicationContext()).addToRequestQueue(stringRequest);
    }
    
    private void refreshImages(String response) {
        imageList = QueryUtils.extractImages(response); //extract needed things from json
        imageGridAdapter.clear();
        imageGridAdapter.addAll(imageList);
    }
    

    Please also be mindful regarding the following points:

    • If you start a request and an orientation change occurs before it completes, you will have memory leaks, and your Activity won't be garbage collected. This is because your stringRequest is an anonymous inner class instance that will reference MainActivity implicitly.

      To circumvent this, I had good results in the past having my Volley requests & responses being managed by an Android service, with the responses being forwarded to the UI through sticky broadcasts. An event bus also works for this purpose.

      The broadcasts needed to be sticky in order to not lose a response if it completed while the activity was being recreated, since while being recreated it wasn't registered as a broadcast receiver. By sending sticky broadcasts though, they would persist and allow me to read the data after Android has finished recreating my Activity.

    • The second approach I mentioned should be fine if your response string is a not-very-large JSON which points to some online images that will later be downloaded. However, if it contains BASE64 enconded images instead, then maybe the default DiskBasedCache from Volley is more appropriate for caching its data.

    Hope this helps!

    这篇关于如何在Android中使Volley Request活动独立?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆