7.1实现多线程
在程序开发时,对于- -些比较耗时的操作,通常会为其开辟一个 单独的线程来执行,以尽可能减少用户的等待时间。在Android中,默认情况下,所有的操作都在主线程中进行,主线程负责管理与UI相关的事件,而在用户自己创建的子线程中,不能对UI组件进行操作。因此,Android 提供了消息处理传递机制来解决这一问题。
在现实生活中,很多事情都是同时进行的,例如,我们可以一边看书,一 边喝咖啡;而计算机则可以一边播放音乐,一边打印文档。对于这种可以同时进行的任务,可以用线程来表示,每个线程完成一个任务,并与其他线程同时执行,这种机制被称为多线程。下面就来介绍如何创建线程、开启线程、让线程休眠和中断线程。
7.1.1 创建线程
1.通过Thread类的构造方法创建线程
Thread(Runnable runnable)
该构造方法的参数runnable可以通过创建一个Runnable类的对象并重写其run0方法来实现,例如,要创建一个名称为thread的线程,可以使用下面的代码:
Thread thread=new Thread(new Runnable(){
//重写run()方法
@Override
public void run() {
//要执行的操作
}
});
说明:在run0方法中,可以编写要执行的操作的代码,当线程被开启时,run()方法将被执行。
2.通过实现Runnable接口创建线程
public class ClassName extends 0bject implements Runnable
当一个类实现Runnable接口后,还需要实现其run0方法,在run0方法中,可以编写要执行的操作的代码。
例如,要创建一个实现了Runnable 接口的Activity,可以使用下面的代码:
public class MainActivity extends Activity implements Runnable {
@Override
public void onCreate ( Bundle savedInstanceState) {
super.onCreate ( savedInstanceState);
setContentView(R. layout .main);
@override
public void run() {
//要执行的操作
}
7.1.2 开启线程
创建线程对象后,还需要开启线程,线程才能执行。Thread 类提供了start()方 法用于开启线程,其语法格式如下:
start()
例如,存在一个名称为thread的线程,如果想开启该线程,可以使用下面的代码:
thread.start();//开启线程
7.1.3 线程的休眠
线程的休眠就是让线程暂停一段时间后再次执行。 同Java一样,在Android中,也可以使用Thread类的sleep()方法让线程休眠指定的时间。sleep()方 法的语法格式如下:
sleep(long time) 其中参数time用于指定休眠的时间,单位为毫秒。
例如,想要线程休眠1秒钟,可以使用下面的代码:
Thread. sleep(1000);
7.1.4 中断线程
当需要中断指定的线程时,可以使用Thread类提供的iterupt()方法来实现。使用interrupt()方法可以向指定的线程发送一个中断请求,并将该线程标记为中断状态。interrupt()方 法的语法格式如下:
interrupt()
例如,存在一个名称为thread的线程,如果想中断该线程,可以使用下面的代码:
.//省略部分代码
thread. interrupt();
//省略部分代码
public void run() {
while( !Thread. currentThread().isInterrupted()){
...//省略部分代码
}
|)
另外,由于当线程执行wait()、join()或 sleep0方法时,线程的中断状态将被清除并抛出InteruptedException,所以,如果想在线程中执行了waitO、join()或 sleep0方法时中断线程,就需要使用一个boolean型的标记变量来记录线程的中断状态,并通过该标记变量来控制循环的执行与停止。例如,通过名称为islnterrupt 的boolean型变量来标记线程的中断,关键代码如下:
private boolean isInterrupt=false;
//定义标记变量
...//省略部分代码
...//在需要中断线程时,将isInterrupt的值设置为true
public void run() {
while(!isInterrupt){
...//省略部分代码
}
}
7.1.5 案例 1:通过实现Runnable接口来创建线程
main.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/startBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="启动线程"
android:layout_gravity="center"
/>
<Button
android:id="@+id/stopBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="中断线程"
android:layout_gravity="center"
/>
</LinearLayout>
MainActivity.java
public class MainActivity extends AppCompatActivity implements Runnable{
private Thread thread;
private int i;
private boolean interruptFlag;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button button1 = (Button) findViewById(R.id.startBtn);
Button button2 = (Button) findViewById(R.id.stopBtn);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
i=0;
thread = new Thread(MainActivity.this);
thread.start();
interruptFlag=true;
}
});
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (thread!=null){
thread.interrupt();
interruptFlag=false;
thread=null;
}
Log.i("线程运行提示====》","线程已中断。。。");
}
});
}
@Override
public void run() {
while (interruptFlag){
i++;
Log.i("线程执行",String.valueOf(i));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
protected void onDestroy() {
if (thread!=null){
thread.interrupt();
interruptFlag=true;
thread=null;
}
super.onDestroy();
}
}
7.1.6 案例 2:开启一个新线程播放背景音乐
main.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/startBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="播放"
android:layout_gravity="center"
/>
</LinearLayout>
MainActivity.java
package com.jingyi.aboutthread;
import androidx.appcompat.app.AppCompatActivity;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity{
private Thread thread;
private static MediaPlayer mediaPlayer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button button1 = (Button) findViewById(R.id.startBtn);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
((Button)v).setEnabled(false);//设置不可用
thread=new Thread(new Runnable() {
@Override
public void run() {
playSound();
}
});
thread.start();
}
});
}
private void playSound() {
if (mediaPlayer!=null){
mediaPlayer.release();//释放资源
}
mediaPlayer = MediaPlayer.create(MainActivity.this, R.raw.qilixiang);
mediaPlayer.start();//开始播放
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
try{
Thread.sleep(10000);//线程休眠5秒
playSound();//重新播放
}catch (InterruptedException e){
e.printStackTrace();
}
}
});
}
@Override
protected void onDestroy() {//释放资源
if (thread!=null){
thread=null;
}
if (mediaPlayer!=null){
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer=null;
}
super.onDestroy();
}
}
7.2 Handler 消息传递机制
直接在新创建的子线程上对UI界面上的内容进行操作会抛出异常。为此,Android 中引入了Handler 消息传递机制,来实现在新创建的线程中操作UI界面。下面将对Handler 消息传递机制进行介绍。
7.2.1 循环者(Looper)
在介绍Looper之前,需要先来了解一下MessageQueue的概念。在Android中, 一个线程对应一个Looper对象,而一个Looper对象又对应一个MessageQueue(消息队列) 。MessageQueue用于存放Message(消息),在MessageQueue中,存放的消息按照FIFO (先进先出)原则执行,由于MessageQueue被封装到Looper里面,所以这里不对
MessageQueue进行过多介绍。
Looper对象用来为一个线程开启一个消息循环,从而操作MessageQueue。默认情况下,Android 中新创建的线程是没有开启消息循环的,但是主线程除外。系统自动为主线程创建Looper 对象,开启消息循环。所以,当在主线程中应用下面的代码创建Handler对象时不会出错,而如果在新创建的非主线程中应用下面的代码创建Handler对象,将产生异常信息。
Handler handler2 = new Handler();
如果想要在非主线程中创建Handler对象,首先需要使用Looper 类的prepare()方法来初始化一个 Looper对象,然后创建该Handler对象,再使用Looper类的loop()方法启动Looper,从消息队列中获取和处理消息。
小DEMO
public class LooperThread extends Thread {
public Handler handler;
@Override
public void run() {
super.run();
Looper.prepare();//初始化Looper对象
handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
Log.i("Looper_MSG", String.valueOf(msg.what));
}
};
Message message = handler.obtainMessage();//获取一个消息
message.what=0x1;
handler.sendMessage(message);//发送消息
Looper.loop();//启动Looper
}
}
LooperThread thread = new LooperThread();
thread.start();
Looper类常用的方法
注意:写在Looper.loop0之后的代码不会被执行,该函数内部是- -个循环,当调用Handler. getLooperO.quit0方法后,loop0方法才会中止,其后面的代码才能运行。
7.2.2 消息处理类(Handler)
消息处理类(Handler) 允许发送和处理Message或Runnable对象到其所在线程的MessageQueue中。Handler主要有以下两个作用。
**1.**将Message或Runnable应用post( )或sendMessage0方法发送到MessageQueue中,在发送时可以指定延迟时间、发送时间及要携带的Bundle数据。当MessageQueue循环到该Message时,调用相应的Handler对象的handlerMessage0方法对其进行处理。
**2.**在子线程中与主线程进行通信,也就是在工作线程中与UI线程进行通信。
说明:在一 个线程中,只能有一个Looper和MessageQueue,但是可以有多个Handler,而且这些Handler可以共享同一个Looper和MessageQueue。
Handler类常用的方法
7.2.3 消息类(Message)
消息类(Message)被存放在MessageQueue中, 一个MessageQueue中可以包含多个Message对象。每个Message对象可以通过Message.obtain0或Handler.obtainMessage0方法获得。一个Message对象具有如表所示的5个属性。
说明:使用Message类的属性可以携带int型数据,如果要携带其他类型的数据,可以先将要携带的数据保存到Bundle对象中,然后通过Message类的setData0方法将其添加到Message中。
总之,Message类的使用方法比较简单,在使用时,需注意以下3点:
[V]尽管Message有public的默认构造方法,但是通常情况下,需要使用Message.obtain()或Handler.obtainMessage0方法来从消息池中获得空消息对象,以节省资源。
[V]如果一个Message只需要携带简单的int型信息,应优先使用Message.arg1和Message.arg2 属性来传递信息,这比用Bundle更节省内存。
[V]尽可能使用Message.what 来标识信息,以便用不同方式处理Message.
7.2.4 案例1 :开启新线程获取网络图片并显示到ImageView中
main.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"
>
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
/>
</LinearLayout>
MainActivity.java
package com.jingyi.picthread;
import androidx.appcompat.app.AppCompatActivity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
public class MainActivity extends AppCompatActivity {
private ImageView imageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
imageView = findViewById(R.id.imageView);
new Thread(new Runnable() {
public void run() {
final Bitmap bm=getPic("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fnimg.ws.126.net%2F%3Furl%3Dhttp%3A%2F%2Fdingyue.ws.126.net%2F2021%2F0702%2Fc248167ej00qvkv29000vc000hs00m8c.jpg%26thumbnail%3D650x2147483647%26quality%3D80%26type%3Djpg&refer=http%3A%2F%2Fnimg.ws.126.net&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1651386571&t=632b6b44ba9408c26b013acb3ce6e11d");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
imageView.post(new Runnable() {
@Override
public void run() {
imageView.setImageBitmap(bm);//在组件中显示图片
}
});
}
}).start();
}
public Bitmap getPic(String path){
Bitmap bm=null;
try {
URL url = new URL(path);
URLConnection connection=url.openConnection();//获取链接
connection.connect();//建立链接
InputStream inputStream = connection.getInputStream();//获取输入流
bm = BitmapFactory.decodeStream(inputStream);
inputStream.close();
}catch (MalformedURLException e){
e.printStackTrace();
}catch (IOException e) {
e.printStackTrace();
}
return bm;
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.jingyi.aboutthread">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Application">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
7.2.5 案例 2:开启新线程实现电子广告牌
main.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"
>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/imageView"
android:layout_gravity="center"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/textView"
android:textSize="30dp"
android:layout_gravity="center"
/>
</LinearLayout>
MainActivity.java
package com.jingyi.adthread;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.Random;
public class MainActivity extends AppCompatActivity implements Runnable{
private ImageView imageView;
private Handler handler;
private int[] imageID=new int[]{R.drawable.adaofu,R.drawable.baicaowei,R.drawable.cola,R.drawable.huaxizii};
private String[] titles=new String[]{"阿道夫","百草味","百事可乐","花西子"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
imageView=findViewById(R.id.imageView);
Thread thread = new Thread(this);
thread.start();
handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
TextView textView = (TextView) findViewById(R.id.textView);
if (msg.what==0x1){
textView.setText(msg.getData().getString("title"));
imageView.setImageResource(imageID[msg.arg1]);
}
super.handleMessage(msg);
}
};
}
@Override
public void run() {
int index=0;//图片索引
while(!Thread.currentThread().isInterrupted()){
index= new Random().nextInt(imageID.length);//随机一个图片索引
Message message = handler.obtainMessage();
message.arg1=index;//保存广告图片的索引值
Bundle bundle = new Bundle();
message.what=0x1;//设置消息标识
bundle.putString("title",titles[index]);//保存广告内容
message.setData(bundle);
handler.sendMessage(message);//发送消息
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
7.3 应用实例
7.3.1 多彩的霓虹灯
main.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"
android:id="@+id/linear1"
>
</LinearLayout>
colors.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="color1">#f00</color>
<color name="color2">#f60</color>
<color name="color3">#ff0</color>
<color name="color4">#0f0</color>
<color name="color5">#0ff</color>
<color name="color6">#00f</color>
<color name="color7">#60f</color>
<color name="color8">#fff</color>
</resources>
MainActivity.java
public class MainActivity extends AppCompatActivity{
private LinearLayout linearLayout;
private Handler handler;
private static TextView [] tv=new TextView[14];
private int[] color=new int[]{R.color.color1,R.color.color2,R.color.color3,R.color.color4,R.color.color5,R.color.color6,R.color.color7};
private int index=0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
linearLayout=findViewById(R.id.linear1);
int height = this.getResources().getDisplayMetrics().heightPixels;//获取屏幕高度
for (int i=0;i<tv.length;i++){
tv[i]=new TextView(this);//新建文本框对象
tv[i].setWidth(this.getResources().getDisplayMetrics().widthPixels);
tv[i].setHeight(height/tv.length);
linearLayout.addView(tv[i]);//将文本框添加到线性布局中
}
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()){
Message message = handler.obtainMessage();
message.what=0x1;
handler.sendMessage(message);
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
thread.start();
handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
int temp=0;
if (msg.what==0x1){
for (int i=0;i<tv.length;i++){
temp= new Random().nextInt(color.length);
if (temp==index){//同一种颜色不能同时挨着出现
temp++;
if (temp==color.length){//越界处理
temp=0;
}
}
index=temp;
tv[i].setBackgroundColor(getResources().getColor(color[index]));
}
}
super.handleMessage(msg);
}
};
}
}
Manifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.jingyi.adthread">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AppCompat.NoActionBar">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>