How to make a Volley Request activity independent in android?

血红的双手。 提交于 2019-12-12 22:18:48

问题


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;
    }
}

回答1:


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!



来源:https://stackoverflow.com/questions/45536102/how-to-make-a-volley-request-activity-independent-in-android

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!