Android | 網(wǎng)絡(luò)技術(shù)基礎(chǔ)梳理——WebView以及HTTP運(yùn)用(HttpURLConnection及OkHttp)、XML以及JSON解析(demo+bug)

本文目錄

  • 1.WebView的用法

    1. 使用HTTP協(xié)議訪問(wèn)網(wǎng)絡(luò)
    • 2.1 使用HttpURLConnection
    • 2.2 使用OkHttp
  • 3.解析XML格式數(shù)據(jù)

    • 3.1 Pull解析方式
    • 3.2 SAX解析方式
  • 4.解析JSON數(shù)據(jù)

    • 4.1使用JSONObject
    • 4.2 使用GSON
  • 5.網(wǎng)絡(luò)編程的最佳實(shí)踐——HttpUtil封裝(巧用回調(diào)機(jī)制、框架)

1.WebView的用法

  • 使用WebView控件,
    借其在自己的應(yīng)用程序中嵌入一個(gè)瀏覽器,
    以輕松展示各種網(wǎng)頁(yè)

新建一個(gè)WebViewTest項(xiàng)目,
修改activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <WebView
        android:id="@+id/web_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

接下來(lái)修改MainActivity.java:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        WebView webView = (WebView) findViewById(R.id.web_view);
        webView.getSettings().setJavaScriptEnabled(true);
        webView.setWebViewClient(new WebViewClient());
        webView.loadUrl("http://www.baidu.com");
    }
}
  • 首先是findViewById實(shí)例化對(duì)象;
  • getSettings()用來(lái)設(shè)置瀏覽器屬性;
    setJavaScriptEnabled(true)讓W(xué)ebView支持JavaScript腳本;
  • 調(diào)用WebView 的setWebViewClient()方法,
    傳入一個(gè)WebViewClient實(shí)例,
    作用是:使當(dāng)需要從一個(gè)網(wǎng)頁(yè)跳轉(zhuǎn)到另外一個(gè)網(wǎng)頁(yè)時(shí),
    目標(biāo)網(wǎng)頁(yè)仍然在當(dāng)前WebView中顯示,而不是打開(kāi)系統(tǒng)瀏覽器;
  • loadUrl()傳入網(wǎng)址,顯示網(wǎng)頁(yè)內(nèi)容;

接下來(lái),還需在AndroidManifest.xml中添加訪問(wèn)網(wǎng)絡(luò)的權(quán)限

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.webviewtest">
    
    <uses-permission android:name="android.permission.INTERNET"/>

...

</manifest>

至此,即可運(yùn)行程序了,其效果如下:

當(dāng)然還要注意一點(diǎn),如果你的模擬器和SDK是Android 9.0(API級(jí)別28),那運(yùn)行如上代碼會(huì)出現(xiàn)下面這個(gè)問(wèn)題:


原因是從Android 9.0(API級(jí)別28)開(kāi)始,默認(rèn)情況下禁用明文支持。
因此http的url均無(wú)法在webview中加載。

解決方法是在AndroidManifest.xml對(duì)應(yīng)的地方加入一句代碼即可:
android:usesCleartextTraffic="true"

解決之后便可以運(yùn)行成功了:

當(dāng)然,小伙伴們,生活往往沒(méi)那么簡(jiǎn)單,
百度搜索引擎框下面有很多吸引我們眼球的文章對(duì)吧,

你會(huì)發(fā)現(xiàn)當(dāng)你隨便點(diǎn)擊一篇文章,想要跳轉(zhuǎn)過(guò)去的時(shí)候,會(huì)出現(xiàn)下方這種報(bào)錯(cuò):

亦或者:

莫慌,其實(shí)都是一個(gè)道理,仔細(xì)看一下報(bào)錯(cuò),我們可以發(fā)現(xiàn)url的前綴都被替換了:
  • 這是因?yàn)槠渥远x了scheme
    類(lèi)似的還有alipays://,weixin://等等。
    webView只能識(shí)別http://https://開(kāi)頭的url,因此才會(huì)報(bào)此錯(cuò)。
    處理方法,對(duì)于這種自定義scheme的url 單獨(dú)處理即可。

修改代碼如下:
我們剛剛寫(xiě)了這段代碼對(duì)吧:
webView.setWebViewClient(new WebViewClient())
現(xiàn)在,
把傳入的WebViewClient實(shí)例變成一個(gè)以WebViewClient父類(lèi)匿名內(nèi)部類(lèi),
并重寫(xiě)shouldOverrideUrlLoading()方法,
在里面對(duì)方才報(bào)錯(cuò)中的自定義scheme進(jìn)行單獨(dú)處理即可:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        final WebView webView = (WebView) findViewById(R.id.web_view);
        ...
        webView.setWebViewClient(new WebViewClient(){
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {

                try{
                    if(url.startsWith("baiduboxapp://") || url.startsWith("baiduboxlite://" )){
                        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
                        startActivity(intent);
                        return true;
                    }
                }catch (Exception e){
                    return false;
                }
                webView.loadUrl(url);
                return true;
            }
        });
        webView.loadUrl("http://www.baidu.com");
    }
}

ok,這時(shí)候再運(yùn)行程序,便可以成功打開(kāi)各種推文了:

參考文章



2. 使用HTTP協(xié)議訪問(wèn)網(wǎng)絡(luò)

  • HTTP基于android的工作原理簡(jiǎn)述
    客戶端服務(wù)器發(fā)出一條HTTP請(qǐng)求,
    服務(wù)器收到請(qǐng)求之后會(huì)返回一些數(shù)據(jù)給客戶端,
    然后客戶端再對(duì)這些數(shù)據(jù)進(jìn)行解析處理就可以。

  • 一個(gè)瀏覽器的基本工作原理也就是如上了。
    上面使用的WebView控件,
    其實(shí)也就是app向百度服務(wù)器發(fā)起一條HTTP請(qǐng)求,
    接著服務(wù)器分析出我們想要訪問(wèn)的是百度的首頁(yè),
    于是會(huì)把該網(wǎng)頁(yè)的HTML代碼進(jìn)行返回,
    然后WebView再調(diào)用手機(jī)瀏覽器的內(nèi)核對(duì)返回的HTML代碼進(jìn)行解析
    最終將頁(yè)面展示出來(lái)。

  • 也即WebView封裝了發(fā)送HTTP請(qǐng)求、接受服務(wù)響應(yīng)解析返回?cái)?shù)據(jù),以及最終頁(yè)面的展示這幾步工作。


  • 下面暫時(shí)擺脫WebView,
    手動(dòng)發(fā)送HTTP請(qǐng)求,直觀地理解一下HTTP協(xié)議的工作過(guò)程。

