android10——Internet

Internet

webView的用法

有时候可能需要在应用中展示一些网页,但是这个任务是浏览器(browser)的,我们不可能编写一个浏览器,因此可以借助Webview控件,在自己的应用程序中嵌入一个浏览器。

    @SuppressLint("SetJavaScriptEnabled")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        WebView webView = (WebView) findViewById(R.id.web_view);
        // 让webview支持js脚本(IDE提示可能受到XSS攻击)=>添加@SuppressLint("SetJavaScriptEnabled")
        webView.getSettings().setJavaScriptEnabled(true);
        // 设置一个WebViewClient实体,作用是当需要从一个网页跳转到另外一个网页时候,希望目标网页依然在当前webview显示,而不是打开系统浏览器。
        webView.setWebViewClient(new WebViewClient());
        webView.loadUrl("http://www.baidu.com");
    }
  • webView.getSettings().setJavaScriptEnabled(true);:支持JS脚本,配合@SuppressLint("SetJavaScriptEnabled")使用
  • webView.setWebViewClient(new WebViewClient());:设置一个WebViewClient实体,作用是当需要从一个网页跳转到另外一个网页时候,希望目标网页依然在当前webview显示,而不是打开系统浏览器
  • webView.loadUrl("http://www.baidu.com");:传入网址并展示相应web内容。

另外,添加权限到manifest.xml:

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

使用HTTP访问网络

webview控件是我们向百度的服务器发起了一条http请求,接着服务器分析出我们想要访问的是百度的首页,于是把时该网页的html代码进行返回,然后webview再调用手机浏览器的内核对返回的html代码进行解析,最终将页面展示出来。

使用HttpURLConnection

Android上发送HTTP请求过去有两种方式:HttpURLConnectionHttpClient。由于HttpClient存在API数量过多,扩展困难等缺点,在Android6.0被移除了。所以只需要学习HTTPURLConnection

  • 首先创建一个URL对象,并传入目标的网络地址
  • 然后url实例的调用openConnection()方法获取HttpURLConnection实例。
  • 设置HTTP请求所使用的方法(GET/POST):connection.setRequestMethod("GET")
  • 自由制定,比如设置连接超时、读取超时以及降希望得到的一些消息头等。
  • 然后connection实例调用getInputStream()方法就可以获取服务器返回的输入流了。
  • 最后调用disconnect()方法将这个HTTP连接关闭。

以上代码应该在一个新的thread中写。

所以代码应该包括内外两个模块:

  • 外部为new Thread,里面是一个new Runnable的匿名实现类。
  • 内部有应该是try catch finally三个部分。
    • 其中try写主体,
    • finnaly则是对readerConnection使用closedisconnect()关闭。
  • 而这整个代码应该放在btn的回调函数里面
  • showResponese(responese.toString())应该另外开个线程展示返回结果(使用runOnUiThread)。
    private void sendRequestWithHttpURLConnection() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                HttpURLConnection connection = null;
                BufferedReader reader = null;
                try {
                    URL url = new URL("https://www.baidu.com");
                    connection =(HttpURLConnection) url.openConnection();
                    connection.setRequestMethod("GET");
                    connection.setConnectTimeout(8000);
                    connection.setReadTimeout(8000);
                    InputStream in = connection.getInputStream();
                    // 对获取的输入流进行读取
                    reader = new BufferedReader(new InputStreamReader(in));
                    StringBuilder response = new StringBuilder();
                    String line;
                    while ((line = reader.readLine()) != null){
                        response.append(line);
                    }
                    showResponse(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(String responese) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                responeseText.setText(responese);
            }
        });
    }

关于POST的使用:

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

使用OkHttp

OkHttp是一个开源的网络通信库,开源代替原生的HttpURLConnection。属于底层的网络框架,和HttpClient, HttpURLConnection这些框架一样,都是底层真正发起http请求的。

项目主页是:https://github.com/square/okhttp

使用OkHttp:

  • 添加依赖【按F4】:

