Android Networking I: OkHttp, Volley and Gson

英文链接:https://medium.com/android-news/android-networking-i-okhttp-volley-and-gson-72004efff196#.ogjc1kdf6

Prologue

This is the first article of a series:(后面跟进)

  • Android Networking II: OkHttp, Retrofit, Moshi and Picasso (coming article)
  • Android Networking III: Ion (coming article)
  • Android Networking IV: Android Async Http (coming article)

Assumptions

Motivation

There’s something that probably you can’t avoid in an Android project: Networking. Whether you are loading images, requesting data from an API server or getting a single byte from internet, you are doing networking.

Given how complex and omnipresent networking is on Android, one of the questions Android developers are facing nowadays is which solutions should I use? There are a lot of good libraries out there and you can stack one upon another in different ways.

The root of so many great networking libraries is that the offered options in the Android framework are not great and were a mess to deal with in the old days (Eclair, Froyo and Gingerbread). You had to write a lot of boilerplate code each time you were doing networking and probably you’ll be doing a sub-optimal (a.k.a. poor) job. This was the ideal scenario to solve once for all a really big and important problem and libraries started to appear and evolve.

In the old days networking in Android was a nightmare, nowadays the problem is to find out which solution best fits the project necessities.

In this article we’ll talk about a particular solution: OkHttp, Volley and Gson.

The aim of this article is just share my discoveries and experiences, learn from others and maybe help some people.

OkHttp

OkHttp is an modern, fast and efficient Http client which supports HTTP/2 and SPDY and does a lot of stuff for you. Reading how many things OkHttp does it’s a good way to understand how hard networking is: Connection pooling, gziping, caching, recovers from network problems, sync and async calls, redirects, retries … and so on.

OkHttp is a very capable networking tool out of the box, without the need of any REST library (Retrofit, Volley…) and probably is the library most developers would choose if they could only include one library in their projects.

OkHttp sits on top of Okio, a library that complements java.io and java.nio to make it much easier to access, store, and process your data. It provides fast I/O and resizable buffers.

OkHttp depends Okio, but Okio can be used by its own.

OkHttp is an modern, fast and efficient Http client which supports HTTP/2 and SPDY and sits on top of Okio.

Volley

Volley is a REST client that makes easy common networking tasks. Takes care of requesting, loading, caching, threading, synchronization and some more stuff. It’s ready to deal with JSON, images, caching, raw text and allow some customization.

Volley was design for RPC style network operations that populate the UI. Is good for short operations.

Volley by default uses as transport layer the Apache Http stack on Froyo and HttpURLConnection stack on Gingerbread and above. The reason is there are problems with those http stacks have on different Android versions.

Volley allow us to easily set up OkHttp as its transport layer.

Volley was developed by Google.

This is what Android networking looks like in Ficus Kirkpatrick (a Googler behind Volley) words. A lot of parallel async calls.

Volley takes care of requesting, loading, caching, threading, synchronization and more. It’s ready to deal with JSON, images, caching, raw text and allow some customization.

Gson

Gson is JSON serialization and deserialization library that uses reflection to populate your Java model objects from JSON objects. You can add your own serializers and deserializers as well to better control and customization.

Gson was developed by Google.

Setup

Gradle dependencies in Android Studio

You need to add the next lines to your app’s build.gradle file.

compile 'com.google.code.gson:gson:2.4.0'
compile 'com.squareup.okhttp:okhttp:2.5.0'
compile 'com.mcxiaoke.volley:library:1.0.19'

Note: The versions may be different as they are updated. Try to avoid + syntax on version numbers as is recommended.

It´s not necessary to explicitly include the Okio dependency because OkHttp already has it.

Update: As of SDK 23, apache http client was removed so it´s necessary to include in the next line in the android section of your build.gradle:

useLibrary 'org.apache.http.legacy'

All the dependencies above are official but Volley, that is not official but is trustworthy. There’s not official gradle dependency for Volley as far as I know as I’m writing this.

Volley

The way Volley works is creating requests and adding them to a queue. One queue is enough for the whole application, so each time you want to make a request you’ll get the (only) Volley queue to add the request to that queue.

I’m using a global application singleton instance of the queue with the next method:

/**
 * Returns a Volley request queue for creating network requests
 *
 * @return {@link com.android.volley.RequestQueue}
 */
public RequestQueue getVolleyRequestQueue()
{
   if (mRequestQueue == null)
   {
      mRequestQueue = Volley.newRequestQueue(this, new OkHttpStack(new OkHttpClient()));
   }
   return mRequestQueue;
}

The method we are using to create a new request queue has an HttpStack as a parameter. If you use the method that don’t provide an HttpStack Volley will create an stack depending on your API level. (based on theAndroidHttpClient for API level 9 and and HttpURLConnection stack for API level 10 and above)

As I mention before, we’d like to use OkHttp as our transport layer, that’s the reason we are using as a parameter an OkHttpStack. The OkHttpClient implementation I’m using is this one.

The next are methods to add the requests to the Volley requests queue:

/**
 * Adds a request to the Volley request queue with a given tag
 * 
 * @param request is the request to be added
 * @param tag is the tag identifying the request
 */
public static void addRequest(Request<?> request, String tag)
{
    request.setTag(tag);
    addRequest(request);
}
/**
 * Adds a request to the Volley request queue
 * 
 * @param request is the request to add to the Volley queue
 */
public static void addRequest(Request<?> request)
{
    getInstance().getVolleyRequestQueue().add(request);    
}

And this is the method to cancel requests that should normally used in theonStop lifecycle method.

/**
 * Cancels all the request in the Volley queue for a given tag
 *
 * @param tag associated with the Volley requests to be cancelled
 */
public static void cancelAllRequests(String tag)
{
    if (getInstance().getVolleyRequestQueue() != null)
    {
        getInstance().getVolleyRequestQueue().cancelAll(tag);
    }
}

So far we already have Volley and OkHttp ready. So we can start making either String, JsonObject or JsonArray request.

A JsonObject request would be like this:

JsonObjectRequest jsonObjectRequest =
        new JsonObjectRequest(Request.Method.GET, mUrl, new Response.Listener<JSONObject>()
        {
            @Override
            public void onResponse(JSONObject response)
            {
                // Deal with the JSONObject here
            }
        },
        new Response.ErrorListener()
        {
            @Override
            public void onErrorResponse(VolleyError error)
            {
                // Deal with the error here
            }
        });

App.addRequest(jsonObjectRequest, mTAG);

We still need to parse the JSON object to our Java model. The response we are receiving on every Volley request (either String, JsonObject or JsonArray) is not really useful as it is.

You are not alone in the Android networking world.

Gson

We can customize the request to get as responses Java objects that match our data model and we are more comfortable with. All it’s needed is aGsonRequest class extending the Volley Request like in this example.

In the next example we can how would be a GET call to retrieve and parse a Json object:

/**
 * Returns a dummy object parsed from a Json Object to the success  listener and a Volley error to the error listener
 *
 * @param listener is the listener for the success response
 * @param errorListener is the listener for the error response
 *
 * @return @return {@link com.sottocorp.sotti.okhttpvolleygsonsample.api.GsonGetRequest}
 */
public static GsonRequest<DummyObject> getDummyObject
(
        Response.Listener<DummyObject> listener,
        Response.ErrorListener errorListener
)
{
    final String url = "http://www.mocky.io/v2/55973508b0e9e4a71a02f05f";

    final Gson gson = new GsonBuilder()
            .registerTypeAdapter(DummyObject.class, new DummyObjectDeserializer())
            .create();

    return new GsonRequest<>
            (
                    url,
                    new TypeToken<DummyObject>() {}.getType(),
                    gson,
                    listener,
                    errorListener
            );
}

In the next example we can how would be a GET call to retrieve and parse a Json array:

/**
 * Returns a dummy object's array in the success listener and a Volley error in the error listener
 *
 * @param listener is the listener for the success response
 * @param errorListener is the listener for the error response
 *
 * @return @return {@link com.sottocorp.sotti.okhttpvolleygsonsample.api.GsonGetRequest}
 */
