这里学习使用的API为Android原生API:HttpUrlConnection。我们平常使用中更多的是使用第三方框架例如OKHttp,Volley、Retrofit等,但是像Volley也是封装了访问网络的一些操作,底层用的还是HttpUrlConnection,而OkHttp和HttpUrlConnection则使用socket实现了网络连接,只是OkHttp比HttpUrlConnection来说功能更强大。当然这里先不说其他的第三方框架如何,先了解http一些基础知识,使用原生API进行学习。
Http 概念:hypertext transfer protocol 超文本传输协议,TCP/IP协议的一个应用层协议,用于定义WEB浏览器与WEB服务器之间交换数据的过程。客户端连上web服务器后,若想获得web服务器中的某个web资源,需遵守一定的通讯格式,Http协议用于定义客户端与web服务器通讯的格式。
这里就以经常会问到的浏览器输入url:www.baidu.com后会发生什么为例,当然现在百度的请求都改为了Https,这里考虑最简单的情况。
DNS解析的最终目的是查找域名对应的ip地址。
之后的步骤可以参考下图(图片来源),主机A相当于我们的PC,浏览器处于应用层,主机B为百度的服务器,总的来说就是我们的请求从A的应用层到A的物理层,通过物理线路传到B的物理层,这中间会涉及到各种路由协议等等,B收到之后又从下至上层层解包,接着响应数据又顺着应用层->传输层->网络层->网络层->传输层->应用层的顺序返回到A,A应用层的浏览器根据获得的响应渲染响应页面。 重要的几个步骤如下:
Http本质上是通过TCP传送数据的(原因就是我们常说的TCP是面向连接的可靠的,可以进行差错控制等,而UDP是无连接的,不可靠的),因此在发送接收http请求之前,先要建立TCP连接。这里就用的是上篇文章里面提到的三次握手。
TCP连接建立后,应用层的http请求加上TCP头部-包括源端口号、目的端口号(http为80)和用于校验数据完整性的序号等,继续向下层传输到网络层,网络层在数据包上加上IP头部(见下图),包含了原IP与目的IP,之后就是加上数据链路层的帧头,中间的细节省略掉,继续向下传输到底层物理层,底层通过物理线路传输到百度服务器。服务器收到后又层层解包,服务器网络层收到数据包后,解析出IP头部,识别数据部分,并将解开的数据包向上传输到传输层,传输层获得数据包后,就解析出TCP头部,识别端口,将解开的数据包向上传输到应用层,应用层HTTP解析请求头和请求体,根据用户请求返回相应资源接着响应数据又顺着应用层->传输层->网络层->网络层->传输层->应用层的顺序返回到PC,PC应用层的浏览器根据获得的响应渲染响应页面。
例如请求百度的网络请求报文如下:
HTTP请求报文一般由3部分组成(请求行+请求头+请求体):
数据传输完成后,为了避免客户端与服务器资源占用和损耗,当双方没有请求或响应传递了,任意一方都可以发起关闭请求,用到的就是四次挥手。Http 1.1版本增加了长连接功能,在Http数据头部加上Connection:Keep-Alive,表示TCP一直保持连接,可以提高资源加载速度。
这里考虑简单的HTML、CSS资源,浏览器通过解析HTML,生成DOM树,解析CSS,生成CSS规则树,然后将DOM树和CSS规则树结合生成渲染树,之后通过Layout可以计算出每个元素具体的宽高颜色位置,结合起来开始绘制,最后显示在屏幕新页面中。
Http协议在通信的过程中是以明文方式发送数据,不提供任何方式的数据加密,如果攻击者截取了浏览器和服务器之间的传输报文,就可以直接读懂其中的信息,因此,Http协议不适合传输一些敏感信息,比如:信用卡号、密码等支付信息。在这样的情况下,Https应用而生,Https可以将数据加密传输,保证数据传输的安全。这里学习一下Https的通信过程,参考iispring博文:
Https协议简单来说由Http协议和SSL/TLS协议构成。Http协议前面已经说过,它扮演的角色就是传输数据,而SSL/TLS是负责加密解密等安全处理的模块,需要用它对数据进行加密和解密,加密后的数据也是通过Http传输的,所以Https的核心在SSL/TLS上面。
SSL的全称是Secure Sockets Layer,即安全套接层协议,是为网络通信提供安全及数据完整性的一种安全协议。SSL协议在1994年被Netscape发明,后来各个浏览器均支持SSL,其最新的版本是3.0 。
TLS的全称是Transport Layer Security,即安全传输层协议,最新版本的TLS(Transport Layer Security,传输层安全协议)是IETF(Internet Engineering Task Force,Internet工程任务组)制定的一种新的协议,它建立在SSL 3.0协议规范之上,是SSL 3.0的后续版本。在TLS与SSL3.0之间存在着显著的差别,主要是它们所支持的加密算法不同,所以TLS与SSL3.0不能互操作。虽然TLS与SSL3.0在加密算法上不同,但是在我们理解HTTPS的过程中,我们可以把SSL和TLS看做是同一个协议。
通信的过程图如下图所示:
借用iispring博文中的一句简短的介绍就是:HTTPS为了兼顾安全与效率,同时使用了对称加密和非对称加密。数据是被对称加密传输的,对称加密过程需要客户端的一个密钥,为了确保能把该密钥安全传输到服务器端,采用非对称加密对对称加密所使用的密钥进行加密传输。具体步骤如下:
https与http的主要区别如下:
这里简单学习下Http 1.0、1.1和最新的2.0三个版本的区别。参考一只好奇的茂博文。
这里挑几个重要的点来说:
Http 2.0相比Http1.x多出一些新的特性:
1.多路复用 (Multiplexing)
Http 2.0使用了多路复用,允许同一个连接并发处理多个请求即连接共享,一个请求对应一个id,这样一个连接上可以有多个请求,每个连接的请求可以随机的混杂在一起,接收方可以根据请求的 id将请求再归属到各自不同的服务端请求里面。连接共享如下图所示:
2.二进制分帧
Http 1.x的解析是基于文本,基于文本协议的格式解析存在缺陷,因为文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只有0和1组合。基于这种考虑Http 2.0的协议解析采用二进制格式,在应用层和传输层之间增加一个二进制分帧层,在二进制分帧层里面将所有传输的信息分割为更小的消息和帧(frame),并对它们采用二进制格式的编码 ,实现方便且健壮。
3.服务端推送
服务端推送可以把客户端可能会用到的资源伴随着网页内容一起发送到客户端,省去了客户端重复请求的步骤,非常合适加载静态资源,可以极大地提升静态资源的加载速度。与普通的客户端请求对比图如下:
普通客户端请求:
GET与POST是我们最常见的两种数据请求方式,都是用来发送数据的,只是发送机制不一样,GET安全性非常低,Post安全性较高, 但是执行效率却比Post方法好,一般获取查询数据的时候用GET,数据增删改的时候用POST。主要区别如下:
从上面也可以看到GET请求的速度比POST请求块,主要的几点原因也在上面提到了:
参考知乎:
Session: 由于Http是无状态的协议,所以服务端需要记录用户的状态时就需要用某种机制来识具体的用户,这个机制就是Session。典型的场景比如购物车,当你点击下单按钮时,由于Http协议无状态,所以并不知道是哪个用户操作的,所以服务端要为特定的用户创建了特定的Session,用用于标识并且跟踪用户,这样才知道用户进行了什么操作例如购物车里面有几本书。Session是保存在服务端的,有一个唯一标识。
Cookie是实现Session机制的一种常用形式(为什么说是之一呢?因为如果客户端的浏览器禁用了 Cookie ,还可以使用做URL重写的技术来进行会话跟踪,即每次Http交互,URL后面都会被附加上一个诸如 SessionID=xxxxx 这样的参数,服务端据此来识别用户;也可以使用Token 机制,当用户第一次登录后,服务器根据提交的用户信息生成一个 Token,响应时将 Token 返回给客户端,以后客户端只需带上这个 Token 前来请求数据即可,无需再次登录验证,服务端也不必像session那样存储session,只负责生成token并验证即可)。举例来说: 一般发送登陆请求后,服务器通过Set-Cookie响应头,返回一个Cookie,浏览器默认保存这个Cookie, 后续访问相关页面的时候会带上这个Cookie,通过Cookie请求头标识用户信息完成访问,如果没Cookie或者 Cookie过期,就提示用户没登陆,登陆超时,访问需要登陆之类的信息。
用户第一次请求服务器的时候,服务器根据用户提交的相关信息,创建创建对应的 Session ,请求返回时将此 Session 的唯一标识信息 SessionID 返回给浏览器,浏览器接收到服务器返回的 SessionID 信息后,会将此信息存入到 Cookie 中,同时 Cookie 记录此 SessionID 属于哪个域名。当用户第二次访问服务器的时候,请求会自动判断此域名下是否存在 Cookie 信息,如果存在自动将 Cookie 信息也发送给服务端,服务端会从 Cookie 中获取 SessionID,再根据 SessionID 查找对应的 Session 信息,如果没有找到说明用户没有登录或者登录失效,如果找到 Session 证明用户已经登录可执行后面操作。
如果只用cookie不用session,那么账户信息全部保存在客户端,一旦被劫持,全部信息都会泄露。并且客户端数据量变大,网络传输的数据量也会变大。
Cookie 和 Session 区别:
app实现持久登录方式:
平常第一次登录app成功后,以后每次启动时都是登录状态,不需要每次启动时再次登录。其具体实现就可以用token:
第一次登录时APP将用户输入的账号和密码提交给服务器;服务器对其进行校验,若账号和密码对得上则校验通过,说明登录成功。并生成一个token值,将其保存在数据库,同时也返回给客户端;客户端拿到返回的token值后,可将其保存在本地。作为公共参数,即以后每次请求服务器时都携带该token,提交给服务器,让服务器校验。服务器接收到请求后,会取出请求头里的token值与数据库存储的token进行对比校验。若两个token值相同,则说明用户登录成功过,且当前正处于登录状态,此时正常返回数据,让APP显示数据。若两个值不一致,则说明原来的的登录已经失效,此时返回错误状态码,提示用户跳转至登录界面重新登录。并且用户每进行一次登录,登录成功后服务器都会更新个token新值返回给客户端。
前面学习了相关知识,这里使用原生API进行练习,请求方式为GET。
布局文件:
<?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"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:id="@+id/tvmenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="加载菜单"
android:clickable="true"
android:gravity="center"
android:textSize="20sp" />
<ImageView
android:id="@+id/mpicture"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"/>
<ScrollView
android:id="@+id/scroll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone">
<TextView
android:id="@+id/scrolltxt"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</ScrollView>
<WebView
android:id="@+id/mwebview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"/>
</LinearLayout>
menu文件:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<!-- checkableBehavior的可选值由三个:single设置为单选,all为多选,none为普通选项 -->
<group android:checkableBehavior="none">
<item android:id="@+id/pic" android:title="@string/query_pic"/>
<item android:id="@+id/code" android:title="@string/query_code"/>
<item android:id="@+id/loadview" android:title="@string/loadview"/>
</group>
</menu>
数据请求类:
public class GetData {
//定义类方法
public static byte[] getImage(String param) throws Exception {
URL url = new URL(param);
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setConnectTimeout(5000);//超时时间为5s
conn.setRequestMethod("GET");
if(conn.getResponseCode() == 200) {
InputStream is = conn.getInputStream();
byte[] bt = StreamTobyte.read(is);
is.close();
return bt;
}else {
throw new RuntimeException("请求失败");
}
}
public static String getHtml(String param) throws Exception {
URL url = new URL(param);
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setConnectTimeout(5000);//超时时间为5s
conn.setRequestMethod("GET");
if(conn.getResponseCode()==200){
InputStream is = conn.getInputStream();
byte[] bt = StreamTobyte.read(is);
String html = new String(bt,"UTF-8");
return html;
}else {
throw new RuntimeException("请求失败");
}
}
}
输入流转换为二进制数组类:
public class StreamTobyte {
//定义类方法 将输入流转换为二进制数组
public static byte[] read(InputStream inStream) throws Exception{
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while((len = inStream.read(buffer)) != -1)
{
outStream.write(buffer,0,len);
}
inStream.close();
return outStream.toByteArray();
}
}
MainActivity:
public class MainActivity extends AppCompatActivity {
private TextView textView;
private ImageView imageView;
private ScrollView scrollView;
private TextView scrolltextview;
private WebView webView;
private Bitmap bitmap;
private String html;
private long exitTime = 0;
private final static String picurl = "https://ww2.sinaimg.cn/large/7a8aed7bgw1evshgr5z3oj20hs0qo0vq.jpg";
private final static String htmlurl = "https://www.baidu.com";
private Handler handler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
switch (msg.what){
case 0x001:
hideAllWidget();
imageView.setVisibility(View.VISIBLE);
imageView.setImageBitmap(bitmap);
Toast.makeText(MainActivity.this,"图片加载完成",Toast.LENGTH_SHORT).show();
break;
case 0x002:
hideAllWidget();
scrollView.setVisibility(View.VISIBLE);
scrolltextview.setText(html);
Toast.makeText(MainActivity.this,"html代码加载完成",Toast.LENGTH_SHORT).show();
break;
case 0x003:
hideAllWidget();
webView.setVisibility(View.VISIBLE);
webView.setWebViewClient(new WebViewClient() {
//设置在webView点击打开的新网页在当前界面显示,而不跳转到新的浏览器中
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
});
webView.getSettings().setJavaScriptEnabled(true); //设置WebView属性,运行执行js脚本
webView.loadUrl(htmlurl); //调用loadUrl方法为WebView加入链接
Toast.makeText(MainActivity.this,"网页加载完成",Toast.LENGTH_SHORT).show();
break;
}
}
};
private void hideAllWidget() {
//INVISIBLE属性界面会保留view控件所占有的空间,GONE属性界面则不保留view控件所占有的空间
imageView.setVisibility(View.GONE);
scrollView.setVisibility(View.GONE);
webView.setVisibility(View.GONE);
}
@Override
public void onBackPressed() {
//重写回退按钮的时间,当用户点击回退按钮,webView.canGoBack()判断网页是否能后退,可以则goback()
//如果不可以连续点击两次退出App,否则弹出提示Toast
if (webView.canGoBack()) {
webView.goBack();
} else {
if ((System.currentTimeMillis() - exitTime) > 2000) {
Toast.makeText(getApplicationContext(), "再按一次退出程序",
Toast.LENGTH_SHORT).show();
exitTime = System.currentTimeMillis();
} else {
super.onBackPressed();
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindviews();
registerForContextMenu(textView);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
MenuInflater inflater = new MenuInflater(this);
inflater.inflate(R.menu.menu_context,menu);
}
@Override
public boolean onContextItemSelected(@NonNull MenuItem item) {
//菜单的点击事件
switch (item.getItemId()){
case R.id.pic:
//UI线程中不能进行网络操作
new Thread(new Runnable() {
@Override
public void run() {
try{
byte[] pic = GetData.getImage(picurl);
bitmap = BitmapFactory.decodeByteArray(pic, 0, pic.length);
}catch (Exception e){
e.printStackTrace();
}
handler.sendEmptyMessage(0x001);
}
}).start();
break;
case R.id.code:
new Thread(new Runnable() {
@Override
public void run() {
try {
html = GetData.getHtml(htmlurl);
} catch (Exception e) {
e.printStackTrace();
}
handler.sendEmptyMessage(0x002);
}
}).start();
break;
case R.id.loadview:
handler.sendEmptyMessage(0x003);
break;
}
return true;
}
private void bindviews() {
textView = findViewById(R.id.tvmenu);
imageView = findViewById(R.id.mpicture);
scrollView = findViewById(R.id.scroll);
scrolltextview = findViewById(R.id.scrolltxt);
webView = findViewById(R.id.mwebview);
}
}
Retrofit是Square公司推出的一个HTTP网络框架,它实际上是对OkHttp网络请求框架的二次封装,本质仍是OkHttp。即网络请求的工作本质上是由OkHttp完成,而Retrofit仅负责网络请求接口的封装。Retrofit可以使我们用面向对象的思维进行网络操作:
如上图所示,App通过Retrofit请求网络,实际上是使用Retrofit接口层封装请求参数、Header、Url等信息,之后由OkHttp完成后续的请求操作。在服务器返回数据之后,OkHttp将原始的结果交给Retrofit,Retrofit会根据用户的需求对结果进行解析。
通过Demo来学习一下,Demo参考《Android第一行代码》:
首先添加依赖库:
dependencies {
...
implementation 'com.squareup.retrofit2:retrofit:2.6.1'
implementation 'com.squareup.retrofit2:converter-gson:2.6.1'
...
}
由于Retrofit是基于OkHttp开发的,因此第一条依赖会自定将Retrofit、OkHttp等一起下载,第二条依赖会将GSON库下载下来,可以使用GSON库解析json文件。
这里搭建了一个简单的Web服务器,并新建了一个get_data.json的文件:
由于GSON库解析json数据的时候用的就是面向对象的思维,因此这里要新建对应的实体类:
public class App {
public String id;
public String version;
public String name;
}
接下来创建用于描述网络请求的接口,接口函数里要定义URL路径、请求参数、返回类型。其中,需要使用注解来描述请求类型和请求参数:
public interface AppService {
@GET("get_data.json")
Call<List<App>> getAppData();
}
上述代码中getAppData上面有个 @GET注解代表调用getAppData时Retrofit会发起一条GET请求,请求地址就是@GET注解的参数,这里只需要传入相对路径即可,跟路径会另行设置。并且getAppData方法的返回类型必须为Retrofit内置的Call类型,并要通过泛型来指定服务器响应的数据应该转换成什么对象,这里有一返回的是包含App数据的JSON数组,因此将泛型声明为List
。
接下来修改布局文件添加按钮,并添加点击事件:
public class MainActivity extends AppCompatActivity {
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Retrofit retrofit = new Retrofit.Builder().baseUrl("http://10.0.2.2/").addConverterFactory(GsonConverterFactory.create()).build();
AppService appService = retrofit.create(AppService.class);
appService.getAppData().enqueue(new Callback<List<App>>() {
@Override
public void onResponse(Call<List<App>> call, Response<List<App>> response) {
List<App> list = response.body();
if(list!=null){
for(int i=0;i<list.size();i++){
App app = list.get(i);
Log.d("MainActivity",app.id+","+app.version+","+app.name);
}
}
}
@Override
public void onFailure(Call<List<App>> call, Throwable t) {
t.printStackTrace();
}
});
}
});
}
}
点击事件中,首先使用Retrofit.Builder()创建Retrofit对象,并设置跟路径以及数据解析器,之后调用了Retrofit对象的create方法,并传入具体Service接口所对应的Class类型以创建该接口的动态代理对象,这样就可以通过这个动态代理对象随意调用接口中定义的方法了。
接着调用之前接口中定义的getAppData方法,返回一个Call
对象,接着调用该对象的enqueue()方法,Retrofit就会根据注解中配置的服务器接口地址进行网络请求(http://10.0.2.2/get_data.json),响应的数据回调到enqueue()方法中传入的Callback实现中。注意这里的网络请求是异步的,也就是发起请求的时候,Retrofit就会自动在内部开启子线程,回调到Callback中后,Retrofit又会自动切回主线程中,接着在Callback的onResponse调用response.body()方法得到Retrofit解析后的对象。完成后,在Manifest.xml文件中添加网络请求权限:>
<uses-permission android:name="android.permission.INTERNET"/>
当然上面的请求只是最简单的情况,因为接口不可能永远是静态不改变的,例如以下情况:
GET http://example.com/<page>/get_data.json
在这种情况下,随着page的变化,服务器返回的数据也不相同,那么接口应该有如下写法:
public interface ExampleService {
@GET("{page}/get_data.json")
Call<Data> getData(@Path("page") int page);
}
在@GET注解中,使用了一个{page}占位符,然后在getAppData中添加了一个page参数,并使用@Path(“page”)注解来声明这个参数,这样当调用getAppData方法发起请求时,Retrofit就会自动将page参数的值替换到占位符的位置,从而组成一个合法的请求地址。
另一种情况下,服务器还会要求我们传入一系列参数,例如:
GET http://example.com/get_data.json?u=<user>&p=<password>
那么我们可以使用@Query:
public interface ExampleService {
@GET("get_data.json")
Call<Data> getData(@Query("u") String user,@Query("p") String password);
}
这里在getAppData方法中添加了user和password两个参数,并使用@Query注解对它们进行声明,这样当发起网络请求的时候,Retrofit就会自动按照参数GET请求的格式将这两个参数构建到请求地址中。
上面的例子都是GET请求,那POST请求该怎么写?例如:
POST http://example.com/data/create
{"id":1,"content":"hello world!"}
我们可以借助@Body注解实现:
public interface ExampleService {
@POST("data/create")
Call<ResponseBody> createData(@Body Data data);
}
在createAppData方法中声明了一个Data类型的参数,并加上了@Body注解,这样当Retrofit发出POST请求时,就会自动将Data对象中的数据转换成JSON格式的文本,并放到POST请求的body部分,服务器接收到请求后只需要将这部分数据解析出来即可。
最后,有时候发送网络请求的时候需要我们在请求的header中指定参数,例如:
GET http://example.com/get_data.json
User-Agent: okhttp
Cache-Control: max-age=0
这些header参数其实就是一个个的键值对,可以使用@Headers注解进行声明:
public interface ExampleService {
@Headers("User-Agent: okhttp","Cache-Control: max-age=0")
@GET("get_data.json")
Call<Data> getData();
}
如果想要动态指定,使用@Header注解:
public interface ExampleService {
@GET("get_data.json")
Call<Data> getData(@Header("User-Agent") String userAgent,@Header("Cache-Control") String cacheControl);
}
当发起网络请求的时候,Retrofit就会自动将参数中传入的值设置到User-Agent和Cache-Control这两个header中,从而实现动态指定header值的功能。
这里列举异步GET请求步骤如下:new OkHttpClient、构造Request对象、通过前两步中的对象构建Call对象、通过Call#enqueue(Callback)方法来提交异步请求,同步请求的方式和异步是相似的,只不过只是最后一部是通过 Call#execute() 来提交请求。
示例:点击按钮后显示图片,由于是从网络上下载图片,因此采用了异步消息机制
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button button;
private ImageView imageView;
private static String image_path = "http://ww4.sinaimg.cn/bmiddle/786013a5jw1e7akotp4bcj20c80i3aao.jpg";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindViews();
button.setOnClickListener(this);
}
private void bindViews() {
button = findViewById(R.id.button);
imageView = findViewById(R.id.imageview);
}
private Handler handler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
Bitmap bitmap = (Bitmap) msg.obj;
imageView.setImageBitmap(bitmap);
}
};
public class dlThread implements Runnable{
@Override
public void run() {
OkHttpClient okHttpClient = new OkHttpClient();//单例模式
Request request = new Request.Builder().url(image_path).build();//建造者模式
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {}
@Override
public void onResponse(Call call, Response response) throws IOException {
InputStream inputStream = response.body().byteStream();
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
Message msg = Message.obtain();//推荐用obtain()获取Message
msg.obj = bitmap;
handler.sendMessage(msg);
}
});
}
}
@Override
public void onClick(View v) {
switch(v.getId()){
case R.id.button:
new Thread(new dlThread()).start();
break;
}
}
}
原理大致如下:
创建好okHttpClient(单例模式)以及Request对象(建造者模式)后,调用okHttpClient的newcall(request)的enqueue方法后,以异步为例,会调用分发器Dispather的enqueue方法,Dispather的enqueue方法会将AsyncCall入队并提交给线程池执行,线程池中的线程又调用Call的execute()方法,Call的execute()方法又会调用getResponseWithInterceptorChain()方法,通过一系列拦截器(责任链模式)对请求进行处理之后发出该请求并读取响应。重要的几个拦截器如下:
之后有时间再学习。
参考: