分类: Android技术

Android技术

Volley — 自定义Request

Volley中提供了几个Request,如果我们有特殊的需求,完全可以自定义Request的,自定义Request自然要继承Request,那么本篇就教大家来一步一步地定义一个自己的Request类。

 

一、继承Request

如果我们的request的对象不是string,也不是JsonObject,而是一个奇怪的对象呢?我这里建立了一个类,叫做:Kale,然后定义了一个CustomReqeust去继承Reqeust,得到如下的代码。

复制代码
复制代码
复制代码
package com.kale.volleytest;

import com.android.volley.NetworkResponse;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.Response.ErrorListener;
import com.android.volley.Response.Listener;

public class CustomReqeust extends Request<Kale>{
    
    public CustomReqeust(int method, String url, ErrorListener listener) {
        super(method, url, listener);
    }

    @Override
    protected Response<Kale> parseNetworkResponse(NetworkResponse response) {
        // TODO 自动生成的方法存根
        return null;
    }

    @Override
    protected void deliverResponse(Kale response) {
        // TODO 自动生成的方法存根
        
    }

}
复制代码
复制代码
复制代码

分析:

public CustomReqeust(int method, String url, ErrorListener listener)

构造函数中调用了父类的方法,初始化了当前对象。传入三个参数:①请求方式,即POST/GET,②请求的URL,③出错时的回调监听器

 

protected Response<Kale> parseNetworkResponse(NetworkResponse response)

解析网络响应的结果,从NetworkResponse的代码中我们就可以知道它里面有什么东西了。

复制代码
复制代码
复制代码
/**
 * Data and headers returned from {@link Network#performRequest(Request)}.
 */
public class NetworkResponse {
    /**
     * Creates a new network response.
     * @param statusCode the HTTP status code
     * @param data Response body
     * @param headers Headers returned with this response, or null for none
     * @param notModified True if the server returned a 304 and the data was already in cache
     * @param networkTimeMs Round-trip network time to receive network response
     */
    public NetworkResponse(int statusCode, byte[] data, Map<String, String> headers,
            boolean notModified, long networkTimeMs)
复制代码
复制代码
复制代码

响应码啊,请求头什么的,**主要的就是这个比特数组的data,响应的结果就在里面。我们可以自由的进行处理了~

 

protected void deliverResponse(Kale response)

分发响应的结果,我们可以通过将这个response放到监听器里来获取响应结果。

 

二、分析StringRequest

我们现在已经对request的子类有了基本的认识,现在就来看看StringRequest的源码吧,别担心,很短!

复制代码
复制代码
复制代码
package com.android.volley.toolbox;public class StringRequest extends Request<String> {
    // 建立监听器来获得响应成功时返回的结果
    private final Listener<String> mListener; 


    // 传入请求方法,url,成功时的监听器,失败时的监听器
    public StringRequest(int method, String url, Listener<String> listener,
            ErrorListener errorListener) {
        super(method, url, errorListener);
        // 初始化成功时的监听器
        mListener = listener;
    }

    /**
     * Creates a new GET request.
     * 建立一个默认的GET请求,调用了上面的构造函数
     */
    public StringRequest(String url, Listener<String> listener, ErrorListener errorListener) {
        this(Method.GET, url, listener, errorListener);
    }

    @Override
    protected void deliverResponse(String response) {
        // 用监听器的方法来传递下响应的结果
        mListener.onResponse(response);
    }

    @Override
    protected Response<String> parseNetworkResponse(NetworkResponse response) {
        String parsed;
        try {
            // 调用了new String(byte[] data, String charsetName) 这个构造函数来构建String对象,
            // 将byte数组按照特定的编码方式转换为String对象
            // 主要部分是data
            parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
        } catch (UnsupportedEncodingException e) {
            parsed = new String(response.data);
        }
        return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
    }
}
复制代码
复制代码
复制代码

分析完代码我们应该就能知道改如何自定义Request了,其实没啥高深的东西。

 

三、自定义XMLRequest

代码来自:http://blog.csdn.net/guolin_blog/article/details/17612763

复制代码
复制代码
复制代码
public class XMLRequest extends Request<XmlPullParser> {  
  
    private final Listener<XmlPullParser> mListener;  
  
    public XMLRequest(int method, String url, Listener<XmlPullParser> listener,  
            ErrorListener errorListener) {  
        super(method, url, errorListener);  
        mListener = listener;  
    }  
  
    public XMLRequest(String url, Listener<XmlPullParser> listener, ErrorListener errorListener) {  
        this(Method.GET, url, listener, errorListener);  
    }  
  
    @Override  
    protected Response<XmlPullParser> parseNetworkResponse(NetworkResponse response) {  
        try {  
            String xmlString = new String(response.data,  
                    HttpHeaderParser.parseCharset(response.headers));  
            XmlPullParserFactory factory = XmlPullParserFactory.newInstance();  
            XmlPullParser xmlPullParser = factory.newPullParser();  
            xmlPullParser.setInput(new StringReader(xmlString));  
            return Response.success(xmlPullParser, HttpHeaderParser.parseCacheHeaders(response));  
        } catch (UnsupportedEncodingException e) {  
            return Response.error(new ParseError(e));  
        } catch (XmlPullParserException e) {  
            return Response.error(new ParseError(e));  
        }  
    }  
  
    @Override  
    protected void deliverResponse(XmlPullParser response) {  
        mListener.onResponse(response);  
    }  
  
}
复制代码
复制代码
复制代码

这里用到了XmlPull的知识,如果不是很了解,可以去这篇文章看看:http://www.cnblogs.com/tianzhijiexian/p/4020250.html

测试代码:

复制代码
复制代码
复制代码
XMLRequest xmlRequest = new XMLRequest(  
                "http://flash.weather.com.cn/wmaps/xml/china.xml",  
                new Response.Listener<XmlPullParser>() {  
                    @Override  
                    public void onResponse(XmlPullParser response) {  
                        try {  
                            int eventType = response.getEventType();  
                            while (eventType != XmlPullParser.END_DOCUMENT) {  
                                switch (eventType) {  
                                case XmlPullParser.START_TAG:  
                                    String nodeName = response.getName();  
                                    if ("city".equals(nodeName)) {  
                                        String pName = response.getAttributeValue(0);  
                                        Log.d("TAG", "pName is " + pName);  
                                    }  
                                    break;  
                                }  
                                eventType = response.next();  
                            }  
                        } catch (XmlPullParserException e) {  
                            e.printStackTrace();  
                        } catch (IOException e) {  
                            e.printStackTrace();  
                        }  
                    }  
                }, new Response.ErrorListener() {  
                    @Override  
                    public void onErrorResponse(VolleyError error) {  
                        Log.e("TAG", error.getMessage(), error);  
                    }  
                });  
        mQueue.add(xmlRequest);
复制代码
复制代码
复制代码

结果:

%title插图%num

 

四、自定义GsonRequest

代码来自:http://blog.csdn.net/guolin_blog/article/details/17612763

复制代码
复制代码
复制代码
public class GsonRequest<T> extends Request<T> {

    private final Listener<T> mListener;

    private Gson mGson;

    private Class<T> mClass;

    public GsonRequest(int method, String url, Class<T> clazz, Listener<T> listener,
            ErrorListener errorListener) {
        super(method, url, errorListener);
        mGson = new Gson();
        mClass = clazz;
        mListener = listener;
    }

    public GsonRequest(String url, Class<T> clazz, Listener<T> listener,
            ErrorListener errorListener) {
        this(Method.GET, url, clazz, listener, errorListener);
    }

    @Override
    protected Response<T> parseNetworkResponse(NetworkResponse response) {
        try {
            String jsonString = new String(response.data,
                    HttpHeaderParser.parseCharset(response.headers));
            return Response.success(mGson.fromJson(jsonString, mClass),
                    HttpHeaderParser.parseCacheHeaders(response));
        } catch (UnsupportedEncodingException e) {
            return Response.error(new ParseError(e));
        }
    }

    @Override
    protected void deliverResponse(T response) {
        mListener.onResponse(response);
    }

}
复制代码
复制代码
复制代码

代码十分简单,先是将服务器响应的数据解析出来,然后通过调用Gson的fromJson方法将数据组装成对象。在deliverResponse方法中仍然是将*终的数据进行回调。

用法:

建立一个对象类,比如这里的weather、WeatherInfo类,然后初始化GsonRequest对象,*后把GsonRequest对象添加到队列中。

Weather:

 

复制代码
public class Weather {

    private WeatherInfo weatherinfo;

    public WeatherInfo getWeatherinfo() {
        return weatherinfo;
    }

    public void setWeatherinfo(WeatherInfo weatherinfo) {
        this.weatherinfo = weatherinfo;
    }

}
复制代码

 

 

 

WeatherInfo:

 

复制代码
public class WeatherInfo {

    private String city;

    private String temp;

    private String time;

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getTemp() {
        return temp;
    }

    public void setTemp(String temp) {
        this.temp = temp;
    }

    public String getTime() {
        return time;
    }

    public void setTime(String time) {
        this.time = time;
    }

}
复制代码

 

 

 

 

Java测试代码:

复制代码
复制代码
复制代码
GsonRequest<Weather> gsonRequest = new GsonRequest<Weather>(  
        "http://www.weather.com.cn/data/sk/101010100.html", Weather.class,  
        new Response.Listener<Weather>() {  
            @Override  
            public void onResponse(Weather weather) {  
                WeatherInfo weatherInfo = weather.getWeatherinfo();  
                Log.d("TAG", "city is " + weatherInfo.getCity());  
                Log.d("TAG", "temp is " + weatherInfo.getTemp());  
                Log.d("TAG", "time is " + weatherInfo.getTime());  
            }  
        }, new Response.ErrorListener() {  
            @Override  
            public void onErrorResponse(VolleyError error) {  
                Log.e("TAG", error.getMessage(), error);  
            }  
        });  
mQueue.add(gsonRequest);
复制代码
复制代码
复制代码

 

五、重写getBody()方法来添加post参数

我们可以在JsonRequest类中发现如下代码:

复制代码
复制代码
复制代码
/** 
  * Returns the raw POST or PUT body to be sent. 
  * 
  * @throws AuthFailureError in the event of auth failure 
  */  
 @Override  
 public byte[] getBody() {  
     try {  
         return mRequestBody == null ? null : mRequestBody.getBytes(PROTOCOL_CHARSET);  
     } catch (UnsupportedEncodingException uee) {  
         VolleyLog.wtf("Unsupported Encoding while trying to get the bytes of %s using %s",  
                 mRequestBody, PROTOCOL_CHARSET);  
         return null;  
     }  
 }
复制代码
复制代码
复制代码

不用看代码,直接看注释,说明这里执行post请求,所以我们可以在这里设置post参数。这里

return mRequestBody == null ? null : mRequestBody.getBytes(PROTOCOL_CHARSET);

返回的就是post参数了。

如果我们想要传递POST数据,可以参考上面的代码,重写Request的getBody()方法,放入自己的参数,举例如下:

复制代码
复制代码
复制代码
   /** http请求编码方式 */  
    private static final String PROTOCOL_CHARSET = "utf-8";  
  
    private String mUserName;
复制代码
复制代码
复制代码
复制代码
复制代码
复制代码
@Override  
    public byte[] getBody() {  
        try {  
            return mUserName == null ? null : mUserName.getBytes(PROTOCOL_CHARSET);  
        } catch (UnsupportedEncodingException uee) {  
            VolleyLog.wtf("Unsupported Encoding while trying to get the bytes of %s using %s", mUserName, PROTOCOL_CHARSET);  
            return null;  
        }  
    }
复制代码
复制代码
复制代码

完整代码(来自:http://blog.csdn.net/ttdevs/article/details/17586205):

 

复制代码
复制代码
public class CustomReqeust extends Request<String> {  
    /** http请求编码方式 */  
    private static final String PROTOCOL_CHARSET = "utf-8";  
  
    private Listener<String> mListener;  
    private String mUserName;  
  
    public CustomReqeust(String url, String userName, Listener<String> listener, ErrorListener errorListener) {  
        super(Method.POST, url, errorListener);  
        mUserName = userName;  
        mListener = listener;  
    }  
  
    @Override  
    protected Response<String> parseNetworkResponse(NetworkResponse response) {  
        String parsed;  
        try {  
            parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));  
        } catch (UnsupportedEncodingException e) {  
            parsed = new String(response.data);  
        }  
        return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));  
    }  
  
    @Override  
    protected void deliverResponse(String response) {  
        mListener.onResponse(response);  
    }  
  
    @Override  
    public byte[] getBody() {  
        try {  
            return mUserName == null ? null : mUserName.getBytes(PROTOCOL_CHARSET);  
        } catch (UnsupportedEncodingException uee) {  
            VolleyLog.wtf("Unsupported Encoding while trying to get the bytes of %s using %s", mUserName, PROTOCOL_CHARSET);  
            return null;  
        }  
    }  
}
复制代码
复制代码

 

 

 

测试代码:

复制代码
复制代码
复制代码
private void customRequest() {  
    CustomReqeust request = new CustomReqeust(URL, "CustomVolley", new Listener<String>() {  
  
        @Override  
        public void onResponse(String arg0) {  
            Toast.makeText(getApplicationContext(), arg0, Toast.LENGTH_LONG).show();  
            Log.d("onResponse", arg0);  
        }  
    }, new ErrorListener() {  
  
        @Override  
        public void onErrorResponse(VolleyError arg0) {  
            Toast.makeText(getApplicationContext(), arg0.toString(), Toast.LENGTH_LONG).show();  
            Log.d("onErrorResponse", arg0.toString());  
        }  
    });  
    mQueue.add(request);  
}
复制代码
复制代码
复制代码

抓包结果:

%title插图%num

Volley — ImageLoader & NetworkImageView

ImageLoader是一个加载网络图片的封装类,其内部还是由ImageRequest来实现的。但因为源码中没有提供磁盘缓存的设置,所以咱们还需要去源码中进行修改,让我们可以更加自如的设定是否进行磁盘缓存。

 

2. ImageLoader的用法

如 果你觉得ImageRequest已经非常好用了,那我只能说你太容易满足了 ^_^。实际上,Volley在请求网络图片方面可以做到的还远远不止这些,而ImageLoader就是一个很好的例子。ImageLoader也可以 用于加载网络上的图片,并且它的内部也是使用ImageRequest来实现的,不过ImageLoader明显要比ImageRequest更加高效, 因为它不仅可以帮我们对图片进行缓存,还可以过滤掉重复的链接,避免重复发送请求。

