ボレーの原理分析

序文

Android の開発とプログラミングのプロセスでは、ネットワーク プログラミングが避けられないため、開発者はネットワーク プログラミングに習熟する必要があります。はは、今日はボレーの分析から始めましょう!

1.0 ボレーとは

Volley は、2013 年の Googel I/O カンファレンスで発表された HTTP ライブラリで、Android アプリケーションがネットワーク リクエストをより簡単に実行できるようにします。ネットワークにアクセスしてデータを取得するだけでなく、ネットワークにアクセスして写真を取得することもできます。

2.0ボレーの長所と短所

ボレーには次のような利点があります。
  • ネットワークリクエストを自動的にディスパッチする
  • 大量の同時ネットワーク接続
  • 標準の HTTP キャッシュ一貫性を介してディスクにキャッシュされるメモリ透過的な応答
  • リクエストの優先順位の指定のサポート
  • リクエスト API をキャンセルするか、リクエスト キャンセル キュー内のリージョンを指定します
  • フレームワークは簡単にカスタマイズできます。たとえば、カスタムの再試行関数やコールバック関数など
  • 強い順序付けにより、ネットワーク データを非同期でロードし、それを UI に正しく表示することが容易になります。
  • デバッグおよびトレースツールが含まれています
ボレーのデメリット:
  • 大きなデータファイルのダウンロードには適していません

3.0 Volley のネットワーク リクエスト キュー

Volley を使用するプロセスは、RequestQueue(リクエスト キュー) オブジェクトを作成し、Requestそれに (リクエストを) 送信することです。

// 代码[1]

final TextView textView = (TextView) MainActivity.this.findViewById(R.id.tv);

//[1.0]创建 RequestQueue 的实例
RequestQueue requestQueue = Volley.newRequestQueue(MainActivity.this); // 1

String url = "http://gank.io/api/data/Android/10/1";

//[2.0]构造一个 request(请求)
StringRequest request = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() {
    @Override
    public void onResponse(String response) {
        textView.setText("Response is: " + response.toString());
    }
}, new Response.ErrorListener() {

    @Override
    public void onErrorResponse(VolleyError error) {
        textView.setText("Error is happenning");
    }
});

//[3.0]把 request 添加进请求队列 RequestQueue 里面
requestQueue.add(request);

上記のコードのロジックは主に、 のStringRequestインスタンスを構築し、このインスタンスを要求キューに追加することですRequestQueueVolley.newRequestQueue(Context)注 1 のメソッドのソース コードを見てみましょう。

// 源码[2]

public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
        File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);  // 1

        String userAgent = "volley/0";
        try {
            String packageName = context.getPackageName();
            PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
            userAgent = packageName + "/" + info.versionCode;
        } catch (NameNotFoundException e) {
        }

        if (stack == null) {
            if (Build.VERSION.SDK_INT >= 9) {  // 2
                stack = new HurlStack();
            } else {
                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
            }
        }

        Network network = new BasicNetwork(stack);

        RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network); // 3
        queue.start(); // 4

        return queue;
    }

public static RequestQueue newRequestQueue(Context context) {
    return newRequestQueue(context, null);
}

上のソースコードからも分かるように、 のオーバーロードメソッドnewRequestQueue(Context)です次にメソッドをnewRequestQueue(Context, HttpStack)中心に見ていきます注1では初期化によりキャッシュ(これは名詞です)が構築されています注3では5Mが割り当てられていますこのキャッシュのストレージ容量;注 2 に戻り、SDK バージョンが 9 以上、つまり Android バージョン番号が 2.3 以上の場合は、 に基づいて作成され、それ以外の場合は実行リクエストに基づいて作成されます次に、注 3 で、リクエスト キューを作成して、次のソース コードを見てみましょう。newRequestQueue(Context, HttpStack)new File(File, String)cacheDirnew DiskBasedCache(cacheDir)
HttpURLConnectionHurlStackHttpClientHttpClientStacknew RequestQueue(Cache, Network)new RequestQueue(Cache, Network)

// 源码[3]

    ...

private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;

    ...

public RequestQueue(Cache cache, Network network) {
        this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
    }
    ...