public static GsonRequest<ArrayList<DummyObject>> getDummyObjectArray
(
        Response.Listener<ArrayList<DummyObject>> listener,
        Response.ErrorListener errorListener
)
{
    final String url = "http://www.mocky.io/v2/5597d86a6344715505576725";

    final Gson gson = new GsonBuilder()
            .registerTypeAdapter(DummyObject.class, new DummyObjectDeserializer())
            .create();

    return new GsonRequest<>
            (
                    url,
                    new TypeToken<ArrayList<DummyObject>>() {}.getType(),
                    gson,
                    listener,
                    errorListener
            );
}

The Gson parsing with a GsonRequest happens on the background worker thread instead of the main thread.

I’m providing a deserializer in the examples above but note that is not mandatory to provide serializers/deserializers and Gson can handle this very well as far as the field names in the class match (including the case) with the names in the JSON file. I like to provide my serializer/deserializer for customization.

On both examples above we are making GET calls. In case the call is a POST one I’ve included an for a GsonPostRequest and how to use it.

OkHttp works as the transport layer for Volley, which on top of OkHttp is a handy way of making network requests that are parsed to Java objects by Gson just before delivering the response to the main thread

Loading images

Image loading in Android is common and complex. Threading, requesting, transformations, memory management, caches…Probably we could right an entire article about it. The good news is there are out there so many good libraries that most of us will never realize how hard it is.

ImageLoader and NetworkImageView

Volley has a custom view called NetworkImageView (subclassingImageView) that is very handy to load images. You can set an URL, a default view holder and an error image.

Example:

mNetworkImageView = (NetworkImageView) itemView.findViewById(R.id.networkImageView);
mNetworkImageView.setDefaultImageResId(R.drawable.ic_sun_smile);
mNetworkImageView.setErrorImageResId(R.drawable.ic_cloud_sad);
mNetworkImageView.setImageUrl(imageUrl, App.getInstance().getVolleyImageLoader());

The important bit in the code above is the setImageUrl method, which receives two parameters: the image address and an ImageLoader (Volleyhelper that handles loading and caching images from remote URLs)

Let’s take a look at the getVolleyImageLoader method and how we can get an ImageLoader.

/**
 * Returns an image loader instance to be used with Volley.
 *
 * @return {@link com.android.volley.toolbox.ImageLoader}
 */
public ImageLoader getVolleyImageLoader()
{
    if (mImageLoader == null)
    {
        mImageLoader = new ImageLoader
                (
                        getVolleyRequestQueue(),
                        App.getInstance().getVolleyImageCache()
                );
    }

    return mImageLoader;
}

/**
 * Returns a bitmap cache to use with volley.
 *
 * @return {@link LruBitmapCache}
 */
private LruBitmapCache getVolleyImageCache()
{
    if (mLruBitmapCache == null)
    {
        mLruBitmapCache = new LruBitmapCache(mInstance);
    }
    return mLruBitmapCache;
}

The only piece missing in this puzzle is LruBitmapCache. Volley does not provide us with an implementation but we can get one from here that looks appropriate and handles the cache size per device specs, which is cool.

ImageRequest

In some cases we might want not to use NetworkImageView. Image for example we want circular images and we are using CircleImageView. In that case we’ll have to use ImageRequest, which works like this:

final CircleImageView circleImageView =
            (CircleImageView) findViewById(R.id.circularImageView);

    // Retrieves an image specified by the URL, displays it in the UI.
    final com.android.volley.toolbox.ImageRequest imageRequest =
            new ImageRequest
            (
                    mImageUrl,
                    new Response.Listener<Bitmap>()
                    {
                        @Override
                        public void onResponse(Bitmap bitmap)
                        {
                            circleImageView.setImageBitmap(bitmap);
                        }
                    },
                    0,
                    0,
                    ImageView.ScaleType.CENTER_INSIDE,
                    null,
                    new Response.ErrorListener()
                    {
                        public void onErrorResponse(VolleyError error)
                        {          circleImageView.setImageResource(R.drawable.ic_cloud_sad);
                        }
                    }
            );
    // Access the RequestQueue through your singleton class.
    App.getInstance().getVolleyRequestQueue().add(imageRequest);
}

Interesting facts

  • All of the components we’ve talk about in this article (Okio, OkHttp, Volley and Gson) can be used as a standalone. They don’t need each other except OkHttp needing Okio.
  • One of the first articles I linked in the introduction (this one) was written by Jesse Wilson. Jesse Wilson is one of the guys behind Android’s HTTP, Gson, OkHttp and Okio. I thought that deserves a mention and my acknowledgement.
  • OkHttp engine is backing HttpURLConnection as of Android 4.4. Twitter, Facebook and Snapchat bundles it as well.
  • Volley was born as a solution to be used in the Google Play Store inside Google.
  • Both OkHttp and Okio are developed by the Square guys.

Final thoughts

The Volley/Gson solution is mature and was quite popular around 2013 and 2014 mainly for been a Google solution and appear in the Android Developers website. Is still a good option to use because it just works well and it´s fast.

On the other hand, the documentation was always poor (still nowadays) and they are not being actively developed anymore (and for a while).

Resources

Show me the Code

Why you shouldn’t use delegates in Swift

https://medium.com/cobe-mobile/why-you-shouldn-t-use-delegates-in-swift-7ef808a7f16b#.ahzia61z6

The delegate pattern is okay. But there’s a better way.

You’re building an iOS app. You have your nice, SOLID architecture in place. You have a model, a networking layer, a UI layer, and maybe some helpers in between. The textbook way you would pass data around those layers would be trough delegation, a really useful and common pattern in iOS development.

Delegation simply means that you pass yourself to someone else, when you want that someone to notify you of changes, so you can react to them. For instance, if a ViewController talks to a network service, and wants to get notified when that service is done with some request, it would make itself the network service’s delegate. The network service would then call the delegate methods when it’s done.

protocol NetworkServiceDelegate {
    func didCompleteRequest(result: String)
}
class NetworkService {
    var delegate: NetworkServiceDelegate?
    
    func fetchDataFromUrl(url: String) {
        API.request(.GET, url) { result in
            delegate?.didCompleteRequest(result)
        }
    }
}
class MyViewController: UIViewController, NetworkServiceDelegate {
    
    let networkService = NetworkService()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        networkService.delegate = self
    }
    func didCompleteRequest(result: String) {
        print("I got \(result) from the server!")
    }
}

Passing delegate references is fine. There’s nothing functionally wrong with it. But there’s a better way, and I’ll tell you why it’s better.


Using callbacks for delegation

Callbacks are similar in function to the delegate pattern. They do the same thing: letting other objects know when something happened, and passing data around.

What differentiates them from the delegate pattern, is that instead of passing a reference to yourself, you are passing a function. Functions are first class citizens in Swift, so there’s no reason why you wouldn’t have aproperty that is a function!

class MyClass {
  var myFunction: (String)->() = { text in
    print(text)
  }
}

MyClass now has a myFunction property that it can call and anyone can set (since properties are internal by default in Swift). This is the basic idea of using callbacks instead of delegation. Here’s the same example as before but with callbacks instead of a delegate:

class NetworkService {
    
    var onComplete: ((result: String)->())? //an optional function
    
    func fetchDataFromUrl(url: String) {
        API.request(.GET, url) { result in
            onComplete?(result: result) 
//                    ^ optional unwrapping for functions!
        }
    }
}
class MyViewController: UIViewController {
    
    let networkService = NetworkService()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        networkService.onComplete = { result in
            print("I got \(result) from the server!")
        }
    }
}

Another great way to use callbacks is when you want to get notified data has been changed. You can do this by calling the callback in a property observer:

var onUsernamesChanged: ([String]->())?
    
var loadedUsernames = [String]() {
    didSet {
        onUsernamesChanged?(loadedUsernames)
    }
}

Quick note about callbacks: Much like delegates should be weak properties to stop retain cycles, you should catch self as a weak variable inside the closure.


So why are callbacks better?

1. Decoupling

Delegates lead to pretty decoupled code. It doesn’t matter to theNetworkService who its delegate is, as long as they implement the protocol. However, the delegate has to implement the protocol, and if you’re using Swift instead of @objc protocols, the delegate has to implement every method in the protocol. (since there’s no optional protocol conformance)

When using callbacks, on the other hand, the NetworkService doesn’t even need to have a delegate object to call methods on, nor does it knowanything about who’s implementing those methods. All it cares about is when to call those methods. Also, not all of the methods need to be implemented.

