读取网络数据

获取网络请求属于耗时操作。在Android主线程当中不允许出现耗时操作,开启异步线程,解决获取网络请求的问题。
获取网络请求必须在子线程当中完成,UI控件的设置必须在主线程当中完成。

线程知识点回顾

开启新线程

  1. 创建Thread子类对象,调用start方法,启动新线程
  2. 编写Runnable实现类,创建Runnable子类的对象,new Thread(new MyRunnable).start()

创建新线程对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//创建新的线程对象
Thread thread = new Thread(new Runnable() {
@Override
public void run() {

}
});
//创建线程对象的方法2
Thread t2 = new Thread(){
@Override
public void run() {
super.run();
}
};
//启动线程
thread.start();

线程的生命周期

线程的生命周期: 新建—–>>就绪—–>>运行<==>阻塞——>>消亡

当调用t1.start()方法时,让线程处于就绪状态,有cpu的执行资格,但是没有执行权力。

原生读取网络数据

配置网络访问权限

AndroidManifest.xml

1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:usesCleartextTraffic="true"
···

读取流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private String getStringByConnection(String path) {
//内存流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
//1. 将网址转换成资源对象
URL url = new URL(path);
//2. 开始连接网络
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//3. 获取网页信息输入流
InputStream is = conn.getInputStream();
//4. 读取流,写入内存
int hasRead = 0;
byte[] buf = new byte[1024];
while ((hasRead = is.read(buf))!=-1){
baos.write(buf, 0, hasRead);
}
is.close();
} catch (Exception e) {
e.printStackTrace();
}
return baos.toString();
}

线程中开启读取方法

1
2
3
4
5
6
7
8
9
10
11
12
13
private void loadDatas() {
//创建新的线程对象
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
//编写获取网络数据的过程
String msg = getStringByConnection(url);
Log.i("lsh", "run: msg====" + msg);
}
});
t1.start();

}

UI的更新(线程间通信)

只有UI线程才能更新控件内容,在子线程当中更新UI会出现bug
但是从子线程获取网络数据,然后通过线程间通信,可以将数据传递给主线程,然后在主线程更新UI控件

步骤

  1. 创建一个线程间通信类,该类可以在子线程当中发送消息,然后在主线程中接收消息。
    发送消息和接收消息得是同一个handler对象

    1
    2
    3
    4
    5
    6
    7
    Handler handler = new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(@NonNull Message msg) {
    //可以接收消息的方法
    return false;
    }
    });
  2. 在子线程发送消息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @Override
    public void run() {
    //表示让当前线程可以作为loop线程,此时该子线程可以创建Handler对象,发送消息
    Looper.myLooper();
    Looper.loop();
    //编写获取网络数据的过程
    String msg = HttpUtils.getStringByConnection(url);
    //将要发送的数据封装到message当中
    Message message = handler.obtainMessage();
    //区别消息的标识号
    message.what = 1;
    message.obj = msg;
    handler.sendMessage(message);
    Log.i("lsh", "run: msg====" + msg);
    }
  3. 在主线程接收消息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Override
    public boolean handleMessage(@NonNull Message message) {
    //可以接收消息的方法
    if (message.what == 1) {
    String str = (String) message.obj;
    binding.mainTv.setText(str);
    }
    return false;
    }
  4. 回到主线程执行任务
    在子线程的run方法中执行runOnIoThread()即可

    1
    2
    3
    4
    5
    6
    7
    //回到主线程执行任务
    runOnUiThread(new Runnable() {
    @Override
    public void run() {
    adapter.notifyDataSetChanged();
    }
    });
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    Handler handler2 = new Handler();
    new Thread(){
    @Override
    public void run() {
    String json = "ksdfjalkjf";
    handler2.post(new Runnable() {
    @Override
    public void run() {
    //回到主线程执行
    }
    });
    }
    }.start();

Handler,Looper,Message,MessageQueue之间的关系

答:从应用的角度举例,可以把地铁当中的安检机看作是一个线程,然后安检机中的传送带就就当中Looper对象,因为有这个传送带,安检机才能无限循环进行安检,接受安检的人就相当于handler对象,在传送带上放置包裹即 Message,然后在传送带到达指定地点后,提示给放置包裹的人接受包裹。

从源码的角度讲,线程当中可以通过Looper.prepare()方法,在线程当中定义looper对象,使线程能够循环。然后通过调用Looper.loop()的方法使他循环起来。然后Looper上有一个封装的MessageQueue对象,用来处理循环消息,调用handler的sendMessage方法,把message消息的target属性设置为当前handler,然后在调用messagequeue当中enqueueMessage方法把消息放置在消息队列上,然后调用handlerdispatchMessage分发这条消息,放置这条消息的handler能够获取到此消息, 通过handleMessage方法。

视图绑定

不用写findViewById

流程

  1. build.gradle中添加viewBinding

    1
    2
    3
    4
    5
    6
    7
    8
    android {
    namespace 'com.xyl.app1110'
    compileSdk 32
    viewBinding {
    enabled true
    }
    ...
    }
  2. 每一个view上设置id

  3. 在Activity类中声明ActivityMainBinding对象,如

    1
    ActivityMainBinding binding;
  4. onCreate()方法中获取绑定对象

    binding = ActivityMainBinding.inflate(getLayoutInflater());

    binding = ItemFoodlvBingding.bind(view)
    —在adapter的用于getView()ViewHolder中使用

    1
    2
    3
    4
    5
    6
    7
      protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    binding = ActivityMainBinding.inflate(getLayoutInflater());
    View rootView = binding.getRoot();
    setContentView(rootView);
    binding.mainTv.setText("今天是个好天气");
    }

三级缓存

三级缓存:
从网络上获取图片或者复杂的数据,可以先从内存当中查找,是否有这个图片,有就显示,没有就查找本地存储文件,如果本地存储有这个图片,就读入内存,然后显示,如果没有这个图片,就上网下载这个图片,下载成功,存放到本地存储,存放到内存,显示图片。
如果下载失败,显示错误图片。