編輯:關於Android編程
前言
因為最近想和後台進行對接,昨天自己在Google上研究了半天關於Volley的內容,覺得很開心。因為原來覺得關於網絡這塊,一直是個很復雜的東西和流程,沒想到Google已經推出了能把這方面封裝的這麼好的工具——Volley。通過它可以方便的實現小流量的數據傳輸,而且還可以根據自己的需求進行定制。其中我還自己看看json的東西,也在這個裡面小實踐了一下。
不過看完教程,我也慢慢看懂了一件事,現在我們學的這些框架都是Google,或者第三方的大神幫我們封裝好的,我們只需要按照接口去調用就行,而學完可只是學習了別人定義的方法怎麼使用,要進一步真正了解這一塊,應該深入其中的源碼,自己去理解其中的過程,甚至創造出一個自己的框架來。
因為Volley教程的最後一講是關於自定義Request的內容,通過查看一些StringRequest或是ImageRequest,我才發現,原來這些也都是大神們一步一步用JAVA語言編寫出來的。真正要繼續走下去還有很遠的路,要相信自己。
正文
這次我一邊看教程一邊對著敲代碼,自己做了一個測試的APP,沒有講究布局什麼的,先放一個圖出來:
vcyzzLzHwrzRp7W9tcTE2sjdo7o8L3A+DQo8cD5Wb2xsZXnKx9K7uPZIVFRQtcRsaWKjrMv8yMNBbmRyb2lkIEFQULfDzsrN+MLnyv2+3bj8vNO88rWlv+y93aOsy/zT0LrctuDTxbXjo7o8L3A+DQrX1LavsLLFxc34wufH68fztfe2yCDNrMqxyrXP1rbguPbBrL3TIL/Jyei2qMfrx/O1xNPFz8i8tiDM4bmpyKHP+8frx/O1xEFQSSC3vbHjtqjWxiBhbmQgc28gb24NCjxwPlZvbGxledTaUlBDwODQzbLZ1/ejqNS2s8y197bI18rUtKOpwLS4/NDCVUm3vcPm0NTE3Ne/1L2ho8v8us2499bW0K3S6ba8xNy63LzytaW1xNX7us+jrNans9ZzdHJpbmcsIGltYWdlcywganNvbtXi0KnUrcn6yv2+3aGjVm9sbGV5ttTSu9Cpu/mxvrXEzfjC58frx/O2vL340NDBy7fi17CjrMTcsO/E473izdGz9rHg0LS7+bG+tPrC67XEv+C6o6OsyMPE47yv1tC+q8GmtKbA7bT6wuvC37ytoaM8L3A+DQo8cD5Wb2xsZXmyu8rKus+088G/tcTN+MLnwfey2df3o6zS8s6qy/zU2r3izva1xMqxuvKw0cv509C1xMfrx/O1xLfF1NrE2rTmwO+ho7bU09q088H3wb/PwtTYo6y/ydLUv7zCx9PDRG93bmxvYWRNYW5hZ2VyoaM8L3A+DQo8cD6908/CwLTIw87Sw8e/tL+01PXDtLyvs8lWb2xsZXm1vdfUvLrP7sS/wO/D5qO6PC9wPg0KPHA+tdrSu7K908NnaXQgY2xvbmUgdm9sbGV5o7o8YnIgLz4NCrTyv6rSu7j2sta/4qOsyLu689axvdPU2mdpdMnPw+bHw8nPyKXPwsPmtcS0+sLro7o8L3A+DQo8cHJlIGNsYXNzPQ=="brush:java;"> git clone https://android.googlesource.com/platform/frameworks/volley
這樣,我們就把volley項目克隆到了當前的倉庫文件夾下面了。
第二步加入自己的項目:
我用的是Andorid Studio,打開要引入Volley的項目,然後點擊File->New->Import Module,然後選中剛才的文件,這樣你就會發現除了app之外,又多了一個volley目錄。然後點擊File->Project Structure->選中APP,點擊Dependencies,選中右上角的綠色加號,選擇Module Dependency,然後選中volley就好了。
基本模式是這樣的,首先創建一個RequestQueue,然後傳遞給它一個Request對象。RequestQueue負責管理網絡操作的線程,讀取或寫入緩存,解析響應。Request負責解析原始的響應信息。先看如何用默認的RequestQueue來完成這些操作:
首先要添加訪問網絡的權限:
android.permission.INTERNET
首先我們用Volley提供的Volley.newRequestQueue方法建立一個默認的RequestQueue:
final TextView mTextView = (TextView) findViewById(R.id.text); ... // Instantiate the RequestQueue. RequestQueue queue = Volley.newRequestQueue(this); String url ="http://www.google.com"; // Request a string response from the provided URL. StringRequest stringRequest = new StringRequest(Request.Method.GET, url, new Response.Listener() { @Override public void onResponse(String response) { // Display the first 500 characters of the response string. mTextView.setText("Response is: "+ response.substring(0,500)); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { mTextView.setText("That didn't work!"); } }); // Add the request to the RequestQueue. queue.add(stringRequest);
Volley會把解析好的響應分發到主線程,我們可以方便的在主線程中通過接收到的數據來完成UI的更新,比如說顯示一張圖片。但是我們也要注意及時的取消不必要的Request,避免造成主線程的阻塞。
添加一個Request很簡單,就像之前代碼中展示的那樣,你先構造一個Request,然後用add()把它添加到RequestQueue裡面就好了。它會自動在並行的線程裡得到響應,處理網絡響應,發送給UI線程。
一旦你調用add(),Volley就會運行一個cache加工線程和網絡線程池,添加到RequestQueue之後,Volley會從先看看這個request能不能由cache處理,如果可以,直接由cache縣城處理,然後把解析過後的響應遞交到UI線程,如果不能,那這個請求就會被放在網絡隊列中,第一個可獲得的網絡線程就把它從隊列中取出來,執行HTTP操作,然後在工作線程中解析響應,把響應寫入換成,然後再把響應遞交給UI線程。
下面這張圖很清晰的描繪的整個流程:
調用cancel()裡取消一個Request,一旦取消,這個Reuqest就再也不能被調用。這意味著你可以在onStop()方法裡面取消所有的預備Request,然後不需要再看是否有Request的實例了,onSaveInstanceState()這些方法你也不必去擔心了。
用好cancel()方法能很好的處理Request,最好的途徑就是給Request添加一個Tag。我們可以給具有相同途徑的Request指定一個相同的標簽,然後在不需要它們的時候一起取消掉,合理實現了Request的分組管理。比如用ViewPaper顯示好幾屏的圖片,滑到下一頁時我們應該及時取消掉上一頁的Request,這樣它不會占著資源影響我們這一屏幕資源的加載。
下面是一個示例,分兩步:
第一步,給Request添加TAG
public static final String TAG = "MyTag"; StringRequest stringRequest; // Assume this exists. RequestQueue mRequestQueue; // Assume this exists. // Set the tag on the request. stringRequest.setTag(TAG); // Add the request to the RequestQueue. mRequestQueue.add(stringRequest);
第二步,在onStop()方法裡面取消掉所有有這個TAG的Request:
@Override protected void onStop () { super.onStop(); if (mRequestQueue != null) { mRequestQueue.cancelAll(TAG); } }
當然,這些都是根據需求來寫的,如果你想預先加載准備好,你就不要調用cancel()。
這個需求對於我這種剛入門的人應該不大,只要用Volley.newRequstQueue()創建一個默認的就好。不過以後肯定會因為需要去定制自己的RequestQueue,畢竟default並不能很好滿足項目開發。
RequestQueue需要兩個東西,一個network負責傳輸request,一個緩存負責處理緩存。在Volley的工具箱裡都有這兩種的標准實現。DiskBasedCache提供帶有內存索引的一個文件一個響應的緩存,BasicNetwork提供一個你偏好的HTTP傳輸方式的network傳輸。
RequestQueue mRequestQueue; // Instantiate the cache Cache cache = new DiskBasedCache(getCacheDir(), 1024 * 1024); // 1MB cap // Set up the network to use HttpURLConnection as the HTTP client. Network network = new BasicNetwork(new HurlStack()); // Instantiate the RequestQueue with the cache and network. mRequestQueue = new RequestQueue(cache, network); // Start the queue mRequestQueue.start(); String url ="http://www.example.com"; // Formulate the request and handle the response. StringRequest stringRequest = new StringRequest(Request.Method.GET, url, new Response.Listener() { @Override public void onResponse(String response) { // Do something with the response } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { // Handle error } }); // Add the request to the RequestQueue. mRequestQueue.add(stringRequest); // ...
如果你只想要實時的發送請求,不想把一些線程留在那裡不管,你可以在需要的時候創建一個ResqustQueue,然後在得到響應之後調用cancel()取消掉這個RequestQueue。不過更常見的一種方式是創建一個RequestQueue的單例,然後在你的APP的生命周期裡保持它一直運行著。
如果APP經常使用網絡,建立起一個單例是一個很好的選擇,因為重復的創建是不必要的,不僅更加無謂的消耗運行內存,更重要的是讓緩存的存在意義基本變為0了。這裡有兩種方式,一種是實現一個單例,裡面有RequstQueue,裡面有其他的一些Volley功能方法。另一種是實現一個Application的子類,然後在onCreate()方法裡創建一個RequestQueue,不過官方更推薦第一種方法,因為它更加模塊化。
實現單例和APP的生命周期相同,傳入一個Application的上下文context就好了,注意不是Activity的。
public class MySingleton { private static MySingleton mInstance; private RequestQueue mRequestQueue; private ImageLoader mImageLoader; private static Context mCtx; private MySingleton(Context context) { mCtx = context; mRequestQueue = getRequestQueue(); mImageLoader = new ImageLoader(mRequestQueue, new ImageLoader.ImageCache() { private final LruCachecache = new LruCache (20); @Override public Bitmap getBitmap(String url) { return cache.get(url); } @Override public void putBitmap(String url, Bitmap bitmap) { cache.put(url, bitmap); } }); } public static synchronized MySingleton getInstance(Context context) { if (mInstance == null) { mInstance = new MySingleton(context); } return mInstance; } public RequestQueue getRequestQueue() { if (mRequestQueue == null) { // getApplicationContext() is key, it keeps you from leaking the // Activity or BroadcastReceiver if someone passes one in. mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext()); } return mRequestQueue; } public void addToRequestQueue(Request req) { getRequestQueue().add(req); } public ImageLoader getImageLoader() { return mImageLoader; } }
下面是使用單例的代碼:
// Get a RequestQueue RequestQueue queue = MySingleton.getInstance(this.getApplicationContext()). getRequestQueue(); // ... // Add a request (in this example, called stringRequest) to your RequestQueue. MySingleton.getInstance(this).addToRequestQueue(stringRequest);
有時候APP訪問網絡數據希望得到string,images,json,滿足這些要求的request,Volley已經為我們封裝好了:
StringRequest 給出一個URL,返回一個原始的字符創資源 ImageRequest 給出一個URL,從response裡返回一個image JsonObjectRequest或是JsonArrayRequest都是給出一個URL,返回一個jsonobject或者是jsonarray這些都是基本的Request,如果他們能滿足你的需求,你就直接用,下一節還會講怎麼訂制自己的Request。
最開始的圖片展示展示了三張圖片,這三張圖片分別用了下面要介紹的三種加載網絡圖片的方式:
ImageRequest 給出一個圖片的URL,在回調方法裡返回一個Bitmap,它還提供了指定圖片尺寸大小的功能。它主要的好處是利用了Volley的自動調度功能,把昂貴的圖片開銷操作放在了工作worker線程(這個worker我不知道怎麼翻譯)
ImageLoader,這個類不僅能加載圖片還能把圖片緩存起來。它適合於處理大量的ImageRequest。比如所ListView的每一個Item都包含一個圖片,ImageLoader提供的緩存可以有效的方式圖片一閃而過的情況。如果能在緩存中找到圖片那麼就可以避免阻塞或是延遲UI線程。ImgaeLoader可以整合很多響應,沒有它的話基本每一個響應都要放一張圖片到View上面,布局不得不傳遞每一個圖片,整合使得同時處理多個響應成為可能,提高了加載性能。
NetworkImageView建立在ImageLoader上面,如果你的ImageView的圖片是從網絡上加載,那麼它是ImageView的一個很好的代替。如果View脫離了視圖層次,NetworkImageView也可以管理取消預備請求。給一個url,然後把加載出來的圖片顯示到View上:
ImageView mImageView; String url = "http://i.imgur.com/7spzG.png"; mImageView = (ImageView) findViewById(R.id.myImage); ... // Retrieves an image specified by the URL, displays it in the UI. ImageRequest request = new ImageRequest(url, new Response.Listener() { @Override public void onResponse(Bitmap bitmap) { mImageView.setImageBitmap(bitmap); } }, 0, 0, null, new Response.ErrorListener() { public void onErrorResponse(VolleyError error) { mImageView.setImageResource(R.drawable.image_load_error); } }); // Access the RequestQueue through your singleton class. MySingleton.getInstance(this).addToRequestQueue(request);
下面是一個使用ImageLoader的例子,給出一個URL,和一個ImageLoader.getImageListener即可:
ImageLoader mImageLoader; ImageView mImageView; // The URL for the image that is being loaded. private static final String IMAGE_URL = "http://developer.android.com/images/training/system-ui.png"; ... mImageView = (ImageView) findViewById(R.id.regularImageView); // Get the ImageLoader through your singleton class. mImageLoader = MySingleton.getInstance(this).getImageLoader(); mImageLoader.get(IMAGE_URL, ImageLoader.getImageListener(mImageView, R.drawable.def_image, R.drawable.err_image));
使用單例的方式得到ImageLoader還是很重要,這關系到用戶體驗,如果用戶每次重新打開這個Activity,你有要重新去創建一個ImageLoader,那麼會照成圖片閃過,用戶等待的不好的情況,用單例的ImageLoader把圖片緩存起來是解決這個問題的有效方式。
給一個圖片URL,還有一個ImageLoader就行:
ImageLoader mImageLoader; NetworkImageView mNetworkImageView; private static final String IMAGE_URL = "http://developer.android.com/images/training/system-ui.png"; ... // Get the NetworkImageView that will display the image. mNetworkImageView = (NetworkImageView) findViewById(R.id.networkImageView); // Get the ImageLoader through your singleton class. mImageLoader = MySingleton.getInstance(this).getImageLoader(); // Set the URL of the image that should be loaded into this view, and // specify the ImageLoader that will be used to make the request. mNetworkImageView.setImageUrl(IMAGE_URL, mImageLoader);
我們加載圖片一定會用到緩存,Volley通過DiskBasedCache類提供了一個標准的緩存實現,它能直接把文件緩存到硬盤上的特定的文件夾中,如果使用ImageLoader,我麼應該提供一個定制好的LRU bitmap cache去實現ImageLoader.ImageCache接口。而且,這個最好是單例模式,因為圖片要緩存的原因。
import android.graphics.Bitmap; import android.support.v4.util.LruCache; import android.util.DisplayMetrics; import com.android.volley.toolbox.ImageLoader.ImageCache; public class LruBitmapCache extends LruCacheimplements ImageCache { public LruBitmapCache(int maxSize) { super(maxSize); } public LruBitmapCache(Context ctx) { this(getCacheSize(ctx)); } @Override protected int sizeOf(String key, Bitmap value) { return value.getRowBytes() * value.getHeight(); } @Override public Bitmap getBitmap(String url) { return get(url); } @Override public void putBitmap(String url, Bitmap bitmap) { put(url, bitmap); } // Returns a cache size equal to approximately three screens worth of images. public static int getCacheSize(Context ctx) { final DisplayMetrics displayMetrics = ctx.getResources(). getDisplayMetrics(); final int screenWidth = displayMetrics.widthPixels; final int screenHeight = displayMetrics.heightPixels; // 4 bytes per pixel final int screenBytes = screenWidth * screenHeight * 4; return screenBytes * 3; } }
下面是一個用cache實例化ImageLoader的例子:
RequestQueue mRequestQueue; // assume this exists. ImageLoader mImageLoader = new ImageLoader(mRequestQueue, new LruBitmapCache( LruBitmapCache.getCacheSize()));
Volley提供了兩種標准的JSON請求:
JsonArrayRequest 給出一個URL,response返回一個JsonArray JsonObjectRequest 給出一個URL,response返回一個JsonObjectTextView mTxtDisplay; ImageView mImageView; mTxtDisplay = (TextView) findViewById(R.id.txtDisplay); String url = "http://my-json-feed"; JsonObjectRequest jsObjRequest = new JsonObjectRequest (Request.Method.GET, url, null, new Response.Listener() { @Override public void onResponse(JSONObject response) { mTxtDisplay.setText("Response: " + response.toString()); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { // TODO Auto-generated method stub } }); // Access the RequestQueue through your singleton class. MySingleton.getInstance(this).addToRequestQueue(jsObjRequest);
如果你的response是一個string, image,或是json,你無需自定義,而如果你向的到一些其他的response,你就要自己去定義Request了。比如說你想修改一下JsonObjectRequest,你想直接通過json得到一個對象,那麼你就要自定義Request,官方教程中的GsonRequest就是講這個。
自定義Request有兩步:
繼承Request < T >類,< T >代表了你想解析得到的響應的類型,比如說你想在response中得到string類型的接口,那麼你就應該繼承Request< String > 實現parseNetworkResponse()和deliverResponse()抽象方法一個Response裡面包括了你想要遞交的解析過後的Response:
@Override protected ResponseparseNetworkResponse需要一個NetworkResponse參數,裡面包含了the response payload as a byte[], HTTP status code, and response headers.。。。。。不知道這些是什麼。 返回一個Response< T >,T代表的你想返回的response類型,比如StringRequest裡面這個位置是Request< String >。parseNetworkResponse( NetworkResponse response) { try { String json = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); return Response.success(gson.fromJson(json, clazz), HttpHeaderParser.parseCacheHeaders(response)); } // handle errors ... }
通過這個方法,Volley會把你在parseNetworkResponse()裡面解析得到的response直接返回給UI線程。
protected void deliverResponse(T response) { listener.onResponse(response);
下面是GsonRequest的示例,Gson是一個lib,能實現json和java對象的相互轉化:
public class GsonRequestextends Request { private final Gson gson = new Gson(); private final Class clazz; private final Map headers; private final Listener listener; /** * Make a GET request and return a parsed object from JSON. * * @param url URL of the request to make * @param clazz Relevant class object, for Gson's reflection * @param headers Map of request headers */ public GsonRequest(String url, Class clazz, Map headers, Listener listener, ErrorListener errorListener) { super(Method.GET, url, errorListener); this.clazz = clazz; this.headers = headers; this.listener = listener; } @Override public Map getHeaders() throws AuthFailureError { return headers != null ? headers : super.getHeaders(); } @Override protected void deliverResponse(T response) { listener.onResponse(response); } @Override protected Response parseNetworkResponse(NetworkResponse response) { try { String json = new String( response.data, HttpHeaderParser.parseCharset(response.headers)); return Response.success( gson.fromJson(json, clazz), HttpHeaderParser.parseCacheHeaders(response)); } catch (UnsupportedEncodingException e) { return Response.error(new ParseError(e)); } catch (JsonSyntaxException e) { return Response.error(new ParseError(e)); } } }
ps: Json小知識:
Person one = new Person(); one.setAge(1); one.setName("一號人物"); Person two = new Person(); two.setName("二號人物"); two.setAge(2); Person[] personArray = new Person[]{one, two}; String jsonString = JGsonSingleton.getInstance(this).getGson().toJson(personArray); jsonText.setText("將Person對象轉化為json字符串:" + "\n" + jsonString + "\n"); Person[] jsonPerson = JGsonSingleton.getInstance(this).getGson().fromJson(jsonString, Person[].class); String personString = ""; for (Person person : jsonPerson) { personString += person.toString(); } jsonObject.setText("將json字符串轉化為Person對象: " + "\n" + personString + "\n");
自己寫的代碼,用Gson的toJson()方法直接把Person對象轉化為Json,也可以用Gson的fromJson()方法,把json字符串直接轉換為Person對象,單個對象的轉化是可以的,對象數組的轉化也是可以的。
好了,今天的內容就這麼多了。
很多朋友都說lollipop出來想試用一下,結果在網官下載的android studio 都是20版本,也沒有看見更新到android 5.0。 我也在網上狂了一下,
屬性動畫是為了彌補之前兩種動畫模式的不足之處產生的(Android3.0之後才有的),特點是 真實對view的屬性進行改動,並且能支持自定義屬性動畫, 基本上能實現所有能
留守公司就剩下幾個人了。我沒有年假故還在堅守。廢話不多說,閒來無事。想練習一下自定義控件的應用以及學習圖片類操作以及處理等等。所以我在網上找了大神文章,鴻洋大神的博客。找
微信紅包自打出世以來就極其受歡迎,搶紅包插件可謂紅極一時.今天,我們重新談談搶紅包插件的哪些事兒.本質上,搶紅包插件的原理不難理解,其過程就是在收到紅包時,自動模擬點擊.