基于Android studio学习安卓之Handler类的使用
文章目录
前言
在Android开发中,Handler是一个重要的类,它用于在不同的线程之间发送和处理消息和Runnable对象。Handler通常与Looper和Message类一起使用,以实现在主线程或其他线程上执行代码的功能。以下是Handler在Android中的常用到的一些场景。
一、轮播图
在Android开发中,实现自动轮播的轮播图功能通常涉及到ViewPager2和Handler的结合使用。ViewPager2是Android Jetpack组件库中的一个强大的滑动视图容器,而Handler则用于在特定时间间隔后执行代码块,从而实现自动轮播的效果。
1.添加依赖
implementation 'androidx.viewpager2:viewpager2:1.0.0' // 请检查并使用最新版本
2.编写布局
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
3.准备适配器模板
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="10dp" <!-- 可选,设置内边距 -->
android:gravity="center"> <!-- 可选,设置子视图的对齐方式 -->
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:scaleType="centerCrop" <!-- 根据需要设置图片的缩放类型 -->
android:src="@drawable/default_image" <!-- 默认图片,可以在代码中动态替换 -->
/>
<!-- 这里可以添加其他视图,比如标题、描述等 -->
</LinearLayout>
注意,在这个模板中包含:
1、LinearLayout 是父布局,它包含了ImageView。你可以根据需要更改父布局的类型(比如RelativeLayout、ConstraintLayout等)。
2、ImageView 用于显示图片。
2.1、android:id属性为imageView,这样你就可以在适配器中通过ID引用这个ImageView来设置图片。
2.2、android:src属性用于设置默认的图片资源。在实际应用中,你通常会在适配器的onBindViewHolder方法中动态设置图片。
2.3、android:adjustViewBounds和android:scaleType属性用于控制图片的缩放和显示方式。
2.4、android:padding和android:gravity属性用于设置布局的内边距和子视图的对齐方式。
4.构建轮播图适配器
// 定义一个名为ImageAdapter的公共类,它继承自RecyclerView.Adapter,并且泛型指定为ImageAdapter内部的ImageViewHolder类
public class ImageAdapter extends RecyclerView.Adapter<ImageAdapter.ImageViewHolder> {
// 定义一个私有List类型的变量imageIds,用于存储图片资源的ID
private List<Integer> imageIds; // 图片资源ID列表
// 构造函数,接收一个List<Integer>类型的参数,赋值给imageIds变量
public ImageAdapter(List<Integer> imageIds) {
this.imageIds = imageIds;
}
// 重写onCreateViewHolder方法,用于创建ViewHolder实例
@NonNull
@Override
public ImageViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
// 使用LayoutInflater从parent的上下文中加载item_image布局文件
// 并将布局文件附加到parent上,但不立即添加到parent中(第三个参数为false)
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_image, parent, false);
// 创建一个新的ImageViewHolder实例,并将itemView作为参数传入
return new ImageViewHolder(itemView);
}
// 重写onBindViewHolder方法,用于将数据绑定到ViewHolder的视图上
@Override
public void onBindViewHolder(@NonNull ImageViewHolder holder, int position) {
// 调用holder的bind方法,传入imageIds列表中指定位置的图片ID
// 这样就可以将图片资源设置到ImageView中
holder.bind(imageIds.get(position));
}
// 重写getItemCount方法,返回列表中图片的数量
@Override
public int getItemCount() {
return imageIds.size();
}
// 定义内部类ImageViewHolder,此类名可以随意命名,继承自RecyclerView.ViewHolder
public class ImageViewHolder extends RecyclerView.ViewHolder {
// 定义一个ImageView类型的变量imageView,用于引用布局中的ImageView控件
ImageView imageView;
// 构造函数,接收一个View类型的参数itemView,并将它传递给父类ViewHolder的构造函数
public ImageViewHolder(@NonNull View itemView) {
super(itemView);
// 使用findViewById方法找到布局中ID为imageView的ImageView控件,并赋值给imageView变量
imageView = itemView.findViewById(R.id.imageView);
}
// 定义一个bind方法,接收一个int类型的参数imageId,用于设置imageView的图片资源
void bind(int imageId) {
// 使用setImageResource方法设置imageView的图片资源为传入的imageId
imageView.setImageResource(imageId);
}
}
}
5.在Activity或Fragment中初始化
在你的Activity或Fragment中初始化ViewPager2和适配器,并设置适配器。
private ViewPager2 viewPager;
private ImageAdapter imageAdapter;
private List<Integer> imageIds; // 图片资源ID列表
// ...其余代码
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// ...其余代码
viewPager = findViewById(R.id.viewPager);
// 初始化图片列表
imageIds = Arrays.asList(/* 初始化你的图片资源ID列表 */);
// 初始化适配器
imageAdapter = new ImageAdapter(imageIds);
// 设置设配器
viewPager.setAdapter(imageAdapter);
// 设置无限循环
viewPager.setOffscreenPageLimit(imageIds.size());
viewPager.setCurrentItem(imageIds.size() * 1000, false); // 初始位置设置为一个较大的数,确保首次显示第一张图
6.设置定时程序(Handler类实现)
注意:在使用Handler类完成定时任务时,需在你的Activity或Fragment中初始化Handler及Runnable类
private Handler handler;
private Runnable runnable;
然后
在主线程中,你可以直接使用Handler来执行一些延迟操作或定时任务,而无需担心线程问题。
// 接上文
// 创建一个Handler对象,用于在指定的延迟后运行Runnable或发送消息
handler = new Handler();
// 创建一个Runnable对象,Runnable是一个没有返回值且没有抛出异常的接口,用于执行后台任务
runnable = new Runnable() {
@Override
public void run() {
// 获取当前ViewPager显示的Item位置
int currentItem = viewPager.getCurrentItem();
// 计算下一个要显示的Item位置,使用模运算确保索引在数组范围内循环
currentItem = (currentItem + 1) % imageIds.size();
// 设置ViewPager的当前Item为计算出的下一个Item,true表示平滑滚动
viewPager.setCurrentItem(currentItem, true);
// 使用handler的postDelayed方法再次安排这个Runnable在3秒后执行,实现轮播效果
handler.postDelayed(this, 3000); // 3秒切换一次
}
};
// 使用handler的postDelayed方法首次延迟3秒后执行Runnable,开始轮播
handler.postDelayed(runnable, 3000); // 首次延迟3秒开始轮播
}
值得注意的是,当你在主线程中使用handler执行定时任务时,需在文件销毁前销毁handler回调
@Override
protected void onDestroy() {
super.onDestroy();
if (handler != null) {
handler.removeCallbacks(runnable);
}
}
二、在子线程中使用Handler
如果你想在子线程中使用Handler,你需要先创建一个Looper对象,然后才能创建Handler。但通常我们不直接在子线程中使用Handler,而是使用HandlerThread,它是一个包含Looper的线程
// 创建一个HandlerThread对象,名为"MyHandlerThread",这个HandlerThread是一个拥有Looper的线程
HandlerThread handlerThread = new HandlerThread("MyHandlerThread");
// 启动HandlerThread,这会创建一个新的线程,并在其中初始化Looper
handlerThread.start();
// 创建一个Handler对象,并将HandlerThread的Looper作为参数传入,这样Handler就会将Runnable或Message发送到HandlerThread中执行
Handler handler = new Handler(handlerThread.getLooper());
// 使用Handler的post方法提交一个Runnable任务到HandlerThread中执行
handler.post(new Runnable() {
@Override
public void run() {
// 这里是子线程中执行的代码,当HandlerThread准备好时,会执行这里的代码
// 注意:这里的代码不是在主线程中执行,而是在HandlerThread这个子线程中执行
}
});
三、发送和处理消息
除了发送Runnable对象外,你还可以使用Handler发送和处理Message对象。
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
// 处理消息的代码
switch (msg.what) {
case 1:
// 处理消息1
break;
case 2:
// 处理消息2
break;
default:
break;
}
}
};
Message message = Message.obtain();
message.what = 1; // 设置消息标识
handler.sendMessage(message); // 发送消息
可配合网络请求(如:OkHttp)一起联用,当网络请求成功时,我们在 onResponse 方法中处理响应数据。但是,由于OkHttp的回调是在子线程中执行的,因此我们不能直接在这个回调中更新UI。相反,我们使用一个 Handler 来将一个 Runnable 任务发送回主线程。在 Runnable 的 run 方法中,我们可以安全地更新UI或处理结果。
示例代码(如下):
/**
* 通用数据请求
* @param url 请求地址
* @param mHandler 回调方法
* @param isHeader 是否有额外请求头
* @param headerList 额外请求头
* @param isPost 是否POST请求
* @param jsonStr POST请求数据
*/
public void commonRequest(String url,Handler mHandler, Boolean isHeader, Map<String,String> headerList, Boolean isPost, String jsonStr){
OkHttpClient okHttpClient = new OkHttpClient();
Request.Builder builder = new Request.Builder();
builder.url(url);
if (isHeader){
for (String key : headerList.keySet()){
builder.addHeader(key,headerList.get(key));
}
}
if (isPost){
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"),jsonStr);
builder.post(requestBody);
}
Request request = builder.build();
Message msg = new Message();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
mHandler.sendEmptyMessage(ERROR_STATE);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
try {
JSONObject jsonObject = new JSONObject(response.body().string());
msg.what = FAIL_STATE;
// 处理相关逻辑
} catch (JSONException e) {
e.printStackTrace();
}
// 发送消息
mHandler.sendMessage(msg);
}
});
}
你可以将这个函数放到一个公共类中,在需要用到的地方调用它。
总结
Handler 在 Android 开发中是一个非常重要的工具,主要用于在不同线程之间进行通信和安排任务。以下是对 Handler 使用的总结:
- 基本概念
Handler:用于发送和处理消息或 Runnable 任务的对象。
Message:包含要传递的数据和标识信息的对象,通常与 Handler 一起使用。
Looper:一个无限循环,用于从消息队列中取出并处理消息。通常与线程相关联。 - 主要用途
线程间通信:在 Android 中,UI 更新必须在主线程(UI 线程)中执行。Handler 允许在子线程中执行耗时操作,然后将结果发送回主线程进行 UI 更新。
任务调度:Handler 可以用来安排任务在特定时间执行,或者延迟执行。
消息传递:通过发送和接收消息,Handler 可以在不同组件或线程之间传递信息。 - 使用方式
创建 Handler:通常在主线程中创建 Handler 对象,以便能够发送消息或任务到主线程的消息队列。
发送消息或任务:使用 sendMessage() 或 post() 方法发送消息或 Runnable 任务到消息队列。
处理消息或任务:重写 Handler 的 handleMessage() 方法来处理接收到的消息,或者在 Runnable 的 run() 方法中执行相应的任务。 - 与其他组件结合使用
与 Looper 结合:Handler 通常与 Looper 一起使用。在 Android 中,每个线程默认没有 Looper,需要显式创建。主线程(UI 线程)在创建时自动带有 Looper。
与 Thread 结合:为了在子线程中使用 Handler,需要创建一个带有 Looper 的新线程(例如使用 HandlerThread),然后将 Handler 与这个线程的 Looper 关联起来。 - 注意事项
避免内存泄漏:如果 Handler 在 Activity 或 Fragment 中被创建,并且持有对它们的引用,那么在这些组件被销毁时,Handler 可能不会立即被垃圾回收,导致内存泄漏。为了解决这个问题,可以在组件的 onDestroy() 或 onPause() 方法中调用 removeCallbacksAndMessages(null) 来移除所有回调和消息。
线程安全:Handler 不是线程安全的,因此在多线程环境中使用时需要格外小心。
UI 更新:确保只在主线程中更新 UI。如果 Handler 关联的是子线程的 Looper,那么不要在该 Handler 中直接更新 UI。
通过正确使用 Handler,可以有效地管理 Android 应用中的线程间通信和任务调度,提高应用的性能和响应速度。