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请求过去有两种方式:HttpURLConnection
和HttpClient
。由于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则是对
reader
和Connection
使用close
和disconnect()
关闭。
- 而这整个代码应该放在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请求。 - 调用
OkHttpClient
的newCall()
方法来创建一个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);
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix