Volley源碼分析

以StringRequest為例子,分析Volley是怎樣執(zhí)行一個(gè)網(wǎng)絡(luò)請(qǐng)求的某残。

先看實(shí)現(xiàn)Request抽象類的StringRequest

public class StringRequest extends Request<String> {
    private Listener<String> mListener;

    /**
     * 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) {
        super(method, url, errorListener);
        mListener = listener;
    }
}

Request有3個(gè)參數(shù):

  • method 對(duì)應(yīng)請(qǐng)求的方式
  • url 請(qǐng)求的地址
  • errorListener 發(fā)生錯(cuò)誤時(shí)回調(diào)

StringRequest多了一個(gè)自己的listener作為成功請(qǐng)求的回調(diào)接口驮俗。
StringRequest實(shí)現(xiàn)了parseNetworkResponse方法。這個(gè)方法會(huì)傳入一個(gè)NetworkResponse對(duì)象,包裝了請(qǐng)求的響應(yīng)結(jié)果琳状。

然后根據(jù)響應(yīng)結(jié)結(jié)果header里的編碼格式構(gòu)造一個(gè)String對(duì)象,最后使用封裝了最終請(qǐng)求的Response類構(gòu)造一個(gè)代表成功的response返回硝桩。

@Override
protected Response<String> parseNetworkResponse(NetworkResponse response) {
    String parsed;
    try {
        //根據(jù)編碼格式構(gòu)造字符串
        parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
    } catch (UnsupportedEncodingException e) {
        //如果格式不支持編碼沿猜,就構(gòu)造一個(gè)默認(rèn)的UTF-8編碼的字符串
        parsed = new String(response.data);
    }

    return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
}

NetworkResponse封裝了具體的請(qǐng)求內(nèi)容:

public class NetworkResponse implements Serializable{
    /**
     * @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) {
        this.statusCode = statusCode;
        this.data = data;
        this.headers = headers;
        this.notModified = notModified;
        this.networkTimeMs = networkTimeMs;
    }
}
  • StringReqeust 負(fù)責(zé)封裝請(qǐng)求
  • NetworkResponse 負(fù)責(zé)封裝從服務(wù)器返回的請(qǐng)求
  • Response 負(fù)責(zé)構(gòu)造最終的結(jié)果。
一個(gè)Volley RequestQueue的創(chuàng)建到運(yùn)行

RequestQueue的創(chuàng)建

Volley.newRequestQueue有幾個(gè)重載方法碗脊,最終都會(huì)執(zhí)行這個(gè):

public static RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) {
    File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
    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) {
            stack = new HurlStack();
        } else {
            stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
        }
    }
    Network network = new BasicNetwork(stack);
    
    RequestQueue queue;
    if (maxDiskCacheBytes <= -1){
        queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
    }
    else{  
        queue = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network);
    }
    queue.start();
    return queue;
}

其中執(zhí)行了一些對(duì)象的創(chuàng)建工作:

  1. 創(chuàng)建緩存文件啼肩,文件名默認(rèn)為volley
  2. 創(chuàng)建一個(gè)UserAgent字符串,代表HTTP頭里的客戶端身份衙伶。默認(rèn)為包名+APPb版本號(hào)祈坠。
  3. 創(chuàng)建執(zhí)行網(wǎng)絡(luò)請(qǐng)求的工具。HurlStack(Android 2.3及以上)或HttpClientStack(Android 2.3以下)矢劲。Stack負(fù)責(zé)真正的HTTP請(qǐng)求赦拘。HurlStack使用的是HttpURLConnection;HttpClientStack使用的是HttpClient
  4. 創(chuàng)建一個(gè)Netwoker對(duì)象芬沉。Netwoker通過調(diào)用Stack進(jìn)行網(wǎng)絡(luò)訪問躺同,并將執(zhí)行結(jié)果封裝為NetworkResponse對(duì)象。
  5. 創(chuàng)建一個(gè)RequestQueue對(duì)象丸逸,同時(shí)創(chuàng)建一個(gè)DiskBasedCache緩存對(duì)象蹋艺,作為本地緩存。
  6. RequestQueue創(chuàng)建完畢之后椭员,就調(diào)用queue.start()開始不斷執(zhí)行添加到RequestQueue中的請(qǐng)求车海。

RequestQueue的創(chuàng)建:

RequestQueue有3個(gè)構(gòu)造函數(shù):

//最終調(diào)用
public RequestQueue(Cache cache, Network network, int threadPoolSize,
        ResponseDelivery delivery) {
    mCache = cache;
    mNetwork = network;
    mDispatchers = new NetworkDispatcher[threadPoolSize];
    mDelivery = delivery;
}

public RequestQueue(Cache cache, Network network, int threadPoolSize) {
    //會(huì)調(diào)用最終的構(gòu)造函數(shù)
    this(cache, network, threadPoolSize,
            new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}

public RequestQueue(Cache cache, Network network) {
    //會(huì)調(diào)用第二個(gè)
    this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
}

三個(gè)構(gòu)造函數(shù)最終都是調(diào)用最上面那個(gè)。第二個(gè)構(gòu)造函數(shù)隘击,則是創(chuàng)建了一個(gè)ExecutorDelivery對(duì)象侍芝,并在創(chuàng)建時(shí)傳入了擁有UI線程的handler
可見ExecutorDelivery是與主線程打交道的工具埋同。

最后一個(gè)構(gòu)造函數(shù)州叠,則創(chuàng)建了:

  • 一個(gè)NetworkDispatcher數(shù)組,數(shù)組大小為threadPoolSize凶赁,默認(rèn)為4咧栗。

NetworkDispatcher

NetworkDispatcher繼承Thread。RequestQueue在創(chuàng)建時(shí)虱肄,創(chuàng)建了一個(gè)NetworkDispatcher數(shù)組致板,實(shí)際就是創(chuàng)建了一個(gè)線程數(shù)組。

public class NetworkDispatcher extends Thread {
    /** The queue of requests to service. */
    private final BlockingQueue<Request<?>> mQueue;
    /** The network interface for processing requests. */
    private final Network mNetwork;
    /** The cache to write to. */
    private final Cache mCache;
    /** For posting responses and errors. */
    private final ResponseDelivery mDelivery;
    /** Used for telling us to die. */
    private volatile boolean mQuit = false;
}

NetworkDispatcher擁有:

  • BlockingQueue<Request<?>> 一個(gè)保存者Request的阻塞隊(duì)列
  • Network 執(zhí)行網(wǎng)絡(luò)訪問咏窿,并返回結(jié)果
  • Cache 本地緩存
  • ResponseDelivery 負(fù)責(zé)與UI線程打交道斟或。ReqeustQueue在創(chuàng)建時(shí),創(chuàng)建的ExecutorDelivery就是一個(gè)實(shí)現(xiàn)了ResponseDelivery接口的類集嵌。
  • volatile boolen mQuit 一個(gè)多線程可以安全訪問的布爾萝挤,負(fù)責(zé)結(jié)束線程

NetworkDispatcher既然繼承自Thread御毅,那么就實(shí)現(xiàn)了run方法:

public void run() {
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    Request<?> request;
    while (true) {
        long startTimeMs = SystemClock.elapsedRealtime();
        // release previous request object to avoid leaking request object when mQueue is drained.
        request = null;
        try {
            // Take a request from the queue.
            request = mQueue.take();
        } catch (InterruptedException e) {
            // We may have been interrupted because it was time to quit.
            if (mQuit) {
                return;
            }
            continue;
        }

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

            // If the request was cancelled already, do not perform the
            // network request.
            if (request.isCanceled()) {
                request.finish("network-discard-cancelled");
                continue;
            }

            addTrafficStatsTag(request);

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

            // If the server returned 304 AND we delivered a response already,
            // we're done -- don't deliver a second identical response.
            if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                request.finish("not-modified");
                continue;
            }

            // Parse the response here on the worker thread.
            Response<?> response = request.parseNetworkResponse(networkResponse);
            request.addMarker("network-parse-complete");

            // Write to cache if applicable.
            // TODO: Only update cache metadata instead of entire record for 304s.
            if (request.shouldCache() && response.cacheEntry != null) {
                mCache.put(request.getCacheKey(), response.cacheEntry);
                request.addMarker("network-cache-written");
            }

            // Post the response back.
            request.markDelivered();
            mDelivery.postResponse(request, response);
        } catch (VolleyError volleyError) {
            volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
            parseAndDeliverNetworkError(request, volleyError);
        } catch (Exception e) {
            VolleyLog.e(e, "Unhandled exception %s", e.toString());
            VolleyError volleyError = new VolleyError(e);
            volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
            mDelivery.postError(request, volleyError);
        }
    }
}
  1. 設(shè)置當(dāng)前線程的優(yōu)先級(jí)為后臺(tái)線程
  2. 從阻塞隊(duì)列中獲取一個(gè)request請(qǐng)求。這里使用的take方法怜珍,這個(gè)方法會(huì)阻塞線程端蛆,直到線程從隊(duì)列中拿到了東西。
  3. 給request添加network-queue-take標(biāo)記
  4. 調(diào)用netwoke的performRequest方法酥泛,并傳入requset獲取請(qǐng)求的結(jié)果networkResponse
  5. 給request添加network-http-complete標(biāo)記
  6. 通過response判斷是否是304狀態(tài)碼今豆,如果是就調(diào)用request.finish(),并跳過下面步驟揭璃。否則繼續(xù)下面的步驟晚凿。
  7. 使用request.parseNetworkResponse(networkResponse);創(chuàng)建一個(gè)Response對(duì)象response
  8. 給request添加network-parse-complete標(biāo)記
  9. 將請(qǐng)求requset和結(jié)果response寫入緩存。
  10. 調(diào)用request.markDelivered();表明瘦馍,當(dāng)前請(qǐng)求已被解決
  11. 調(diào)用mDelivery.postResponse(request, response);將請(qǐng)求和結(jié)果傳遞到UI線程歼秽。