2. Multiple delegation

What if you want to notify a ViewController when a request finishes, but maybe also a some sort of logger class, and some sort of analytics class.

With delegates, you would have to have an array of delegates, or three different delegate properties that might even have different protocols! (I’ll be the first to admit I’ve done this)

With callbacks, however, you could define an array of functions (I love Swift) and call each of those when something’s done. There’s no need to have a bunch of different objects and protocols risking retain cycles and writing boilerplate code.

3. Clearer separation of concerns

The way I see the difference between delegates and callbacks is that with delegates, the NetworkService is telling the delegate “Hey, I’ve changed.” With callbacks, the delegate is observing the NetworkService.

In reality, the difference is minimal, but thinking in the latter way helps prevent anti-patterns often found with delegation, like making the NetworkService transform results for presentation, which should not be its job!

4. Easier testing!

Ever felt like your codebase is twice as big with unit tests, because you have to mock every protocol, including all of the delegates in your app?

With callbacks, not only do you not have to mock any delegates, but it lets use use whatever callback you want in each test!

In one test, you might test if the callback gets called, then in another you might test if it’s called with the right results. And none of those require a complicated mocked delegate with someFuncDidGetCalled booleans and similar properties.


I personally think callbacks lead to clearer code and tests, and are a muchSwiftier (and better) way to pass data around in your app. I hope you’ve learnt something new today, and good luck out there!

iOS新手入门常见问题1-UITableViewController相关

UITableViewController的TableViewCell的高度自定义

self.tableView.estimatedRowHeight = 88;
self.tableView.rowHeight = UITableViewAutomaticDimension; 

据说这个自动调整rowHeight一事,在iOS前几个版本中比较混乱,存在很多历史遗留问题,这里初学者就不去追究了。 PS:听说iOS8以后系统中rowHeight的默认值已经设置成了UITableViewAutomaticDimension,所以第二行代码可以省略。 是真的吗?我在iOS9上试了一下,是真的。

自定义TableViewCell的布局约束一定不要Cell,指向其ContentView才可以

这一点和上面第一条有关联性,若此处设置约束的参照对象错误,则极有可能TabelViewCell的高度还是实现不了自定义。

UITableViewController中的tableView的TableViewCell要自定义。 对自定义TableViewCell的布局做约束时,不能把里面子控件的参照约束指向Cell本身,一定要指向其ContentView才行。

UILabel一行显示不开,自动分行

label.lineBreakMode = NSLineBreakByWordWrapping;
label.numberOfLines = 0; 

UINavigationController左中右导航项目分别设置

首先左边的导航项目,一般是默认的一个小于号箭头< ,和上一个页面的title

导航条的左边返回键只想要小于号箭头<,不要文字显示

self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:nil]; 

这里我遇到了一个坑,具体是这样的:

从A画面 跳转到 B画面

B画面导航条左侧的返回导航中 我不想要A画面的Title,只想要一个<箭头。

在B画面对应的类中修改上述代码,怎么都不能成功。

而把修改backButtonItem的代码放到A画面对应的类中才可以实现效果。

神奇吧,是我哪地方理解错了吗?我还不知道这是神马原因。

导航条的中间的标题动态设置

这个好办 self.title = @”自定义标题”;

导航条的右边添加自定义项目

下面的例子是添加一个Add默认样式的菜单项目

self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(rightItemEvent:)]; 

最后别忘记添加右侧菜单的点击事件 – (void)rightItemEvent:(id)sender { NSLog(@”rightBarButtonItem Clicked…”); }

UIImageView作为父控件(容器)上放置一个UIButton

UIImageView继承自UIView,都是可以当做容器来使用的。

我想把一个UIButton放到一个UIImageView容器之中。直接在StoryBoard上拖拽控件是不能把UIButton放到UIImageView的子控件之中的。

但布局约束等信息可以在StoryBoard可视化的创建与管理,所以这些活就可以在StoryBoard利用Xcode的先进性完成。 把UIButton设置为UIImageView的子控件就可以用代码实现了。

[_imgView addSubview:btn]; 

另外为了让UIImageView上放置的UIButton能点击,还要加上下面一句:

imageView.userInteractionEnabled = YES;