如何使Android中的Volley Request活动独立?

时间:2017-08-06 20:01:55

标签: android networking singleton android-volley

我通过创建单例类并将应用程序上下文传递给它来阅读documentation关于使网络请求活动独立的问题。我类似地实现了它,但是我仍然发现在轮换时应用程序在显示任何数据之前等待再次完成调用。那么我做错了什么以及如何正确设置它以便调用持续应用程序的生命周期,以便它不会每次按照文档调用方向更改时调用。我知道它可以使用加载器或改造或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;
    }
}

1 个答案:

答案 0 :(得分:1)

嗯,有几点:

每次重新创建活动时,您都会在onCreate中提出请求。从理论上讲,如果确实需要在打开活动时刷新数据,这并不一定是坏事,因为看起来Volley在创建时自动为自己设置DiskBasedCache RequestQueue newRequestQueue Disk(见here)。

这意味着即使您在每次更改方向后都要发出新请求,Volley也会向您提取缓存响应,而不是点击网络。通过启用详细日志记录,您应该看到Volley何时使用网络或缓存来提供请求。

要启用详细日志记录,请参阅此处:https://stackoverflow.com/a/23654407/2220337

但是,默认缓存仍然只是一个RequestQueue缓存,它比内存缓存慢。如果您需要更快地缓存,可以通过实施Cache界面,然后按照here所述创建onSaveInstanceState并提供自己的内容来实现自己的内存缓存构造函数中的自定义内存缓存。

更好的方法是在方向更改后根本不提出请求,而是依靠onRestoreInstanceState / 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); } 中保存的数据。

stringRequest

请注意以下几点:

  • 如果您启动请求并在更改之前发生方向更改,则会出现内存泄漏,并且您的活动不会被垃圾回收。这是因为您的<?php //index.php function load_state() { $connect = mysqli_connect("localhost", "root", "","state_city"); $output = ''; $sql = "SELECT * FROM state ORDER BY state_name"; $result = mysqli_query($connect, $sql); while($row = mysqli_fetch_array($result)) { $output .= '<option value="'.$row["state_id"].'">'.$row["state_name"].'</option>'; } return $output; } ?> <!DOCTYPE html> <html> <head> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" /> <!--<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>--> </head> <body> <br /><br /> <div class="container" style="width:600px;"> <h2 align="center">Dependent Drop-Down using Ajax - for Testing</h2><br /><br /> <select name="state" id="state" class="form-control action"> <option value="">Select State</option> <?php echo load_state(); ?> </select> <br /> <select name="city" id="city" class="form-control"> <option value="">Select City</option> </select> </div> </body> </html> <script> $(document).ready(function(){ $('#state').change(function(){ var state_id = $(this).val(); $.ajax({ url:"fetch.php", method:"POST", data:{stateId:state_id}, dataType:"text", success:function(data) { $('#city').html(data); } }); }); }); </script> 是一个匿名的内部类实例,它将隐式引用MainActivity。

    为了避免这种情况,过去我的排球要求和我有很好的结果。响应由Android服务管理,响应通过粘性广播转发到UI。事件总线也可用于此目的。

    广播必须是粘性的,以便在重新创建活动时完成后不会丢失响应,因为在重新创建时它没有被注册为广播接收器。通过发送粘性广播,它们会持续存在并允许我在Android完成重新创建活动后读取数据。

  • 我提到的第二种方法应该没问题,如果您的响应字符串是一个不太大的JSON,它指向稍后将下载的一些在线图像。但是,如果它包含BASE64 enconded图像,那么Volley的默认DiskBasedCache可能更适合缓存其数据。

希望这有帮助!