1.Handler
因为安卓开发中,除了主线程,其他线程不允许修改视图,所以需要Handler去做消息传递
常见方法
//发送延时消息
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
//发送定时消息
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
//发送普通消息,这里可以看出,他使用了延时消息,只是事件为0
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
MainActivity
package com.example.handlertest;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private String TAG="Ms";
@Override
protected void onCreate(Bundle savedInstanceState) {
/*
UI线程,做轻量级的事情
*/
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView textview = findViewById(R.id.textView);
//创建handler
Handler handler=new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
//处理消息
/*
子线程,做大量耗时操作,操作完成后,通知UI更新
*/
Log.e(TAG, "handleMessage: "+msg.what );
if(msg.what == 1001){
textview.setText("好好学习 天天向上");
}
}
};
Button button = findViewById(R.id.btn);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//做大量耗时操作
new Thread(()->{
handler.sendEmptyMessage(1001);
}).start();
}
});
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
android:id="@+id/textView"
android:text="Hello World!"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/btn"
android:text="New Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
UI线程做轻量级操作,子线程处理耗时操作,发送标识
1.异步下载文件更新进度条
DownloadActivity
package com.example.handlertest;
import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.ProgressBar;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
public class DownloadActivity extends Activity {
private Handler mHandler;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_dowload);
/*
主线程点击 start
按键发起下载
开启子线程做瞎下载
下载过程中通知主线程
主线程更新进度
*/
ProgressBar progressbar = findViewById(R.id.progressBar);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
new Thread(()->{
download("https://img.ixintu.com/upload/jpg/20210522/949cf3d6c57299d6b7683eb5868e8ffd_24455_644_541.jpg!con");
}).start();
}
});
mHandler=new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
switch (msg.what){
case 100001:
progressbar.setProgress((Integer) msg.obj);
break;
}
}
};
}
private void download(String appurl) {
try {
URL url=new URL(appurl);
URLConnection urlConnection = url.openConnection();
InputStream inputStream = urlConnection.getInputStream();
//获取文件总长度
int contentLength=urlConnection.getContentLength();
String path = "data"+ File.separator+"data"+File.separator+"com.example.handlertest" +File.separator
+ "aaa" + File.separator;
File file=new File(path);
if(!file.exists()){
file.mkdir();
}
String filename=path+"as.jpg";
File file1 = new File(filename);
if(file1.exists()){
file1.delete();
}
int downloadSize=0;
byte[] bytes=new byte[1024];
int length=0;
FileOutputStream fileOutputStream = new FileOutputStream(filename);
while ((length=inputStream.read(bytes))!=-1){
fileOutputStream.write(bytes,0,length);
downloadSize+=length;
Message message=new Message();
message.obj=downloadSize*100/contentLength;
message.what=100001;
mHandler.sendMessage(message);
}
inputStream.close();
fileOutputStream.close();
} catch (MalformedURLException e) {
Message message=new Message();
message.what=100002;
mHandler.sendMessage(message);
e.printStackTrace();
} catch (IOException e) {
Message message=new Message();
message.what=100002;
mHandler.sendMessage(message);
e.printStackTrace();
}
}
}
File.separator就是/ 由于我用的虚拟机所以安装的软件再data/data/下使用File.separator拼接
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.handlertest">
<uses-permission android:name="android.permission.INTERNET"/><!--下载权限-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.HandlerTest"
tools:targetApi="31">
<activity
android:name=".DownloadActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
添加权限后,最好把这个软件卸载一下再运行,如虚拟机里,长按这个项目卸载
activity_dowload.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button" />
<ProgressBar
android:max="100"
android:progress="100"
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
Handler可能会导致内存泄露,因为他需要传递上下文,因为是异步的,他的上下文可能会摧毁,但是GC不会回收掉,Handler中一直持有上下文引用,所以会导致内存泄漏
2.倒计时的实现
package com.example.countdowntime;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.TextView;
import java.util.concurrent.TimeUnit;
public class MainActivity extends AppCompatActivity {
public static final int COUNTDOWN_TIME_CODE = 100001;
public static final int DELAY_MILLIS = 1000;
int i=10;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//得到控件
TextView contdownTimeTextView = findViewById(R.id.cTextView);
Handler handler=new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
if(msg!=null && msg.what==COUNTDOWN_TIME_CODE){
i-=1;
contdownTimeTextView.setText(String.valueOf(i));
}
}
};
new Thread(()->{
for (int j = 0; j < 10; j++) {
try {
Message message=Message.obtain();
message.what= COUNTDOWN_TIME_CODE;
handler.sendMessageDelayed(message, DELAY_MILLIS);
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:paddingBottom="16dp"
android:paddingLeft="16dp"
android:paddingTop="16dp"
android:paddingRight="16dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/cTextView"
android:textSize="25sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/maxTime"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</RelativeLayout>
对handler进行优化
package com.example.countdowntime;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.TextView;
import java.lang.ref.WeakReference;
import java.util.concurrent.TimeUnit;
public class MainActivity extends AppCompatActivity {
//倒计时标记
public static final int COUNTDOWN_TIME_CODE = 100001;
//倒计时间隔
public static final int DELAY_MILLIS = 1000;
//倒计时最大值
public static final int MAX_COUNT = 10;
private TextView contdownTimeTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//得到控件
contdownTimeTextView = findViewById(R.id.cTextView);
//创建了一个handler 静态的
CountdownTimeHandler handler=new CountdownTimeHandler(MainActivity.this);
//新建了一个message
Message message=Message.obtain();
message.what= COUNTDOWN_TIME_CODE;
message.arg1= MAX_COUNT;
//第一次发送这个message
handler.sendMessageDelayed(message, DELAY_MILLIS);
}
public static class CountdownTimeHandler extends Handler{
final WeakReference<MainActivity> mWeakReference;
public CountdownTimeHandler(MainActivity activity) {
this.mWeakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
//获取activity的弱引用
MainActivity activity = mWeakReference.get();
switch (msg.what){
case COUNTDOWN_TIME_CODE:
int values = msg.arg1;
activity.contdownTimeTextView.setText(String.valueOf(values--));
//循环发消息控制
if(values >= 0){
Message msg1=Message.obtain();
msg1.what=COUNTDOWN_TIME_CODE;
msg1.arg1=values;
sendMessageDelayed(msg1,DELAY_MILLIS);
}
break;
}
}
}
}
使用弱引用对其优化,创建静态handler用完即回收
这里的handleMessage中Message需要重新构建,不能直接拿之前的msg否则会报错,再重新构建从消息池里拿消息,依旧是原来的消息
2.Message
常见方法
public static Message obtain(Message orig) {
Message m = obtain(); //使用消息池
m.what = orig.what; //message的标识 发送消息后 后续判断标识 再进行业务
m.arg1 = orig.arg1; //message携带的参数
m.arg2 = orig.arg2;
m.obj = orig.obj; //上下文
m.replyTo = orig.replyTo; //实现异步通信
m.sendingUid = orig.sendingUid;
m.workSourceUid = orig.workSourceUid;
if (orig.data != null) {
m.data = new Bundle(orig.data);
}
m.target = orig.target;
m.callback = orig.callback;
return m;
}