2.1 使用HttpURLConnection
  • 首先,
    獲取HttpURLconnection的實(shí)例:

    a. 以參數(shù)為目標(biāo)的網(wǎng)絡(luò)地址,new 出一個(gè)URL對(duì)象;
    b. url對(duì)象調(diào)用openConnection();
     返回的結(jié)果轉(zhuǎn)型付給HttpURLConnection對(duì)象;
URL url = new URL("http://www.baidu.com");
HttpURLConnection connection = (HttpURLConnection)url.openConnection();


  • 得到HttpURLConnection實(shí)例之后,設(shè)置HTTP請(qǐng)求所使用的方法;

    常使用的方法主要有兩個(gè):GET和POST。
    GET表示希望從服務(wù)器獲取數(shù)據(jù),
    POST希望提交數(shù)據(jù)給服務(wù)器:
connection.setRequestMethod("GET");


  • 接下來(lái)進(jìn)行一些自由的定制,
    如設(shè)置連接超時(shí)、讀取超時(shí)的毫秒數(shù)、服務(wù)器希望得到的一些消息頭等。
    這些都是自己根據(jù)實(shí)際情況進(jìn)行編寫(xiě):
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);


  • 之后再調(diào)用getInputStream()方法,
    就可以獲取到服務(wù)器返回的輸入流了,
    后續(xù)的對(duì)輸入流進(jìn)行讀取即可:
InputStream in = connection.getInputStream();
  • 最后調(diào)用disconnect()關(guān)閉HTTP連接:
connection.disconnect();

下面新建一個(gè)名為NetworkTest的空活動(dòng),調(diào)試一下上面的知識(shí)點(diǎn),
修改對(duì)應(yīng)的xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".NetworkTest">

    <Button
        android:id="@+id/send_request"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Send Request"/>

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TextView
            android:id="@+id/response_text"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </ScrollView>

</LinearLayout>

以上,

  • ScrollView用于滾動(dòng)查看內(nèi)容;
  • Button用來(lái)發(fā)送HTTP請(qǐng)求;
  • TextView用來(lái)顯示服務(wù)器返回的數(shù)據(jù);

接著修改NetworkTest.java:

public class NetworkTest extends AppCompatActivity implements View.OnClickListener{

    TextView responseText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_network_test);

        Button sendRequest = (Button) findViewById(R.id.send_request);
        responseText = (TextView) findViewById(R.id.response_text);
        sendRequest.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if(v.getId() == R.id.send_request){
            sendRequestWithHttpURLConnection();
        }
    }

    private void sendRequestWithHttpURLConnection() {

        new Thread(new Runnable() {
            @Override
            public void run() {
                HttpURLConnection connection = null;
                BufferedReader reader = null;

                try {
                    URL url = new URL("https://hao.#/");
                    connection = (HttpURLConnection)url.openConnection();

                    connection.setRequestMethod("GET");

                    connection.setConnectTimeout(8000);
                    connection.setReadTimeout(8000);

                    InputStream in = connection.getInputStream();

                    //下面對(duì)獲取的輸入流進(jìn)行讀取
                    //InputStreamReader賦值給BufferedReader讓其來(lái)讀
                    reader = new BufferedReader(new InputStreamReader(in));
                    StringBuilder response = new StringBuilder();
                    String line;
                    //一行一行地讀取并加進(jìn)stringbuilder
                    while((line = reader.readLine()) != null){
                        response.append(line);
                    }
                    showResponse(response.toString());
                    Log.d("NetworkTest:  ", response.toString());
                } catch (IOException e) {
                    e.printStackTrace();
                }finally{
                    if(reader !=null){
                        try {
                            reader.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    if(connection !=null){
                        connection.disconnect();
                    }
                }
            }
        }).start();

    }

    private void showResponse(final String response) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                responseText.setText(response);
            }
        });
    }
}
  • sendRequestWithHttpURLConnection()中:
    開(kāi)啟一個(gè)子線程,
    在子線程里使用HttpURLConnection發(fā)出一條HTTP請(qǐng)求,
    請(qǐng)求的目標(biāo)地址就是百度的首頁(yè);
    接著用BufferedReader讀取返回的輸入流,
    轉(zhuǎn)成string傳給showResponse()
  • showResponse()中通過(guò)runOnUiThread()將返回的數(shù)據(jù)顯示到界面上;

關(guān)于runOnUiThread()方法,
因?yàn)锳ndroid不允許在子線程中進(jìn)行UI操作,
我們需要通過(guò)這個(gè)方法在子線程中將線程切換到主線程,
然后再更新UI元素。

運(yùn)行效果如下:

注:
如網(wǎng)址不正確,
會(huì)出現(xiàn)
NetworkSecurityConfig: No Network Security Config specified, using platform default的提示。
另外,用手機(jī)熱點(diǎn)也是如此?。。?br> 只有穩(wěn)定PC端接線網(wǎng)絡(luò)或其熱點(diǎn)才可順利使用??!

  • 服務(wù)器返回的就是這些HTML代碼,
    只是通常瀏覽器都會(huì)將這些代碼解析成漂亮的網(wǎng)頁(yè)再展示出來(lái);

  • 如果想提交數(shù)據(jù)給服務(wù)器,
    只需將HTTP請(qǐng)求方法改成POST,
    并在獲取輸入流之前要提交的數(shù)據(jù)寫(xiě)出即可。
    每條數(shù)據(jù)都要以鍵值對(duì)的形式存在,
    數(shù)據(jù)與數(shù)據(jù)之間用“&”符號(hào)隔開(kāi),如提交用戶名和密碼

connection.setRequestMethod("POST");
DataOutputStream out  = new DataOutputStream(connection.getOutputStream());
out.writeBytes("username=admin&password=123456");



2. 使用OkHttp

  • OkHttp由Square公司開(kāi)發(fā),其不僅在接口封裝上面做的簡(jiǎn)單易用,
    就連在底層實(shí)現(xiàn)上也是自成一派,
    比起原生的HttpURLConnection,可以說(shuō)是有過(guò)之而無(wú)不及,
    現(xiàn)在已經(jīng)成了廣大Android開(kāi)發(fā)者的首選網(wǎng)絡(luò)通信庫(kù)。
  • OkHttp項(xiàng)目主頁(yè)地址:https://github.com/square/okhttp

  • 使用之前,需添加OkHttp庫(kù)依賴(lài),
    打開(kāi)app/buid.gradle,在dependencies閉包中添加如下內(nèi)容:

    implementation("com.squareup.okhttp3:okhttp:3.14.0")

添加此依賴(lài),會(huì)自動(dòng)下載兩個(gè)庫(kù):OkHttp庫(kù)、Okio庫(kù)(是前者的通信基礎(chǔ))。

  • 注意,添加前最好是訪問(wèn)一下OkHttp項(xiàng)目主頁(yè)查看當(dāng)前最新的版本是多少,再在gradle處添加依賴(lài);
下面是OkHttp具體用法
  • 首先,需要?jiǎng)?chuàng)建OkHttpClient實(shí)例,如下:
OkHttpClient client = new OkHttpClient();
  • 接下來(lái),如想發(fā)起一條HTTP請(qǐng)求,需創(chuàng)建Request對(duì)象
Request request = new Request.Builder().build();
  • 當(dāng)然上述代碼只是創(chuàng)建一個(gè)空的Request對(duì)象,
    需要在build()方法之前可連綴很多其他方法豐富此Request對(duì)象。
Request request = new Request.Builder()
.url("https://www.baidu.com")
.build();
  • 之后調(diào)用OkHttpCilent的newCall()方法創(chuàng)建一個(gè)Call對(duì)象,
    并調(diào)用它的execute()方法發(fā)送請(qǐng)求,
    獲取服務(wù)器返回的數(shù)據(jù):
Response response = client.newCall(request).execute();
  • request存請(qǐng)求;
  • newCall接收request
  • execute執(zhí)行request
  • Response對(duì)象接收服務(wù)器返回的數(shù)據(jù);
  • 下面得到返回的具體內(nèi)容
String responseData = response.body().string();



如果發(fā)起一條POST請(qǐng)求,會(huì)比GET復(fù)雜些;

  • 需先構(gòu)建RequestBody對(duì)象存放待提交的參數(shù)
RequestBody requestBody = new FormBody.Builder()
.add("username", "admin")
.add("password", "123456")
.build();
  • 然后在Request.Builder中以RequestBody對(duì)象為傳入的參數(shù)調(diào)用post()方法,:
Request request = new Request.Builder()
.url("http://www.baidu.com")
.post(requestBody)
.build();
  • 接下來(lái)的操作就和GET請(qǐng)求一樣了,
    調(diào)用execute()方法發(fā)送請(qǐng)求并獲取服務(wù)器返回的數(shù)據(jù)即可。

現(xiàn)在改用OkHttp的方式把剛剛NewworkTest的內(nèi)容再實(shí)現(xiàn)一遍:

    @Override
    public void onClick(View v) {
        if(v.getId() == R.id.send_request){
//            sendRequestWithHttpURLConnection();
            sendRequestWithOkHttp();
        }
    }

private void sendRequestWithOkHttp() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    OkHttpClient client = new OkHttpClient();

                    Request request = new Request.Builder()
                            .url("https://hao.#/")
                            .build();

                    Response response = client.newCall(request).execute();
                    String responseData = response.body().string();
                    showResponse(responseData);
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }).start();
    }

    private void showResponse(final String response) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                responseText.setText(response);
            }
        });
    }

運(yùn)行時(shí)有可能出現(xiàn)下面的問(wèn)題,解決方法是把gradle中minsdk改成26以上即可:

運(yùn)行效果圖:



3.解析XML格式數(shù)據(jù)

  • 通常,每個(gè)需要訪問(wèn)網(wǎng)絡(luò)的應(yīng)用程序都會(huì)有一個(gè)自己的服務(wù)器,
    我們可以向服務(wù)器提交數(shù)據(jù)或者從服務(wù)器上獲取數(shù)據(jù);
  • 為了雙方能夠快速知道文本的用途,一般在網(wǎng)絡(luò)傳輸?shù)臄?shù)據(jù)都是格式化后的;
    這種數(shù)據(jù)會(huì)有一定的結(jié)構(gòu)規(guī)格和語(yǔ)義;
    當(dāng)另一方收到數(shù)據(jù)消息之后就可以按照相同的結(jié)構(gòu)規(guī)格進(jìn)行解析,從而去除他想要的那部分內(nèi)容;



搭建一個(gè)本地服務(wù)器
  • 在網(wǎng)絡(luò)上傳輸數(shù)據(jù)最常用的格式有兩種:XMLJSON

在開(kāi)始學(xué)習(xí)這兩種數(shù)據(jù)格式之前,
我們還需要搭建一個(gè)本地服務(wù)器,

  • 進(jìn)度大概進(jìn)行到
    可以在本地服務(wù)器文件夾下放置文件,
    然后在本地瀏覽器可以訪問(wèn)即可;

這里提供兩種方法:

  1. 可以使用單模塊原生的本地服務(wù)器Apache,
    具體的操作我之前已經(jīng)寫(xiě)過(guò)一篇詳細(xì)的博文:
博文剪影1
博文剪影2
  1. 或者學(xué)過(guò)PHP的朋友也可以使用PhpStudy集成環(huán)境(中的Apache模塊)來(lái)做服務(wù)器,具體的相關(guān)我也寫(xiě)過(guò)相關(guān)的博文哈:

PHPStudy的話,在如下文件途徑放下文件即可:

文件內(nèi)容:
<apps>
    <app>
        <id>1</id>
        <name>Google Maps</name>
        <version>1.0</version>
    </app>
    <app>
        <id>2</id>
        <name>Chrome</name>
        <version>2.1</version>
    </app>
    <app>
        <id>3</id>
        <name>Google Play</name>
        <version>2.3</version>
    </app>
</apps>

接著瀏覽器鍵入localhost/get_data.xml即可訪問(wèn):

  • 當(dāng)然,鍵入127.0.0.1/get_data.xml也是可以的:



  • 解析XML格式數(shù)據(jù)有很多方式,PullSAX解析是常用的兩種。

3.1 Pull解析方式

  • 這里我們依舊在NetworkTest 這個(gè)活動(dòng)上面做開(kāi)發(fā),重用方才網(wǎng)絡(luò)通信的代碼,把重心放在XML數(shù)據(jù)解析上;

  • 以上,我們已經(jīng)準(zhǔn)備好XML格式的數(shù)據(jù),
    現(xiàn)在編寫(xiě)代碼從中解析出我們想要得到的那部分內(nèi)容;

修改NetworkTest.java:

@Override
    public void onClick(View v) {
        if(v.getId() == R.id.send_request){
//            sendRequestWithHttpURLConnection();
            sendRequestWithOkHttp();
        }
    }

    private void sendRequestWithOkHttp() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    OkHttpClient client = new OkHttpClient();

                    //http://10.0.2.2/對(duì)于模擬器來(lái)說(shuō)是電腦本機(jī)IP地址**
                    Request request = new Request.Builder()
                            .url("http://47.100.78.251/testPackage/get_data.xml")
                            .build();

                    Response response = client.newCall(request).execute();
                    String responseData = response.body().string();

                    parseXMLWithPull(responseData);//解析服務(wù)器返回的數(shù)據(jù)**

//                    showResponse(responseData);
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }).start();
    }

    private void parseXMLWithPull(String xmlData) {
        try{
            XmlPullParserFactory factory = XmlPullParserFactory.newInstance();//獲取一個(gè)XmlPullParserFactory 實(shí)例
            XmlPullParser xmlPullParser = factory.newPullParser();//借助XmlPullParserFactory 實(shí)例得到 XmlPullParser對(duì)象
            xmlPullParser.setInput(new StringReader(xmlData));//XmlPullParser對(duì)象 setInput 把服務(wù)器返回的數(shù)據(jù)傳入即可開(kāi)始解析

            int eventType = xmlPullParser.getEventType();//通過(guò)getEventType()得到當(dāng)前的解析事件
            String id = "";
            String name = "";
            String version = "";

            //如果不到文末
            //解析事件不為XmlPullParser.END_DOCUMENT,說(shuō)明解析工作沒(méi)完成
            responseText.setText("");

            while(eventType != XmlPullParser.END_DOCUMENT){
                //解析事件不為XmlPullParser.END_DOCUMENT,說(shuō)明解析工作沒(méi)完成

                String nodeName = xmlPullParser.getName();//獲取當(dāng)前結(jié)點(diǎn)名字

                switch (eventType) {
                    //開(kāi)始解析某個(gè)節(jié)點(diǎn)
                    case XmlPullParser.START_TAG:
                        if ("id".equals(nodeName)) {
                            id = xmlPullParser.nextText();//獲取節(jié)點(diǎn)具體內(nèi)容
                        } else if ("name".equals(nodeName)) {
                            name = xmlPullParser.nextText();
                        } else if ("version".equals(nodeName)) {
                            version = xmlPullParser.nextText();
                        }
                        break;
                    //完成解析某個(gè)節(jié)點(diǎn)
                    case XmlPullParser.END_TAG:
                        if ("app".equals(nodeName)) {

                            //解析完一個(gè)app節(jié)點(diǎn)后打印獲取到的內(nèi)容
                            String finalId = id;
                            String finalName = name;
                            String finalVersion = version;

                            runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    responseText.append("MainActivity: id is " + finalId + "\n");
                                    responseText.append("MainActivity: name is " + finalName + "\n");
                                    responseText.append("MainActivity: version is " + finalVersion + "\n");
                                }
                            });

                        }
                        break;
                    default:
                        break;
                }
                eventType = xmlPullParser.next();//獲取下一個(gè)解析事件
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

Pull解析的使用思路是:

  • 通過(guò)XmlPullParserFactory等一系列API,
    得到一個(gè)XmlPullParser實(shí)例,
    再把數(shù)據(jù)
    傳給XmlPullParser實(shí)例xmlPullParser.setInput(new StringReader(xmlData));
    接著就可以開(kāi)始解析了;

  • 把XML的文末標(biāo)志、起始標(biāo)簽以及結(jié)束標(biāo)簽,
    約定為一個(gè)事件int eventType = xmlPullParser.getEventType();

    • 文末標(biāo)志事件用于判斷文件是否解析完,
    • 起始標(biāo)簽事件用于 判斷 以及 獲取標(biāo)簽節(jié)點(diǎn)中的內(nèi)容,
    • 結(jié)束標(biāo)簽事件則用于 判斷 以及
      去實(shí)現(xiàn)一個(gè)解析階段結(jié)束后的業(yè)務(wù)邏輯;


3.2 SAX解析方式

  • 除了Pull解析,SAX解析也是一種常用的解析方式,
    其用法比Pull解析復(fù)雜一些,
    但語(yǔ)義上會(huì)更清楚;

用法:

  • 新建一個(gè)類(lèi)繼承自DefaultHandler,并重寫(xiě)父類(lèi)5個(gè)方法。
public class MyHandler extends DefaultHandler {

    @Override
    public void startDocument() throws SAXException {
        super.startDocument();
    }

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        super.startElement(uri, localName, qName, attributes);
    }

    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        super.characters(ch, start, length);
    }

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        super.endElement(uri, localName, qName);
    }

    @Override
    public void endDocument() throws SAXException {
        super.endDocument();
    }
}

  • startDocument()在開(kāi)始XML解析時(shí)候調(diào)用;
  • startElement()在開(kāi)始解析某個(gè)節(jié)點(diǎn)時(shí)調(diào)用;
  • characters()在獲取節(jié)點(diǎn)中的內(nèi)容時(shí)候調(diào)用;
  • endElement()在完成解析某個(gè)節(jié)點(diǎn)的時(shí)候調(diào)用;
  • endDocument()在完成整個(gè)XML解析時(shí)調(diào)用;
  • startElement()、characters()、endElement()三個(gè)方法是有參數(shù)的,
    從XML中解析的數(shù)據(jù)會(huì)以參數(shù)的形式傳入到這些方法中;

  • 在獲取節(jié)點(diǎn)中的內(nèi)容時(shí),
    characters()方法可能會(huì)被調(diào)用多次,
    一些換行符也被當(dāng)做內(nèi)容解析出來(lái),
    我們需要針對(duì)這種情況在代碼中做好控制;

實(shí)踐

  • 新建一個(gè)類(lèi)繼承自DefaultHandler,并重寫(xiě)父類(lèi)5個(gè)方法:
    (注意代碼中的注釋?zhuān)?/strong>
public class ContentHandler extends DefaultHandler {

    public String nodeName;

    //面向XML文件配置全局屬性
    public StringBuilder id;

    public StringBuilder name;

    public StringBuilder version;

    @Override
    public void startDocument() throws SAXException {
        id = new StringBuilder();
        name = new StringBuilder();
        version = new StringBuilder();
    }

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        //記錄當(dāng)前節(jié)點(diǎn)
        nodeName = localName;
    }

    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        //根據(jù)當(dāng)前的節(jié)點(diǎn)名判斷將內(nèi)容添加到哪一個(gè)StringBuilder對(duì)象中
        if ("id".equals(nodeName)){
            id.append(ch, start, length);
        }else if ("name".equals(nodeName)){
            name.append(ch, start, length);
        }else if ("version".equals(nodeName)){
            version.append(ch, start, length);
        }
    }

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        if ("app".equals(localName)){

            // !!!!!!!!!!!
            //id\name\version中可能包含回車(chē)或換行符,需調(diào)用trim()方法除去
            // !!!!!!!!!!!
            Log.d("ContentHandler", "id is " + id.toString().trim());
            Log.d("ContentHandler", "name is " + name.toString().trim());
            Log.d("ContentHandler", "version is " + version.toString().trim());
            //最后要將StringBuilder清空,避免影響下一次內(nèi)容讀取
            id.setLength(0);
            name.setLength(0);
            version.setLength(0);
        }
    }

    @Override
    public void endDocument() throws SAXException {
        super.endDocument();
    }
}

修改MainActivity:

@Override
    public void onClick(View v) {
        if(v.getId() == R.id.send_request){
//            sendRequestWithHttpURLConnection();
            sendRequestWithOkHttp();
        }
    }

    private void sendRequestWithOkHttp() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    OkHttpClient client = new OkHttpClient();

                    //http://10.0.2.2/對(duì)于模擬器來(lái)說(shuō)是電腦本機(jī)IP地址**
                    Request request = new Request.Builder()
                            .url("http://47.100.78.251/testPackage/get_data.xml")
                            .build();

                    Response response = client.newCall(request).execute();
                    String responseData = response.body().string();

//                    parseXMLWithPull(responseData);//解析服務(wù)器返回的數(shù)據(jù)**

                    parseXMLWithSAX(responseData);//解析服務(wù)器返回的數(shù)據(jù)**

//                    showResponse(responseData);
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }).start();
    }

    private void parseXMLWithSAX(String xmlData) {
        try {
            SAXParserFactory factory = SAXParserFactory.newInstance();
            XMLReader xmlReader = factory.newSAXParser().getXMLReader();
            ContentHandler handler = new ContentHandler();
            //將ContentHandler的實(shí)例設(shè)置到XMLReader中
            xmlReader.setContentHandler(handler);
            //開(kāi)始執(zhí)行解析
            xmlReader.parse(new InputSource(new StringReader(xmlData)));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

運(yùn)行程序,點(diǎn)擊按鈕,查看日志:

除了Pull和SAX,還有DOM解析方式可用;




4.解析JSON數(shù)據(jù)

  • JSON的體積比XML更小,網(wǎng)絡(luò)傳輸更省流量,
    但語(yǔ)義性差,不如XML直觀。

  • 在對(duì)應(yīng)的服務(wù)器目錄下,新建一個(gè)get_data.json文件,內(nèi)容如下:

[{"id":"5","name":"Clash of Clans","version":"5.5"},
{"id":"6","name":"Boom Beach","version":"7.0"},
{"id":"7","name":"Clash Royale","version":"3.5"}]

4.1使用JSONObject

  • 解析JSON數(shù)據(jù)也有很多方法,可使用官方的JSONObject,
    谷歌的開(kāi)源庫(kù)GSON,
    或第三方的開(kāi)源庫(kù)如Jackson、FastJSON等.

  • 我們?cè)诜?wù)器中定義的json文件get_data.json的內(nèi)容是一個(gè)JSON數(shù)組,
    因此這里獲取到服務(wù)器的數(shù)據(jù)之后,
    直接將數(shù)據(jù)傳入到一個(gè)JSONArray對(duì)象中;

  • 然后循環(huán)遍歷這個(gè)JSONArray,
    從中取出的每一個(gè)元素都是一個(gè)JSONObject對(duì)象;

  • 這個(gè)JSONObject對(duì)象又會(huì)包含id、name和version這些數(shù)據(jù),
    即我們定義的json文件中的鍵值;

  • 接著只要調(diào)用getString()將這些數(shù)據(jù)取出來(lái)即可;

使用JSONObject,修改MainActivity:

@Override
    public void onClick(View v) {
        if(v.getId() == R.id.send_request){
//            sendRequestWithHttpURLConnection();
            sendRequestWithOkHttp();
        }
    }

    //子線程中進(jìn)行!?。。。。。。?!
    private void sendRequestWithOkHttp() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    OkHttpClient client = new OkHttpClient();

                    //http://10.0.2.2/對(duì)于模擬器來(lái)說(shuō)是電腦本機(jī)IP地址**
                    Request request = new Request.Builder()
                            .url("http://47.100.78.251/testPackage/get_data.json")
                            .build();

                    Response response = client.newCall(request).execute();
                    String responseData = response.body().string();

                    parseJSONWithJSONObject(responseData);

//                    parseXMLWithPull(responseData);//解析服務(wù)器返回的數(shù)據(jù)**

//                    parseXMLWithSAX(responseData);//解析服務(wù)器返回的數(shù)據(jù)**

//                    showResponse(responseData);
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (JSONException e) {
                    e.printStackTrace();
                }

            }
        }).start();
    }

    private void parseJSONWithJSONObject(String responseData) throws JSONException {
        //我們?cè)诜?wù)器中定義的json文件get_data.json的內(nèi)容是一個(gè)JSON數(shù)組
        JSONArray jsonArray = new JSONArray(responseData);


        responseText.setText("");
        for (int i = 0; i < jsonArray.length(); i++) {
            JSONObject jsonObject = jsonArray.getJSONObject(i);

            String id = jsonObject.getString("id");
            String name = jsonObject.getString("name");
            String version = jsonObject.getString("version");

            runOnUiThread(new Runnable() {
                @Override
                public void run() {

                    responseText.append("MainActivity: id is " + id + "\n");
                    responseText.append("MainActivity: name is " + name + "\n");
                    responseText.append("MainActivity: version is " + version + "\n");
                }
            });

        }
    }

運(yùn)行程序:


4.2 使用GSON

  • 添加依賴(lài):
    implementation 'com.google.code.gson:gson:2.8.5'
  • 它主要可以將一段JSON格式的字符串自動(dòng)映射成一個(gè)對(duì)象(定義一個(gè)類(lèi)對(duì)應(yīng)),
    不需手動(dòng)編寫(xiě)代碼解析。

  • 比如說(shuō)一段json格式的數(shù)據(jù)如下所示:
    {"name":"Tom","age":20}
    則定義一個(gè)Person類(lèi),
    加入name和age這兩個(gè)字段;

面向需要解析的json數(shù)據(jù)定義JavaBean類(lèi),
如果一個(gè)json數(shù)據(jù)有十幾幾十個(gè)鍵值對(duì),
而我們的業(yè)務(wù)只需要取其中的5個(gè)鍵值,
那這個(gè)JavaBean類(lèi),就定義需要的5個(gè)字段即可,
Gson會(huì)將json數(shù)據(jù)字符串
根據(jù)我們定義JavaBean類(lèi),
提取出相應(yīng)的數(shù)據(jù)并映射對(duì)應(yīng)的List;
json字符串中有多少套JavaBean類(lèi)字段對(duì)應(yīng)的鍵值,
映射得到的Listsize就有多少;

  • 接著簡(jiǎn)單調(diào)用如下代碼即可將JSON數(shù)據(jù)
    自動(dòng)解析成一個(gè)Person對(duì)象了:
Gson gson = new Gson();
Person person = gson.fromJson(jsonData, Person.class);
  • 如果需要解析的是一段JSON數(shù)組會(huì)稍微麻煩一點(diǎn),
    需要借助TypeToken將期望解析成的數(shù)據(jù)類(lèi)型傳入到fromJson()方法中,如:
    List<Person> people = gson.fromJson(jsonData, new TypeToken<List<Person>>(){}.getType());

基本用法就如上所述了,
接下來(lái)用GSON解析一下一開(kāi)始的數(shù)據(jù);

  • 首先新建一個(gè)App類(lèi):
public class App {
    private String id;
    private String name;
    private String version;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }
}

修改MainActivity:


    @Override
    public void onClick(View v) {
        if(v.getId() == R.id.send_request){
//            sendRequestWithHttpURLConnection();
            sendRequestWithOkHttp();
        }
    }

    //!?。。。。。?!
    //注意這里在子線程中進(jìn)行??!
    // UI更新需要切到主線程
    // ?。。。。。?!
    private void sendRequestWithOkHttp() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    OkHttpClient client = new OkHttpClient();

                    //http://10.0.2.2/對(duì)于模擬器來(lái)說(shuō)是電腦本機(jī)IP地址**
                    Request request = new Request.Builder()
                            .url("http://47.100.78.251/testPackage/get_data.json")
                            .build();

                    Response response = client.newCall(request).execute();
                    String responseData = response.body().string();

//                    parseJSONWithJSONObject(responseData);

                    parsJSONWithGSON(responseData);

//                    parseXMLWithPull(responseData);//解析服務(wù)器返回的數(shù)據(jù)**

//                    parseXMLWithSAX(responseData);//解析服務(wù)器返回的數(shù)據(jù)**

//                    showResponse(responseData);
                } catch (IOException e) {
                    e.printStackTrace();
                }
//                catch (JSONException e) {
//                    e.printStackTrace();
//                }

            }
        }).start();
    }

    private void parsJSONWithGSON(String jsonData) {
        Gson gson = new Gson();
        List<App> appList = gson.fromJson(jsonData, new TypeToken<List<App>>(){}.getType());

        for (App app : appList) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {

                    responseText.append("MainActivity: id is " + app.getId() + "\n");
                    responseText.append("MainActivity: name is " + app.getName() + "\n");
                    responseText.append("MainActivity: version is " + app.getVersion() + "\n");
                }
            });
        }
    }

運(yùn)行程序,效果同上。