dependencies {
	...
    implementation 'com.squareup.okhttp3:okhttp:4.1.0'
}
  • 添加上述依赖后,会添加两个库,一个是OkHttp库,一个是Okio库。

  • 具体用法:

    • 创建一个OkHttpClient实例。
    • 创建一个Request对象,通过url方法设置目标的网络地址,用于发起HTTP请求。
    • 调用OkHttpClientnewCall()方法来创建一个Call对象,并调用他的exeute方法来发送请求并获取服务器返回的数据。
    • 返回的Response对象就是服务器返回的数据,
    • showResponse()展示结果。
        private void sendRequestWithOkHttp(){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        // 创建OkHttpClient实例
                        OkHttpClient client = new OkHttpClient();
                        // 创建Request对象
                        Request request = new Request.Builder()
                                .url("https://www.baidu.com")
                                .build();
                        // 创建Responese对象
                        Response response = client.newCall(request).execute();
                        // 获取返回数据。不能用toString()
                        String responeseData = request.body().toString();
                        showResponse(responeseData);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    

okhttp对于HTML还好,但是对于XML是无法解析的:

okhttp3.internal.http.RealResponseBody@2be8f5f6

将 String res = response.body().toString();换成String res = response.body().string();即可。

解析XML格式数据

首先自己有个服务器,然后写一个xml文件。然后可以远程访问。

我这里使用tomcat在webapp/android目录下写了get_data.xml文件:

<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>

然后放在我的阿里云中。【2021.07到期】

执行一下命令打开tomcat【另外阿里云要自己打开端口】:

# 打开tomcat,把vue项目build生成的dist包,放到Tomcat的webapps/ROOT路径下
bash /usr/local/apache-tomcat-8.5.57/bin/startup.sh 

到此,准备工作结束。

解析xml格式的数据其实有很多种方式,常用的为Pull解析和SAX解析。

Pull解析数据

//    String responeseData = response.body().string();    
private void pasreXMLwithPull(String xmlData) {
        try{
            XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
            XmlPullParser xmlPullParser = factory.newPullParser();
            xmlPullParser.setInput(new StringReader(xmlData));
            int eventType = xmlPullParser.getEventType();
            String 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(TAG, "id is " + id);
                            Log.d(TAG, "name is" + name);
                            Log.d(TAG, "version is " + version);
                        }
                        break;
                    }
                    default:
                        break;
                }
                eventType = xmlPullParser.next();
            }

        } catch (XmlPullParserException | IOException e) {
            e.printStackTrace();
        }
    }
  • 创建一个XmlPulParserFactory()的实例。
  • 通过factory实例得到一个XmlPullParser对象。
  • 调用xmlPullParser.setInput(new StringReader(xmlData));将服务器返回的XML数据设置进去。【如果没有就会出现错误!!!】
  • 解析过程:
    • 通过getEventType()可以得到当前时事件,然后在一个while循环中不断地进行解析,如果当前的解析事件不等于XmlPullParser.END_DOCUMENT。说明解析还没有完成,调用next()方法后可以获取下一个解析事件。
    • 在while循环中,可以通过getName()方法得到当前节点的名字,如果发现节点名等于id等,就调用nextText()方法获取当前节点具体的内容。

SAX解析方式

SAX的用法比Pull解析复杂一点,但在语义上更加清楚一点。要使用SAX解析,通常情况下我们会新建一个类继承自DefaultHandler,并重写父类的5个方法:

  • startDocument():在开始XML解析的时候调用。
  • startElement():在开始解析某个节点的实话调用
  • characters():会在获取节点中内容的时候调用。【可能会被多次调用,一些换行也会被当做内容解析出来,需要注意!】
  • endElement():会在完成解析某个节点的时候调用
  • endDocument():会在完成整个XML解析的时候调用。
    private static final String TAG = "ContentHandler";
    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 {
        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)){
            Log.d(TAG, "id is " + id.toString().trim());
            Log.d(TAG, "name is " + name.toString().trim());
            Log.d(TAG, "version is " + version.toString().trim());
            // 最后清空sb
            id.setLength(0);
            name.setLength(0);
            version.setLength(0);
        }


    }

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

解析过程:

    private void parseXMLwitSAX(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 (IOException | SAXException | ParserConfigurationException e) {
            e.printStackTrace();
        }
    }

解析JSON格式数据

类似的解析JSON数据也有很多种方法,可以使用官方提供的JSONObject,也可以使用Google的开源库GSON。另外第三方的开源库如Jackson,FastJSON等也非常不错。本节学习两种。【略】

使用JSONObject

使用GSON

网络请求网络回调的实现方式

通常来说,我们应该将通用的网络操作提取到一个公共类里面,并提供一个通用的方法,当想要放弃网络请求的时候,只需简单地调用一下这个方法即可。以实现一下的效果:

String address = "https://www.baidu.com";
String responeseData = HttpUtil.sendHttpRequest(address);

在获取到服务器响应的数据后,我们就可以对它进行解析和处理了。但是需要注意,网络请求通常属于耗时操作,sendHttpRequest()方法内部并没有开启线程,这样就有可能导致调用sendHttpRequest()方法的时候主线程被阻塞。

我们不能在sendHttpRequest()内部添加一个线程,因此所有耗时逻辑都是在子线程进行的,sendHttpRequest()方法会在服务器还没来得及响应的时候就执行结束了。因此,这种情况需要使用回调机制来实现。

定义一个回调接口HttpCallbackListener,其包括两个方法:

  • onFinish():表示当服务器成功响应我们请求的时候调用。
  • onError():表示当进行网络操作出现错误的时候调用。

因此HttpUtil代码应该如下所示,他调用了okhttp3.Callback接口:

public class HttpUtil{
    public static void sendHttpRequest(String address, Callback callback) throws IOException {
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url(address)
                .build();
        client.newCall(request).enqueue(callback);
//        Response response = client.newCall(request).execute();
//        ResponseBody body = response.body();
//        return body == null ? "" : body.string();
    }
}

而在调用sendHttpRequest()方法的时候就可以这么写:

HttpUtil.sendHttpRequest(address,new okhttp3.Callback(){
    @Override
    public void onFailure(@NotNull Call call, @NotNull IOException e) {
    }

    @Override
    public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
        ResponseBody body = response.body();
        String responseData = body == null ? "":body.string();
        // 解析XML也在这里
    }
});

值得注意的是,无论是使用HttpURLConnection还是OkHttp,最终回调的接口都还是在子进程中运行的,因此我们不可以在这里执行任何UI操作,除了是借助了runOnUiThread()方法。

最好用的网络库:Retrofit

Retrofit 是一个 RESTful 的 HTTP 网络请求框架的封装,网络请求的工作本质上是 OkHttp 完成,而 Retrofit 仅负责 网络请求接口的封装。另外Retrofit还会将服务器返回的JSON数据自动解析成对象,他是借助GSON来解析的。当然Retrofit还支持其他的主流JSON解析库,包括Jackson、Moshi等。

项目地址:https://github.com/square/retrofit

Retrofit的基本用法

  • app/buidl.gradle中添加依赖:

    implementation 'com.squareup.retrofit2:retrofit:2.8.2'
    implementation 'com.squareup.retrofit2:converter-gson:2.8.2'
    
  • 创建一个App类,加入id、name、version这三个字段。

  • 新建一个AppService接口,并添加getAppData()方法

    • getAppData()方法添加@GET注解:这个是retrofit的一个注解,表示当调用这个方法的时候retrofit会发起一条GET请求,里面填path。【很像springMVC】
    • getAppData()方法的返回值必须声明成Retrofit内置的Call类型,并通过泛型来指定服务器响应的数据应该转换成什么对象。【当然Retrofit还提供了强大的Call Adapters功能,可以retrofit和RxJava结合使用返回成Observable等类型】
  • 调用retrofit的相关代码:

    • new Retrofit.Builder()创建retrofit实例。
    • 设置baseUrl、addConverterFactory、addCallAdapterFactory等。
    • 调用retrofit实例的create方法。并传入具体Service接口所对应的Class类型,创建一个该接口的动态代理对象。
    • 通过动态代理对象调用getAppData()方法,获取Call<List<App>>对象。
    • 调用enqueue()方法,retrofit机会根据注解配置的服务器接口地址去进行网络请求,当发起请求的时候,retrofit会自动在内部开启子线程,当数据回调到Callback之后,retrofit又会切换回主线程。

