「2020 新手必備 」極速入門 Retrofit + OkHttp 網(wǎng)絡(luò)框架到實(shí)戰(zhàn),這一篇就夠了刮萌!

老生常談

  • 什么是 Retrofit 驮配?
  • Retrofit 早已不是什么新技術(shù)了,想必看到這篇博客的大家都早已熟知着茸,這里就不啰嗦了壮锻,簡(jiǎn)單介紹下:
  • Retrofit 是一個(gè)針對(duì) Java 和 Android 的設(shè)計(jì)的 REST 客戶機(jī)。它通過(guò)基于 REST 的 web 服務(wù)檢索和上傳 JSON (或其他結(jié)構(gòu)化數(shù)據(jù))變得相對(duì)容易涮阔。在使用中猜绣,您可以配置用于數(shù)據(jù)序列化的轉(zhuǎn)換器。對(duì)于 JSON 敬特,通常使用Gson 掰邢,但是可以添加自定義轉(zhuǎn)換器來(lái)處理 XML 或其他協(xié)議。Retrofit 對(duì) HTTP 請(qǐng)求使用 OkHttp 庫(kù)伟阔。

A type-safe HTTP client for Android and Java

  • 好了介紹結(jié)束辣之,想必大家的大刀都饑渴難耐了,那么我們直接開(kāi)始吧

本文流程

依賴注入

  • so Easy 不用說(shuō)了吧
  • 在 app module 下的 build.gradle 中添加以下依賴:
// OkHttp3
api 'com.squareup.okhttp3:okhttp:3.10.0'
api 'com.squareup.okio:okio:1.8.0'
// Retrofit
api 'com.squareup.retrofit2:retrofit:2.7.0'
// Gson 服務(wù)器數(shù)據(jù)交互
api 'com.google.code.gson:gson:2.8.6'

依賴注入很簡(jiǎn)單皱炉, Retrofit 一直是結(jié)合 OkHttp 和 Gson(無(wú)所謂什么 JSON 解析器都行怀估,這里就用 Gson 了)
我這里專門找了最新的版本庫(kù),so~ 大家直接用即可

  • 別急合搅,前面也說(shuō)了 Retrofit 是結(jié)合 OkHttp 做網(wǎng)絡(luò)請(qǐng)求用的多搀,所以悉心提醒記得開(kāi)下網(wǎng)絡(luò)權(quán)限:
<uses-permission android:name="android.permission.INTERNET" />

全面進(jìn)擊

  • 網(wǎng)上關(guān)于 Retrofit 的教程可謂琳瑯滿目,但是總給人一種云里霧里的感覺(jué)
  • 所以本文的亮點(diǎn)就在于灾部,我會(huì)通過(guò)我自己實(shí)際項(xiàng)目的代碼來(lái)給大家介紹 Retrofit 到底牛在哪

Retrofit 開(kāi)始之前

  • 這里我將以我的一個(gè)開(kāi)源項(xiàng)目 FIWKeepApp 的登錄模塊舉例
  • Retrofit 出現(xiàn)之前康铭,原始社會(huì)的我們一般是這樣進(jìn)行網(wǎng)絡(luò)請(qǐng)求的:
    public void login2() {
        OkHttpClient okHttpClient = new OkHttpClient();
        //Form表單格式的參數(shù)傳遞
        FormBody formBody = new FormBody
            .Builder()
            //設(shè)置參數(shù)名稱和參數(shù)值
            .add("username",mAccountEdit.getText().toString())
            .add("password",mPasswordEdit.getText().toString())
            .build();
        Request request = new Request
            .Builder()
            //Post請(qǐng)求的參數(shù)傳遞
            .post(formBody)
            .url("http://hyh.hljdx.net:8080/SitUpWebServer/login")
            .build();
        okHttpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(okhttp3.Call call, IOException e) {
                Log.d("my_Test", e.getMessage());
            }

            @Override
            public void onResponse(okhttp3.Call call, Response response) throws IOException {
                String result = response.body().toString();
                UserBean userBean = JSON.parseObject(result, UserBean.class);
                Log.d("my_Test",userBean.getUser_head_img());
                response.body().close();
            }
        });
    }
  • 有沒(méi)有一種云里霧里的感覺(jué)?
  • 首先你得先將要發(fā)送的表單信息封裝為 Post 請(qǐng)求的 Body 對(duì)象赌髓,那么有的同學(xué)會(huì)問(wèn)什么是 POST 麻削,什么是 Body?這個(gè)問(wèn)題建議大家 Google 下春弥,這里我建議大家學(xué)一些后端或者計(jì)網(wǎng)的知識(shí),很簡(jiǎn)單也很有必要
  • 接著你需要再封裝一個(gè) Request 對(duì)象叠荠,也就是我們的請(qǐng)求體匿沛,在這里設(shè)置信息要提交到哪去
  • 最后調(diào)用 okHttpClient 的相應(yīng)方法,將前面實(shí)現(xiàn)的東西組合發(fā)送榛鼎,并在回調(diào)里接收
  • 所以逃呼,這一步步鳖孤,又是封裝 FormBody 又是封裝 Request ,搞了半天還要用 okHttpClient 發(fā)送抡笼,一套下來(lái)頭暈眼花苏揣,那么如何解決呢?
  • 那么 Retrofit 救世主就出現(xiàn)了

Retrofit 實(shí)現(xiàn)

  • 還是我項(xiàng)目中的登錄模塊推姻,我將其改為 Retrofit 的形式
  • 同樣完成上面的功能平匈,如果用 Retrofit 實(shí)現(xiàn)只需要:
    // baseUrl() 設(shè)置路由地址
    Retrofit retrofit = new Retrofit
        .Builder()
        .baseUrl(ApiUtils.BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .build();
        
    // 設(shè)置參數(shù)
    Call<UserBean> call = retrofit.create(UserMgrService.class)
        .login( mAccountEdit.getText().toString(),
            mPasswordEdit.getText().toString());
            
    // 回調(diào)
    call.enqueue(new Callback<UserBean>() {
        @Override
        public void onResponse(Call<UserBean> call, Response<UserBean> response) {
            Log.d("123123", "msg--" + response.body().getUser_head_img());
        }

        @Override
        public void onFailure(Call<UserBean> call, Throwable t) {
            // 失敗時(shí)做處理
        }
    });
  • 如上就實(shí)現(xiàn)了和純 okHttp 代碼一樣的功能
  • 大家可能會(huì)覺(jué)得,這也沒(méi)簡(jiǎn)單多少啊 藏古?但細(xì)心觀察發(fā)現(xiàn)增炭,第一步 Retrofit 的實(shí)例化過(guò)程,只要服務(wù)器不換代碼幾乎是不變的拧晕,所以我們完全可以將它封裝
  • 而且大家有沒(méi)有發(fā)現(xiàn)隙姿,如果單單使用 OkHttp 我們的返回值是一個(gè) Response 對(duì)象,我們還需要在其中提取相應(yīng) JSON 對(duì)象厂捞,進(jìn)行類型轉(zhuǎn)換输玷,而在 Retrofit 中,由于使用了數(shù)據(jù)解析器靡馁,所以這一大塊代碼都省略了
  • 還有很多優(yōu)點(diǎn)欲鹏,這里就不嘮叨了,我們直接開(kāi)始學(xué)習(xí)使用之路吧奈嘿!

實(shí)現(xiàn)流程

  • 那么現(xiàn)在就給大家解釋下使用的每個(gè)步驟

創(chuàng)建接口

  • 首先我們要?jiǎng)?chuàng)建 UserMgrService 接口
/**
 * @author fishinwater-1999
 * @version 2019-12-21
 */
public interface UserMgrService {

    /**
     * GET 用 Query
     */
    @GET("login")
    Call<UserBean> login(@Query("username") String username, @Query("password") String password);

}
  • @GET() 注解就可以猜到貌虾,這將會(huì)是一個(gè) Get 請(qǐng)求
  • 我們?cè)诳捶椒w,返回值會(huì)是一個(gè)封裝了 UserBeanCall<> 對(duì)象
  • 參數(shù)有兩個(gè)裙犹,分別是 String usernameString password
  • 與平常方法不同的是尽狠,這兩個(gè)參數(shù)各自帶上了 @Query("...") 注解
  • 通過(guò) @Query("...") 里的參數(shù)我們發(fā)現(xiàn),這與 okHttp 創(chuàng)建 FormBody 時(shí)叶圃,add 的參數(shù)不謀而合

看到這里想必大家都明白了袄膏,如果大家還不明白什么是 Get 請(qǐng)求,以及 @Query("...") 里的 username 和 password 是怎么的話掺冠,我這里簡(jiǎn)單說(shuō)下
比如說(shuō)我們現(xiàn)在隨便打開(kāi)一個(gè)網(wǎng)頁(yè)沉馆,就拿百度圖片里搜索 Github 頁(yè)面為例:

  • 后端寫(xiě)服務(wù)器的同學(xué)會(huì)通過(guò)這些參數(shù),像 HashMap get(“key”) 方法取值一樣拿出來(lái)

POST

  • 這樣解釋德崭,想必大家就明白了
  • 除了 GET 方法之外 還有一種 POST 方法斥黑,相比于使用 GET ,使用 POST 有很多其他的優(yōu)點(diǎn)眉厨,這里就不多說(shuō)了
  • 他使用和 GET 的思路一樣锌奴,如果用 POST 那么我們的代碼將會(huì)是這樣的:
public interface UserMgrService {

    /**
     * POST 用 Field
     */
    @POST("login")
    @FormUrlEncoded
    Call<UserBean> login(@Field("username") String username, @Field("password") String password);

}
  • 就是把注解換了套名字,然后在 @POST("...") 下再加上一個(gè) @FormUrlEncoded 注解
  • 這里就不多說(shuō)了憾股,我們直接進(jìn)入下一步

生成 Retrofit 對(duì)象

  • 我們先看下怎么創(chuàng)建和設(shè)置的:
// baseUrl() 設(shè)置路由地址
Retrofit retrofit = new Retrofit
    .Builder()
    .baseUrl(ApiUtils.BASE_URL)
    .addConverterFactory(GsonConverterFactory.create())
    .build();
  • 這里主要是兩步鹿蜀,設(shè)置 baseUrl 箕慧、設(shè)置數(shù)據(jù)解析器
  • 老樣子什么是 baseUrl ?就拿我之前用 OkHttp 設(shè)置的那個(gè) url 為例
http://hyh.hljdx.net:8080/SitUpWebServer/login
  • 大家可以這么理解:上面的這個(gè) url = baseurl + @GET("...") 注解里傳入的字符串
  • 如果我們前面設(shè)置的是 @GET("login") 那這里 baseurl 就是:http://hyh.hljdx.net:8080/SitUpWebServer/ 是不是一下子就明白了茴恰,但是其他博客不照顧新人颠焦,從沒(méi)說(shuō)清楚
  • 然后就是數(shù)據(jù)解析器,大家應(yīng)該還記得剛開(kāi)始的時(shí)候我們導(dǎo)入了一個(gè)三方庫(kù):
// Gson 服務(wù)器數(shù)據(jù)交互
api 'com.google.code.gson:gson:2.8.6'
  • 我們和服務(wù)器的數(shù)據(jù)往枣,都是以 JSON 的形式交互的伐庭,比如 Bing 每日壁紙接口
  • 設(shè)置了這個(gè)數(shù)據(jù)解析器,就可以把返回的信息自動(dòng)封裝為相應(yīng)的對(duì)象婉商,明白了吧

具體這個(gè)對(duì)象怎么獲得似忧,大家可以聯(lián)系后端,或者百度搜下 JsonFormat 插件使用或者 JSON 對(duì)象生成器丈秩,門路很多這里都告訴你們啦

生成接口對(duì)象

  • 老樣子盯捌,先看看代碼
UserMgrService service = retrofit.create(UserMgrService.class);
  • 過(guò)于簡(jiǎn)單,調(diào)用前面 retrofit 對(duì)象的 create() 方法傳入接口的 class 文件即可

獲得 Call 對(duì)象

  • 由剛開(kāi)始的代碼我們知道
  • 我們向服務(wù)器發(fā)送請(qǐng)求需要調(diào)用 call 對(duì)象的 enqueue() 方法
  • 那么 Call 對(duì)象怎么獲得呢蘑秽?其實(shí)很簡(jiǎn)單:
Call<UserBean> call = service.login( mAccountEdit.getText().toString(), mPasswordEdit.getText().toString());
  • 說(shuō)白了就是饺著,直接調(diào)用接口的相應(yīng)方法,他返回的直接就是一個(gè) Call 對(duì)象

發(fā)送請(qǐng)求

  • 請(qǐng)求分兩種 同步的和異步的
  • 由于請(qǐng)求是耗時(shí)的肠牲,假設(shè)我們發(fā)送同步請(qǐng)求 幼衰,在請(qǐng)求就過(guò)返回之前,應(yīng)用界面會(huì)進(jìn)去阻塞狀態(tài)
  • 說(shuō)白了就是會(huì)卡缀雳,甚至卡死渡嚣。。肥印。所以說(shuō)這種請(qǐng)求很少用到
  • 雖然不用识椰,但負(fù)責(zé)的我還是也給大家代碼:
Response<UserBean> response = call.execute();
Log.d("123123", "msg--" + response.body().getUser_head_img());
  • 具體就不說(shuō)了,就是調(diào)用 callexecute() 會(huì)返回一個(gè)值
  • 這個(gè)值就是請(qǐng)求結(jié)果深碱,大家直接用就是( 但是在這個(gè)只沒(méi)返回腹鹉,比如網(wǎng)速慢時(shí),手機(jī)會(huì)卡在那動(dòng)不了甚至 ANR
  • 這里我介紹下異步請(qǐng)求:
// 回調(diào)
call.enqueue(new Callback<UserBean>() {
    @Override
    public void onResponse(Call<UserBean> call, Response<UserBean> response) {
        Log.d("123123", "msg--" + response.body().getUser_head_img());
    }

    @Override
    public void onFailure(Call<UserBean> call, Throwable t) {
        // 失敗時(shí)做處理
    }
});
  • 這就是異步方法敷硅,直接調(diào)用 callenqueue 方法功咒,傳入一個(gè) Callback 接口即可
  • 調(diào)用后系統(tǒng)自動(dòng)釋放資源,不會(huì)阻塞绞蹦,等到請(qǐng)求結(jié)果返回時(shí)
  • 就會(huì)自動(dòng)調(diào)用 onResponse 方法力奋,方法 里的 response 就是處理好的結(jié)果
  • 本文代碼運(yùn)行后結(jié)果 Demo Example 是不是特別簡(jiǎn)單!

登錄功能實(shí)戰(zhàn)

  • 到這里想必大家都已經(jīng)學(xué)會(huì)了 Retrofit 的使用
  • 那么現(xiàn)在我就拿登錄功能舉例幽七,看看如何在項(xiàng)目中引用 Retrofit
  • 實(shí)戰(zhàn)部分先置條件是 MVP + ButterKnife景殷,大家很容易在網(wǎng)上找到資料,這就不贅述了

搭建 Model 層

  • 創(chuàng)建接口 ILoginModel
  • 接口對(duì)外暴露 username password 和 一個(gè)監(jiān)聽(tīng)回調(diào)接口 (接口通過(guò)泛型傳入)
/**
 * @author fishinwater-1999
 * @version 2019-11-12
 */
public interface IBaseLog<L> {

    /**
     * 登錄 Api
     * @param userAccount
     * @param mPassword
     * @param loginCallback
     */
    void login(String userAccount, String mPassword, L loginCallback);

}
  • 實(shí)現(xiàn)回調(diào)接口
  • 觀察者模式,當(dāng)請(qǐng)求信息返回后動(dòng)態(tài)通知 P 層
/**
 * @author fishinwater-1999
 * @version 2019-12-23
 */
public interface IBaseRetCallback<T> {

    void onSucceed(Response<T> response);

    void onFailed(Throwable t);

}
  • 創(chuàng)建 LoginModel 實(shí)現(xiàn) ILoginModel 接口
  • 實(shí)現(xiàn) login 方法滨彻,請(qǐng)求成功后回調(diào) IBaseRetCallback 監(jiān)聽(tīng)
/**
 * @author fishinwater-1999
 * @version 2019-11-12
 */
public class LogViewModel implements IBaseLog<IBaseRetCallback<UserBean>> {

    private final String TAG = "LogViewModel";

    @Override
    public void login(String userAccount, String userPassword, final IBaseRetCallback<UserBean> retCallback) {
        // baseUrl() 設(shè)置路由地址
        Retrofit retrofit = new Retrofit
                .Builder()
                .baseUrl(ApiUtils.BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        // 設(shè)置參數(shù)
        UserMgrService service = retrofit.create(UserMgrService.class);
        retrofit2.Call<UserBean> call = service.login( userAccount, userPassword);
        // 回調(diào)
        call.enqueue(new Callback<UserBean>() {
            @Override
            public void onResponse(retrofit2.Call<UserBean> call, Response<UserBean> response) {
                retCallback.onSucceed(response);
            }

            @Override
            public void onFailure(retrofit2.Call<UserBean> call, Throwable t) {
                // 失敗時(shí)做處理
                retCallback.onFailed(t);
            }
        });
    }

}

搭建 Presenter 層

  • 首先實(shí)現(xiàn) Presenter 層基類
  • 同樣的,要搭建 Presenter 層基類挪蹭,首先要實(shí)現(xiàn)器接口
/**
 * @author fishinwater-1999
 * @version 2019-11-12
 */
public interface IBasePresenter<V> {

    /**
     * 綁定
     * @param mLogView
     */
    void attachView(V mLogView);

    /**
     * 解綁
     */
    void detachView();

    /**
     * 登錄
     * @param userName
     * @param userPassword
     * @param resultListener
     */
    void login(String userName, String userPassword, V resultListener);

}
  • 編寫(xiě)抽象類 BasePresenter 實(shí)現(xiàn) IBasePresenter 接口
/**
 * @author fishinwater-1999
 * @version 2019-11-12
 */
public abstract class BasePresenter<V> implements IBasePresenter<V> {

    private V view;

    @Override
    public void attachView(V mLogView) {
        this.view = mLogView;
    }

    @Override
    public void detachView() {
        this.view = null;
    }

    @Override
    public V getLoginVew() {
        return this.view;
    }

}
  • 然后就到了我們具體的 LogPresenter 類的實(shí)現(xiàn)
  • LogPresenter 類需要持有 View 層和 Model 層接口
/**
 * @author fishinwater-1999
 * @version 2019-11-12
 */
public class LogPresenter extends BasePresenter<ILoginView> {

    private IBaseLog logViewModel;

    public LogPresenter(IBaseLog logViewModel) {
        this.logViewModel = logViewModel;
    }

    @Override
    public void login(String userName, String userPassword, final ILoginView iLoginView) {
        logViewModel.login(userName, userPassword, new IBaseRetCallback<UserBean>() {
            @Override
            public void onSucceed(Response<UserBean> response) {
                UserBean userBean = response.body();
                if (userBean != null) {
                    String user_id = userBean.getUser_id();
                    iLoginView.showLoginSuccess(user_id);
                }
            }

            @Override
            public void onFailed(Throwable t) {
                iLoginView.showLoginFailed(ILoginView.ErrCode.WRONG_NET_WORK);
            }
        });

    }
}
  • 上面的代碼中亭饵,構(gòu)造方法 LogPresenter 持有了 Model 層
  • 同時(shí)暴露了 login(..., ..., Listener) 接口,可供調(diào)用者調(diào)用

View 層實(shí)現(xiàn)

  • View 層負(fù)責(zé)實(shí)例化 Model 層梁厉,并與 Presenter 層綁定
  • 老樣子辜羊,創(chuàng)建 BaseFragment<V , P extends IBasePresenter<V>> 基類
/**
 * @author fishinwater-1999
 * @version 2019-11-12
 */
public abstract class BaseFragment<V , P extends IBasePresenter<V>> extends Fragment {

    /**
     * Presenter 層
     */
    private P mBaseResister;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 自動(dòng)綁定
        if (mBaseResister == null) {
            mBaseResister = createProsenter();
        }
    }

    /**
     * 在這里確定要生成的 Presenter 對(duì)象類型
     * @return
     */
    public abstract P createProsenter();

    /**
     * 獲得 Presenter 對(duì)象
     * @return
     */
    public P getPresenter() {
        if (mBaseResister == null) {
            createProsenter();
        }
        return mBaseResister;
    }

    /**
     * 碎片銷毀時(shí)解綁
     */
    @Override
    public void onStop() {
        super.onStop();
        mBaseResister = null;
    }
}
  • 實(shí)現(xiàn) View 層邏輯
  • View 層只負(fù)責(zé)用戶界面響應(yīng)
/**
 * @author fishinwater-1999
 */
public class LoginFragment extends BaseFragment<ILoginView, LogPresenter> implements ILoginView {

    private static final String TAG = "LoginFragment";

    private LogViewModel mLogViewModel;

    private LoginFragmentBinding binding;

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        binding = DataBindingUtil.inflate(inflater, R.layout.login_fragment, container, false);
        View view = binding.getRoot();
        binding.setLogCallback(getLogActivity());
        binding.setFragment(this);
        return view;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if (mLogViewModel == null) {
            mLogViewModel = new LogViewModel();
        }
    }

    public void login(View v) {
        getPresenter().login(
                getUserName(),
                getUserPwd(),
                this);
    }

    @Override
    public LogPresenter createPresenter() {
        if (mLogViewModel == null) {
            mLogViewModel = new LogViewModel();
        }
        return new LogPresenter(mLogViewModel);
    }

    @Override
    public String getUserName() {
        return binding.userAccount.getText().toString();
    }

    @Override
    public String getUserPwd() {
        return binding.userPassword.getText().toString();
    }

    @Override
    public void showLoginSuccess(String response) {
        Toast.makeText(getActivity(), "登錄成功", Toast.LENGTH_LONG).show();
        SharedPreferencesUtil.putString(getActivity(), SharedPreferencesUtil.PRE_NAME_SITUP, SharedPreferencesUtil.USER_ID, response);
        ARouter.getInstance().build(RouteUtils.MainActivity).navigation();
        getActivity().finish();
    }

    @Override
    public void showLoginFailed(ErrCode errCode) {
        if (errCode == ErrCode.WRONG_USER_NAME) {
            Toast.makeText(getActivity(), "用戶名錯(cuò)誤", Toast.LENGTH_LONG).show();
        }else if (errCode == ErrCode.WRONG_USER_PWD){
            Toast.makeText(getActivity(), "密碼錯(cuò)誤", Toast.LENGTH_LONG).show();
        }else if (errCode == ErrCode.WRONG_NET_WORK) {
            Toast.makeText(getActivity(), "未知,請(qǐng)檢查網(wǎng)絡(luò)", Toast.LENGTH_LONG).show();
        }
    }
}
  • 這里我使用了 DataBinding 的形式词顾,對(duì)數(shù)據(jù)進(jìn)行綁定
  • 當(dāng)然八秃,你也可以選用 ButterKnife 等優(yōu)秀的三方庫(kù)
  • 那么為什么我選 DataBinding 呢?親兒子 懂吧肉盹? /壞笑