5.網(wǎng)絡(luò)編程的最佳實(shí)踐

  • 方法提取
    應(yīng)用程序很可能會(huì)在許多地方都使用網(wǎng)絡(luò)功能,
    而發(fā)送HTTP請(qǐng)求的代碼基本相同,
    所以我們不能每次都去編寫(xiě)一遍發(fā)送HTTP請(qǐng)求的代碼,
    通常應(yīng)該把通用的網(wǎng)絡(luò)操作提取到一個(gè)公共類(lèi)里,
    并提供一個(gè)靜態(tài)方法,
    當(dāng)想要發(fā)起網(wǎng)絡(luò)請(qǐng)求的時(shí)候,
    只需要簡(jiǎn)單地調(diào)用一下這個(gè)方法即可。

    耗時(shí)操作
    另外,
    網(wǎng)絡(luò)請(qǐng)求通常都是屬于耗時(shí)操作,
    我們提取的發(fā)送HTTP請(qǐng)求的方法內(nèi)部
    如果沒(méi)有開(kāi)啟子線程,
    則有可能導(dǎo)致在調(diào)用的時(shí)候使得主線程阻塞,
    這里則需開(kāi)啟子線程來(lái)發(fā)起HTTP請(qǐng)求,

    數(shù)據(jù)返回
    另外還要考慮到,
    如果我們?cè)谝粋€(gè)請(qǐng)求方法內(nèi)部的
    開(kāi)啟了一個(gè)子線程來(lái)發(fā)送HTTP請(qǐng)求,
    服務(wù)器響應(yīng)的數(shù)據(jù)是無(wú)法進(jìn)行返回的,
    所有的耗時(shí)邏輯都是在子線程里進(jìn)行的,
    這個(gè)請(qǐng)求方法會(huì)在服務(wù)器還沒(méi)來(lái)得及響應(yīng)的時(shí)候就執(zhí)行結(jié)束了,
    當(dāng)然也就無(wú)法返回響應(yīng)的數(shù)據(jù)了;
  • 遇到這種既需要子線程來(lái)處理耗時(shí)操作,
    又要求能實(shí)時(shí)接收到服務(wù)器響應(yīng)到的數(shù)據(jù)的情況,
    可以考慮使用Java的回調(diào)機(jī)制來(lái)實(shí)現(xiàn):
  • 實(shí)現(xiàn)一個(gè)接口就是寫(xiě)一個(gè)插座,
    把封裝的東西寫(xiě)進(jìn)實(shí)現(xiàn)接口的類(lèi)中,
    把這個(gè)(匿名內(nèi)部)類(lèi)賦給回調(diào)方法(如setOnClickListener())
  • 內(nèi)部抽象調(diào)用,外部具體實(shí)現(xiàn)(的方法);
    內(nèi)部只管調(diào)用,
    外部只管實(shí)現(xiàn)(具體的處理方法、邏輯)!
  • 連接處:
    外部實(shí)現(xiàn)的方法,
    封裝在一個(gè)匿名內(nèi)部類(lèi)接口實(shí)現(xiàn)類(lèi)實(shí)例中,
    實(shí)例傳給抽象調(diào)用的工具類(lèi)設(shè)置方法或者構(gòu)造方法,
    實(shí)現(xiàn)內(nèi)外連接;

    這樣內(nèi)部調(diào)用的接口方法,
    就是源自外部傳進(jìn)來(lái)
    接口實(shí)例
    (在外部具體實(shí)現(xiàn)好的)接口方法(邏輯)

    數(shù)據(jù)在內(nèi)部抽象調(diào)用時(shí)傳遞給內(nèi)部 未具體實(shí)現(xiàn)的 接口方法,
    這里抽象調(diào)用未具體實(shí)現(xiàn)的 接口方法實(shí)際上在程序運(yùn)行使用時(shí),
    就是外部具體實(shí)現(xiàn)的接口及其方法(處理邏輯)
    (來(lái)自set傳進(jìn)來(lái)的 外部實(shí)現(xiàn)好諸接口方法接口實(shí)例
    如此一來(lái)數(shù)據(jù)便通過(guò)回調(diào)機(jī)制,
    由內(nèi)而外間接傳遞給了外部(給外部處理)

    小結(jié):
    內(nèi)部形式抽象調(diào)用,
    外部具體邏輯實(shí)現(xiàn)
    外部實(shí)現(xiàn)封裝在接口類(lèi)實(shí)例中傳進(jìn)來(lái),
    使得內(nèi)部形式調(diào)用能夠調(diào)用到了外部具體邏輯實(shí)現(xiàn)
  • 首先需要定義一個(gè)接口,這里取名HttpCallbackListener
    onFinish(String response)
    當(dāng)服務(wù)器成功響應(yīng)請(qǐng)求時(shí)調(diào)用,參數(shù)為服務(wù)器返回的數(shù)據(jù);
    onError(Exception e)
    當(dāng)進(jìn)行網(wǎng)絡(luò)操作出現(xiàn)錯(cuò)誤時(shí)調(diào)用,參數(shù)記錄錯(cuò)誤的詳細(xì)信息;
public interface HttpCallbackListener {
    void onFinish(String response);
    void onError(Exception e);
}
  • 接著新建一個(gè)剛剛說(shuō)的放著提取了通用網(wǎng)絡(luò)操作公共類(lèi)
    listener.onFinish(response.toString());
    回調(diào)外部傳進(jìn)來(lái)寫(xiě)好
    匿名內(nèi)部接口類(lèi)具體實(shí)現(xiàn)好的方法,
    這里公共類(lèi)抽象調(diào)用,
    調(diào)用公共類(lèi)方法的地方具體實(shí)現(xiàn)接口類(lèi)(常用匿名內(nèi)部類(lèi)方式實(shí)現(xiàn)),
    實(shí)現(xiàn)好了賦到這里來(lái);
public class HttpUtil {

    public static void sendHttpRequest( final String address, final HttpCallbackListener listener){

        new Thread(new Runnable() {

            @Override
            public void run() {
                HttpURLConnection connection = null;

                try {
                    URL url = new URL(address);

                    connection = (HttpURLConnection) url.openConnection();
                    connection.setRequestMethod("GET");
                    connection.setConnectTimeout(8000);
                    connection.setReadTimeout(8000);
                    connection.setDoInput(true);
                    connection.setDoOutput(true);

                    InputStream in = connection.getInputStream();
                    //對(duì)獲取到的輸入流進(jìn)行讀取
                    BufferedReader reader = new BufferedReader(new InputStreamReader(in));

                    StringBuilder response = new StringBuilder();
                    String line;

                    while ((line = reader.readLine()) != null){
                        response.append(line);
                    }

                    if (listener != null){
                        
                        //回調(diào)onFinish方法
                        listener.onFinish(response.toString());
                    }

                } catch (Exception e) {

                    if (listener != null){
                        //回調(diào)onError方法
                        listener.onError(e);
                    }
                }finally {
                    if (connection != null){
                        connection.disconnect();
                    }
                }
            }
        }).start();
    }
}
  • sendHttpRequest()注意點(diǎn):

    • HttpCallbackListener參數(shù);

    • 內(nèi)部開(kāi)啟子線程,子線程中進(jìn)行具體的網(wǎng)絡(luò)操作;
      子線程中是無(wú)法通過(guò)return語(yǔ)句來(lái)返回?cái)?shù)據(jù)的,
      因此這里將服務(wù)器響應(yīng)的數(shù)據(jù)
      傳入了HttpCallbackListeneronFinish()方法中,
      在調(diào)用者(調(diào)用公共類(lèi)方法者)處的接口(匿名)實(shí)現(xiàn)類(lèi)中處理,
      調(diào)用剛剛說(shuō)的在外部(調(diào)用者處)
      實(shí)現(xiàn)好接口(匿名)實(shí)現(xiàn)類(lèi)實(shí)例中的具體的onFinish()方法;
      異常原因
      傳入了HttpCallbackListeneronError()方法中,
      在調(diào)用者(調(diào)用公共類(lèi)方法者)處的接口(匿名)實(shí)現(xiàn)類(lèi)中處理,
      調(diào)用剛剛說(shuō)的在外部(調(diào)用者處)
      實(shí)現(xiàn)好接口(匿名)實(shí)現(xiàn)類(lèi)實(shí)例中的具體的onError()方法

  • 公共類(lèi)調(diào)用案例:(如上所述)利用回調(diào)機(jī)制將響應(yīng)數(shù)據(jù)成功返回給調(diào)用方:

String address = "http://www/baidu.com";
HttpUtil.sendHttpRequest(address, new HttpCallbackListener(){
            @Override
            public void onFinish(String response){
                //在這里根據(jù)返回內(nèi)容執(zhí)行具體的邏輯
            }
            @Override
            public void onError(Exception e){
                //在這里對(duì)異常情況進(jìn)行處理
            }
        });
  • 使用HttpURLConnection的寫(xiě)法總體比較復(fù)雜;
    使用OkHttp會(huì)簡(jiǎn)單一些;

  • 在HttpUtil中加入一個(gè)sendOkHttpRequest()方法:

public static void sendOkHttpRequest(String address, okhttp3.Callback callback) {
        OkHttpClient client = new OkHttpClient();
        
        Request request = new Request.Builder()
                .url(address)
                .build();

        client.newCall(request).enqueue(callback);
    }
  • 注意:
    方法中有一個(gè)okhttp3.Callback參數(shù),
    這個(gè)是OkHttp庫(kù)中自帶的一個(gè)回調(diào)接口,
    類(lèi)似于我們剛剛自己編寫(xiě)的HttpCallbackListener;

    然后在client.newCall()之后,
    沒(méi)有像之前那樣直接調(diào)用execute(),
    而是調(diào)用了enqueue()
    并把okhttp3.Callback參數(shù)傳入,

    OkHttpenqueue()中已經(jīng)幫我們開(kāi)好了子線程,
    在子線程中去執(zhí)行HTTP請(qǐng)求
    并將最后的請(qǐng)求結(jié)果回調(diào)到okhttp3.Callback,
    (也就是說(shuō),
    我們剛剛在sendHttpRequest()做的事情,
    子線程、請(qǐng)求、數(shù)據(jù)返回,
    OkHttp都幫我們做好了)

    最后,
    我們?cè)谕獠繉?shí)例化一個(gè)接口對(duì)象并具體實(shí)現(xiàn)方法,
    再把接口實(shí)例傳進(jìn)來(lái)sendOkHttpRequest(),
    賦值給對(duì)應(yīng)的enqueue()方法,
    完成任務(wù)!





參考自《第一行代碼》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • JSON JSON和XML都是需要解析的 JSON是一種輕量級(jí)的數(shù)據(jù)格式,一般用于數(shù)據(jù)交互服務(wù)器返回給客戶端的數(shù)據(jù)...
    JonesCxy閱讀 2,005評(píng)論 2 10
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類(lèi)型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,641評(píng)論 1 32
  • 一、簡(jiǎn)歷準(zhǔn)備 1、個(gè)人技能 (1)自定義控件、UI設(shè)計(jì)、常用動(dòng)畫(huà)特效 自定義控件 ①為什么要自定義控件? Andr...
    lucas777閱讀 5,383評(píng)論 2 54
  • 一. XML數(shù)據(jù)交換格式 XML數(shù)據(jù)交換格式是一種自描述的數(shù)據(jù)交互格式,雖然XML數(shù)據(jù)格式不如JSON "輕便",...
    __season____閱讀 2,615評(píng)論 0 7
  • 夢(mèng)話 蓍草填詞/江北客@伏羲夢(mèng)蝶@千江尋一客 和《遠(yuǎn)大前程》電視劇主題曲《真假》(欒杰女俠詞/譚旋老師曲)韻律 碼...
    江北客閱讀 1,188評(píng)論 0 1

友情鏈接更多精彩內(nèi)容