基于Android studio学习安卓之Handler类的使用

基于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 使用的总结:

  1. 基本概念
    Handler:用于发送和处理消息或 Runnable 任务的对象。
    Message:包含要传递的数据和标识信息的对象,通常与 Handler 一起使用。
    Looper:一个无限循环,用于从消息队列中取出并处理消息。通常与线程相关联。
  2. 主要用途
    线程间通信:在 Android 中,UI 更新必须在主线程(UI 线程)中执行。Handler 允许在子线程中执行耗时操作,然后将结果发送回主线程进行 UI 更新。
    任务调度:Handler 可以用来安排任务在特定时间执行,或者延迟执行。
    消息传递:通过发送和接收消息,Handler 可以在不同组件或线程之间传递信息。
  3. 使用方式
    创建 Handler:通常在主线程中创建 Handler 对象,以便能够发送消息或任务到主线程的消息队列。
    发送消息或任务:使用 sendMessage() 或 post() 方法发送消息或 Runnable 任务到消息队列。
    处理消息或任务:重写 Handler 的 handleMessage() 方法来处理接收到的消息,或者在 Runnable 的 run() 方法中执行相应的任务。
  4. 与其他组件结合使用
    与 Looper 结合:Handler 通常与 Looper 一起使用。在 Android 中,每个线程默认没有 Looper,需要显式创建。主线程(UI 线程)在创建时自动带有 Looper。
    与 Thread 结合:为了在子线程中使用 Handler,需要创建一个带有 Looper 的新线程(例如使用 HandlerThread),然后将 Handler 与这个线程的 Looper 关联起来。
  5. 注意事项
    避免内存泄漏:如果 Handler 在 Activity 或 Fragment 中被创建,并且持有对它们的引用,那么在这些组件被销毁时,Handler 可能不会立即被垃圾回收,导致内存泄漏。为了解决这个问题,可以在组件的 onDestroy() 或 onPause() 方法中调用 removeCallbacksAndMessages(null) 来移除所有回调和消息。
    线程安全:Handler 不是线程安全的,因此在多线程环境中使用时需要格外小心。
    UI 更新:确保只在主线程中更新 UI。如果 Handler 关联的是子线程的 Looper,那么不要在该 Handler 中直接更新 UI。
    通过正确使用 Handler,可以有效地管理 Android 应用中的线程间通信和任务调度,提高应用的性能和响应速度。
  • 7
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值