Android 你必須了解的網(wǎng)絡(luò)框架Retrofit2.0

上一篇學習了okhttp的入門篇淮菠,這一篇學習的retrofit底層默認使用的就是okhttp衅金,相信大家多少也聽過這個框架核芽,下面我們就來一起學習下妖谴,講真窿锉,學會之后這個框架用起來真的很爽,特別靈活膝舅。

按照習慣先來說一下它的優(yōu)缺點

優(yōu)點:

  1. 可以配置不同HTTP client來實現(xiàn)網(wǎng)絡(luò)請求嗡载,如okhttp、httpclient等
  1. 請求的方法參數(shù)注解都可以定制
  2. 支持同步仍稀、異步和RxJava
  3. 超級解耦
  4. 可以配置不同的反序列化工具來解析數(shù)據(jù)洼滚,如json、xml等
  5. 使用非常方便靈活
  6. 框架使用了很多設(shè)計模式(感興趣的可以看看源碼學習學習)

缺點:

  1. 不能接觸序列化實體和響應(yīng)數(shù)據(jù)
  1. 執(zhí)行的機制太嚴格
  2. 使用轉(zhuǎn)換器比較低效
  3. 只能支持簡單自定義參數(shù)類型

相關(guān)學習資料的網(wǎng)址

  1. retrofit官網(wǎng):http://square.github.io/retrofit/
  2. github地址:https://github.com/square/retrofit
  3. Simple HTTP with Retrofit2:
    https://realm.io/news/droidcon-jake-wharton-simple-http-retrofit-2/

環(huán)境配置

在builde.gradle里面添加上

compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
compile 'com.squareup.okhttp3:okhttp:3.4.1'

在AndroidManifest.xml添加所需權(quán)限

<uses-permission android:name="android.permission.INTERNET" />

基本使用

  • get異步請求

public interface GitHubService {
  @GET("users/{user}/repos")
  Call<ResponseBody> listRepos(@Path("user") String user);
}
Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .addConverterFactory(GsonConverterFactory.create()) 
    .build();
GitHubService service = retrofit.create(GitHubService.class);
Call<ResponseBody> repos = service.listRepos("octocat");
repos.enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                Log.e("APP",response.body().source().toString());
            }
            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                t.printStackTrace();
            }
        });

.baseUrl設(shè)置最基本url,也就是http請求的url前綴,可以把項目中重復(fù)的前綴用這個來設(shè)置

.addConverterFactory(GsonConverterFactory.create())是添加Gson數(shù)據(jù)解析ConverterFactory,后面會專門介紹下這個技潘,這里就不做過多解釋

ResponseBody 這個是okhttp里面的對象遥巴,可以直接返回整個字符串千康,也可以獲取流的形式

  • post異步請求

POST與GET實現(xiàn)基本上是一樣的,只是把注解GET換成POST就OK.為了測試POST铲掐,專門去網(wǎng)上找了個接口測試拾弃,下面就分享給大家,既可以用GET也可以用POST請求

http://www.kuaidi100.com/query?type=快遞公司代號&postid=快遞單號 
ps:快遞公司編碼:申通="shentong" EMS="ems" 順豐="shunfeng" 圓通="yuantong" 中通="zhongtong" 韻達="yunda" 天天="tiantian" 匯通="huitongkuaidi" 全峰="quanfengkuaidi" 德邦="debangwuliu" 宅急送="zhaijisong"

拿著這個接口來實現(xiàn)一下POST異步請求

public interface GitHubService {
    @POST("query")
    Call<PostQueryInfo> search(@Query("type") String type, @Query("postid") String postid);
}
public class PostQueryInfo {
    private String message;
    private String nu;
    private String ischeck;
    private String com;
    private String status;
    private String condition;
    private String state;
    private List<DataBean> data;
    public static class DataBean {
        private String time;
        private String context;
        private String ftime;
    }
}
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://www.kuaidi100.com/")
                .addConverterFactory(GsonConverterFactory.create()) 
                .build();
        GitHubService apiService = retrofit.create(GitHubService.class);
        Call<PostQueryInfo> call = apiService.search("yuantong","500379523313");
        call.enqueue(new Callback<PostQueryInfo>(){
            @Override
            public void onResponse(Call<PostQueryInfo> call,Response<PostQueryInfo> response){
                 Log.e("APP",response.body().getNu());
            }
            @Override
            public void onFailure(Call<PostQueryInfo> call,Throwable t){
                t.printStackTrace();
            }
        });

PostQueryInfo實體類省略了get和set方法,大家可以自行快捷鍵摆霉,最終打印返回回來的快遞單號豪椿,實現(xiàn)POST異步請求就是這么簡單

http://www.bejson.com/knownjson/webInterface/這網(wǎng)站里面還有一些其它免費接口,感興趣的可以去看看

  • 常用注解的使用介紹

上面GitHubService里面的注解大家應(yīng)該都能猜它的作用了吧携栋,下面就給大家介紹下

@GET@POST分別是get和post請求搭盾。括號里面的value值與上面.baseUrl組成完整的路徑

@Path動態(tài)的URL訪問。像上面get請求中的{user}可以把它當做一個占位符刻两,通過@Path("user")標注的參數(shù)進行替換

@Query請求參數(shù)增蹭。無論是GET或POST的參數(shù)都可以用它來實現(xiàn)

@QueryMap請求參數(shù)使用Map集合“跄。可以傳遞一個map集合對象

@Body實體請求參數(shù)。顧名思義可以傳遞一個實體對象來作為請求的參數(shù)霎奢,不過實體屬性要與參數(shù)名一一致

@FormUrlEncoded@Field簡單的表單鍵值對户誓。兩個需要結(jié)合使用,使用如下:

@FormUrlEncoded
@POST("user/edit")
Call<User> updateUser(@Field("first_name") String first, @Field("last_name") String last);

@Multipart@PartPOST表單的方式上傳文件可以攜帶參數(shù)幕侠。兩個需要結(jié)合使用帝美,使用方式查看下面文件上傳中介紹。

@PartMap@PartPOST表單上傳多個文件攜帶參數(shù)晤硕。兩個結(jié)合使用悼潭,使用方式查看下面文件上傳中介紹。

這里只介紹了一些常用的舞箍,大家如果想了解更多可以查看相關(guān)文檔

  • 文件上傳

1舰褪、單文件上傳攜帶參數(shù)(使用注解@Multipart@Part),需要在手機SD卡目錄下的Pictures文件夾下添加xuezhiqian.png圖片

@Multipart
@POST("UploadServlet")
Call<ResponseBody> uploadfile(@Part MultipartBody.Part photo, @Part("username") RequestBody username, @Part("password") RequestBody password);
Retrofit retrofitUpload = new Retrofit.Builder()
                .baseUrl("http://192.168.1.8:8080/UploadFile/")
                .addConverterFactory(GsonConverterFactory.create())
                .build();
GitHubService service = retrofitUpload.create(GitHubService.class);
File file = new File(Environment.getExternalStorageDirectory()+"/Pictures", "xuezhiqian.png");
//設(shè)置Content-Type:application/octet-stream
RequestBody photoRequestBody = RequestBody.create(MediaType.parse("application/octet-stream"), file);
//設(shè)置Content-Disposition:form-data; name="photo"; filename="xuezhiqian.png"
MultipartBody.Part photo = MultipartBody.Part.createFormData("photo", file.getName(), photoRequestBody);
//添加參數(shù)用戶名和密碼疏橄,并且是文本類型
RequestBody userName = RequestBody.create(MediaType.parse("text/plain"), "abc");
RequestBody passWord = RequestBody.create(MediaType.parse("text/plain"), "123"); 
Call<ResponseBody> loadCall = service.uploadfile(photo, userName,passWord);
loadCall.enqueue(new Callback<ResponseBody>() {
    @Override
    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
        Log.e("APP", response.body().source().toString());
    }
    @Override
    public void onFailure(Call<ResponseBody> call, Throwable t) {
        t.printStackTrace();
    }
});             

2占拍、多文件上傳攜帶參數(shù)(使用注解@PartMap@Part),需要再在手機SD卡目錄下的Pictures文件夾下添加xuezhiqian2.png圖片

@Multipart
@POST("UploadServlet")
Call<ResponseBody> uploadfile(@PartMap Map<String, RequestBody> params,  @Part("password") RequestBody password);
Retrofit retrofitUpload = new Retrofit.Builder()
                .baseUrl("http://192.168.1.8:8080/UploadFile/")
                .addConverterFactory(GsonConverterFactory.create())
                .build();
GitHubService service = retrofitUpload.create(GitHubService.class);
File file = new File(Environment.getExternalStorageDirectory()+"/Pictures", "xuezhiqian.png");
File file2 = new File(Environment.getExternalStorageDirectory()+"/Pictures", "xuezhiqian2.png");
RequestBody photoRequestBody = RequestBody.create(MediaType.parse("application/octet-stream"), file);
RequestBody photoRequestBody2 = RequestBody.create(MediaType.parse("application/octet-stream"), file2);  
RequestBody userName = RequestBody.create(MediaType.parse("text/plain"), "abc");
RequestBody passWord = RequestBody.create(MediaType.parse("text/plain"), "123"); 
Map<String,RequestBody> photos = new HashMap<>();
//添加文件一
photos.put("photos\"; filename=\""+file.getName(), photoRequestBody);
//添加文件二
photos.put("photos\"; filename=\""+file2.getName(), photoRequestBody2);
//添加用戶名參數(shù)
photos.put("username",  userName);
Call<ResponseBody> loadCall = service.uploadfile(photos, passWord);
loadCall.enqueue(new Callback<ResponseBody>() {
    @Override
    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
        Log.e("APP", response.body().source().toString());
    }
    @Override
    public void onFailure(Call<ResponseBody> call, Throwable t) {
        t.printStackTrace();
    }
});                            

要說明的是,多個文件上傳不能像單個文件上傳使用MultipartBody.Part對象捎迫,而是使用注解@PartMap添加多個RequestBody拼接filename來實現(xiàn)晃酒,@part("?")@Multipart注解已經(jīng)幫我們設(shè)置好成Content-Disposition:form-data; name="?"這樣子了,@part里的問號對應(yīng)name后面的問號窄绒,而我們要添加多個文件贝次,則需要在name里面作文章。所以就有了上面MAP集合中的KEY的拼接字符串彰导,我們想設(shè)置成Content-Disposition:form-data; name="photo"; filename="xuezhiqian.png",MAP集合KEY的值設(shè)置為photo"; filename="xuezhiqian.png替換name后面的問號就OK了,里面的引號在使用的時候需要加上反斜杠轉(zhuǎn)義嵌套使用蛔翅。

多文件上傳攜帶參數(shù)恼布,參考自:
http://blog.csdn.net/jdsjlzx/article/details/52246114

一直找不到免費上傳文件的接口來做測試,我就花了點時間自己做了一個搁宾,代碼也是參考網(wǎng)上的折汞,這里也給大家貼一下后臺核心接收文件保存的代碼,希望對大家有所幫助盖腿,自己動手豐衣足食爽待。

    /**
     * 上傳文件
     * @param request
     * @param response
     * @throws IOException
     */
    @SuppressWarnings("unchecked")
    private void uploadFile(HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.setContentType("text/html;charset=utf-8");// 設(shè)置響應(yīng)編碼
        request.setCharacterEncoding("utf-8");
        PrintWriter writer = response.getWriter();// 獲取響應(yīng)輸出流
        //ServletInputStream inputStream = request.getInputStream();// 獲取請求輸入流
        /*
         * 1、創(chuàng)建DiskFileItemFactory對象翩腐,設(shè)置緩沖區(qū)大小和臨時文件目錄 該類有兩個構(gòu)造方法一個是無參的構(gòu)造方法鸟款,
         * 另一個是帶兩個參數(shù)的構(gòu)造方法
         * 
         * @param int sizeThreshold,該參數(shù)設(shè)置內(nèi)存緩沖區(qū)的大小,默認值為10K茂卦。當上傳文件大于緩沖區(qū)大小時何什,
         * fileupload組件將使用臨時文件緩存上傳文件
         * 
         * @param java.io.File
         * repository,該參數(shù)指定臨時文件目錄,默認值為System.getProperty("java.io.tmpdir");
         * 
         * 如果使用了無參的構(gòu)造方法等龙,則使用setSizeThreshold(int
         * sizeThreshold),setRepository(java.io.File repository) 方法手動進行設(shè)置
         */
        DiskFileItemFactory factory = new DiskFileItemFactory();
        int sizeThreshold = 1024 * 1024;
        factory.setSizeThreshold(sizeThreshold);
        File repository = new File(request.getSession().getServletContext().getRealPath("temp"));
        // System.out.println(request.getSession().getServletContext().getRealPath("temp"));
        // System.out.println(request.getRealPath("temp"));
        factory.setRepository(repository);
        /*
         * 2处渣、使用DiskFileItemFactory對象創(chuàng)建ServletFileUpload對象,并設(shè)置上傳文件的大小
         * 
         * ServletFileUpload對象負責處理上傳的文件數(shù)據(jù)蛛砰,并將表單中每個輸入項封裝成一個FileItem 該對象的常用方法有:
         * boolean isMultipartContent(request);判斷上傳表單是否為multipart/form-data類型
         * List parseRequest(request);解析request對象罐栈,并把表單中的每一個輸入項包裝成一個fileItem
         * 對象,并返回一個保存了所有FileItem的list集合 void setFileSizeMax(long
         * filesizeMax);設(shè)置單個上傳文件的最大值 void setSizeMax(long sizeMax);設(shè)置上傳溫江總量的最大值
         * void setHeaderEncoding();設(shè)置編碼格式泥畅,解決上傳文件名亂碼問題
         */
        ServletFileUpload upload = new ServletFileUpload(factory);
        upload.setHeaderEncoding("utf-8");// 設(shè)置編碼格式荠诬,解決上傳文件名亂碼問題
        /*
         * 3、調(diào)用ServletFileUpload.parseRequest方法解析request對象,得到一個保存了所有上傳內(nèi)容的List對象
         */
        List<FileItem> parseRequest = null;
        try {
            parseRequest = upload.parseRequest(request);
        } catch (FileUploadException e) {
            e.printStackTrace();
        }
        /*
         * 4位仁、對list進行迭代柑贞,每迭代一個FileItem對象,調(diào)用其isFormField方法判斷是否是文件上傳
         * true表示是普通表單字段聂抢,則調(diào)用getFieldName钧嘶、getString方法得到字段名和字段值
         * false為上傳文件,則調(diào)用getInputStream方法得到數(shù)據(jù)輸入流涛浙,從而讀取上傳數(shù)據(jù)
         * FileItem用來表示文件上傳表單中的一個上傳文件對象或者普通的表單對象 該對象常用方法有: boolean
         * isFormField();判斷FileItem是一個文件上傳對象還是普通表單對象 true表示是普通表單字段康辑,
         * 則調(diào)用getFieldName、getString方法得到字段名和字段值 false為上傳文件轿亮,
         * 則調(diào)用getName()獲得上傳文件的文件名疮薇,注意:有些瀏覽器會攜帶客戶端路徑,需要自己減除
         * 調(diào)用getInputStream()方法得到數(shù)據(jù)輸入流我注,從而讀取上傳數(shù)據(jù) 
         * delete();表示在關(guān)閉FileItem輸入流后按咒,刪除臨時文件。
         */
        for (FileItem fileItem : parseRequest) {
            if (fileItem.isFormField()) {// 表示普通字段
                if ("username".equals(fileItem.getFieldName())) {
                    String username = fileItem.getString();
                    writer.write("您的用戶名:" + username + "<br>");
                }
                if ("password".equals(fileItem.getFieldName())) {
                    String userpass = fileItem.getString();
                    writer.write("您的密碼:" + userpass + "<br>");
                }
            } else {// 表示是上傳的文件
                    // 不同瀏覽器上傳的文件可能帶有路徑名但骨,需要自己切割
                String clientName = fileItem.getName();
                String filename = "";
                if (clientName.contains("\\\\\\\\")) {// 如果包含"\\\\"表示是一個帶路徑的名字,則截取最后的文件名
                    filename = clientName.substring(clientName.lastIndexOf("\\\\\\\\")).substring(1);
                } else {
                    filename = clientName;
                }
                UUID randomUUID = UUID.randomUUID();// 生成一個128位長的全球唯一標識
                filename = randomUUID.toString() + filename;
                /*
                 * 設(shè)計一個目錄生成算法励七,如果所用用戶上傳的文件總數(shù)是億數(shù)量級的或更多智袭,放在同一個目錄下回導(dǎo)致文件索引非常慢,
                 * 所以掠抬,設(shè)計一個目錄結(jié)構(gòu)來分散存放文件是非常有必要吼野,且合理的 將UUID取哈希算法,散列到更小的范圍两波,
                 * 將UUID的hashcode轉(zhuǎn)換為一個8位的8進制字符串瞳步,
                 * 從這個字符串的第一位開始,每一個字符代表一級目錄腰奋,這樣就構(gòu)建了一個八級目錄单起,每一級目錄中最多有16個子目錄
                 * 這無論對于服務(wù)器還是操作系統(tǒng)都是非常高效的目錄結(jié)構(gòu)
                 */
                int hashUUID = randomUUID.hashCode();
                String hexUUID = Integer.toHexString(hashUUID);
                // System.out.println(hexUUID);
                // 獲取將上傳的文件存存儲在哪個文件夾下的絕對路徑
                String filepath = request.getSession().getServletContext().getRealPath("upload");
                System.out.println("filePath="+filepath);
                for (char c : hexUUID.toCharArray()) {
                    filepath = filepath + "/" + c;
                }
                // 如果目錄不存在就生成八級目錄
                File filepathFile = new File(filepath);
                if (!filepathFile.exists()) {
                    filepathFile.mkdirs();
                }
                // 從Request輸入流中讀取文件,并寫入到服務(wù)器
                InputStream inputStream2 = fileItem.getInputStream();
                // 在服務(wù)器端創(chuàng)建文件
                File file = new File(filepath + "/" + filename);
                BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
                byte[] buffer = new byte[10 * 1024];
                int len = 0;
                while ((len = inputStream2.read(buffer, 0, 10 * 1024)) != -1) {
                    bos.write(buffer, 0, len);
                }
                writer.write("您上傳文件" + clientName + "成功<br>");
                // 關(guān)閉資源
                bos.close();
                inputStream2.close();
            }
        }
        // 注意Eclipse的上傳的文件是保存在項目的運行目錄劣坊,而不是workspace中的工程目錄里嘀倒。
    }

其實很簡單就是一個servelt,利用了commons-fileupload.jar和commons-io.jar兩個庫來實現(xiàn),兩個庫網(wǎng)上都可以找到

  • 文件下載

可以采用OKHTTP下載文件的方式局冰,利用ResponseBody對象测蘑,調(diào)用response.body().byteStream()方法獲取InputStream輸入流,通過寫文件操作來實現(xiàn)锐想。

  • 同步請求和結(jié)合RxJava的使用

1帮寻、同步請求

Call.execute()同步請求網(wǎng)絡(luò),要注意的是Android4.0以后不能在主線程里調(diào)用赠摇,要開一個異步線程來使用,
Call.enqueue()異步請求網(wǎng)絡(luò)浅蚪,加入一個回調(diào)藕帜,同步異步需要可按照不同的場景來使用。
Call.cancel()取消此次請求惜傲,有一些場景還是會用到該方法的洽故。

2、結(jié)合RxJava使用

添加RxJava環(huán)境盗誊,在builde.gradle里面添加上

compile 'io.reactivex:rxandroid:1.2.1'
compile 'io.reactivex:rxjava:1.1.6'
compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'

我們就拿上面post異步請求改成RxJava模式

@POST("query")
Observable<PostQueryInfo> searchRx(@Query("type") String type, @Query("postid") String postid);
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://www.kuaidi100.com/")
                 //添加數(shù)據(jù)解析ConverterFactory
                .addConverterFactory(GsonConverterFactory.create()) 
                 //添加RxJava
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())   
                .build();
        GitHubService apiService = retrofit.create(GitHubService.class);
        apiService.searchRx("yuantong","500379523313")
                //訪問網(wǎng)絡(luò)切換異步線程
                .subscribeOn(Schedulers.io())
                //響應(yīng)結(jié)果處理切換成主線程
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<PostQueryInfo>() {
                    @Override
                    public void onCompleted() {
                         //請求結(jié)束回調(diào)
                    }
                    @Override
                    public void onError(Throwable e) {
                         //錯誤回調(diào)
                        e.printStackTrace();
                    }
                    @Override
                    public void onNext(PostQueryInfo postQueryInfo) {
                         //成功結(jié)果返回
                        Log.e("APP",postQueryInfo.getNu());
                    }
                });

