Android开发基础知识整理之多线程与网络技术

本篇主要涉及Android中的多线程和网络技术。

一、 多线程

(一) 线程的基本用法

1. 继承方式

class MyThread extends Thread {

    @Override
    public void run() {
        // 处理具体的逻辑
    }

}

new MyThread().start(); // 启动线程

2. 实现Runnable接口方式

class MyThread implements Runnable {

    @Override
    public void run() {
        // 处理具体的逻辑
    }

}

MyThread myThread = new Mythread(); 
new Thread(myThread).start(); //启动线程

3. 匿名类方式

new Thread(new Runnable() {

    @Override
    public void run() {
        // 处理具体的逻辑
    }

}).start();

(二) 子线程中更新UI

更新UI必须在主线程中进行,否则会出错。Android提供了异步消息处理机制,来解决子线程中进行UI操作的问题。

1. 主线程中:new出一个Handler对象,重写父类的 handleMessage() 方法,对Message进行处理;

2. 子线程中:创建一个Message对象,添加一些数据,调用 Handler的 sendMessage() 发送出去。

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    public static final int UPDATE_TEXT = 1;

    private TextView text;

    private Handler handler = new handler() {

        public void handleMessage(Message msg) {
            swtich (msg.what) {
                case UPDATE_TEXT:
                    // 在这里可以进行UI操作
                    text.setText("Nice to meet you");
                    break;
                default:
                    break;
            }
        }
    };

    ...

    @Override
    public void onClick(View v) {
        switch(v.getId()) {
            case R.id.change_text:
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Message message = new Message();
                        message.what = UPDATE_TEXT;
                        handler.sendMessage(message); // 将Message对象发送出去
                    }
                }).start();
                break:
            default:
                break;
        }       
    }

}

(三) 解析异步消息处理机制

异步消息处理主要由4个部分组成:Message、Handler、MessageQueue和Looper。

1. Message

Message是在线程之间传递的消息,可在内部携带少量信息(what字段,arg1和arg2携带整型数据,obj字段携带Object对象)。

2. Handler

用于发送和处理消息。sendMessage()handleMessage() 方法。

3. MessageQueue

消息队列,用于存放所有Handler发送的消息。每个线程中只会有一个MessageQueue对象。

4. Looper

Looper是消息队列的管家,调用它的 loop() 方法后就会进入循环中,每当发现队列中有一条消息就会取出并传递到 handleMessage() 方法中。每个线程也只有一个Looper对象。


异步消息处理流程:

异步消息处理流程

runOnUiThread() 方法就是一个异步消息处理机制的接口封装。

(四) 使用AsyncTask

1. 创建子类继承AsyncTask抽象类,为AsyncTask类指定3个泛型参数;

  • Params:执行AsyncTask时需传入的参数,可用于在后台任务中使用。
  • Progress:后台执行时若需在界面上显示进度,使用这里指定的泛型作为进度单位。
  • Result:任务执行完毕若需返回结果,使用这里指定的泛型作为返回值类型。
class DownloadTask extend AsyncTask<Void, Integer, Boolean> {
    ...                         // 不需传入参数给后台任务,使用整型作为进度单位,使用布尔型反馈执行结果
}

2. 重写几个方法完成对任务的定制;

  • onPreExecute():后台任务开始执行之前调用,用于进行一些界面上的初始化操作,如显示一个进度对话框等。
  • doInBackground(Params...):在子线程中运行,在这里处理所有耗时任务,完成后通过return返回结果。如果需要更新UI元素可以调用 publishProgress(Progress...) 方法。
  • onProgressUpdate(Progress...):后台任务中调用了publishProgress方法后此方法很快被调用,Progress参数就是从后台任务中传递过来的。此方法中可以对UI进行操作更新。
  • onPostExecute(Result):后台任务执行完并return返回结果时此方法很快被调用。可以利用返回的数据进行UI操作,如提醒任务执行结果、关闭进度对话框等。
class DownloadTask extends AsyncTask<Void, Integer, Boolean> {

    @Override
    protected void onPreExecute() {
        progressDialog.show(); // 显示进度对话框@Override
    protected void doInBackground(Void... params) {
        try {
            while (true) {
                int downloadPercent = doDownload(); // 这是一个虚构的方法
                publishProgress(downloadPercent);
                if (downloadPercent >= 100( {
                    break;
                }
            }
        } catch (Exception e) {
            return false;
        }
        return true;
    }

    @Override
    protected onProgressUpdate(Integer... values) {
        // 这里更新下载进度
        progressDialog.setMessage("Download " + values[0] + "%");
    }

    @Override
    protected void onPostExectute(Boolean result) {
        progressDialog.dismiss(); // 关闭进度对话框
        if (result) {
            Toast.makeText(context, "Download succeeded", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(context, "Download failed", Toast.LENGTH_SHORT).show();
        }
    }

}

总结:doInBackground() 中执行具体耗时任务,onProgressUpdate() 中进行UI操作,onPostExecute() 中执行一些任务收尾工作。

3. 启动任务。

调用 execute() 方法。

new DownloadTask().execute();

二、 网络技术

(一) 使用WebView

  1. 获取WebView实例后调用它的 getSettings() 方法可以设置一些浏览器属性;
  2. 调用它的 setWebViewClient() 方法传入一个WebViewClient实例,表示跳转网页时仍然在当前WebView中显示,而不是打开系统浏览器;
  3. 调用WebView的 loadUrl() 传入网址。
<WebView
    android:id="@+id/web_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
WebView webview = (WebView) findViewById(R.id.web_view);
webview.getSettings().setJavaScriptEnabled(true);
webView.setWebViewClient(new WebViewClient());
webView.loadUrl("http://www.baidu.com")

(二) 使用HTTP协议访问网络

1. 使用HttpURLConnection

(1) 获取HttpURLConnection实例;

new出一个URL对象,传入目标网址,调用 openConnection() 方法即可。

URL url = new URL("http://www.baidu.com")
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
(2) 设置HTTP请求使用的方法;

常用方法有两个:GET(从服务器获取数据)和POST(向服务器提交数据)。

connection.setRequestMethod("GET");
(3) 进行一些定制;

如设置连接超时、读取超时的毫秒数,服务器希望得到的消息头等。

connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
(4) 调用 getInputStream() 获取服务器返回的输入流;
InputStream in = connection.getInputStream();
(5) 调用 disconnect() 关闭HTTP连接。
connection.disconnect();

2. 使用OkHttp

(1) 创建一个OkHttpClient实例;
OkHttpClient client = new OkHttpClient();
(2) 创建一个Request对象;
  • GET请求:
Request request = new Request.Builder().url("http://www.baidu.com").build();
  • POST请求:
RequestBody requestBody = new FormBody.Builder()
        .add("username", "admin")
        .add("password", "123456")
        .build();                         // 先构建一个RequestBody对象存放待提交的参数

Request request = new Requset.Builder()
        .url("http://www.baidu.com")
        .post(requestBody)
        .build();                         // 在Builder中调用post()方法并将RequestBody对象传入
(3) 调用OkHttpClient的 newCall() 方法来创建一个Call对象,并调用它的 execute() 方法来发送请求并获取服务器返回的数据。
Response response = client.newCall(request).execute(); // 获取返回的数据
String responseData = response.body().string(); // 得到返回的具体内容

(三) 解析XML格式数据

1. Pull解析方式

(1) 获取XmlPullFactory的实例,借助这个实例得到XmlPullParser对象,调用它的 setInput() 方法将服务器返回的XML数据设置进去即可开始解析;
(2) 通过 getEvent() 方法可得到当前解析事件,在一个while循环中进行解析,若不等于XmlPullParser.END_DOCUMENT说明解析未完成,调用 next() 方法获取下一个解析事件;
(3) while循环中,通过 getName() 得到当前节点名字,若等于id、name或version就调用 nextText() 方法获取节点内具体内容。

private void parseXMLWithPull(String xmlData) {
    try {
        XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
        XmlPullParser xmlPullParser = factory.newPullParser();
        xmlPullParser.setInput(new StringReader(xmlData));
        int eventType = xmlPullParser.getEventType();
        Sting id = "";
        String name = "";
        String version = "";
        while (eventType != XmlPullParser.END_DOCUMENT) {
            String nodeName = xmlPullParser.getName();
            switch (eventType) {
                // 开始解析某个节点
                case XmlPullParser.START_TAG: {
                    if ("id".equals(nodeName)) {
                        id = xmlPullParser.nextText();
                    } else if ("name".equals(nodeName)) {
                        name = xmlPullParser.nextText();
                    } else if ("version".equals(nodeName)) {
                        version = xmlPullParser.nextText();
                    }
                    break; 
                }
                // 完成解析某个节点
                case XmlPullParser.END_TAG: {
                    if ("app".equals(nodeName)) {
                        Log.d("MainActivity", "id is " + id);
                        Log.d("MainActivity", "name is " + name);
                        Log.d("MainActivity", "version is " + version);
                    }
                    break;
                }
                default:
                    break;
            } 
            eventType = xmlPullParser.next();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

2. SAX解析方式

(1) 新建一个类ContentHandler继承自DefaultHandler,重写父类的5个方法:startDocument()startElement()characters()endElement()endDocument()
(2) MainActivity中获取SaxParserFactory的实例,调用它的 newSAXParser().getXmlReader() 获取到XmlSAXParser的XMLReader对象,再将ContentHandler的实例设置到XMLReader中,最后调用 parse(InputSource) 方法执行解析。

public class ContentHandler extends DefaultHandler {

    private String nodeName;
    private StringBuilder id;
    private StringBuilder name;
    private 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 {
        // 记录当前节点名
        nodeName = localName;
    }

    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        // 根据当前的节点名将内容添加到哪一个StringBuilder对象中
        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(nodeName)) {
            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清空掉
            id.setLength(0);
            name.setLength(0);
            version.setLength(0);
        }
    }

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

}           
private void parseXMLWithSAX(String xmlData) {
    try {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        XMLReader xmlReader = factory.newSAXParser().getXMLReader();
        ContentHandler handler = new ContentHandler();
        // 将ContentHandler的实例设置到XMLReader中
        xmlReader.setContentHandler(handler);
        // 开始执行解析
        xmlReader.parse(new InputSource(new StringReader(xmlData));
    } catch (Exception e) {
        e.printStackTrace();
    }
}

3. Pull、SAX、DOM比较

  • SAX(Simple API for XML)解析器是一种基于事件的解析器,它的核心是事件处理模式,主要是围绕着事件源以及事件处理器来工作的。SAX解析器的优点是解析速度快,占用内存少。
  • DOM在内存中以树形结构存放,因此检索和更新效率会更高。但是对于特别大的文档,解析和加载整个文档将会很耗资源。
  • PULL解析器的运行方式和SAX类似,都是基于事件的模式。PULL解析器小巧轻便,解析速度快,简单易用。

  • Pull解析器和SAX解析器虽有区别但也有相似性。
    • 他们的区别为:SAX解析器的工作方式是自动将事件推入注册的事件处理器进行处理,因此你不能控制事件的处理主动结束;而Pull解析器的工作方式为允许你的应用程序代码主动从解析器中获取事件,正因为是主动获取事件,因此可以在满足了需要的条件后不再获取事件,结束解析。也就是说pull是一个while循环,随时可以跳出,而sax不是,sax是只要解析了,就必须解析完成,在解析过程中在读取到特定tag时调用相应处理事件。这是他们主要的区别。
    • 而他们的相似性在运行方式上,Pull解析器也提供了类似SAX的事件,开始文档START_DOCUMENT和结束文档END_DOCUMENT,开始元素START_TAG和结束元素END_TAG,遇到元素内容TEXT等,但需要调用 next() 方法提取它们(主动提取事件)。

(四) 解析JSON格式数据

1. 使用JSONObject

将服务器返回的数据传入到一个JSONArray对象中,循环遍历取出其中的每个JSONObject对象,调用 getString() 方法取出即可。

private void parseJSONWithJSONObject(String jsonData) {
    try {
        JSONArray jsonArray = new JSONArray(jsonData);
        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");
            Log.d("MainActivity", "id is " + id);
            Log.d("MainActivity", "name is " + name);
            Log.d("MainActivity", "version is " + version); 
        }
    } catch (Exception e) {
        e.printStackTrace;
    }
}

2. 使用GSON

GSON可以将一段JSON格式字符串自动映射成一个对象,调用 gson.fromJson(jsonData, XX.class)

  • 解析一段JSON数据:
Gson gson = new Gson();
Person person = gson.fromJson(jsonData, Person.class);
  • 解析一段JSON数组:借助TypeToken
List<Person> people = gson.fromJson(jsonData, new TypeToken<List<Person>>(){}.getType());

例如:新增App类,加入id、name和version三个字段和相应的getter和setter方法后,修改MainActivity中代码:

private void parseJSONWithGSON(String jsonData) {
    Gson gson = new Gson();
    List<App> appList = gson.fromJson(jsonData, new TypeToken<List<App>>(){}.getType());
    for (APP app : appList) {
        Log.d("MainActivity", "id is " + app.getId());
        Log.d("MainActivity", "name is " + app.getName());
        Log.d("MainActivity", "version is " + app.getVersion());
    }
}

(五) 网络编程最佳实践

通用的网络操作一般提取到一个公共的类里,并提供一个静态方法。同时使用JAVA的回调机制,防止子线程中服务器还没来得及相应静态方法就执行结束。

1. 对HttpURLConnection封装

先定义一个接口,服务器成功响应时调用 onFinish()、出现错误时调用 onError()

public interface HttpCallbackListener {

    void onFinishi(String response);
    void onError(Exception e);

}

sendHttpRequest() 添加一个HttpCallbackListener参数,在方法内部开启一个子线程进行网络操作,将服务器响应的数据传入HttpCallbackListener的 onFinish() 方法中,将异常传入 onError() 中。

public class HttpUtil {

    public static void sengHttpRequest(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); // 可以使用conn.getInputStream().read()
                    connection.setDoOutput(true); // 可以使用conn.getOutputStream().write()
                    InputStream in = connection.getInputStream();
                    BufferedReader reader = new BufferedReader(new InputStreamReader(in));
                    StringBuilder response = new StringBuilder();
                    String line;
                    while ((line = reader.readLine()) != null) {
                        response.append(line);
                    }
                    if (Listener != null) {
                        // 回调onFinish()方法
                        listener.onFinish(response.toString());
                    }
                } catch (Exception e) {
                    if (Listener != null) {
                        // 回调onError()方法
                        listener.onError(e);
                    }
                } finally {
                    if (connection != null) {
                        connection.disconnect();
                    }
                }
            }
        }).start();
    }

}

调用时将HttpCallbackListener实例传入,重写 onFinish()onError() 来执行具体逻辑。

HttpUtil.sendHttpRequest(address, new HttpCallbackListener() {
    @Override
    public void onFinishi(String response) {
        // 在这里根据返回内容执行具体的逻辑@Override
    public void onError(Exception e) {
        // 在这里对异常情况进行处理
    }
});

2. 使用OkHttp

使用OkHttp库中自带的回调结构okhttp3.Callback,newCall之后调用 enqueue(callback) 方法会在子线程中执行HTTP请求并将结果回调到okhttp3.Callback中。

pubic class HttpUtil {

    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);
    }

}

调用时重写 onResponse()onFailure() 来执行具体逻辑。

HttpUtil.sendOkHttpRequset("http://www.baidu.com", new okhttp3.Callback() {
    @Override
    public void onResponse(Call call, Response response) throws IOException {
        // 得到服务器返回的具体内容
        String responseData = response.body().string();

    @Override
    public void onFailure(Call call, IOException e) {
        // 在这里对异常情况进行处理
    }

});
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值