這就是一個(gè)NetworkDispatcher線程執(zhí)行一個(gè)Request的大致流程。

ResponseDelivery->ExecutorDelivery

NetworkDispatcher線程中情组,最終結(jié)果是通過mDeliery這個(gè)ResponseDelivery對(duì)象傳遞到UI線程的燥筷。在創(chuàng)建NetworkDispatcher時(shí),mDeliery被賦予的實(shí)際是ExecutorDelivery的實(shí)例院崇。ExecutorDeliveryResponseDelivery接口的實(shí)現(xiàn)類肆氓。

ExecutorDelivery類的postResponse方法:

public void postResponse(Request<?> request, Response<?> response) {
    postResponse(request, response, null);
}

@Override
public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
    request.markDelivered();
    request.addMarker("post-response");
    mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}

postResponse 方法調(diào)用了 mResponsePosterexecute()方法,并傳入了一個(gè)Runnable對(duì)象底瓣。

mResponsePoster 對(duì)象是一個(gè) Executor對(duì)象谢揪,并在 ExecutorDelivery 并創(chuàng)建時(shí)就創(chuàng)建。它的execute方法捐凭,就是調(diào)用 RequestQueue在創(chuàng)建 ExecutorDelivery 傳入的擁有UI線程的Looper的handlerpost(Runnable)方法拨扶。

public ExecutorDelivery(final Handler handler) {
    // Make an Executor that just wraps the handler.
    mResponsePoster = new Executor() {
        @Override
        public void execute(Runnable command) {
            handler.post(command);
        }
    };
}

handler post 的runnable對(duì)象是一個(gè)內(nèi)部類:在 run 方法里調(diào)用了Request對(duì)象的deliverResponse deliverError finish方法。

private class ResponseDeliveryRunnable implements Runnable {
    private final Request mRequest;
    private final Response mResponse;
    private final Runnable mRunnable;
    public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) {
        mRequest = request;
        mResponse = response;
        mRunnable = runnable;
    }
    @SuppressWarnings("unchecked")
    @Override
    public void run() {
        // If this request has canceled, finish it and don't deliver.
        if (mRequest.isCanceled()) {
            mRequest.finish("canceled-at-delivery");
            return;
        }
        // Deliver a normal response or error, depending.
        if (mResponse.isSuccess()) {
            mRequest.deliverResponse(mResponse.result);
        } else {
            mRequest.deliverError(mResponse.error);
        }
        // If this is an intermediate response, add a marker, otherwise we're done
        // and the request can be finished.
        if (mResponse.intermediate) {
            mRequest.addMarker("intermediate-response");
        } else {
            mRequest.finish("done");
        }
        // If we have been provided a post-delivery runnable, run it.
        if (mRunnable != null) {
            mRunnable.run();
        }
   }
}

經(jīng)過這樣的轉(zhuǎn)化茁肠,相當(dāng)于Request的幾個(gè)方法就是在UI線程執(zhí)行了:

//Requset自己實(shí)現(xiàn)的deliverError
public void deliverError(VolleyError error) {
    if (mErrorListener != null) {
        mErrorListener.onErrorResponse(error);
    }
}

//StringRequest實(shí)現(xiàn)的Request的抽象方法deliverResponse
protected void deliverResponse(String response) {
    if (mListener != null) {
        mListener.onResponse(response);
    }
}

Requset的deliver方法實(shí)際就是調(diào)用的在創(chuàng)建Request的時(shí)候患民,傳入的Listener接口的方法。


Volley每創(chuàng)建一個(gè)消息隊(duì)列垦梆,就創(chuàng)建了4個(gè)這樣的NetworkDispatcher線程一直從請(qǐng)求隊(duì)列中獲取請(qǐng)求匹颤,然后執(zhí)行,最后post到UI線程托猩。4個(gè)線程都去拿請(qǐng)求印蓖,不會(huì)發(fā)生沖突是因?yàn)檎?qǐng)求放在了BlockingQueue中,保證了每次take獲取操作只有一個(gè)線程能獲取京腥。而且Volley的BlockingQueue使用的是PriorityBlockingQueue另伍,這個(gè)隊(duì)列在擁有BlockingQueue功能的同時(shí),還對(duì)隊(duì)列中的請(qǐng)求進(jìn)行了排序。