由于ImageLoader已经不是继承自Request的了,所以它的用法也和我们之前学到的内容有所不同,总结起来大致可以分为以下四步:

1. 创建一个RequestQueue对象。

2. 创建一个ImageLoader对象。

3. 获取一个ImageListener对象。

4. 调用ImageLoader的get()方法加载网络上的图片。

下面我们就来按照这个步骤,学习一下ImageLoader的用法吧。首先*步的创建RequestQueue对象我们已经写过很多遍了,相信已经不用再重复介绍了,那么就从第二步开始学习吧,新建一个ImageLoader对象,代码如下所示:

复制代码
复制代码
    ImageLoader imageLoader = new ImageLoader(mQueue, new ImageCache() {  
        @Override  
        public void putBitmap(String url, Bitmap bitmap) {  
        }  
      
        @Override  
        public Bitmap getBitmap(String url) {  
            return null;  
        }  
    });
复制代码
复制代码

 

 

可以看到,ImageLoader的构造函数接收两个参数,*个参数就是RequestQueue对象,第二个参数是一个ImageCache对象,这里我们先new出一个空的ImageCache的实现即可。

 

接下来需要获取一个ImageListener对象,代码如下所示:

    ImageListener listener = ImageLoader.getImageListener(imageView,  
            R.drawable.default_image, R.drawable.failed_image);

 

 

我 们通过调用ImageLoader的getImageListener()方法能够获取到一个ImageListener对 象,getImageListener()方法接收三个参数,*个参数指定用于显示图片的ImageView控件,第二个参数指定加载图片的过程中显示 的图片,第三个参数指定加载图片失败的情况下显示的图片。

 

*后,调用ImageLoader的get()方法来加载图片,代码如下所示:

    imageLoader.get("https://img-my.csdn.net/uploads/201404/13/1397393290_5765.jpeg", listener);

 

 

get()方法接收两个参数,*个参数就是图片的URL地址,第二个参数则是刚刚获取到的ImageListener对象。当然,如果你想对图片的大小进行限制,也可以使用get()方法的重载,指定图片允许的*大宽度和高度,如下所示:

    imageLoader.get("https://img-my.csdn.net/uploads/201404/13/1397393290_5765.jpeg",  
                    listener, 200, 200);

 

 

 

现在运行一下程序并开始加载图片,你将看到ImageView中会先显示一张默认的图片,等到网络上的图片加载完成后,ImageView则会自动显示该图,效果如下图所示。

虽然现在我们已经掌握了ImageLoader的用法,但是刚才介绍的ImageLoader的优点却还没有使用到。为什么呢?因为这里创建的 ImageCache对象是一个空的实现,完全没能起到图片缓存的作用。其实写一个ImageCache也非常简单,但是如果想要写一个性能非常好的 ImageCache,*好就要借助Android提供的LruCache功能了,如果你对LruCache还不了解,可以参考我之前的一篇文章Android高效加载大图、多图解决方案,有效避免程序OOM

这里我们新建一个BitmapCache并实现了ImageCache接口,如下所示:

复制代码
复制代码
    public class BitmapCache implements ImageCache {  
      
        private LruCache<String, Bitmap> mCache;  
      
        public BitmapCache() {  
            int maxSize = 10 * 1024 * 1024;  
            mCache = new LruCache<String, Bitmap>(maxSize) {  
                @Override  
                protected int sizeOf(String key, Bitmap bitmap) {  
                    return bitmap.getRowBytes() * bitmap.getHeight();  
                }  
            };  
        }  
      
        @Override  
        public Bitmap getBitmap(String url) {  
            return mCache.get(url);  
        }  
      
        @Override  
        public void putBitmap(String url, Bitmap bitmap) {  
            mCache.put(url, bitmap);  
        }  
      
    }
复制代码
复制代码

 

可以看到,这里我们将缓存图片的大小设置为10M。接着修改创建ImageLoader实例的代码,第二个参数传入BitmapCache的实例,如下所示:

ImageLoader imageLoader = new ImageLoader(mQueue, new BitmapCache());

 

 

  这样我们就把ImageLoader的功能优势充分利用起来了

一、添加对磁盘缓存的控制

我们默默的打开源码,添加如下代码:

复制代码
复制代码
复制代码
    private boolean mShouldCache = true;
    /**
     * Set whether or not responses to this request should be cached(Disk Cache).
     *
     * @return This Request object to allow for chaining.
     */
    public void setShouldCache(boolean shouldCache) {
        mShouldCache = shouldCache;
    }
    
    /**
     * Returns true if responses to this request should be cached.
     */
    public final boolean shouldCache() {
        return mShouldCache;
    }
复制代码
复制代码
复制代码

定位到get方法

public ImageContainer get(String requestUrl, ImageListener imageListener,
int maxWidth, int maxHeight)

找到初始化Request<Bitmap>的地方。

复制代码
复制代码
复制代码
    public ImageContainer get(String requestUrl, ImageListener imageListener,
            int maxWidth, int maxHeight) {
        // only fulfill requests that were initiated from the main thread.
        throwIfNotOnMainThread();

        final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight);

        // Try to look up the request in the cache of remote images.
        Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
        if (cachedBitmap != null) {
            // Return the cached bitmap.
            ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);
            imageListener.onResponse(container, true);
            return container;
        }

        // The bitmap did not exist in the cache, fetch it!
        ImageContainer imageContainer =
                new ImageContainer(null, requestUrl, cacheKey, imageListener);

        // Update the caller to let them know that they should use the default bitmap.
        imageListener.onResponse(imageContainer, true);

        // Check to see if a request is already in-flight.
        BatchedImageRequest request = mInFlightRequests.get(cacheKey);
        if (request != null) {
            // If it is, add this request to the list of listeners.
            request.addContainer(imageContainer);
            return imageContainer;
        }

        // The request is not already in flight. Send the new request to the network and
        // track it.
        Request<Bitmap> newRequest = makeImageRequest(requestUrl, maxWidth, maxHeight, cacheKey);
        mRequestQueue.add(newRequest);
        mInFlightRequests.put(cacheKey,
                new BatchedImageRequest(newRequest, imageContainer));
        return imageContainer;
    }
复制代码
复制代码
复制代码

把红色代码中间添加:newRequest.setShouldCache(mShouldCache);*终效果如下:

复制代码
复制代码
复制代码
     // The request is not already in flight. Send the new request to the network and
        // track it.
        Request<Bitmap> newRequest = makeImageRequest(requestUrl, maxWidth, maxHeight, cacheKey);
        newRequest.setShouldCache(mShouldCache);
        mRequestQueue.add(newRequest);
复制代码
复制代码
复制代码

 

二、ImageLoader

复制代码
复制代码
复制代码
    /**
     * Constructs a new ImageLoader.
     * @param queue The RequestQueue to use for making image requests.
     * @param imageCache The cache to use as an L1 cache.
     */
    public ImageLoader(RequestQueue queue, ImageCache imageCache) {
        mRequestQueue = queue;
        mCache = imageCache;
    }
复制代码
复制代码
复制代码

初始化要传入两个参数:①RequestQueue对象;②ImageCache对象(不能传null!!!!)

RequestQueue这个我就不多说了,之前的文章已经讲解过了,下面来说说ImageCache这个对象。

 

2.1 建立ImageCache对象来实现内存缓存

ImageCache是一个图片的内存缓存对象,源码中叫做L1缓存,其实缓存分为L1、L2两种,L1就是所谓的内存缓存,将展示过的图片放入内存中进行缓存,L2就是磁盘缓存,如果这个图片下载完成,它可以被存放到磁盘中,在没有网络的时候就可以调出来使用了。

为了简单我先空实现ImageCache接口,产生一个MyImageCache对象

复制代码
复制代码
复制代码
    class MyImageCache implements ImageCache {

        @Override
        public Bitmap getBitmap(String url) {
            return null;
        }

        @Override
        public void putBitmap(String url, Bitmap bitmap) {
        }
    }
复制代码
复制代码
复制代码

这个接口提供的方法简单明了,今后我们可以用自己的内存缓存来完善这个类,当前getBitmap返回的是null,说明这个内存缓存没啥用处,和没缓存一样。

 

2.2 实现加载网络图片

复制代码
复制代码
复制代码
        ImageLoader imageLoader = new ImageLoader(mQueue, new MyImageCache());
        ImageListener listener = ImageLoader.getImageListener(iv, R.drawable.default_photo, R.drawable.error_photo);
        imageLoader.setShouldCache(true);
        imageLoader.get("http://img5.duitang.com/uploads/item/201409/14/20140914162144_MBEmX.jpeg", listener);
复制代码
复制代码
复制代码

代码的思路是产生ImageLoader后,再初始化一个监听器,监听器中传入imageview对象,还有默认的图片,出错时展示的图片,这个很好理解。*后在imageLoader的get方法中传入URL,还有监听器对象即可。

值得注意的是,get方法还有一种变体:

imageLoader.get("http://img5.duitang.com/uploads/item/201409/14/20140914162144_MBEmX.jpeg", listener, 0 ,0);

这里*后传入的数值是得到图片的*大宽、高,其意义和ImageRequest中的宽、高完全一致,可以参考之前的文章。其实,如果你去源码中找找的话,你会发现这两个参数*终都是传给ImageRequest的,所以在此就不做过多讲解了。

 

2.3 设置缓存

因为我们一上来就修改了源码,所以当我们在执行get()方法前可以通过setShouldCache(false)来取消磁盘缓存,如果你不进行设置的话默认是执行磁盘缓存的。那么如何配置L1缓存呢?刚刚我们的MyImageCache仅仅是一个空实现,现在就开始来完善它。

我的想法是通过LruCache进行图片缓存,分配的缓存空间是5m。如果对LruCache不是很了解,可以看看我之前的文章:详细解读LruCache类

复制代码
复制代码
复制代码
class MyImageCache implements ImageCache {

        private LruCache<String, Bitmap> mCache;  
        
        public MyImageCache() {
            int maxSize = 5 * 1024 * 1024;  
            mCache = new LruCache<String, Bitmap>(maxSize) {  
                @Override  
                protected int sizeOf(String key, Bitmap bitmap) {  
                    return bitmap.getRowBytes() * bitmap.getHeight();  
                }  
            };
        }
        
        @Override
        public Bitmap getBitmap(String url) {
            return mCache.get(url);  
            
        }

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

    }
复制代码
复制代码
复制代码

每次执行get方法时,Volley会到MyImageCache中调用getBitmap(),看看有没有内存缓存,如果你返回了null,那么Volley就会从网络上下载,如果不为null,Volley会直接把取得的bitmap展示到imageview中。当图片展示到屏幕上后(无论是这个图片是从内存中读的,还是从磁盘中读的,或者是从网络上下载的),Volley都会自动调用putBitmap,把图片放入内存中缓存起来。

说明:缓存的size是:bitmap.getRowBytes() * bitmap.getHeight(),这里getRowBytes()是返回图片每行的字节数,图片的size应该乘以高度。

 

注意:imageLoader.setShouldCache(false);仅仅是设置了不实用磁盘缓存,和内存缓存没有任何关系。如果你想要不实用内存缓存,请在自定义的ImageCache中进行处理。

 

2.4 其他方法

public final boolean shouldCache()

查看是否已经做了磁盘缓存。

 

void setShouldCache(boolean shouldCache)

设置是否运行磁盘缓存,此方法需要在get方法前使用

 

public boolean isCached(String requestUrl, int maxWidth, int maxHeight)

判断对象是否已经被缓存,传入url,还有图片的*大宽高

 

public void setBatchedResponseDelay(int newBatchedResponseDelayMs)

Sets the amount of time to wait after the first response arrives before delivering all responses. Batching can be disabled entirely by passing in 0.

设置*次响应到达后到分发所有响应之前的整体时间,单位ms,如果你设置的时间是0,那么Batching将不可用。

 

三、NetworkImageView

NetworkImageView继承自ImageView,你可以认为它是一个可以实现加载网络图片的imageview,十分简单好用。这个控件在被从父控件分离的时候,会自动取消网络请求的,即完全不用我们担心相关网络请求的生命周期问题。

3.1 XML

复制代码
复制代码
复制代码
    <com.android.volley.toolbox.NetworkImageView
        android:id="@+id/network_image_view"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_gravity="center_horizontal" />
复制代码
复制代码
复制代码

 

3.2 JAVA

复制代码
复制代码
复制代码
     NetworkImageView networkImageView = (NetworkImageView) findViewById(R.id.network_image_view);
        networkImageView.setDefaultImageResId(R.drawable.default_photo);
        networkImageView.setErrorImageResId(R.drawable.error_photo);
        networkImageView.setImageUrl("http://img5.duitang.com/uploads/item/201409/14/20140914162144_MBEmX.jpeg", imageLoader);
复制代码
复制代码
复制代码

 

3.3 设置图片的宽高

NetworkImageView没有提供任何设置图片宽高的方法,这是由于它是一个控件,在加载图片的时候它会自动获取自身的宽高,然后对比网络图片的宽度,再决定是否需要对图片进行压缩。也就是说,压缩过程是在内部完全自动化的,并不需要我们关心。NetworkImageView*终会始终呈现给我们一张大小比控件尺寸略大的网络图片,因为它会根据控件宽高来等比缩放原始图片,这点需要注意,如果你想要了解详细原理,请看我之前的ImageRequest介绍。

如果你不想对图片进行压缩的话,只需要在布局文件中把NetworkImageView的layout_width和layout_height都设置成wrap_content就可以了,这样它就会将该图片的原始大小展示出来,不会进行任何压缩。

Volley — ImageRequest & Request简介

上 篇文章我们讲 到了如何用volley进行简单的网络请求,我们可以很容易的接受到string、JsonObjec类型的返回结果,之前的例子仅仅是一次请求,这里需 要说明volley本身就是适合高并发的,所以它可以运行你用volley在短时间内进行多次请求,并且不用去手动管理线程数。仅仅是请求文字过于基础 了,本篇将讲述如何用volley从网络下载图片。

 

一、用ImageRequest来请求图片

ImageRequest是一个图片请求对象,它继承自Request<Bitmap>,所以请求得到的结果是一个bitmap。

1.1 使用步骤

ImageRequest仍旧是一个request对象,所以使用方式和StringRequest、JsonObjectRequest、JsonArrayRequest十分相似。

步骤:

  1. 建立一个RequestQueue对象
  2. 建立一个ImageRequest对象
  3. 将ImageRequest添加到RequestQueue中

*步、第三步我们在上篇文章中已经做好了,如果不清楚的话可以去上一篇文章查看。

 

1.2 分析构造函数

源码中的构造函数是这样定义的:

    public ImageRequest(String url, Response.Listener<Bitmap> listener, int maxWidth, int maxHeight,
            Config decodeConfig, Response.ErrorListener errorListener) {
        super(Method.GET, url, errorListener);
        setRetryPolicy(
                new DefaultRetryPolicy(IMAGE_TIMEOUT_MS, IMAGE_MAX_RETRIES, IMAGE_BACKOFF_MULT));
        mListener = listener;
        mDecodeConfig = decodeConfig;
        mMaxWidth = maxWidth;
        mMaxHeight = maxHeight;
    }

默认的请求方式是GET,初始化方法需要传入:图片的url,一个响应结果监听器,图片的*大宽度,图片的*大高度,图片的颜色属性,出错响应的监听器。

说明:图片的颜色属性,Bitmap.Config下的几个常量都可以在这里使用,其中ARGB_8888可以展示*好的颜色属性,每个图片像素占据4个字节的大小,而RGB_565则表示每个图片像素占据2个字节大小

 

参数说明:decodeConfig是图片的颜色属性,下面的几个值都可以使用。

 

 

Bitmap.Config中的颜色属性(枚举类型)
ALPHA_8
ARGB_4444 由于质量低,已经被弃用,推荐用ARGB_8888
ARGB_8888 每个像素用4byte存储
RGB_565 每个像素用2byte存储,红色占5位,绿色占6位,蓝色占5位

 

    /** Socket timeout in milliseconds for image requests */
    private static final int IMAGE_TIMEOUT_MS = 1000;

    /** Default number of retries for image requests */
    private static final int IMAGE_MAX_RETRIES = 2;

    /** Default backoff multiplier for image requests */
    private static final float IMAGE_BACKOFF_MULT = 2f;
  • 设定超时时间:1000ms;
  • *大的请求次数:2次;
  • 发生冲突时的重传延迟增加数:2f(这个应该和TCP协议有关,冲突时需要退避一段时间,然后再次请求);

 

1.3 解释maxWidth,maxHeight参数

注释中详细说明了图片宽高的意义和作用,为了便于理解我再详细说一下。

    /**
     * Creates a new image request, decoding to a maximum specified width and
     * height. If both width and height are zero, the image will be decoded to
     * its natural size. If one of the two is nonzero, that dimension will be
     * clamped and the other one will be set to preserve the image's aspect
     * ratio. If both width and height are nonzero, the image will be decoded to
     * be fit in the rectangle of dimensions width x height while keeping its
     * aspect ratio.
     *
     * @param url URL of the image
     * @param listener Listener to receive the decoded bitmap
     * @param maxWidth Maximum width to decode this bitmap to, or zero for none
     * @param maxHeight Maximum height to decode this bitmap to, or zero for
     *            none
     * @param decodeConfig Format to decode the bitmap to
     * @param errorListener Error listener, or null to ignore errors
     */

先来完整解释下注释的意思:

  • 建立一个请求对象,按照*大宽高进行解码 。
  • 如果设定的宽和高都是0,那么下载到的图片将会按照实际的大小进行解码,也就是不压缩。
  • 如果宽和高中的一个或两个值不为0,那么图片的宽/高(取决于你设定了宽还是高)会压缩至设定好的值,而另一个宽/高将会按原始比例改变。
  • 如果宽和高都不是0,那么得到的图片将会“按比例”解码到你设定的宽高,也就是说*终得到的图片大小不一定是你*初设定的大小。

举个例子:

我的图片原本像素是:850×1200.

%title插图%num

当maxWidth = 0,maxHeight = 0时,*终得到的bitmap的宽高是850×1200

当maxWidth = 0,maxHeight = 600时,得到的bitmap是425×600.这就说明它会按照一个不为0的边的值,将图片进行等比缩放。

当maxWidth = 100,maxHeight = 600时,我们得到的bitmap竟然是100×141,是按照100进行等比缩小后的图片,而不是100×600.

要弄清这个问题,我们还得看源码,源码中解析响应结果的方法叫做doParse(…)

    /**
     * The real guts of parseNetworkResponse. Broken out for readability.
     */
    private Response<Bitmap> doParse(NetworkResponse response) {
        byte[] data = response.data;
        BitmapFactory.Options decodeOptions = new BitmapFactory.Options();
        Bitmap bitmap = null;
        if (mMaxWidth == 0 && mMaxHeight == 0) {
       // 如果宽高都是0,那么就返回原始尺寸
            decodeOptions.inPreferredConfig = mDecodeConfig;
            bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
        } else {
            // If we have to resize this image, first get the natural bounds.
      // 如果我们已经重设了image的尺寸(宽高中有一个或两个不为0),那么先得到原始的大小

            decodeOptions.inJustDecodeBounds = true; // 设置先不得到bitmap,仅仅获取bitmap的参数。
            BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions); // *次解码,主要获得的是bitmap的实际宽、高
            int actualWidth = decodeOptions.outWidth; // 得到bitmap的宽
            int actualHeight = decodeOptions.outHeight; // 得到bitmap的高

            // Then compute the dimensions we would ideally like to decode to.
            // 然后计算我们想要得到的*终尺寸
            int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight,
                    actualWidth, actualHeight);
            int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth,
                    actualHeight, actualWidth);

            // Decode to the nearest power of two scaling factor.
            // 把图片解码到*接近2的幂次方的大小
            decodeOptions.inJustDecodeBounds = false;
            // TODO(ficus): Do we need this or is it okay since API 8 doesn't support it?
            // decodeOptions.inPreferQualityOverSpeed = PREFER_QUALITY_OVER_SPEED;
            decodeOptions.inSampleSize =
                findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight);
            Bitmap tempBitmap =
                BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);

            // If necessary, scale down to the maximal acceptable size.
            // 如果有必要的话,把得到的bitmap的*大边进行压缩来适应尺寸
            if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth ||
                    tempBitmap.getHeight() > desiredHeight)) {
                // 通过createScaledBitmap来压缩到目标尺寸
                bitmap = Bitmap.createScaledBitmap(tempBitmap,
                        desiredWidth, desiredHeight, true);
                tempBitmap.recycle();
            } else {
                bitmap = tempBitmap;
            }
        }
        if (bitmap == null) {
            return Response.error(new ParseError(response));
        } else {
            return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response));
        }
    }
    
    /**
     * Returns the largest power-of-two divisor for use in downscaling a bitmap
     * that will not result in the scaling past the desired dimensions.
     *
     * @param actualWidth Actual width of the bitmap
     * @param actualHeight Actual height of the bitmap
     * @param desiredWidth Desired width of the bitmap
     * @param desiredHeight Desired height of the bitmap
     */
    // Visible for testing.
    static int findBestSampleSize(
            int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) {
                // 计算inSampleSize的方法,详细知识自行百度吧。*终原图会被压缩为inSampleSize分之一
                // inSampleSize的值计算出来都是2的幂次方
        double wr = (double) actualWidth / desiredWidth;
        double hr = (double) actualHeight / desiredHeight;
        double ratio = Math.min(wr, hr);
        float n = 1.0f;
        while ((n * 2) <= ratio) {
            n *= 2;
        }

        return (int) n;
    }

此时我们发现重要的方法是getResizedDimension,它*终确定了图片的*终尺寸。

    /**
     * Scales one side of a rectangle to fit aspect ratio.
     *
     * @param maxPrimary Maximum size of the primary dimension (i.e. width for
     *        max width), or zero to maintain aspect ratio with secondary
     *        dimension
     * @param maxSecondary Maximum size of the secondary dimension, or zero to
     *        maintain aspect ratio with primary dimension
     * @param actualPrimary Actual size of the primary dimension
     * @param actualSecondary Actual size of the secondary dimension
     */
    private static int getResizedDimension(int maxPrimary, int maxSecondary, int actualPrimary,
            int actualSecondary) {
        // If no dominant value at all, just return the actual.
        if (maxPrimary == 0 && maxSecondary == 0) {
            return actualPrimary;
        }

        // If primary is unspecified, scale primary to match secondary's scaling ratio.
        if (maxPrimary == 0) {
            double ratio = (double) maxSecondary / (double) actualSecondary;
            return (int) (actualPrimary * ratio);
        }

        if (maxSecondary == 0) {
            return maxPrimary;
        }

        double ratio = (double) actualSecondary / (double) actualPrimary;
        int resized = maxPrimary;
        if (resized * ratio > maxSecondary) {
            resized = (int) (maxSecondary / ratio);
        }
        return resized;
    }

在我们目标宽、高都不为0时会调用下面的代码段:

double ratio = (double) actualSecondary / (double) actualPrimary;
        int resized = maxPrimary;
        if (resized * ratio > maxSecondary) {
            resized = (int) (maxSecondary / ratio);
        }

它会计算一个ratio(比值),这就是为啥它会按比例缩小的原因。

 

1.4 初始化对象并使用

        ImageRequest imageRequest = new ImageRequest(
                "http://img5.duitang.com/uploads/item/201409/14/20140914162144_MBEmX.jpeg", 
                new ResponseListener(), 
                0, // 图片的宽度,如果是0,就不会进行压缩,否则会根据数值进行压缩
                0, // 图片的高度,如果是0,就不进行压缩,否则会压缩
                Config.ARGB_8888, // 图片的颜色属性
                new ResponseErrorListener());

监听器:

    private class ResponseListener implements Response.Listener<Bitmap> {

        @Override
        public void onResponse(Bitmap response) {
           //  Log.d("TAG", "-------------\n" + response.toString());
            iv.setImageBitmap(response);
        }
    }

    private class ResponseErrorListener implements Response.ErrorListener {

        @Override
        public void onErrorResponse(VolleyError error) {
            Log.e("TAG", error.getMessage(), error);
        }
    }

*后将其添加到请求队列即可:

 mQueue.add(imageRequest);

 

1.5   题外话

这 样我们就用volley获得了网络图片,代码也十分简单。你可能会说,有没有其 他的,更好的方式来获取图片呢?当然有的,比如volley还提供了ImageLoader、NetworkImageView这样的对象,它们可以更加 方便的获取图片。值得一提的是这两个对象的内部都是使用了ImageRequest进行操作的,也就是说imageRequest是本质,这也就是为啥我 专门写一篇来分析ImageRequest的原因。

说话要言之有理,所以贴上ImageLoader、NetworkImageView源码中部分片段来证明其内部确实是用了ImageRequest。

 

ImageLoader的源码片段:

    public ImageContainer get(String requestUrl, ImageListener imageListener,
            int maxWidth, int maxHeight) {
        // ………// The request is not already in flight. Send the new request to the network and
        // track it.
        Request<Bitmap> newRequest = makeImageRequest(requestUrl, maxWidth, maxHeight, cacheKey);
        newRequest.setShouldCache(mShouldCache);
        mRequestQueue.add(newRequest);
        mInFlightRequests.put(cacheKey,
                new BatchedImageRequest(newRequest, imageContainer));
        return imageContainer;
    }
    protected Request<Bitmap> makeImageRequest(String requestUrl, int maxWidth, int maxHeight, final String cacheKey) {
        return new ImageRequest(requestUrl, new Listener<Bitmap>() {
            @Override
            public void onResponse(Bitmap response) {
                onGetImageSuccess(cacheKey, response);
            }
        }, maxWidth, maxHeight,
        Config.RGB_565, new ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                onGetImageError(cacheKey, error);
            }
        });
    }

在 ImageLoader重要的get()方法中,建立了一个newRequest对象,并将其放入请求队列中。这里的newRequest是通过 makeImageRequest()来产生的,而makeImageRequest()实际是返回了一个ImageRequest对象。所以用到了 ImageRequest对象。

 

NetworkImageView的源码片段:

    public void setImageUrl(String url, ImageLoader imageLoader) {
        mUrl = url;
        mImageLoader = imageLoader;
        // The URL has potentially changed. See if we need to load it.
        loadImageIfNecessary(false);
    }

它本身就调用的是ImageLoader对象,所以自然也是用到了ImageRequest。

 

二、Request简介

2.1 前言

Request是Volley中**核心的类,之前讲到的对象都是它的子类。从字面意思看,这个对象是用来执行请求的,但通过之前的使用我们发现,它还做了很多别的事情。先贴一个Request的子类。

        ImageRequest imageRequest = new ImageRequest(
                "http://img5.duitang.com/uploads/item/201409/14/20140914162144_MBEmX.jpeg", 
                new ResponseListener(), 
                0, // 图片的宽度,如果是0,就不会进行压缩,否则会根据数值进行压缩
                0, // 图片的高度,如果是0,就不进行压缩,否则会压缩
                Config.ARGB_8888, // 图片的颜色属性
                new ResponseErrorListener());

从中我们可以发现这个ImageRequest中传入了请求的url,毕竟是request嘛,请求的url是必须的,但我们还发现这个请求对象还处理了两个监听器,这就说明它不仅仅做了请求,同时对于响应的结果也做了分发处理。

 

2.2 部分API

getCacheKey()

Returns the cache key for this request. By default, this is the URL.

返回这个请求对象中缓存对象的key,默认返回的是请求的URL

 

getBodyContentType()

Returns the content type of the POST or PUT body.

返回POST或PUT请求内容的类型,我测试的结果是:application/x-www-form-urlencoded; charset=UTF-8

从源码就能看出,默认的编码方式是UTF-8:

/**
     * Default encoding for POST or PUT parameters. See {@link #getParamsEncoding()}.
     */
    private static final String DEFAULT_PARAMS_ENCODING = "UTF-8";
    /**
     * Returns the content type of the POST or PUT body.
     */
    public String getBodyContentType() {
        return "application/x-www-form-urlencoded; charset=" + getParamsEncoding();
    }

 

getSequence()

Returns the sequence number of this request.

返回请求的序列数

 

getUrl()

Returns the URL of this request.

返回请求的URL

 

setShouldCache(boolean bl)

Set whether or not responses to this request should be cached.

设 置这个请求是否有缓存,这个缓存是磁盘缓存,和内存缓存没什么事情,默认是true,也就是说如果你不设置为false,这个请求就会在磁盘中进行缓存。 其实,之前讲的的StringRequest,JsonRequest,ImageRequest得到的数据都会被缓存,无论是Json数据,还是图片都 会自动的缓存起来。然而,一旦你设置setShouldCache(false),这些数据就不会被缓存了。

 

getBody()

Returns the raw POST or PUT body to be sent.

返回POST或PUT的请求体

 

deliverError()

分发错误信息,这个就是调用监听器的方法,贴源码就明白了。

    /**
     * Delivers error message to the ErrorListener that the Request was
     * initialized with.
     *
     * @param error Error details
     */
    public void deliverError(VolleyError error) {
        if (mErrorListener != null) {
            mErrorListener.onErrorResponse(error);
        }
    }

 

setRetryPolicy(RetryPolicy retryPolicy)

对一个request的重新请求策略的设置,不同的项目是否需要重新请求,重新请求几次,请求超时的时间,这些就在这设置到里面。

  /**
     * Sets the retry policy for this request.
     *
     * @return This Request object to allow for chaining.
     */
    public Request<?> setRetryPolicy(RetryPolicy retryPolicy) {
        mRetryPolicy = retryPolicy;
        return this;
    }

从上面的源码可以看出,这里需要传入一个RetryPlicy的子类,就是重新请求策略的子类,Volley会在构造Request时传一个默认的对象,叫做DefaultRetryPolicy。

    /**
     * Creates a new request with the given method (one of the values from {@link Method}),
     * URL, and error listener.  Note that the normal response listener is not provided here as
     * delivery of responses is provided by subclasses, who have a better idea of how to deliver
     * an already-parsed response.
     */
    public Request(int method, String url, Response.ErrorListener listener) {
        mMethod = method;
        mUrl = url;
        mErrorListener = listener;
        setRetryPolicy(new DefaultRetryPolicy());

        mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url);
    }

如果你对于网络请求有具体的要求,可以实现RetryPolicy接口,进行自由的配置。下面贴一下DefaultRetryPolicy源码,方便参考。

 

/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.volley;

/**
 * Default retry policy for requests.
 */
public class DefaultRetryPolicy implements RetryPolicy {
    /** The current timeout in milliseconds. */
    private int mCurrentTimeoutMs;

    /** The current retry count. */
    private int mCurrentRetryCount;

    /** The maximum number of attempts. */
    private final int mMaxNumRetries;

    /** The backoff multiplier for the policy. */
    private final float mBackoffMultiplier;

    /** The default socket timeout in milliseconds */
    public static final int DEFAULT_TIMEOUT_MS = 2500;

    /** The default number of retries */
    public static final int DEFAULT_MAX_RETRIES = 1;

    /** The default backoff multiplier */
    public static final float DEFAULT_BACKOFF_MULT = 1f;

    /**
     * Constructs a new retry policy using the default timeouts.
     */
    public DefaultRetryPolicy() {
        this(DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DEFAULT_BACKOFF_MULT);
    }

    /**
     * Constructs a new retry policy.
     * @param initialTimeoutMs The initial timeout for the policy.
     * @param maxNumRetries The maximum number of retries.
     * @param backoffMultiplier Backoff multiplier for the policy.
     */
    public DefaultRetryPolicy(int initialTimeoutMs, int maxNumRetries, float backoffMultiplier) {
        mCurrentTimeoutMs = initialTimeoutMs;
        mMaxNumRetries = maxNumRetries;
        mBackoffMultiplier = backoffMultiplier;
    }

    /**
     * Returns the current timeout.
     */
    @Override
    public int getCurrentTimeout() {
        return mCurrentTimeoutMs;
    }

    /**
     * Returns the current retry count.
     */
    @Override
    public int getCurrentRetryCount() {
        return mCurrentRetryCount;
    }

    /**
     * Returns the backoff multiplier for the policy.
     */
    public float getBackoffMultiplier() {
        return mBackoffMultiplier;
    }

    /**
     * Prepares for the next retry by applying a backoff to the timeout.
     * @param error The error code of the last attempt.
     */
    @Override
    public void retry(VolleyError error) throws VolleyError {
        mCurrentRetryCount++;
        mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier);
        if (!hasAttemptRemaining()) {
            throw error;
        }
    }

    /**
     * Returns true if this policy has attempts remaining, false otherwise.
     */
    protected boolean hasAttemptRemaining() {
        return mCurrentRetryCount <= mMaxNumRetries;
    }
}

 

 

2.3 产生Request对象

虽然我们在代码中都会初始化一个Request对象,但是我们要在把他添加到响应队列中后才能得到它的完整体。