代码如下(注意baseUrl):

    private void sendRequestWithRetrofit() {
            Retrofit retrofit = new Retrofit.Builder()
                    // IllegalArgumentException: baseUrl must end in /:
                    .baseUrl("http://121.41.230.127:8080/android/")
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
            IAppService appService = retrofit.create(IAppService.class);
            appService.getAppData().enqueue(new Callback<List<App>>() {
                @Override
                public void onResponse(retrofit2.Call<List<App>> call, retrofit2.Response<List<App>> response) {
                    List<App> body = response.body();
                    if (body!=null){
                        for(App app : body){
                            Log.d(TAG, "id is "+ app.getId());
                            Log.d(TAG, "name is "+ app.getName());
                            Log.d(TAG, "version is "+ app.getVersion());
                        }
                    }
                }

                @Override
                public void onFailure(retrofit2.Call<List<App>> call, Throwable t) {
                    t.printStackTrace();
                }
            });
    }

接口代码:

import java.util.List;

import retrofit2.Call;
import retrofit2.http.GET;

public interface IAppService {
    @GET("get_data.json")
    Call<List<App>> getAppData();
}

结果:

2020-12-14 21:20:16.915 20502-20502/com.ssozh.networktest D/MainActivity: id is 5
2020-12-14 21:20:16.915 20502-20502/com.ssozh.networktest D/MainActivity: name is Clash of Clans
2020-12-14 21:20:16.915 20502-20502/com.ssozh.networktest D/MainActivity: version is 5.5
2020-12-14 21:20:16.915 20502-20502/com.ssozh.networktest D/MainActivity: id is 6
2020-12-14 21:20:16.915 20502-20502/com.ssozh.networktest D/MainActivity: name is Boom Beach
2020-12-14 21:20:16.915 20502-20502/com.ssozh.networktest D/MainActivity: version is 7.0
2020-12-14 21:20:16.915 20502-20502/com.ssozh.networktest D/MainActivity: id is 7
2020-12-14 21:20:16.915 20502-20502/com.ssozh.networktest D/MainActivity: name is Clash Royale
2020-12-14 21:20:16.915 20502-20502/com.ssozh.networktest D/MainActivity: version is 3.5

处理复杂的接口地址类型

很显然服务器不可能总是给我们提供静态类型的接口,在很多场景下,接口地址中的部分内容可能是动态比阿努哈的,比如接口地址:

GET http://example.com/<page>/get_data.json

在这个接口中,<page>部分代表页数。对于这种接口,retrofit的下发应该如下:

@GET("{page}/get_data.json")
Call<List<App>> getAppData(@Path("page") int page);

retrofit对常用的HTTP请求类型都进行了支持,使用@GET,@POST,@PUT,@PATCH,@DELETE注解,就可以让retrofit发送相应的请求了。

个人认为retrofit和SpringMVC很像,这里就不在多了解了。 更多还是去读文档吧。

Retrofit构建器的最佳写法

和上面的网络请求一样,retrofit也没有必要每次都要写一遍,因为构建出的retrofit对象是全局通用的,只需要在调用create()方时针对不同的Service接口传入相应的Class类型即可。因此,我们可以把这个通用的部分封装起来,从而简化获取Service接口动态代理对象的过程。

新建一个ServiceCreator单例类,代码如下所示:

/**
 * serviceCreator单例类,基于 Retrofit2实现
 */
public class ServiceCreator {
    private static volatile ServiceCreator singleton;
    private final Retrofit retrofit;
    private ServiceCreator(){
        String BASE_URL = "http://121.41.230.127:8080/android/";
        retrofit = new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
    }

    public static ServiceCreator getServiceCreator(){
        if(singleton == null){
            synchronized (ServiceCreator.class){
                if(singleton == null){
                    singleton = new ServiceCreator();
                }
            }
        }
        return singleton;
    }

    public <T> T create( Class<T> cl){
        return retrofit.create(cl);
    }
}

服务的最佳实践——完整版的下载示例

原文地址:https://www.cnblogs.com/SsoZhNO-1/p/14138521.html