構築方法new RequestQueue(Cache, Network)ネットワークリクエストに4つのスレッドを割り当てます。このクラスのRequestQueue主な機能は、キュー内のリクエストをディスパッチするためのスレッド プールとして機能することです。呼び出し時に、受信リクエストadd(Request)(リクエスト) はキャッシュ キュー (キャッシュ) またはネットワーク キュー (ネットワーク) で解析されてから渡されます。メインスレッド、つまりコード [1] のコールバック関数に戻り、onResponse(String)コールonErrorResponse(VolleyError)バックのリクエスト内容を取得します。
コード [1] のコメント 4 で、add(Request)メソッドが呼び出されます。

// 源码[4]

public <T> Request<T> add(Request<T> request) {
        request.setRequestQueue(this);
        //同步代码块,保证 mCurrentRequests.add(request) 在一个进程里面的所有线程里面,有且只能在
        //一个线程里面执行
        synchronized (mCurrentRequests) { 
            mCurrentRequests.add(request);
        }

        request.setSequence(getSequenceNumber());
        request.addMarker("add-to-queue");

        //如果不可以存储,就把请求(request)添加进网络调度队列里面
        if (!request.shouldCache()) {
            mNetworkQueue.add(request);  
            return request;
        }

        //如果可以存储:
        //同步代码块
        synchronized (mWaitingRequests) {  
            String cacheKey = request.getCacheKey();
            //如果之前有相同的请求并且还没有返回结果的,就把此请求加入到 mWaitingRequests 里面
            if (mWaitingRequests.containsKey(cacheKey)) {   
                Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
                //如果没有请求在进行中,重新初始化 Queue<Request<?>> 的实例
                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 {
                //如果没有的话,就把请求添加到 mCacheQueue 队列里面
                mWaitingRequests.put(cacheKey, null);
                mCacheQueue.add(request);  
            }
            return request;
        }
    }
}

上記のメソッドのソース コードからadd(Request)わかるように、主なロジックは次のとおりです: リクエスト (リクエスト) をキャッシュできない場合は直接キャッシュ キューに追加され、そうでない場合はキャッシュ キューに追加されます。メソッドのソース コードを表示すると、リクエストを返してメソッドmNetworkQueueを呼び出します(ソース コード [2] の注 4 など) 。mCacheQueuestart()start()

// 源码[5]

public void start() {
        stop();  // 确保当前的缓存调度和网络调度都已经停止
        mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
        mCacheDispatcher.start();

        for (int i = 0; i < mDispatchers.length; i++) {
            NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                    mCache, mDelivery);
            mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }
    }

主に、CacheDispatcher(BlockingQueue<Request<?>>, BlockingQueue<Request<?>>, Cache, ResponseDelivery)のインスタンスを作成して を通じてmCacheDispatcher.start()キャッシュ スケジューリング スレッドを開始し、 を通じてNetworkDispatcher(BlockingQueue<Request<?>>,Network, Cache,ResponseDelivery)のインスタンスを作成してnetworkDispatcher.start()ネットワーク スケジューリング スレッドを開始します。

4.0 ネットワーク ディスパッチャ スレッド NetworkDispatcher

ネットワーク スケジューリング スレッドは、から継承されたスレッドNetworkDispatcherですThread。そのタスク メソッドのソース コードを確認すると、次のようになりますrun()

// 源码[6]

@Override
    public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        Request<?> request;
        while (true) {
            try {  
                // 从网络队列中取出请求      
                request = mQueue.take();
            } catch (InterruptedException e) {           
                if (mQuit) {
                    return;
                }
                continue;
            }

            try {
                request.addMarker("network-queue-take");

                // 如果请求被取消
                if (request.isCanceled()) {
                    request.finish("network-discard-cancelled");
                    continue;
                }

                addTrafficStatsTag(request);

                NetworkResponse networkResponse = mNetwork.performRequest(request);
                request.addMarker("network-http-complete");

                if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                    request.finish("not-modified");
                    continue;
                }

                Response<?> response = request.parseNetworkResponse(networkResponse);
                request.addMarker("network-parse-complete");

                // 把网络请求到的实体缓存到 源码[2] 的注释 1 处的本地缓存文件里面
                if (request.shouldCache() && response.cacheEntry != null) {
                    mCache.put(request.getCacheKey(), response.cacheEntry);
                    request.addMarker("network-cache-written");
                }

                request.markDelivered();
                // 回调请求到的响应给主线程
                mDelivery.postResponse(request, response);
            } catch (VolleyError volleyError) {
                ...

            } catch (Exception e) {
                ...

            }
        }
    }

ネットワーク スケジューリングの主なロジックは、まずネットワーク リクエストがキャンセルされたかどうかを判断し、キャンセルされていない場合は、リクエストmNetwork.performRequest(request)ネットワークの応答を実行します。応答を取得した後、まずローカルにキャッシュし、その後メイン スレッドにコールバックします。

5.0 キャッシュ スケジューリング スレッド CacheDispatcher

キャッシュ スケジューリング スレッドのソース コードCacheDispatcherは次のとおりです。

@Override
    public void run() {
        if (DEBUG) VolleyLog.v("start new dispatcher");
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

        mCache.initialize();

        while (true) {
            try {            
                final Request<?> request = mCacheQueue.take();
                request.addMarker("cache-queue-take");

                //如果请求被取消
                if (request.isCanceled()) {
                    request.finish("cache-discard-canceled");
                    continue;
                }

                // 尝试在本地缓存中取回数据
                Cache.Entry entry = mCache.get(request.getCacheKey());
                if (entry == null) {
                    //本地缓存丢失或者没有
                    request.addMarker("cache-miss");                   
                    mNetworkQueue.put(request);
                    continue;
                }

                // 本地缓存过期
                if (entry.isExpired()) {
                    request.addMarker("cache-hit-expired");
                    request.setCacheEntry(entry);
                    mNetworkQueue.put(request);
                    continue;
                }

                // 命中缓存
                request.addMarker("cache-hit");
                Response<?> response = request.parseNetworkResponse(
                        new NetworkResponse(entry.data, entry.responseHeaders));
                request.addMarker("cache-hit-parsed");

                if (!entry.refreshNeeded()) {
                    // 回调相应给主线程
                    mDelivery.postResponse(request, response);
                } else {

                    request.addMarker("cache-hit-refresh-needed");
                    request.setCacheEntry(entry);

                    response.intermediate = true;

                    mDelivery.postResponse(request, response, new Runnable() {
                        @Override
                        public void run() {
                            try {
                                mNetworkQueue.put(request);
                            } catch (InterruptedException e) {
                                ...
                            }
                        }
                    });
                }

            } catch (InterruptedException e) {
                ...
            }
        }
    }

同様に、リクエストがキャンセルされない場合は、ローカル キャッシュが取得されます。ローカル キャッシュが存在しないか、失われた場合、または期限切れの場合、リクエストはネットワーク リクエスト キューに追加されます。ローカル キャッシュがヒットすると、キャッシュされた応答が返されます。メインスレッドにコールバックされます。

6.0 ボレー原理の分析

リクエストを送信するには、リクエストを作成し、add()メソッドを介してリクエストを追加するだけですRequestQueueリクエストが追加されると、キューを通過し、一連のディスパッチを経て、生の応答データが返されます。

メソッドが実行されるとadd()、Volley はキャッシュ ハンドラー スレッドと一連のネットワーク ハンドラー スレッドの実行をトリガーします。リクエストをキューに追加すると、リクエストはキャッシュ スレッドによってキャッチされ、トリガーされます。リクエストがキャッシュで処理できる場合、応答データの解析がキャッシュ スレッドで実行され、メイン スレッドに返されます。リクエストがキャッシュで処理できない場合、リクエストはネットワーク キューに入れられます。ネットワーク スレッド プール内の最初の利用可能なネットワーク スレッドは、キューからリクエストを取得して HTTP 操作を実行し、ワーカー スレッドの応答データを解析し、データをキャッシュに書き込み、解析されたデータをメイン スレッドに返します。

リクエストのライフサイクル

リクエストのライフサイクル

まとめ

この時点で、Volley ライブラリの分析は一時的に終了します。今回は、リクエストがリクエストキューに追加されてからレスポンスが返されるまでのプロセスを主に分析し、Volley がどのようにリクエストをスケジュールし、レスポンスを取得して返すかを理解します。笑 読んでいただきありがとうございます...


ソースコードのダウンロードTestVolley

おすすめ

転載: blog.csdn.net/HongHua_bai/article/details/78308331