運(yùn)行

  • 關(guān)于 測(cè)序的大致便是如此了
  • 至于細(xì)枝末節(jié)的東西大家可以直接到這個(gè)庫(kù)里面看昔驱,地址在文末

更多模塊實(shí)戰(zhàn) FIWKeepApp

  • 這里我將上述過(guò)程寫(xiě)在我的 Demo 里,地址在 GitHub 大家可以直接查看改倉(cāng)庫(kù)源碼上忍,記得給我點(diǎn)個(gè) star 哦~:

  • Demo 地址:FIWKeepApp - LoginFragment

總結(jié)

  • 想必看到這兒的讀者對(duì) Retrofit 的使用都已近有了一定的了解骤肛,但 Retrofit 的好處并不只是這些,還有很多跟深入的只是需要了解窍蓝,但本文限于篇幅腋颠,無(wú)法向大家一一介紹
  • 對(duì)于我前面的 FIWKeepApp 這個(gè)倉(cāng)庫(kù),我將一步步轉(zhuǎn)換到 Retrofit + OkHttp 的形式下吓笙,歡迎大家關(guān)注我的 這個(gè)倉(cāng)庫(kù)淑玫,進(jìn)行學(xué)習(xí),也歡迎各位老鐵給個(gè) star
  • 后面我還會(huì)對(duì) Android 的各種知識(shí)點(diǎn)面睛、Framework 層源碼絮蒿,三方庫(kù)等進(jìn)行解析,歡迎大家關(guān)注 _yuanhao 簡(jiǎn)書(shū) 及時(shí)接收更多優(yōu)質(zhì)博文侮穿!
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末歌径,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子亲茅,更是在濱河造成了極大的恐慌回铛,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件克锣,死亡現(xiàn)場(chǎng)離奇詭異茵肃,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)袭祟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門验残,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人巾乳,你說(shuō)我怎么就攤上這事您没∧裾伲” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵氨鹏,是天一觀的道長(zhǎng)欧募。 經(jīng)常有香客問(wèn)我,道長(zhǎng)仆抵,這世上最難降的妖魔是什么跟继? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮镣丑,結(jié)果婚禮上舔糖,老公的妹妹穿的比我還像新娘。我一直安慰自己莺匠,他們只是感情好金吗,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著慨蛙,像睡著了一般辽聊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上期贫,一...
    開(kāi)封第一講書(shū)人閱讀 49,007評(píng)論 1 284
  • 那天跟匆,我揣著相機(jī)與錄音,去河邊找鬼通砍。 笑死玛臂,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的封孙。 我是一名探鬼主播迹冤,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼虎忌!你這毒婦竟也來(lái)了泡徙?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤膜蠢,失蹤者是張志新(化名)和其女友劉穎堪藐,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體挑围,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡礁竞,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了杉辙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片模捂。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出狂男,到底是詐尸還是另有隱情综看,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布岖食,位于F島的核電站寓搬,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏县耽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一镣典、第九天 我趴在偏房一處隱蔽的房頂上張望兔毙。 院中可真熱鬧,春花似錦兄春、人聲如沸澎剥。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)哑姚。三九已至,卻和暖如春芜茵,著一層夾襖步出監(jiān)牢的瞬間叙量,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工九串, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留绞佩,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓猪钮,卻偏偏與公主長(zhǎng)得像品山,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子烤低,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

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