public <T> Request<T> add(Request<T> request) {

举例:

com.android.volley.Request<Bitmap> bitmapRequest = mQueue.add(imageRequest);

说明:如果你要设定这个request是不需要进行磁盘缓存的,那么请在把它添加到响应队列之前就进行设置,否则会得到不想要的效果。原因:源码在添加队列时会判断是否需要缓存。

    /**
     * Adds a Request to the dispatch queue.
     * @param request The request to service
     * @return The passed-in request
     */
    public <T> Request<T> add(Request<T> request) {
        // Tag the request as belonging to this queue and add it to the set of current requests.
        request.setRequestQueue(this);
        synchronized (mCurrentRequests) {
            mCurrentRequests.add(request);
        }

        // Process requests in the order they are added.
        request.setSequence(getSequenceNumber());
        request.addMarker("add-to-queue");

        // If the request is uncacheable, skip the cache queue and go straight to the network.
        if (!request.shouldCache()) {
            mNetworkQueue.add(request);
            return request; // 如果不需要缓存,直接返回request对象,不会执行下面的代码
        }

        // Insert request into stage if there's already a request with the same cache key in flight.
        synchronized (mWaitingRequests) {
            String cacheKey = request.getCacheKey();
            if (mWaitingRequests.containsKey(cacheKey)) {
                // There is already a request in flight. Queue up.
                Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
                if (stagedRequests == null) {
                    stagedRequests = new LinkedList<Request<?>>();
                }
                stagedRequests.add(request);
                mWaitingRequests.put(cacheKey, stagedRequests);
                if (VolleyLog.DEBUG) {
                    VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
                }
            } else {
                // Insert 'null' queue for this cacheKey, indicating there is now a request in
                // flight.
                mWaitingRequests.put(cacheKey, null);
                mCacheQueue.add(request);
            }
            return request;
        }
    }

 

Volley — 框架简介

一、引言

虽然网上已经有很多大神、高手都写过了类似的帖子,但作为新人,必须要走模仿的道路,再考虑超越,因此学习大神的笔记,记录自己的理解,是一个菜鸟走向成功的必经之路啊。如签名所言,记录自己摸爬滚打的经历,享受不悔的青春。废话不多说,言归正传。

二、Volley是什么?

Volley是 Google 推出的 Android 异步网络请求框架和图片加载框架。

三、Volley的主要特性

(1). 提供对网络请求自动调控的管理功能RequestQueue请求队列
(2).提供对网络请求进行缓存的功能,数据可以二级缓存,图片可以三级缓存,缓存有效时间默认使用请求响应头内CacheControl设置的缓存有效时间,也就是缓存有效时间由服务端动态确定
(3). 提供强大的请求取消功能
(4). 提供简便的图片加载工具ImageLoader&NetworkImageView

(5).提供强大的自定制功能,可以自己按照自己的实际需求定制request<T>子类

四、volley的总体设计图

%title插图%num

上图源自网络。如上图所示,volley主要是通过两种Diapatch Thread不断从RequestQueue中取出请求,根据是否已缓存调用Cache或Network这两类数据获取接口之一,从内存缓存或是服务器取 得请求的数据,然后交由ResponseDelivery去做结果分发及回调处理。

五、volley中的一些概念

  • Volley:Volley 对外暴露的 API,通过 newRequestQueue(…) 函数新建并启动一个请求队列RequestQueue。
  • Request:表示一个请求的抽象类。StringRequest、JsonRequest、ImageRequest都是它的子类,表示某种类型的请求。
  • RequestQueue:表示请求队列,里面包含一个CacheDispatcher(用于处理走缓存请求的调度线程)、 NetworkDispatcher数组(用于处理走网络请求的调度线程),一个ResponseDelivery(返回结果分发接口),通过 start() 函数启动时会启动CacheDispatcher和NetworkDispatchers。
  • CacheDispatcher:一个线程,用于调度处理走缓存的请求。启动后会不断从缓存请求队列中取请求处理,队列为空则等待,请求 处理结束则将结果传递给ResponseDelivery去执行后续处理。当结果未缓存过、缓存失效或缓存需要刷新的情况下,该请求都需要重新进入 NetworkDispatcher去调度处理。
  • NetworkDispatcher:一个线程,用于调度处理走网络的请求。启动后会不断从网络请求队列中取请求处理,队列为空则等待,请求处理结束则将结果传递给ResponseDelivery去执行后续处理,并判断结果是否要进行缓存。
  • ResponseDelivery:返回结果分发接口,目前只有基于ExecutorDelivery的在入参 handler 对应线程内进行分发。
  • HttpStack:处理 Http 请求,返回请求结果。目前 Volley 中有基于 HttpURLConnection 的HurlStack和 基于 Apache HttpClient 的HttpClientStack。
  • Network:调用HttpStack处理请求,并将结果转换为可被ResponseDelivery处理的NetworkResponse。
  • Cache:缓存请求结果,Volley 默认使用的是基于 sdcard 的DiskBasedCache。NetworkDispatcher得到请求结果后判断是否需要存储在 Cache,CacheDispatcher会从 Cache 中取缓存结果。

六、volley请求处理流程图

%title插图%num

上图源自网络。

Volley基本Request对象 & RequestQueue&请求取消

Volley它非常适合去进行数据量不大,但通信频繁的网络操作,而对于大数据量的网络操作,比如说下载文件等,Volley的表现就会非常糟糕。 所以不建议用它去进行下载文件、加载大图的操作。有人可能会问,如果我服务器中的图片都挺大的,activity中listview要加载这些图片,是不 是不能用这个框架呢?其实这个问题本身就是错误的,你想如果你服务器的图片都是大图,你要在手机上用照片墙进行展示,下载图片都会慢的要死,这个本身就是 不可行的。所以在项目初期就应该建立好服务器端的小图,照片墙用加载小图,点击后再从网络上下载大图,这才是正确思路。这时你就可以用volley加载小 图了,如果是要加载大图,可以用别的算法,强烈建议手动完成大图清除的工作,否则很可能会出现OOM。Volley本身没有做什么回收算法,还是用*基本 的GC,实际使用中可以根据需要自定义一下。

零、准备工作

Git项目,添加为lib,申请权限

git clone https://android.googlesource.com/platform/frameworks/volley
<uses-permission android:name="android.permission.INTERNET" />

 

一、初始化请求对象——RequestQueue

public class MyApplication extends Application {

    public static RequestQueue requestQueue;

    @Override
    public void onCreate() {
        super.onCreate();
        // 不必为每一次HTTP请求都创建一个RequestQueue对象,推荐在application中初始化
        requestQueue = Volley.newRequestQueue(this);
    }
}

既然是Http操作,自然有请求和响应,RequestQueue是一个请求队列 对象,它可以缓存所有的HTTP请求,然后按照一定的算法并发地发出这些请求。RequestQueue内部的设计就是非常合适高并发的,因此我们不必为 每一次HTTP请求都创建一个RequestQueue对象,这是非常浪费资源的。所以在这里我建立了一个application,然后用单例模式定义了 这个对象。当然,你可以选择在一个activity中定义一个RequestQueue对象,但这样可能会比较麻烦,而且还可能出现请求队列包含 activity强引用的问题,因此我还是推荐在application中定义。

 

二、使用StringRequest接收String类型的响应

前 面定义了请求对象,那么自然就有接收响应的对象了,这个框架中有多个响应对象,像StringRequest接受到的响应就是string类型 的;JsonRequest接收的响应就是Json类型对象。其实它们都是继承自Request<T>,然后根据不同的响应数据来进行特殊的 处理。

%title插图%num

 

2.1 初始化

**
     * Creates a new request with the given method.
     *
     * @param method the request {@link Method} to use
     * @param url URL to fetch the string at
     * @param listener Listener to receive the String response
     * @param errorListener Error listener, or null to ignore errors
     */
    public StringRequest(int method, String url, Listener<String> listener, ErrorListener errorListener)
    /**
     * Creates a new GET request.
     *
     * @param url URL to fetch the string at
     * @param listener Listener to receive the String response
     * @param errorListener Error listener, or null to ignore errors
     */
    public StringRequest(String url, Listener<String> listener, ErrorListener errorListener) {
        this(Method.GET, url, listener, errorListener);
    }

这就是StringRequest的两个构造函数,不同之处是一个传入了一个method的参数,一个没有。其实看上面的源码就知道,如果你不传入method,默认会调用GET方式进行请求。当你传入了Method.POST就会用post来请求。

【参数解释】

url:请求的地址

listener:响应成功的监听器

errorListener:出错时的监听器

StringRequest getStringRequest = new StringRequest("http://www.baidu.com", new ResponseListener(), new ResponseErrorListener());
StringRequest postStringRequest = new StringRequest(Method.POST, "http://www.baidu.com", new ResponseListener(),null);

 

2.2 配置监听器

/**
     * @author:Jack Tony
     * @description  :设置响应结果监听器,因为用的是StringRequest,所以这里的结果我定义为string类型
     * @date  :2015年1月24日
     */
    private class ResponseListener implements Response.Listener<String>{

        @Override
        public void onResponse(String response) {
            // TODO 自动生成的方法存根
             Log.d("TAG", "-------------\n" + response);  
        }
    }
    
    
    /**
     * @author:Jack Tony
     * @description  :访问出错时触发的监听器
     * @date  :2015年1月28日
     */
    private class ResponseErrorListener implements Response.ErrorListener{

        @Override
        public void onErrorResponse(VolleyError error) {
            // TODO 自动生成的方法存根
             Log.e("TAG", error.getMessage(), error);  
        }
        
    }

这两个监听器没啥可说的,因为是StringRequest调用的,所以成功时触发的监听器中得到的response就是String类型。如果访问出错,那么就打印出错信息。

 

2.3 执行GET请求

现在我们有了请求对象和响应对象,外加处理响应结果的监听器,那么就执行*后一步——发送请求。发送请求很简单,将响应对象添加到请求队列即可。

      mQueue.add(getStringRequest);

完整代码:

        RequestQueue mQueue = MyApplication.requestQueue;
        StringRequest getStringRequest = new StringRequest("http://www.baidu.com", new ResponseListener(), new ResponseErrorListener());
        mQueue.add(getStringRequest);

通过简单的add()方法就直接发送了请求,如果服务器响应了请求就会触发我们的结果监听器,然后被打印出啦。现在请求的是百度,所以得到了网页的源码:

%title插图%num

 

2.4 执行POST请求

POST和GET一样,仅仅是传入的方法不同。但一般我们的post都是要带一些参数的,volley没有提供附加参数的方法,所以我们必须要在StringRequest的匿名类中重写getParams()方法:

StringRequest stringRequest = new StringRequest(Method.POST, url,  listener, errorListener) {  
    @Override  
    protected Map<String, String> getParams() throws AuthFailureError {  
        Map<String, String> map = new HashMap<String, String>();  
        map.put("params1", "value1");  
        map.put("params2", "value2");  
        return map;  
    }  
};

这 样就传入了value1和value2两个参数了。现在可能有人会问为啥这个框架不提供这个传参的方法,还非得让我们重写。我个人觉得这个框架本身的目的 就是执行频繁的网络请求,比如下载图片,解析json数据什么的,用GET就能很好的实现了,所以就没有提供传参的POST方法。为了简单起见,我重写了 Request类中的getParams(),添加了传参的方法,以后通过setParams()就可以传参数了。

重写的代码块:

    Map<String, String> mParams = null;
    
    /**
     * Returns a Map of parameters to be used for a POST or PUT request.  Can throw
     * {@link AuthFailureError} as authentication may be required to provide these values.
     *
     * <p>Note that you can directly override {@link #getBody()} for custom data.</p>
     *
     * @throws AuthFailureError in the event of auth failure
     */
    protected Map<String, String> getParams() throws AuthFailureError {
        return mParams;
    }

    public void setParams(Map<String, String> params){
        mParams = params;
    }

完整代码:

 

/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.volley;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.Map;

import android.net.TrafficStats;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.text.TextUtils;

import com.android.volley.VolleyLog.MarkerLog;

/**
 * Base class for all network requests.
 *
 * @param <T> The type of parsed response this request expects.
 */
public abstract class Request<T> implements Comparable<Request<T>> {

    /**
     * Default encoding for POST or PUT parameters. See {@link #getParamsEncoding()}.
     */
    private static final String DEFAULT_PARAMS_ENCODING = "UTF-8";

    /**
     * Supported request methods.
     */
    public interface Method {
        int DEPRECATED_GET_OR_POST = -1;
        int GET = 0;
        int POST = 1;
        int PUT = 2;
        int DELETE = 3;
        int HEAD = 4;
        int OPTIONS = 5;
        int TRACE = 6;
        int PATCH = 7;
    }

    /** An event log tracing the lifetime of this request; for debugging. */
    private final MarkerLog mEventLog = MarkerLog.ENABLED ? new MarkerLog() : null;

    /**
     * Request method of this request.  Currently supports GET, POST, PUT, DELETE, HEAD, OPTIONS,
     * TRACE, and PATCH.
     */
    private final int mMethod;

    /** URL of this request. */
    private final String mUrl;

    /** Default tag for {@link TrafficStats}. */
    private final int mDefaultTrafficStatsTag;

    /** Listener interface for errors. */
    private final Response.ErrorListener mErrorListener;

    /** Sequence number of this request, used to enforce FIFO ordering. */
    private Integer mSequence;

    /** The request queue this request is associated with. */
    private RequestQueue mRequestQueue;

    /** Whether or not responses to this request should be cached. */
    private boolean mShouldCache = true;

    /** Whether or not this request has been canceled. */
    private boolean mCanceled = false;

    /** Whether or not a response has been delivered for this request yet. */
    private boolean mResponseDelivered = false;

    // A cheap variant of request tracing used to dump slow requests.
    private long mRequestBirthTime = 0;

    /** Threshold at which we should log the request (even when debug logging is not enabled). */
    private static final long SLOW_REQUEST_THRESHOLD_MS = 3000;

    /** The retry policy for this request. */
    private RetryPolicy mRetryPolicy;

    /**
     * When a request can be retrieved from cache but must be refreshed from
     * the network, the cache entry will be stored here so that in the event of
     * a "Not Modified" response, we can be sure it hasn't been evicted from cache.
     */
    private Cache.Entry mCacheEntry = null;

    /** An opaque token tagging this request; used for bulk cancellation. */
    private Object mTag;

    /**
     * Creates a new request with the given URL and error listener.  Note that
     * the normal response listener is not provided here as delivery of responses
     * is provided by subclasses, who have a better idea of how to deliver an
     * already-parsed response.
     *
     * @deprecated Use {@link #Request(int, String, com.android.volley.Response.ErrorListener)}.
     */
    @Deprecated
    public Request(String url, Response.ErrorListener listener) {
        this(Method.DEPRECATED_GET_OR_POST, url, listener);
    }

    /**
     * Creates a new request with the given method (one of the values from {@link Method}),
     * URL, and error listener.  Note that the normal response listener is not provided here as
     * delivery of responses is provided by subclasses, who have a better idea of how to deliver
     * an already-parsed response.
     */
    public Request(int method, String url, Response.ErrorListener listener) {
        mMethod = method;
        mUrl = url;
        mErrorListener = listener;
        setRetryPolicy(new DefaultRetryPolicy());

        mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url);
    }

    /**
     * Return the method for this request.  Can be one of the values in {@link Method}.
     */
    public int getMethod() {
        return mMethod;
    }

    /**
     * Set a tag on this request. Can be used to cancel all requests with this
     * tag by {@link RequestQueue#cancelAll(Object)}.
     *
     * @return This Request object to allow for chaining.
     */
    public Request<?> setTag(Object tag) {
        mTag = tag;
        return this;
    }

    /**
     * Returns this request's tag.
     * @see Request#setTag(Object)
     */
    public Object getTag() {
        return mTag;
    }

    /**
     * @return this request's {@link com.android.volley.Response.ErrorListener}.
     */
    public Response.ErrorListener getErrorListener() {
        return mErrorListener;
    }

    /**
     * @return A tag for use with {@link TrafficStats#setThreadStatsTag(int)}
     */
    public int getTrafficStatsTag() {
        return mDefaultTrafficStatsTag;
    }

    /**
     * @return The hashcode of the URL's host component, or 0 if there is none.
     */
    private static int findDefaultTrafficStatsTag(String url) {
        if (!TextUtils.isEmpty(url)) {
            Uri uri = Uri.parse(url);
            if (uri != null) {
                String host = uri.getHost();
                if (host != null) {
                    return host.hashCode();
                }
            }
        }
        return 0;
    }

    /**
     * Sets the retry policy for this request.
     *
     * @return This Request object to allow for chaining.
     */
    public Request<?> setRetryPolicy(RetryPolicy retryPolicy) {
        mRetryPolicy = retryPolicy;
        return this;
    }

    /**
     * Adds an event to this request's event log; for debugging.
     */
    public void addMarker(String tag) {
        if (MarkerLog.ENABLED) {
            mEventLog.add(tag, Thread.currentThread().getId());
        } else if (mRequestBirthTime == 0) {
            mRequestBirthTime = SystemClock.elapsedRealtime();
        }
    }

    /**
     * Notifies the request queue that this request has finished (successfully or with error).
     *
     * <p>Also dumps all events from this request's event log; for debugging.</p>
     */
    void finish(final String tag) {
        if (mRequestQueue != null) {
            mRequestQueue.finish(this);
        }
        if (MarkerLog.ENABLED) {
            final long threadId = Thread.currentThread().getId();
            if (Looper.myLooper() != Looper.getMainLooper()) {
                // If we finish marking off of the main thread, we need to
                // actually do it on the main thread to ensure correct ordering.
                Handler mainThread = new Handler(Looper.getMainLooper());
                mainThread.post(new Runnable() {
                    @Override
                    public void run() {
                        mEventLog.add(tag, threadId);
                        mEventLog.finish(this.toString());
                    }
                });
                return;
            }

            mEventLog.add(tag, threadId);
            mEventLog.finish(this.toString());
        } else {
            long requestTime = SystemClock.elapsedRealtime() - mRequestBirthTime;
            if (requestTime >= SLOW_REQUEST_THRESHOLD_MS) {
                VolleyLog.d("%d ms: %s", requestTime, this.toString());
            }
        }
    }

    /**
     * Associates this request with the given queue. The request queue will be notified when this
     * request has finished.
     *
     * @return This Request object to allow for chaining.
     */
    public Request<?> setRequestQueue(RequestQueue requestQueue) {
        mRequestQueue = requestQueue;
        return this;
    }

    /**
     * Sets the sequence number of this request.  Used by {@link RequestQueue}.
     *
     * @return This Request object to allow for chaining.
     */
    public final Request<?> setSequence(int sequence) {
        mSequence = sequence;
        return this;
    }

    /**
     * Returns the sequence number of this request.
     */
    public final int getSequence() {
        if (mSequence == null) {
            throw new IllegalStateException("getSequence called before setSequence");
        }
        return mSequence;
    }

    /**
     * Returns the URL of this request.
     */
    public String getUrl() {
        return mUrl;
    }

    /**
     * Returns the cache key for this request.  By default, this is the URL.
     */
    public String getCacheKey() {
        return getUrl();
    }

    /**
     * Annotates this request with an entry retrieved for it from cache.
     * Used for cache coherency support.
     *
     * @return This Request object to allow for chaining.
     */
    public Request<?> setCacheEntry(Cache.Entry entry) {
        mCacheEntry = entry;
        return this;
    }

    /**
     * Returns the annotated cache entry, or null if there isn't one.
     */
    public Cache.Entry getCacheEntry() {
        return mCacheEntry;
    }

    /**
     * Mark this request as canceled.  No callback will be delivered.
     */
    public void cancel() {
        mCanceled = true;
    }

    /**
     * Returns true if this request has been canceled.
     */
    public boolean isCanceled() {
        return mCanceled;
    }

    /**
     * Returns a list of extra HTTP headers to go along with this request. Can
     * throw {@link AuthFailureError} as authentication may be required to
     * provide these values.
     * @throws AuthFailureError In the event of auth failure
     */
    public Map<String, String> getHeaders() throws AuthFailureError {
        return Collections.emptyMap();
    }

    /**
     * Returns a Map of POST parameters to be used for this request, or null if
     * a simple GET should be used.  Can throw {@link AuthFailureError} as
     * authentication may be required to provide these values.
     *
     * <p>Note that only one of getPostParams() and getPostBody() can return a non-null
     * value.</p>
     * @throws AuthFailureError In the event of auth failure
     *
     * @deprecated Use {@link #getParams()} instead.
     */
    @Deprecated
    protected Map<String, String> getPostParams() throws AuthFailureError {
        return getParams();
    }

    /**
     * Returns which encoding should be used when converting POST parameters returned by
     * {@link #getPostParams()} into a raw POST body.
     *
     * <p>This controls both encodings:
     * <ol>
     *     <li>The string encoding used when converting parameter names and values into bytes prior
     *         to URL encoding them.</li>
     *     <li>The string encoding used when converting the URL encoded parameters into a raw
     *         byte array.</li>
     * </ol>
     *
     * @deprecated Use {@link #getParamsEncoding()} instead.
     */
    @Deprecated
    protected String getPostParamsEncoding() {
        return getParamsEncoding();
    }

    /**
     * @deprecated Use {@link #getBodyContentType()} instead.
     */
    @Deprecated
    public String getPostBodyContentType() {
        return getBodyContentType();
    }

    /**
     * Returns the raw POST body to be sent.
     *
     * @throws AuthFailureError In the event of auth failure
     *
     * @deprecated Use {@link #getBody()} instead.
     */
    @Deprecated
    public byte[] getPostBody() throws AuthFailureError {
        // Note: For compatibility with legacy clients of volley, this implementation must remain
        // here instead of simply calling the getBody() function because this function must
        // call getPostParams() and getPostParamsEncoding() since legacy clients would have
        // overridden these two member functions for POST requests.
        Map<String, String> postParams = getPostParams();
        if (postParams != null && postParams.size() > 0) {
            return encodeParameters(postParams, getPostParamsEncoding());
        }
        return null;
    }
 
    Map<String, String> mParams = null;
    
    /**
     * Returns a Map of parameters to be used for a POST or PUT request.  Can throw
     * {@link AuthFailureError} as authentication may be required to provide these values.
     *
     * <p>Note that you can directly override {@link #getBody()} for custom data.</p>
     *
     * @throws AuthFailureError in the event of auth failure
     */
    protected Map<String, String> getParams() throws AuthFailureError {
        return mParams;
    }

    public void setParams(Map<String, String> params){
        mParams = params;
    }
    
    /**
     * Returns which encoding should be used when converting POST or PUT parameters returned by
     * {@link #getParams()} into a raw POST or PUT body.
     *
     * <p>This controls both encodings:
     * <ol>
     *     <li>The string encoding used when converting parameter names and values into bytes prior
     *         to URL encoding them.</li>
     *     <li>The string encoding used when converting the URL encoded parameters into a raw
     *         byte array.</li>
     * </ol>
     */
    protected String getParamsEncoding() {
        return DEFAULT_PARAMS_ENCODING;
    }

    /**
     * Returns the content type of the POST or PUT body.
     */
    public String getBodyContentType() {
        return "application/x-www-form-urlencoded; charset=" + getParamsEncoding();
    }

    /**
     * Returns the raw POST or PUT body to be sent.
     *
     * <p>By default, the body consists of the request parameters in
     * application/x-www-form-urlencoded format. When overriding this method, consider overriding
     * {@link #getBodyContentType()} as well to match the new body format.
     *
     * @throws AuthFailureError in the event of auth failure
     */
    public byte[] getBody() throws AuthFailureError {
        Map<String, String> params = getParams();
        if (params != null && params.size() > 0) {
            return encodeParameters(params, getParamsEncoding());
        }
        return null;
    }

    /**
     * Converts <code>params</code> into an application/x-www-form-urlencoded encoded string.
     */
    private byte[] encodeParameters(Map<String, String> params, String paramsEncoding) {
        StringBuilder encodedParams = new StringBuilder();
        try {
            for (Map.Entry<String, String> entry : params.entrySet()) {
                encodedParams.append(URLEncoder.encode(entry.getKey(), paramsEncoding));
                encodedParams.append('=');
                encodedParams.append(URLEncoder.encode(entry.getValue(), paramsEncoding));
                encodedParams.append('&');
            }
            return encodedParams.toString().getBytes(paramsEncoding);
        } catch (UnsupportedEncodingException uee) {
            throw new RuntimeException("Encoding not supported: " + paramsEncoding, uee);
        }
    }

    /**
     * Set whether or not responses to this request should be cached.
     *
     * @return This Request object to allow for chaining.
     */
    public final Request<?> setShouldCache(boolean shouldCache) {
        mShouldCache = shouldCache;
        return this;
    }

    /**
     * Returns true if responses to this request should be cached.
     */
    public final boolean shouldCache() {
        return mShouldCache;
    }

    /**
     * Priority values.  Requests will be processed from higher priorities to
     * lower priorities, in FIFO order.
     */
    public enum Priority {
        LOW,
        NORMAL,
        HIGH,
        IMMEDIATE
    }

    /**
     * Returns the {@link Priority} of this request; {@link Priority#NORMAL} by default.
     */
    public Priority getPriority() {
        return Priority.NORMAL;
    }

    /**
     * Returns the socket timeout in milliseconds per retry attempt. (This value can be changed
     * per retry attempt if a backoff is specified via backoffTimeout()). If there are no retry
     * attempts remaining, this will cause delivery of a {@link TimeoutError} error.
     */
    public final int getTimeoutMs() {
        return mRetryPolicy.getCurrentTimeout();
    }

    /**
     * Returns the retry policy that should be used  for this request.
     */
    public RetryPolicy getRetryPolicy() {
        return mRetryPolicy;
    }

    /**
     * Mark this request as having a response delivered on it.  This can be used
     * later in the request's lifetime for suppressing identical responses.
     */
    public void markDelivered() {
        mResponseDelivered = true;
    }

    /**
     * Returns true if this request has had a response delivered for it.
     */
    public boolean hasHadResponseDelivered() {
        return mResponseDelivered;
    }

    /**
     * Subclasses must implement this to parse the raw network response
     * and return an appropriate response type. This method will be
     * called from a worker thread.  The response will not be delivered
     * if you return null.
     * @param response Response from the network
     * @return The parsed response, or null in the case of an error
     */
    abstract protected Response<T> parseNetworkResponse(NetworkResponse response);

    /**
     * Subclasses can override this method to parse 'networkError' and return a more specific error.
     *
     * <p>The default implementation just returns the passed 'networkError'.</p>
     *
     * @param volleyError the error retrieved from the network
     * @return an NetworkError augmented with additional information
     */
    protected VolleyError parseNetworkError(VolleyError volleyError) {
        return volleyError;
    }

    /**
     * Subclasses must implement this to perform delivery of the parsed
     * response to their listeners.  The given response is guaranteed to
     * be non-null; responses that fail to parse are not delivered.
     * @param response The parsed response returned by
     * {@link #parseNetworkResponse(NetworkResponse)}
     */
    abstract protected void deliverResponse(T response);

    /**
     * Delivers error message to the ErrorListener that the Request was
     * initialized with.
     *
     * @param error Error details
     */
    public void deliverError(VolleyError error) {
        if (mErrorListener != null) {
            mErrorListener.onErrorResponse(error);
        }
    }

    /**
     * Our comparator sorts from high to low priority, and secondarily by
     * sequence number to provide FIFO ordering.
     */
    @Override
    public int compareTo(Request<T> other) {
        Priority left = this.getPriority();
        Priority right = other.getPriority();

        // High-priority requests are "lesser" so they are sorted to the front.
        // Equal priorities are sorted by sequence number to provide FIFO ordering.
        return left == right ?
                this.mSequence - other.mSequence :
                right.ordinal() - left.ordinal();
    }

    @Override
    public String toString() {
        String trafficStatsTag = "0x" + Integer.toHexString(getTrafficStatsTag());
        return (mCanceled ? "[X] " : "[ ] ") + getUrl() + " " + trafficStatsTag + " "
                + getPriority() + " " + mSequence;
    }
}

 

 

 

使用示例:

        StringRequest postStringRequest = new StringRequest(Method.POST, "http://m.weather.com.cn/data/101010100.html",
                new ResponseListener(), null);
        Map<String, String> map = new HashMap<String, String>();
        map.put("params1", "value1");
        map.put("params2", "value2");
        postStringRequest.setParams(map);
        mQueue.add(postStringRequest);

结果:

%title插图%num

 

三、使用JsonObjectRequest接收Json类型的响应

类似于StringRequest,JsonRequest也是继承自Request类的,不过由于JsonRequest是一个抽象类,因此我们无法直接创建它的实例,那么只能从它的子类入手了。JsonRequest有两个直接的子类,JsonObjectRequest和JsonArrayRequest,从名字上你应该能就看出它们的区别了吧?一个是用于请求一段JSON数据的,一个是用于请求一段JSON数组的。

 

3.1 构造函数

    /**
     * Creates a new request.
     * @param method the HTTP method to use
     * @param url URL to fetch the JSON from
     * @param jsonRequest A {@link JSONObject} to post with the request. Null is allowed and
     *   indicates no parameters will be posted along with request.
     * @param listener Listener to receive the JSON response
     * @param errorListener Error listener, or null to ignore errors.
     */
    public JsonObjectRequest(int method, String url, JSONObject jsonRequest,
     Listener<JSONObject> listener, ErrorListener errorListener) {
        super(method, url, (jsonRequest == null) ? null : jsonRequest.toString(), listener,
                    errorListener);
    }

    /**
     * Constructor which defaults to <code>GET</code> if <code>jsonRequest</code> is
     * <code>null</code>, <code>POST</code> otherwise.
     *
     * @see #JsonObjectRequest(int, String, JSONObject, Listener, ErrorListener)
     */
    public JsonObjectRequest(String url, JSONObject jsonRequest, Listener<JSONObject> listener,
            ErrorListener errorListener) {
        this(jsonRequest == null ? Method.GET : Method.POST, url, jsonRequest,
                listener, errorListener);
    }

 

3.2 发送请求

和之前讲过的StringRequest一样,可以传入请求的类型,如果没传就默认是GET请求。参数也是如出一辙,就是泛型变了下。定义和使用的方式也完全一致,初始化对象后,添加到请求队列即可。

        JsonObjectRequest request = new JsonObjectRequest("http://m.weather.com.cn/data/101010100.html",
                null, new ResponseListener(), new ResponseErrorListener());
        mQueue.add(request);
    /**
     * @author:Jack Tony
     * @description :设置响应结果监听器,这里用的是JsonObjectRequest,所以返回的结果是JSONObject
     * @date :2015年1月24日
     */
    private class ResponseListener implements Response.Listener<JSONObject> {

        @Override
        public void onResponse(JSONObject response) {
            // TODO 自动生成的方法存根
            Log.d("TAG", "-------------\n" + response.toString());
        }

    }

    /**
     * @author:Jack Tony
     * @description :访问出错时触发的监听器
     * @date :2015年1月28日
     */
    private class ResponseErrorListener implements Response.ErrorListener {

        @Override
        public void onErrorResponse(VolleyError error) {
            Log.e("TAG", error.getMessage(), error);
        }
    }

 

结果:

你怎么查看解析是否成功了呢?服务器端的数据:

{"weatherinfo":{"city":"北京","city_en":"beijing","date_y":"2014年3月4日","date":"","week":"星期二","fchh":"11","cityid":"101010100","temp1":"8℃~-3℃","temp2":"8℃~-3℃","temp3":"7℃~-3℃","temp4":"8℃~-1℃","temp5":"10℃~1℃","temp6":"10℃~2℃","tempF1":"46.4℉~26.6℉","tempF2":"46.4℉~26.6℉","tempF3":"44.6℉~26.6℉","tempF4":"46.4℉~30.2℉","tempF5":"50℉~33.8℉","tempF6":"50℉~35.6℉","weather1":"晴","weather2":"晴","weather3":"晴","weather4":"晴转多云","weather5":"多云","weather6":"多云","img1":"0","img2":"99","img3":"0","img4":"99","img5":"0","img6":"99","img7":"0","img8":"1","img9":"1","img10":"99","img11":"1","img12":"99","img_single":"0","img_title1":"晴","img_title2":"晴","img_title3":"晴","img_title4":"晴","img_title5":"晴","img_title6":"晴","img_title7":"晴","img_title8":"多云","img_title9":"多云","img_title10":"多云","img_title11":"多云","img_title12":"多云","img_title_single":"晴","wind1":"北风4-5级转微风","wind2":"微风","wind3":"微风","wind4":"微风","wind5":"微风","wind6":"微风","fx1":"北风","fx2":"微风","fl1":"4-5级转小于3级","fl2":"小于3级","fl3":"小于3级","fl4":"小于3级","fl5":"小于3级","fl6":"小于3级","index":"寒冷","index_d":"天气寒冷,建议着厚羽绒服、毛皮大衣加厚毛衣等隆冬服装。年老体弱者尤其要注意保暖防冻。","index48":"冷","index48_d":"天气冷,建议着棉服、羽绒服、皮夹克加羊毛衫等冬季服装。年老体弱者宜着厚棉衣、冬大衣或厚羽绒服。","index_uv":"中等","index48_uv":"中等","index_xc":"较适宜","index_tr":"一般","index_co":"较舒适","st1":"7","st2":"-3","st3":"8","st4":"0","st5":"7","st6":"-1","index_cl":"较不宜","index_ls":"基本适宜","index_ag":"易发"}}

如果解析错误,就会出现警告,这时错误监听器就会被触发:

%title插图%num

如果解析成功,就不会出现错误,这就是泛型的好处,保证了程序的正确性。

%title插图%num

*终我们就可以在Response.Listener<JSONObject>中得到JSONObject对象,通过这个对象就能进行下一步的处理了。

 

3.3 解析Json

比如要解析出上面Json数据中city的字段,就可以按照如下方式编码:

     try {
                response = response.getJSONObject("weatherinfo");
                Log.i(TAG, "City = " + response.getString("city"));
            } catch (JSONException e) {
                // TODO 自动生成的 catch 块
                e.printStackTrace();
            }

完整监听器代码:

    private class ResponseListener implements Response.Listener<JSONObject> {

        @Override
        public void onResponse(JSONObject response) {
            // TODO 自动生成的方法存根
            Log.d("TAG", "-------------\n" + response.toString());
            
            try {
                response = response.getJSONObject("weatherinfo");
                Log.i(TAG, "City = " + response.getString("city"));
            } catch (JSONException e) {
                // TODO 自动生成的 catch 块
                e.printStackTrace();
            }
        }

    }

结果:

%title插图%num

 

四、JsonArrayRequest简介

除此之外,还有一个相关的响应对象叫做JsonArrayRequest,这个获得的就是一个Json序列,使用方式没有任何改变,这里就不做过多介绍了,因为剩下的就是Json的知识了,和Volley没有任何关系。

源码:

/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.volley.toolbox;

import com.android.volley.NetworkResponse;
import com.android.volley.ParseError;
import com.android.volley.Response;
import com.android.volley.Response.ErrorListener;
import com.android.volley.Response.Listener;

import org.json.JSONArray;
import org.json.JSONException;

import java.io.UnsupportedEncodingException;

/**
 * A request for retrieving a {@link JSONArray} response body at a given URL.
 */
public class JsonArrayRequest extends JsonRequest<JSONArray> {

    /**
     * Creates a new request.
     * @param url URL to fetch the JSON from
     * @param listener Listener to receive the JSON response
     * @param errorListener Error listener, or null to ignore errors.
     */
    public JsonArrayRequest(String url, Listener<JSONArray> listener, ErrorListener errorListener) {
        super(Method.GET, url, null, listener, errorListener);
    }

    @Override
    protected Response<JSONArray> parseNetworkResponse(NetworkResponse response) {
        try {
            String jsonString =
                new String(response.data, HttpHeaderParser.parseCharset(response.headers));
            return Response.success(new JSONArray(jsonString),
                    HttpHeaderParser.parseCacheHeaders(response));
        } catch (UnsupportedEncodingException e) {
            return Response.error(new ParseError(e));
        } catch (JSONException je) {
            return Response.error(new ParseError(je));
        }
    }
}

通过源码我们知道,这个响应对象发送的请求是Get,而且它是继承自JsonRequest,如果你想用POST来做,自行添加新的构造函数即可。

 

四、请求取消

      Activity里面启动了网络请求,而在这个网络请求还没返回结果的时候,Activity被结束了,此时如果继续使用其中的Context等,除了无辜的浪费CPU,电池,网络等资源,有可能还会导致程序crash,所以,我们需要处理这种一场情况。使用Volley的话,我们可以在Activity停止的时候,同时取消所有或部分未完成的网络请求。Volley里所有的请求结果会返回给主进程,如果在主进程里取消了某些请求,则这些请求将不会被返回给主线程。Volley支持多种request取消方式。
1)可以针对某些个request做取消操作:

@Override  
   public void onStop() {  
       for (Request <?> req : mRequestQueue) {  
           req.cancel();  
       }  
   }

 

 

2)取消这个队列里的所有请求:

    @Override  
    protected void onStop() {  
        // TODO Auto-generated method stub  
        super.onStop();  
        mRequestQueue.cancelAll(this);  
    }

 

3)可以根据RequestFilter或者Tag来终止某些请求

给请求设置标签:

 

如果你想取消所有的请求,在onStop方法中添加如下代码:

@Override
protected void onStop() {
    super.onStop();
    mRequestQueue.cancelAll(new RequestQueue.RequestFilter() {
        @Override
        public boolean apply(Request<?> request) {
            // do I have to cancel this?
            return true; // -> always yes
        }
    });
}

 

这样你就不必担心在onResponse被调用的时候用户已经销毁Activity。这种情况下会抛出NullPointerException 异。但是post请求则需要继续,即使用户已经改变了Activity。我们可以通过使用tag来做到,在构造GET请求的时候,添加一个tag给它。

// after declaring your request
request.setTag("My Tag");
mRequestQueue.add(request);

 

如果要取消所有指定标记My Tag的请求,只需简单的添加下面的一行代码:

mRequestQueue.cancelAll("My Tag");

这样你就只会取消My Tag请求,让其它请求不受影响。注意你必须手动在销毁的Activity中处理这种情况。

@Override  
rotected void onStop() {  
// TODO Auto-generated method stub  
super.onStop();  
  
mRequestQueue.cancelAll( new RequestFilter() {});  
or  
mRequestQueue.cancelAll(new Object());

 

求助安卓车机 adb 问题

yikyo · 29 天前 · 2283 次点击

*近想折腾一下车机,想把内置的导航软件更新一下,adb devices 找不到设备,想请教一下问题出在哪边

领克 02,安卓版本是 4.4.3,已打开开发者模式,使用 type-c 数据线,命令行中 adb devices 无设备清单

换过数据线,也同样没有解决,车内的 usb 接口应该正常,网上的教程也是那个接口,且连接时有充电,

有一种说法是,数据线有方向的,我个人不太信这个说法

如果无法连接上车机的问题 车上有两个 USB 接口 尝试换个接口! 如果两个都试了 还是不行 请将电脑那边的调转 插到车机的 usb 里 就是两边 usb 调换 因为这个线 一头输出 一头输入 你可能插到 车机输出给电脑了!

第 1 条附言  ·  10 天前

购买公对公线,用 type-c usb hub + 公对公,已解决

我自己测试 TYPE-C 转 USB 线( 4 根)无效
type-c usb hub + 2 根 micro usb 线 无效

40 条回复    2021-07-12 14:32:36 +08:00

Sricecake
    1

Sricecake   29 天前   ❤️ 1

数据线确实没有方向,但是 USB 协议是分主从的
yikyo
    2

yikyo   29 天前

@Sricecake 所以问题还是出在线上面??
ShuoHui
    3

ShuoHui   29 天前 via iPhone

没设备应该是驱动问题
ShuoHui
    4

ShuoHui   29 天前 via iPhone   ❤️ 1

@ShuoHui #3 进设备管理器看一下
shiguiyou
    5

shiguiyou   29 天前

开启调试的时候,电脑上会有授权对话框的,对话框出来了没
yikyo
    6

yikyo   29 天前

@ShuoHui 等周末再试试,我猜测是没有
yikyo
    7

yikyo   29 天前

@shiguiyou 你指的是车机端的授权框?这个没出现
vk42
    8

vk42   29 天前

USB 当然有方向,你需要找的是车机的 USB slave 接口,但一般原厂车机这个口是不会开放给用户的
imhd
    9

imhd   29 天前 via Android

你进安卓后台打开 wifi adb debug 然后用 adb 连接
yikyo
    10

yikyo   29 天前

@vk42 领克车机破解教程非常多,车机的接口应该不是问题

yikyo
    11

yikyo   29 天前

@imhd 感谢回复,我回去再找找看,印象中当时是没有这个选项,

newtrek
    12

newtrek   29 天前

能打开开发者选项吗?车机应该默认不会启用 adb 的
yikyo
    13

yikyo   29 天前

@newtrek 开了,有特殊方式可以打开
newtrek
    14

newtrek   29 天前

@yikyo 那可能是口子不对,有的 USB 口只能用 U 盘,没办法的话就下个终端模拟应用,启用 wifi adb 吧
shily
    15

shily   29 天前

adb 也是有版本的,有时候需要特殊版本的 adb 才可以识别,或者*新的 adb 才可以
xy19009188
    16

xy19009188   29 天前

国产车机的系统版本也是安卓 4.x 嘛
Wicheol
    17

Wicheol   29 天前

@xy19009188 不是,比亚迪新版 dilink 是 Android 10 了
karatsuba
    18

karatsuba   29 天前

adb devices 的时候会弹出来确认窗口,要授权才能拿到
gam2046
    19

gam2046   29 天前

借楼问一下,有大佬知道 2021 款哈弗 H6,ADB 的打开方式嘛,自带的百度地图已经让我吐了,只想装个高德
jzphx
    20

jzphx   29 天前   ❤️ 1

默认是 host 模式吧,得想办法切换
zek
    21

zek   29 天前

这也敢玩吗? 万一调乱了 车子会不会突然失控?
Sentan
    22

Sentan   29 天前

@gam2046 新款 H6 自带的地图是高德啊,怎么是百度的
wms
    23

wms   29 天前   ❤️ 1

一般车机有好几个 USB 口, 其中一个支持 OTG 可以用作 ADB, 但厂家出厂的时候会封掉, 有的需要打开车机,在 PCB 上短路两个点.不是车机内部员工是不知道是怎么进的
idealhs
    24

idealhs   29 天前

@zek 车机只是个娱乐系统和导航辅助系统。和行车电脑是两套独立的系统,车机拆了也不会影响行车安全的。
gam2046
    25

gam2046   29 天前

@Sentan 应该不是同一代吧,我的是二代 2021 款 H6,问了 4S 店,说是和百度合作的,所以上面全套都是百度系的东西,他们也不知道怎么自己装应用
AaronLee01
    26

AaronLee01   29 天前

需要开发者选项里去开启 usb 调试才能连接 adb
fightingZ
    27

fightingZ   29 天前

可能车机上面的 usb 是 host 模式吧。尝试用 wifi adb 连接吧
fightingZ
    28

fightingZ   29 天前   ❤️ 1

@fightingZ 数据线没有方向 但是 usb 口应该是有方向的,用来设置拥有这个 usb 口的设备是作为 host 使用还是作为 device 使用。
janus77
    29

janus77   29 天前

我接触过的车机都没有 AOSP 原生设置界面,没有所谓的开发者模式选项。都是一个隐藏的方法拉起开发者 debug 界面,不能随便打开的。类似于工程模式。
yikyo
    30

yikyo   29 天前

@fightingZ 感谢回复,对 usb 有点基本的概念了,从网上的教程来看,不需要切换 usb 口设备,但是确实有看到其他品牌车机需要切换模式
raiz
    31

raiz   29 天前

otg 通常是由检测 ID 线来切换 host 和 device,拉低 id 引脚,则变为 host,可以接 u 盘键盘等,比如手机就是这样,otg 转接头里拉低了; 车机直接插 u 盘,说明要么硬件强制拉低了 id 引脚,要么软件上固定, 如果是硬件拉,还是有机会,如果是软件设置的,他们厂家有隐藏的触发方式。
0312birdzhang
    32

0312birdzhang   29 天前

@zek #21 车机是车上无关紧要的部件,亲测在丰田车上拆了开机自检都能过
wjploop
    33

wjploop   28 天前   ❤️ 1

搞车机的难得遇到车机相关的问题~

首先明确,使用 ADB 需要将 USB 模式从默认的 ”主机“ 切换到”从机“,而更改这个设置项需要进入工厂设置,一般是多次点击版本号显示弹窗输入工厂密码进入。

问题在于不知道工厂密码,可以试试电话客服看能不能弄到。

cz5424
    34

cz5424   28 天前

我的车机是 host 和 device 模式手动切换,同安卓 4.4,非领克;在开发者模式的界面右上角有一个切换按钮;需要双公头的 usb;
dolphintwo
    35

dolphintwo   28 天前

领克车机直接破解 然后高德 5.3 啊,流程网上都写的很清晰,adb 不是强需求,真纠结可以买 2950 车机,原厂开放 adb
yikyo
    36

yikyo   28 天前

@wjploop 感谢老哥的回复,我按教程上面的操作,是没有 usb 模式相关的操作

aHR0cHM6Ly9jbHViLmF1dG9ob21lLmNvbS5jbi9iYnMvdGhyZWFkLzFkM2IxMTdhOGVhZGMwNjMvODEyMjA0MjMtMS5odG1s

base64

wm5d8b
    37

wm5d8b   28 天前 via Android

@dolphintwo 2950 因为缺芯片生产不出来,我都排了两三个月了
HHPLow
    38

HHPLow   26 天前 via iPhone

@gam2046 系统设置系统信息,连续点击软件版本号,然后会弹出来密码,密码是*#1831aa,就能进工程模式了。
ciddechan
    39

ciddechan   26 天前

…这。。。会不会有风险~
dolphintwo
    40

dolphintwo   26 天前

@wm5d8b 我也在排。。

安卓现在有什么简单不用 Root 的方法可以挂载 NFS 吗?

5 条回复    2021-07-12 
noahhhh
    1

noahhhh   27 天前 via Android

下个 termux,然后在里面装个 Ubuntu 再挂 NFS?
yanqiyu
    2

yanqiyu   27 天前 via Android

@noahhhh 我猜也不行?因为 Android 的内核不太可能有编译进去 NFS,于是 userspace 整不了活。可能 fuse 能研究下能不能用,但是我怀疑 Android 的安全标准是否允许程序访问 fuse

估计要找一下有没有单纯提供 nfs 访问的 app ?

vk42
    3

vk42   27 天前

不用 root 基本不用想了,NFS 现在非全 Linux 原先环境基本已经被抛弃了,SMB 支持都要广泛的多……
vk42
    4

vk42   27 天前

@vk42 原先环境->原生环境……
billccn
    5

billccn   26 天前

只有试试那种 User-mode Linux(把一个内核作为一个普通进程跑)的项目放在 termux 能不能用,不过估计那些是需要 tun/tap 连到物理网卡上,Android 需要编写一个 * wrapper 才行。网上搜到( unix.stackexchange.com/a/46270)不支持内核外 NFS 的主要原因是这个协议需要直接访问文件句柄等操作,在用户空间巨难实现,所以没人想写是正常的。

另一种思路实现类似冰箱、黑阈的功能

Android 上实现不 root 管理其他 App,基本上有几种方案:

  1. 利用设备管理员模式。代表应用 App Ops/小黑屋。
  2. 利用 Adb 调试权限。代表应用 冰箱 /黑阈。
  3. 另有一派非主流,利用无障碍服务,模拟点击杀进程。如KillApps。

对我个人来说,设备管理员模式的操作过于复杂,而且有相当一部分设备不支持。主要研究了一下 Adb 模式。

冰箱 /黑阈在非 root 情况下,需要用户在每次设备重启后用 adb 运行一个脚本,这个脚本在后台起一个有 adb 权限的进程。后面需要 adb 权限时,通过 socket 和这个进程通信,利用它的 adb 权限代替执行相关操作。

但其实原生 Android 上已经有一个进程在做一模一样的事,那就是 adbd 。

熟悉 Adb 调试的人都知道,adb 调试是通过 PC 端的 adb 命令程序和 Android 设备端的 adbd 服务进程通信实现的。不管底层是走 usb 还是 tcpip,他们之间交互的协议是固定的。

那么能不能在设备端实现一个 adb 命令程序,实现在设备上对其他 App 的管理呢。

答案是肯定的,Adb 协议相当简单明了,实现并没有太大难度。https://android.googlesource.com/platform/packages/modules/adb/+/HEAD/protocol.txt

这样一来,App 获取 ADB 权限流程变成:

1.开启 adb 调试,连上设备,执行: adb tcpip 5555

2.App 中连接 localhost 的 5555 端口,发送 adb 调试证书授权,用户点确认,获取 adb 权限。

相比于冰箱 /黑阈,这个方案的好处是,利用的是 Android 官方的 adbd,后台没有任何第三方进程,不使用 App 时系统零开销。 安全性,稳定性更有保障。

利用这个原理,我写一个 App,有兴趣的 tx 可以试用一下

Ran: Rule your Apps with Adb on devices

https://play.google.com/store/apps/details?id=com.cloudmonad.ran

目前功能比较简陋,主要利用 adb 权限实现了 获取 App 运行状态,杀死 App,frozen/unfrozen(利用 pm disable/enable )

17 条回复    2021-07-13 18:02:53 +08:00

44670
    1

44670   26 天前

wifi adb 重启后会保留吗?
Jirajine
    2

Jirajine   26 天前 via Android

你说的这些主流应用,现在就是这样做的啊。Android 11 以上可以直接开启 WiFi adb,之前的需要你先通过有线连接然后开启,重启后仍然会重置。
另外 shizuku 这种服务的目的是为了能够直接使用 Java api,而不是只能用 shell 命令。
cache
    3

cache   26 天前

@44670 不能,每次重启要重新执行一下 adb tcpip 命令
cache
    4

cache   26 天前

@Jirajine 具体哪个是这么做的,至少我测试的时候他们都是起了后台进程。
shizuku 是 root 方案,不在讨论里
AoEiuV020
    5

AoEiuV020   26 天前 via Android

我记得黑域就有支持这种方案,记得是 root, wifi adb, 电脑 adb 都支持的,
Jirajine
    6

Jirajine   26 天前 via Android

@cache 这么做的是指,它们都能在开启了 WiFi adb 的情况下,直接在本地和 adbd 通讯激活。
shizuku 可以用 root 启动也可以用 adb 启动,其他的也一样。这类后台服务*主要的目的是通过 binder 导出 Java API,从而可以直接调用。直接用 adbd 的话,一来只能用 shell 指令非常麻烦,并且在用户 WiFi 断开的情况下就不能操作了。
x2009again
    7

x2009again   26 天前

不知道是不是安卓备份出来的原因,安卓 7 打开闪退,我从一个手机的 google play 下载安装后备份下来然后安装到安卓 7
cache
    8

cache   26 天前

@AoEiuV020
我*早就是从黑域的 adb 激活入坑的

你从 https://brevent.sh/安装后,ps 看一下,有两个 shell 权限后台进程。

另外还有小黑屋的麦克斯韦妖,原理都一样。

Cielsky
    9

Cielsky   26 天前 via Android

安卓 10 才需要运行 ADB tcpip 吧,11 设置里可以直接开启无线调试了
cache
    10

cache   26 天前

@Jirajine
我只测试了官方提供的激活方式

@x2009again 有 adb 日志么

cache
    11

cache   26 天前

@Cielsky adb tcpip 所有版本都支持,通用性好。

Andoid 11 以后 adb 协议增加了 A_STLS 命令,理论上不需要电脑就可以完成 adb 授权了。目前还不支持

vk42
    12

vk42   26 天前

@cache 后台是用来执行黑名单的啊,你这个就相当于是手动黑名单了,每次把需要冻的 app 手动执行一遍
cache
    13

cache   26 天前

@vk42 没错,现在只是提供了手动功能

后面也可以起个 Service 做自动清理,不需要自动功能的用户可以关闭,不影响激活

ikas
    14

ikas   25 天前

这种很早就用过了…主要问题还是 adb 的权限远远不够..
cache
    15

cache   25 天前

@ikas 够用就行

adb 权限的好处是可以稳定获取
而能 root 的手机并不是主流

pipilu
    16

pipilu   24 天前

每次得开启 wifiadb,这里有安全隐患,相对于 冰箱 /黑阈,实际还是每次都得执行 adb

如果都是在本机运行,是不是可以虚拟 usb 驱动来连接 adbd ?

cache
    17

cache   24 天前

@pipilu 安全隐患是指什么? *次连接 adb 是有认证弹窗的

都有内核驱动级权限也看不上 adbd 这点权限了吧

请教现在市面上手机, LCD 党购买建议

预算 2000 左右。google 服务是刚需。看到你们都说 k40,但是去实体店看了,屏幕实在是受不了,本身就近视。那屏幕看的眼睛累。

现在用的是 mate20 。内存太小( 64G )。看到 vivo 有款 neo5,870+LCD 屏幕+nfc 。不知道有手持的能用 google 服务么?

另外,小米的刷国际版系统的话,保修还有没?

75 条回复    2021-07-14 15:38:39 +08:00

takeshima
    1

takeshima   26 天前 via Android   ❤️ 1

k30s,有谷歌服务,lcd 屏
gesse
    2

gesse   26 天前

一加?
cydysm
    3

cydysm   26 天前

1# +1 我觉得的缺点就是太重了
XiLingHost
    4

XiLingHost   26 天前

pixel
Cooky
    5

Cooky   26 天前

opengapp 自己装
xunandotme
    6

xunandotme   26 天前

非主力机的话,强烈推荐 unihertz,哈哈,纯净安卓,国际版
Death
    7

Death   26 天前

neo5 还是 oled
neo5 活力版是 lcd,能用 google play
yyyyda
    8

yyyyda   26 天前

荣耀 v20
pipilu
    9

pipilu   26 天前

k30s 刷国际版
令 mate20 出吗
gzf6
    10

gzf6   26 天前 via Android

无码兔

israinbow
    11

israinbow   26 天前

pixel, xperia.

Jim142857
    12

Jim142857   26 天前

Google 全套服务*好用的不是 iPhone 吗 23333
ysc3839
    13

ysc3839   26 天前 via Android

没记错的话小米解锁刷机还是有保修的,即使刷了非官方的系统都有。
banricho
    14

banricho   26 天前

MIUI 不用刷机也能用 Play,直接装就行了
samsa89
    15

samsa89   26 天前

推荐 pixel 的各位是认真的吗,pixel 都是 oled 啊
ysc3839
    16

ysc3839   26 天前 via Android

@banricho 不一定的,得看是否有预装 Play 服务,小米的部分机型是没有预装的。
shoto
    17

shoto   26 天前 via Android

k30 pro 5G 刷国陆版 ,用着很爽.
nosugar
    18

nosugar   26 天前

中亚买德亚的 moto g100,日亚可能有拍照声音,国行叫 edge s,LCD PWM=0,骁龙 870
noqwerty
    19

noqwerty   26 天前 via Android

2000 左右红米 note 10 pro 可以,Google 服务都正常
jadehare
    20

jadehare   26 天前

http://storage.googleapis.com/play_public/supported_devices.html
dicbldicbl
    21

dicbldicbl   26 天前 via iPhone

k30s 其实满足楼主要求,预算更充足可以考虑 10tpro,屏幕更好
dicbldicbl
    22

dicbldicbl   26 天前 via iPhone

ios 的话 11 或者 xr 都行,就是贵一点
code4you
    23

code4you   26 天前

k30s
Wicheol
    24

Wicheol   26 天前

moto edge s
ai277014717
    25

ai277014717   26 天前

vivo 可以用 play 但是不能解锁刷机
Akariiin
    26

Akariiin   26 天前

一打 XZP (
um1ng
    27

um1ng   26 天前

K30s + 1
sevenyangdx
    28

sevenyangdx   26 天前   ❤️ 1

mate20 我觉得还能打啊,nm 扩容又方便(有双卡需求的话,我记得华为现在有官方扩容)
titanium98118
    29

titanium98118   25 天前

@nosugar #18 国行 edge s 能不能刷国外的 ROM?
banjidan
    30

banjidan   25 天前

redmi note9 5g 刚刚安装 google play,成功测试可以下载油管
Mosugar
    31

Mosugar   25 天前

我买了小米 10s
nosugar
    32

nosugar   25 天前

@titanium98118 折腾下还是可以刷的,但是升级有问题,省心还是中国亚马逊上买德亚的 moto g100,价钱加税大概接近国行的两倍
11dad
    33

11dad   25 天前 via iPhone

前阵子亚马逊的 poco x3 pro 还行 除了没有 5G 外
titanium98118
    34

titanium98118   25 天前

@nosugar #32 请问是否有刷机传送门( URL )?想了解一下
JensenQian
    35

JensenQian   25 天前 via Android   ❤️ 1

k30s,lcd 党*近的选择 you 了,海外叫小米 10T
865 和 870 没啥区别
小米刷机保修的
AndyZhuAZ
    36

AndyZhuAZ   25 天前

好像 xz2p 都不需要 2000
JensenQian
    37

JensenQian   25 天前 via Android

也不是保修吧,就是可以去售后免费 9008 刷机,反正高通的机子基本上刷炸的,9008 都救的回来的
你要刷的话刷 eu 好了,钱包问题用国内版提取再刷入就好了
nosugar
    38

nosugar   25 天前

@titanium98118 https://forum.xda-developers.com/f/motorola-moto-g100-edge-s.12173/
fightff
    39

fightff   25 天前 via Android

k30s + MIUI EU 或者其他 ROM
JensenQian
    40

JensenQian   25 天前 via Android

我觉得还是别买外版机子,你刷炸了想 9008 救下,某米的至少还能去售后
Jakarta
    41

Jakarta   25 天前

@jadehare 好像不太准。redmi note 8 pro *新的稳定版已经不支持 play 服务了,但是仍出现在列表里。
JensenQian
    42

JensenQian   25 天前 via Android

现在 2000 左右的 8 系列 LCD 机子就三个
k30s,neo5 活力版,moto edge s
k30s 和 neo5 活力国行都自带 gms,
某米你需要绑定七天才能解锁刷外版
nosugar
    43

nosugar   25 天前

LCD 手机基本快*迹了,iPhone 唯一的几款 LCD 都是因特尔基带,信号定位有点儿拉跨,可以换个思路:OLED 手机+平板电脑。平板好多 LCD 选择,9 月可能出的 iPad mini 6 是一个非常不错的日常携带选择,iPad Pro 2021 11 寸携带起来也还行,再等等会有小米平板 5,以及联想 tab p11 plus 。
JensenQian
    44

JensenQian   25 天前 via Android

@Jakarta note8pro,小米 8 这些旧机子 12.5 国行 miui 是没了,而且还不能自己装不过海外上市的肯定有国际版,国际版自带的,要用的话退回去或者自己刷外版
*近的 k40g,note9,10x 这几个玩意国行本来没有 gms 的后面 12.5 加进去了
反正海外有上市的国行没有的现在整外版,高通的外版都没有的还有 eu
JensenQian
    45

JensenQian   25 天前 via Android

%title插图%num
ToPoGE
    46

ToPoGE   25 天前

@JensenQian 也不要为所欲为,售后保底的方法是通过短接 9008 端口刷回,如果不行主板就废了,这样是不保修的
JensenQian
    47

JensenQian   25 天前 via Android

提醒一句,现在 miui 的 bug 有点多,大 bug 我没怎么遇到,就是小 bug 有点多,你可以试着刷类原生的,缺点就是国内软件压不住,想用国内的软件的话还是国行 miui,反正也是自带 gms,打开应用商店更新 play 就能用了
JensenQian
    48

JensenQian   25 天前 via Android

@ToPoGE 是的
Vegetables
    49

Vegetables   25 天前

Motorola Edge s
xiaohuihuihui616
    50

xiaohuihuihui616   25 天前

K30S 6.18 时候东哥能搞到 1800 左右没舍得下手,没想到还是价格这么硬。
今天中午刚在小米直营店看了 note10 pro,刚好现在天气热阳光强烈,屏幕亮度实在是一言难尽。
不纠结了,东哥家下单 vivo 了。
JensenQian
    51

JensenQian   25 天前

@xiaohuihuihui616 #50 也行,橘子系统挺好用的
JensenQian
    52

JensenQian   25 天前   ❤️ 1

@ToPoGE #46 %title插图%num
changchong
    53

changchong   25 天前

@gesse 一加就算了,一言难尽
JensenQian
    54

JensenQian   25 天前 via Android

@xiaohuihuihui616,neo5 活力版是 lcd,neo5 是 oled
Vindroid
    55

Vindroid   25 天前

要 root 和刷系统的话就只有 k40 是较好的选择了,neo5 刷机和 root 是个大问题
Lemeng
    56

Lemeng   25 天前

小米的新系统不清楚,以前的是可以的,一直在用
sharpy
    57

sharpy   25 天前

我 k20 刷了 pixelexperience
Admin8012
    58

Admin8012   25 天前 via Android

Edge s 刷 LOS 既可
YOKAMIA
    59

YOKAMIA   25 天前

歪个楼,Razer phone2 的屏幕是 LCD 里面*好的,2k+120hz
gmywq0392
    60

gmywq0392   25 天前

LG G5
VENIVIDIVICI
    61

VENIVIDIVICI   25 天前

Redmi k30s 至尊纪念版 (不要用 MIUI 12.5 )
flyingwings
    62

flyingwings   25 天前

g5 确实还行
wtks1
    63

wtks1   25 天前 via Android

我这个就是 neo5,可以 googleplay,有预装框架
ste
    64

ste   25 天前 via Android

Redmi K30S,或者 Mi 10T Pro 。
两者都有很多第三方机型,实际上可以看作同一款机子。机型代码一样的。
类原生的人话,官方 LOS 也有,其他的也有好多。
quxinna
    65

quxinna   25 天前

我用的 sony xperia 5 II,个人觉得 Sony 的设计比较符合我的口味
scleom
    66

scleom   25 天前

Google Pixel 4a.
2400+.
flynaj
    67

flynaj   25 天前 via Android

LCD 只能上米 6 了,除了拍照没有新手机好,其它的都可以
20015jjw
    68

20015jjw   25 天前 via Android

我至今理解不了 oled 瞎眼的说法
S179276SP
    69

S179276SP   25 天前

华为 mate 20,Google 服务稳定
S179276SP
    70

S179276SP   25 天前

@S179276SP 额当我没说
jalen
    71

jalen   25 天前

@Death #7 mtalk.google.com 能正常连接吗?
shelterz
    72

shelterz   25 天前

@S179276SP 手持 mate 20pro,前段时间 google play store 无法下载 app 可把我恶心坏了, *后还是卸载 store 重新安装搞定的。
还遇到过使用某宝买手机卡时无法上传身份证的问题,也是卸载重装某宝解决的。
0747916
    73

0747916   24 天前

这些要求必上 k30s
jerryjhou
    74

jerryjhou   24 天前 via Android

小米主板坏不保,免费 9008 换主板自费。别的毛病正常保修
OV 都自带 GMS 但功能不完整,不能解锁不能 Root 不能刷国际版
sagowave2
    75

sagowave2   24 天前

小米 11u 可以装 google play 不过不是 LCD 屏
en20
    76

en20   23 天前

自用 oppo findx2pro ,自带 google 服务,现在二手应该是 2k 多,屏幕很好,用了一年比 lcd 可强太多了

请问 pixel3 锁屏密码输错多少次会重置数据?

紧急啊之前设置了一个复杂密码
有两个月没玩就忘了
系统是安卓 10 一月份补丁
怎么办
现在是输错了 29 次了
间隔时间到了三十秒
会不会到三十次就重置数据那我要疯了

22 条回复    2021-07-17 00:25:21 +08:00

RiESA
    1

RiESA   26 天前

都玩 pixel 了,应该解 BL 了吧,直接 REC 删密码呗
leeyuzhe
    2

leeyuzhe   26 天前

都玩 pixel 了,应该登录 google 账号了吧,直接用 google 密码登录呗
missx
    3

missx   26 天前 via iPhone

@RiESA 怎么删锁屏密码
missx
    4

missx   26 天前 via iPhone

@leeyuzhe 没有登陆谷歌账号
RiESA
    5

RiESA   26 天前

@missx 进第三方 rec,然后文件管理器,删掉 data/system 里的 lock 还是什么 key 的,忘了,反正你网上搜一下就行了
Huskyubaba
    6

Huskyubaba   25 天前 via Android

输错 30 手机自爆
missx
    7

missx   25 天前 via iPhone

@RiESA android10 这样不会有风险么 会有自动保护程序什么的 另外我 pixel 3 只解锁了 没刷 twrp 好像
iColdCat
    8

iColdCat   25 天前

你都两个月没动了想必里边也没啥重要数据了吧。。
zpxshl
    9

zpxshl   25 天前 via Android

没有哪个手机厂商敢在没有用户明确操作的情况下清用户数据。
lfzyx
    10

lfzyx   25 天前

@zpxshl 但是厂商可以确认输入 30 遍错误密码的人是小偷

RiESA
    11

RiESA   25 天前

@missx #7 解锁了就行,刷个 twrp 删就行了,解锁了就你说了算,没解锁就真的没救

zpxshl
    12

zpxshl   25 天前 via Android

@lfzyx 拿头确认。
dingwen07
    13

dingwen07   25 天前 via iPhone

https://support.google.com/pixelphone/thread/80031532/how-many-attempts-to-unlock-with-pin-on-pixel-3a?hl=en

似乎是没有限制

安卓也用锁屏密码加密数据了吧,清除密码估计是不行,pixel 安全系数很高。就算好到方法暴力破解。数字密码应该可以接受,图案和长密码就不好说了。。。

anguiao
    14

anguiao   25 天前 via Android

安卓现在都是全盘加密,没有必要搞输错多少次就重置数据的操作。
xurubin
    15

xurubin   25 天前 via Android

不会重置数据,但是没锁屏密码解不出数据,BL 也绕不过。
30 次错误以后每 10 次额外错误会使尝试间隔加倍,一天封顶。https://cs.android.com/android/platform/superproject/+/master:system/gatekeeper/gatekeeper.cpp;l=291
Greatshu
    16

Greatshu   25 天前

这台 pixel 不会是专门用来二步验证,生成谷歌验证码的吧,我也干过这事,手机密码忘了,好在 authy 有备份?
cathedrel
    17

cathedrel   25 天前

@zpxshl 有,以前的黑莓手机连续输错密码十次就重置数据了
zpxshl
    18

zpxshl   25 天前 via Android

@cathedrel 好吧打脸
leeyuzhe
    19

leeyuzhe   25 天前

@missx 那只能暴力破解了,pixel 密码是全盘加密文件的,跳过锁屏也白给
missx
    20

missx   24 天前 via iPhone

@xurubin 咋办啊 各种密码都试了 想不起来了!
missx
    21

missx   24 天前 via iPhone

@zpxshl 会 iphone 就是 输错多少次密码就自动删除数据 好像都不用用户设置
20015jjw
    22

20015jjw   21 天前

@missx
> 会 iphone 就是 输错多少次密码就自动删除数据 好像都不用用户设置

iPhone 和 Pixel 都会 warning 的,就“你还有多少次机会”
如果没 warn 就是没开
pixel 默认是 10 次

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速