第九章 网络编程
安卓系统提供了以下几种方式实现网络通信:Socket通信 , HTTP通信 ,URL通信和WebView。
其中最常用的就是HTTP通信。本章介绍如何在手机端使用HTTP协议与服务器端相互通信。
9.1 HTTP协议简介
HTTP 即超文本传输协议,它规定了浏览器和服务器之间通信的规则。
HTTP是一种 请求/ 相应式的协议,当客户端与服务器段建立连接后,向服务器发送的请求称作HTTP请求。服务器收到请求后会做出响应,称为HTTP响应。
9.2 访问网络
安卓对HTTP通信提供了很好的支持,通过标准的java类HttpURLConnection便可实现基于基于URL的请求和响应功能。HttpURLConnection继承自URLConnection,用它可以发送和接收任何类型和长度的数据,也可以设置请求方式。也可以设置请求方式与超时时间。
9.2.1 HttpURLConnection
URL url = new URL("http://itcast.cn"); //获得要访问资源的url对象
HttpURLConnection conn = (HttpURLConnection) url.openConnection();//获取连接
conn.setRequestMethod("GET");//设置请求方式
conn.setConnectTimeout(5000);//设置超时时间
InputStream is = conn.getInputStream();//获取服务器返回的输入流
conn.disconnect();//关闭连接
上面演示了与服务器建立连接并获取服务器返回的数据的过程。需要注意的是,在使用HttpURLConnection对象访问网络时需要设置超时时间,以防止连接被阻塞时无响应,影响用户体验。
9.2.2 GET 与 POST请求方式
请求方式是指请求指定资源的不同方式。
-
GET方式提交数据
GET方式是以实体的方式得到请求URL所指向的资源信息,它向服务器提交的参数跟在请求URL后面。使用GET方式访问网络URL的长度一般要小于1KB。
-
POST方式发出请求,需要在请求后附加实体。它向服务器提交的参数在请求后的实体中,POST方式对URL长度是没有限制的。使用POST方式请求时,请求参数跟在请求实体中。用户不能在浏览器中看到向服务器提交的请求参数,因此POST相对GET方式安全。
String path = "http://192.168.1.100:8080/web/LoginServlet"; URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection)url.openConnection(); conn.setConnectTimeout(5000); conn.setRequestMethod("POST"); //准备数据并给参数编码 String data = "username = "+ URLEncoder.encode("zhangsan")+"&password="+URLEncoder.encode("123"); //设置请求头数据提交方式,这里是以form表单的方式提交 conn.setRequestProperty("Content-Type","application/x-www-form-urlencoded"); //设置提交数据的长度 conn.setRequestProperty("Content-Length",data.length()+""); //post方式,实际上是浏览器把数据写给了服务器 conn.setDoOutput(true); //设置允许向外写数据 OutputStream os = conn.getOutputStream(); os.write(data.getBytes()); //将参数写给服务器 int code = conn.getResponseCode(); if(code == 200) { InputStream is = conn.getInputStream(); }
注意:提交中文是容易出现乱码,一定要注意编码方式与解码方式是相匹配的
9.2.3Handler消息机制
当程序启动时,安卓首先会开启一个UI线程(主线程),UI线程负责管理UI界面的控件,并进行事件分发。例如,当单击UI界面的Button时安卓会分发事件到Button上,来响应要执行的操作,如果执行的是耗时操作,比如访问网络读取数据,并将获取到的结果返回到UI界面上,此时就会出现假死现象,如果5s还没有完成,会收到安卓系统的一个错误提示“ 强制关闭 ”。这时,初学者会想到把那些操作放到子线程中完成,但在安卓中更新UI界面只能在主线程中完成,其他线程是无法直接对主线程操作的。
为了解决以上问题,安卓提供了一种异步回调机制Handler,由Handler来负责与主线程进行通信。一般情况下,在主线程中绑定了Handler对象,并在事件触发上面创建子线程用于完成某些耗时操作,当子线程中的工作完成后,会向Handler发送一个已完成的信号(Message对象),当Handler接收到信号后,会对主线程UI进行更新操作。
Handler机制主要包括4个关键对象,分别是Message,Handler,MessageQueue,Looper。下面对这四个关键对象进行简要介绍。
-
Message
Message是在线程之间传递的消息,它可以在内部携带少量信息,用于在不同的线程之间交换信息。Meessage的what字段可以用来携带一些整型数据,obj字段可以用来携带一个Object对象。
-
Handler
Handler是处理者的意思,它主要用于发送消息和处理消息。一般使用Handler对象的sendMessage方法发送消息,发送的消息经过一系列的处理之后,最终会传递到Handler对象的handlerMessage方法中。
-
MessageQueue
MessageQueue是消息队列的意思,它主要用来存放通过Handler发送的消息。通过Handler发送的消息会存在MessageQueue中等待处理,每个线程只有一个MessageQueue对象。
-
Looper
Looper是每个线程中MessageQueue的管家。调用Looper的loop方法后,就会进到一个无限循环中。每当发现MessageQueue中存在一条消息,就将它取出,并传递到Handler的handlerMessage方法中。此外,每个线程也只会有一个Looper对象。在主线中创建Handler对象时,系统已经默认存在一个Looper对象,所以不用手动创建。而在子线程中的Handler对象需要调用Looper.loop方法开启消息循环。
9.2.4 实战演练----网络图片浏览器
-
创建程序
创建一个ImageView程序,布局代码如下所示:
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout 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"> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/line1" android:orientation="horizontal" android:layout_marginTop="5dp"> <EditText android:layout_width="0dp" android:layout_weight="5" android:layout_height="wrap_content" android:id="@+id/et1" android:hint="请输入路径"></EditText> <Button android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" android:text="浏览" android:textSize="19sp" android:id="@+id/bt1" android:onClick="click"></Button> </LinearLayout> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:layout_below="@+id/line1"> </ImageView> </RelativeLayout> </androidx.constraintlayout.widget.ConstraintLayout>
-
编写界面交互代码
使用HttpURLConnection获取指定地址的网络图片,并将服务器返回的图片展示在界面上,代码如下:
package cn.luoxin88.imageview; import androidx.appcompat.app.AppCompatActivity; import android.Manifest; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.text.TextUtils; import android.view.View; import android.widget.EditText; import android.widget.ImageView; import android.widget.Toast; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; public class MainActivity extends AppCompatActivity { protected static final int CHANGE_UI = 1; protected static final int ERROR = 2; private static final int REQUEST_CODE_ASK_PERMISSIONS = 123; private static EditText et; private static ImageView iv; //主线程创建消息处理器 private static Handler handler = new Handler() { public void handlerMessage(android.os.Message msg) { if(msg.what == CHANGE_UI){ Bitmap bitmap = (Bitmap)msg.obj; iv.setImageBitmap(bitmap); } else if(msg.what == ERROR) { // Toast.makeText(MainActivity.this,"显示图片错误",Toast.LENGTH_SHORT).show(); } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); et = (EditText)findViewById(R.id.et1); iv = (ImageView) findViewById(R.id.iv1); } public void click(View view) { final String path = et.getText().toString().trim(); if(TextUtils.isEmpty(path)) { Toast.makeText(this,"图片路径不能为空",Toast.LENGTH_SHORT).show(); } else { System.out.println(path); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { int hasReadSmsPermission = checkSelfPermission(Manifest.permission.INTERNET); if (hasReadSmsPermission != PackageManager.PERMISSION_GRANTED) { requestPermissions(new String[]{Manifest.permission.READ_SMS}, REQUEST_CODE_ASK_PERMISSIONS); return; } } System.out.println(1); //子线程访问网络,安卓4.0后访问网络不能放在主线程 new Thread() { private HttpURLConnection conn; private Bitmap bitmap; public void run() { //连接服务器GET请求获取图片 try { URL url = new URL(path); conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setConnectTimeout(5000); int code = conn.getResponseCode(); System.out.println(code); if(code == 200) { InputStream is = conn.getInputStream(); //将流转换为Bitmap对象 bitmap = BitmapFactory.decodeStream(is); //将更改主界面的消息发给主线程 Message message = new Message(); message.what = CHANGE_UI; message.obj = bitmap; handler.sendMessage(message); } else { //请求服务器失败 Message msg = new Message(); msg.what = ERROR; handler.sendMessage(msg); } } catch (IOException e) { e.printStackTrace(); Message msg = new Message(); msg.what = ERROR; handler.sendMessage(msg); } conn.disconnect(); } }.start(); } } }
-
添加权限
由于网络图片需要访问网络,因此需要在Manifest文件中配置INTENET权限
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
-
运行程序
输入https://www.photophoto.cn/m6/018/030/0180300388