add請(qǐng)求操作

RequestQueue中的線程們一直在跑著摆尝,它們不斷的有序的從消息阻塞隊(duì)列中拿請(qǐng)求,執(zhí)行請(qǐng)求因悲,傳遞到UI線程堕汞。

ReqeustQueue的add操作就是將請(qǐng)求添加到請(qǐng)求隊(duì)列中。

// 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;
}
// 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;
}
  • 如果加入的請(qǐng)求沒有被緩存過晃琳,就直接加入到消息隊(duì)列讯检。直接加入不用獲取消息隊(duì)列的鎖。因?yàn)橄㈥?duì)列是個(gè)BlockingQueue卫旱,本就支持并發(fā)操作人灼。而且即使add操作是在UI線程,也不會(huì)阻塞UI線程顾翼,因?yàn)?br> mNetworkQueue.add(request);內(nèi)部是調(diào)用BlockingQueueoffer操作投放,offer入隊(duì)操作不會(huì)阻塞線程,如果入隊(duì)失敗适贸,它會(huì)返回false灸芳。

以上只是我分析的Volley的RequestQueue的大概執(zhí)行過程。其中還有CacheQueue WaitRequests CurrentRequests等一些細(xì)節(jié)和HurlStack和HttpClientStack的網(wǎng)絡(luò)請(qǐng)求部分沒有具體分析拜姿。
如果有哪里不對(duì)的烙样,希望指正。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蕊肥,一起剝皮案震驚了整個(gè)濱河市谒获,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌壁却,老刑警劉巖批狱,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異儒洛,居然都是意外死亡精耐,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門琅锻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來卦停,“玉大人,你說我怎么就攤上這事恼蓬【辏” “怎么了?”我有些...
    開封第一講書人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵处硬,是天一觀的道長(zhǎng)小槐。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么凿跳? 我笑而不...
    開封第一講書人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任件豌,我火速辦了婚禮,結(jié)果婚禮上控嗜,老公的妹妹穿的比我還像新娘茧彤。我一直安慰自己,他們只是感情好疆栏,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開白布曾掂。 她就那樣靜靜地躺著,像睡著了一般壁顶。 火紅的嫁衣襯著肌膚如雪珠洗。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評(píng)論 1 299
  • 那天若专,我揣著相機(jī)與錄音许蓖,去河邊找鬼。 笑死富岳,一個(gè)胖子當(dāng)著我的面吹牛蛔糯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播窖式,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蚁飒,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了萝喘?” 一聲冷哼從身側(cè)響起淮逻,我...
    開封第一講書人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎阁簸,沒想到半個(gè)月后爬早,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡启妹,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年筛严,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片饶米。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡桨啃,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出檬输,到底是詐尸還是另有隱情照瘾,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布丧慈,位于F島的核電站析命,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鹃愤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一簇搅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧昼浦,春花似錦馍资、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽乌妙。三九已至使兔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間藤韵,已是汗流浹背虐沥。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留泽艘,地道東北人欲险。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像匹涮,于是被迫代替她去往敵國(guó)和親天试。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • Volley源碼分析之流程和緩存 前言 Android一開始提供了HttpURLConnection和HttpCl...
    大寫ls閱讀 618評(píng)論 0 6
  • 我的博客: Volley 源碼分析 Volley 的使用流程分析 官網(wǎng)示例 創(chuàng)建一個(gè)請(qǐng)求隊(duì)列 RequestQue...
    realxz閱讀 2,040評(píng)論 1 11
  • 前言 本文是一篇日常學(xué)習(xí)總結(jié)性的文章然低,筆者通過分析經(jīng)典網(wǎng)絡(luò)框架Volley的源碼喜每,望以鞏固Android網(wǎng)絡(luò)框架中...
    Armstrong_Q閱讀 574評(píng)論 0 7
  • 關(guān)閉了朋友圈,取關(guān)了一些公眾號(hào)雳攘,刪除了網(wǎng)易新聞带兜,世界安寧祥和了許多……
    簡(jiǎn)單滴人生閱讀 254評(píng)論 0 0
  • 今天的朋友圈再次被《小鎮(zhèn)》刷屏,雖然這已經(jīng)不是第一次了吨灭,從首演到全國(guó)巡演刚照,到團(tuán)帥姐夫的梅花獎(jiǎng),再到戲曲界的奧...
    心依87閱讀 709評(píng)論 2 1