2017-08-06 2 views
2

J'ai lu le documentation sur la façon de rendre une activité de requête réseau indépendante en créant une classe singleton et en lui passant le contexte d'application. Je l'ai implémenté de manière similaire, mais je trouve toujours que l'application attend à nouveau la fin de l'appel avant d'afficher des données. Alors qu'est-ce que je fais mal et comment le configurer correctement de sorte que l'appel dure la vie de l'application de sorte qu'il n'appelle pas chaque fois sur le changement d'orientation selon la documentation. Je sais que cela peut être fait en utilisant des chargeurs ou des rénovations ou okhttp mais je veux savoir comment y parvenir à l'aide volleyComment rendre indépendante une activité Volley Request dans android?

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

Répondre

1

Eh bien, quelques points:

Vous faites une demande à chaque fois que votre acti vity est recréé, en onCreate. En théorie, ce n'est pas nécessairement mauvais si vous avez vraiment besoin d'actualiser vos données à chaque fois que l'activité est ouverte, car il semble que Volley définit automatiquement DiskBasedCache pour lui-même lors de la création RequestQueue avec newRequestQueue (voir here). Cela signifie que même si vous faites une nouvelle demande après chaque changement d'orientation, Volley vous cherchera une réponse mise en cache, au lieu de frapper le réseau. En activant la journalisation détaillée, vous devriez voir quand Volley utilise le réseau ou le cache pour répondre à une requête.

Pour activer la journalisation détaillée, voir ici: https://stackoverflow.com/a/23654407/2220337

Cependant, le cache par défaut est toujours juste un cache Disk, ce qui est plus lent que d'un cache en mémoire. Si vous souhaitez que votre cache soit plus rapide, vous pouvez implémenter votre propre cache en mémoire en implémentant l'interface Cache, puis en créant votre RequestQueue comme décrit here et en fournissant votre propre cache en mémoire personnalisé dans le constructeur.

Un moyen encore meilleur serait de ne pas faire de requête du tout après un changement d'orientation, et de s'appuyer sur onSaveInstanceState/onRestoreInstanceState pour persister et restaurer vos données. Ainsi, si une requête est déjà terminée, vous n'en lancerez pas une nouvelle lorsque l'activité sera recréée.

Au lieu de cela, il vous suffit d'afficher les données enregistrées dans 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); 
} 

S'il vous plaît également garder à l'esprit en ce qui concerne les points suivants:

  • Si vous commencez une demande et un changement d'orientation se produit avant de terminer, vous aurez des fuites de mémoire, et votre activité ne sera pas ordures collectées. En effet, votre stringRequest est une instance de classe interne anonyme qui référencera implicitement MainActivity. Pour contourner cela, j'ai eu de bons résultats dans le passé, mes réponses Volley demandes & étant gérés par un service Android, avec les réponses transmises à l'interface utilisateur via des diffusions collantes. Un bus d'événement fonctionne également à cet effet.Les diffusions devaient être collantes afin de ne pas perdre de réponse si elles se terminaient pendant que l'activité était en cours de recréation, puisqu'en étant recréée, elle n'était pas enregistrée en tant que récepteur de diffusion. En envoyant des émissions persistantes, elles persisteraient et me permettraient de lire les données après qu'Android ait fini de recréer mon activité.

  • La deuxième approche que j'ai mentionnée devrait être bonne si votre chaîne de réponse est un JSON pas très grand qui pointe vers certaines images en ligne qui seront téléchargées plus tard. Cependant, s'il contient des images enchaînées BASE64 à la place, alors le DiskBasedCache par défaut de Volley est peut-être plus approprié pour la mise en cache de ses données.

Espérons que cela aide!

+0

Donc, juste une question, donc cela conduira également à une fuite de mémoire sur le changement d'orientation, alors pourquoi voudrait-on une classe séparée singleton pour volley? Ou quel est son but au lieu de simplement avoir quelque chose [comme ceci] (https://developer.android.com/training/volley/simple.html) – Nobody

+0

juste pour clarifier, avec "** ceci ** conduira également à une mémoire fuite .. ", est-ce que ** ceci ** fait référence au fragment de code de ma réponse, ou à l'approche avec le service, mentionné après le fragment de code? – cjurjiu

+0

Non, je voulais dire l'approche [singleton] (https://developer.android.com/training/volley/requestqueue.html) mentionnée dans la documentation par rapport à l'approche normale mentionnée dans [l'ancienne page] (https: // developer. android.com/training/volley/simple.html) de la documentation. Je veux juste savoir de quelle façon avoir une approche de classe singleton est meilleure par rapport à celle-ci. – Nobody