可能有些童鞋沒接觸過RxJava时甚,這里了解一下就可以,后面我會單獨寫一篇關(guān)于RxJava的文章哈踱,到時可以再回來看看荒适。

配置設(shè)置

  • 配置OKHttp

HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(10000L,TimeUnit.MILLISECONDS)       //設(shè)置連接超時
                .readTimeout(10000L,TimeUnit.MILLISECONDS)          //設(shè)置讀取超時
                .writeTimeout(10000L,TimeUnit.MILLISECONDS)         //設(shè)置寫入超時
                .cache(new Cache(getCacheDir(),10 * 1024 * 1024))   //設(shè)置緩存目錄和10M緩存
                .addInterceptor(interceptor)    //添加日志攔截器(該方法也可以設(shè)置公共參數(shù),頭信息)
                .build();
Retrofit retrofit = new Retrofit.Builder()
                .client(client)     //設(shè)置OkHttp
                .baseUrl("http://www.kuaidi100.com/")
                .addConverterFactory(GsonConverterFactory.create()) //  添加數(shù)據(jù)解析ConverterFactory
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())   //添加RxJava
                .build();
...省略后面的代碼

日志攔截器需要添加OkHttp對應(yīng)庫,okhttp的庫是3.4.1开镣,這里也需要設(shè)成3.4.1

compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'
  • 多種ConverterFactory設(shè)置

常見的ConverterFactory有

Gson: com.squareup.retrofit2:converter-gson:2.1.0
Jackson: com.squareup.retrofit2:converter-jackson:2.1.0
Moshi: com.squareup.retrofit2:converter-moshi:2.1.0
Protobuf: com.squareup.retrofit2:converter-protobuf:2.1.0
Wire: com.squareup.retrofit2:converter-wire:2.1.0
Simple XML: com.squareup.retrofit2:converter-simplexml:2.1.0
Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars:2.1.0

添加對應(yīng)得ConverterFactory只需要先在builde.gradle里面配置好上面對應(yīng)的庫刀诬,然后通過.addConverterFactory該方法添加即可

  • 添加混淆

# Platform calls Class.forName on types which do not exist on Android to determine platform.
-dontnote retrofit2.Platform
# Platform used when running on RoboVM on iOS. Will not be used at runtime.
-dontnote retrofit2.Platform$IOS$MainThreadExecutor
# Platform used when running on Java 8 VMs. Will not be used at runtime.
-dontwarn retrofit2.Platform$Java8
# Retain generic type information for use by reflection by converters and adapters.
-keepattributes Signature
# Retain declared checked exceptions for use by a Proxy instance.
-keepattributes Exceptions

介紹兩個好用好玩的AS插件

1、GsonFormat JSON解析成對象
2邪财、Sexy Editor 工作區(qū)間背景設(shè)置
都可以在Preferences下Plugins里搜索到

寫在最后的話:
這篇文章還是花了蠻長時間的陕壹,包括期間有一些其它事情耽擱了质欲,加上還是邊學邊寫變測試,雖然花的時間有點長但是我覺得還是學到了蠻多東西的糠馆,我相信我能一直堅持學下去嘶伟,一直寫下去。篇幅有點長謝謝大家能夠看到最后又碌!

成功源于不斷的學習和積累九昧。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市赠橙,隨后出現(xiàn)的幾起案子耽装,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扫俺,死亡現(xiàn)場離奇詭異剧罩,居然都是意外死亡,警方通過查閱死者的電腦和手機姓建,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缤苫,“玉大人速兔,你說我怎么就攤上這事』盍幔” “怎么了涣狗?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長舒憾。 經(jīng)常有香客問我镀钓,道長,這世上最難降的妖魔是什么镀迂? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任丁溅,我火速辦了婚禮,結(jié)果婚禮上探遵,老公的妹妹穿的比我還像新娘窟赏。我一直安慰自己,他們只是感情好箱季,可當我...
    茶點故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布涯穷。 她就那樣靜靜地躺著,像睡著了一般规哪。 火紅的嫁衣襯著肌膚如雪求豫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天,我揣著相機與錄音蝠嘉,去河邊找鬼最疆。 笑死,一個胖子當著我的面吹牛蚤告,可吹牛的內(nèi)容都是我干的努酸。 我是一名探鬼主播,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼杜恰,長吁一口氣:“原來是場噩夢啊……” “哼获诈!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起心褐,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤舔涎,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后逗爹,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體亡嫌,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年掘而,在試婚紗的時候發(fā)現(xiàn)自己被綠了挟冠。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,654評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡袍睡,死狀恐怖知染,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情斑胜,我是刑警寧澤控淡,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站止潘,受9級特大地震影響逸寓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜覆山,卻給世界環(huán)境...
    茶點故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望泥栖。 院中可真熱鬧簇宽,春花似錦、人聲如沸吧享。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽钢颂。三九已至钞它,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背遭垛。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工尼桶, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人锯仪。 一個月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓泵督,卻偏偏與公主長得像,于是被迫代替她去往敵國和親庶喜。 傳聞我的和親對象是個殘疾皇子小腊,可洞房花燭夜當晚...
    茶點故事閱讀 43,543評論 2 349

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,790評論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)久窟,斷路器秩冈,智...
    卡卡羅2017閱讀 134,633評論 18 139
  • 參考Android網(wǎng)絡(luò)請求心路歷程Android Http接地氣網(wǎng)絡(luò)請求(HttpURLConnection) 一...
    合肥黑閱讀 21,260評論 7 63
  • 楚館秦樓,醉花眠柳輕撫葉斥扛。 帕巾三疊入问,笑拭桃花靨。 冷雨寒風犹赖,庭院春光絕队他。 還道妾,落淚成血峻村,不解風花月麸折。
    霜染公子閱讀 414評論 1 3
  • 冬天來了,他們走在寒氣疼疼的小道上粘昨,大雪似洪水泛濫垢啼,沖破了天籟的寧靜,直接刺向兩張單薄的影子张肾,他看看她凍的通紅的臉...
    瑩子墨閱讀 171評論 0 0