一、多线程
在Android中,默认情况所有的操作都在主线程中进行,它负责管理操作UI相关事务,用户自己创建的子线程不能操作这些UI组件,但是提供了消息处理机制来解决该问题
1.线程创建
创建进程有两种方法:一通过Thread类的构造方法创建线程对象,重写run方法
二通过实现Runnable接口实现
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
}
});
public class MainActivity extends AppCompatActivity implements Runnable{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public void run() {
}
}
2.线程启动
thread.run启动
run()和start()的区别
每个线程通过Thread对象的run方法来操作,成为线程体,通过调用start来启动 run方法用于执行线程时候的运行代码,可以重复调用,start只能调用一次 start用来启动线程,实现多线程,先处于就绪状态,再通过run来完成其运行状态,而run的执行路径只有一条,如果先run的话,不会run方法达成main线程下的普通方法去执行,不会在线程中执行,不是多线程的工作
3.线程休眠
线程的暂停
Thread.sleep(long time)
time的单位为毫秒
4.线程中断
interrupt,使用这个方法可以给线程发送中断请求,标记它为中断状态
也可以通过Thread.currentThread().isInterrrupted()来获取所有线程是否中断,返回布尔值
如果在线程已经别标记为终端时进行wait,join或sleep,将会抛出InterruptedException异常,所以就需要isInterrupt来标记线程是否中断,通过对它的判断来完成操作
写了以下代码
package com.thundersoft.session7;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity implements Runnable{
private int i;
private Thread thread;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button start = findViewById(R.id.start);
Button stop = findViewById(R.id.stop);
start.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
i = 0;
thread = new Thread(MainActivity.this);
thread.start();
Log.i("thread", "thread =====>" + thread);
}
});
stop.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (thread != null){
thread.interrupt();
}
Log.i("thread","stop");
}
});
}
@Override
public void run() {
Thread currentThread = Thread.currentThread();
while (!currentThread.isInterrupted()){
i++;
try {
Thread.sleep(1000);
Log.i("thread","" + i);
}catch (InterruptedException e){
e.printStackTrace();
thread.interrupt();
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}
Activity实现了Runnable接口,重写run方法,在run方法中,判断当前线程是否为中断状态,如果不是,休眠一秒,输出日志
在sleep的源码中,有以下内容
if (millis == 0 && nanos == 0) {
if (Thread.interrupted()) {
throw new InterruptedException();
}
return;
}
判断sleep的线程对象是否为中断状态,如果是,则清楚状态(interrupted的含义),并且抛出异常,回到自己的代码中,抛出异常,则中断线程
start按钮的点击方法,创建了线程并且开启
stop按钮的点击方法,如果该线程对象为不为空就标记为中断状态,会被sleep检测到
5.播放音乐
通过线程播放音乐的实例
package com.thundersoft.session7;
import android.app.Activity;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Toast;
import androidx.annotation.Nullable;
import java.io.IOException;
public class MusicActivity extends Activity implements Runnable {
private Thread thread;
private MediaPlayer mediaplayer;
private String path;
boolean isAlive = false;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout linearLayout = new LinearLayout(this);
setContentView(linearLayout);
Button button1 = new Button(this);
Button button2 = new Button(this);
button1.setText("点我开启线程播放音乐");
button2.setText("点我关闭线程停止播放音乐");
linearLayout.addView(button1);
linearLayout.addView(button2);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
thread = new Thread(MusicActivity.this);
if (isAlive){
Toast.makeText(MusicActivity.this, "线程已经执行", Toast.LENGTH_SHORT).show();
}else {
thread.start();
Toast.makeText(MusicActivity.this, "开始播放", Toast.LENGTH_SHORT).show();
isAlive = true;
}
}
});
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (thread != null){
thread.interrupt();
}
}
});
}
@Override
public void run() {
Thread c = Thread.currentThread();
try {
display();
} catch (IOException e) {
e.printStackTrace();
}
while (!c.isInterrupted()){
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
thread.interrupt();
mediaplayer.stop();
}
}
}
private void display() throws IOException {
path = "http://m10.music.126.net/20211228095512/4d1bde16a0860270925710fef8812e12/ymusic/2d31/cbe1/b405/9694012993255d512f94cb24e8d760bd.mp3";
if (path == "") {
Toast.makeText(MusicActivity.this, "未找到您要播放的音乐",
Toast.LENGTH_LONG).show();
}
mediaplayer = new MediaPlayer();
mediaplayer.setDataSource(path);
mediaplayer.prepare();
mediaplayer.start();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mediaplayer != null){
mediaplayer.release();
mediaplayer = null;
}
}
}
二、消息处理类Handler
在一个线程之中,只能有一个Looper和MessageQueue,但是可以有很多个Handler,这些个Handler之间可以共享Looper和MessageQueue
Handler有两个主要作用,第一个是Message或者是Runnable应用post或者是通过sendMessage方法发送到消息队列中,发送时可以指定时间,延迟,还有Bundle数据
消息队列循环到当前队列时调用相应的Handle对象的HandleMessage方法来处理
第二个作用是在子线程和主线程之间进行通信,在工作线程和UI线程之间进行通信
1.Handler类常用方法
handleMessage(Message msg)处理消息的方法,通常被重写,在发送消息时,该方法会被回调
post(Runnable r)立即发送Runnable对象,最终将改对象封装撑Message对象
postAtTime(Runnable r,long uptimeMillis)定时发送runnable
postDelayed(Runnable r,long delayMillis)延迟发送runnable
sendEmptyMessage(int what)发送空消息
sendMessage(Message msg)立即发送Message对象
sendMessageAtTime/sendMessageDelayed定时和延时发送
2.Message
一个消息队列中会有多个消息对象,每个消息对象都可以通过
**Message.obtain()或Handler.obtainMessage()**方法获得消息
每个Message对象都包含以下五个属性
arg1/arg2都为int型,用来存放整形数据
obj为Object,用来存放发送给接收器的Object类对象
replyTo为Messenger,用来指定Message发送到何处的可选的Messager对象
what为int型,指定自定义的消息代码,用于给接收者了解
如果要携带其他类型的数据,通过将数据存放在Bundle中,使用Message的setDate方法将其添加到Message中
优先使用Message.arg1/2来存储信息,使用what来标识信息,比使用Bundle更节省内存
3.加载图片
实现通过线程加载图片,以播放音乐那个例子写的这次代码,但是出现Only the original thread that created a view hierarchy can touch its views报错
意为加载view需要通过view线程来操作,利用handle来解决问题,代码如下
package com.thundersoft.session7;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.StrictMode;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
public class PicActivity extends Activity implements Runnable{
boolean state = true;
private Thread thread;
private ImageView imageView;
private Handler handler;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout linearLayout = new LinearLayout(this);
linearLayout.setOrientation(LinearLayout.VERTICAL);
setContentView(linearLayout);
imageView = new ImageView(this);
Button button1 = new Button(this);
Button button2 = new Button(this);
button1.setText("点我获取图片");
button2.setText("点我关闭图片");
linearLayout.addView(imageView);
linearLayout.addView(button1);
linearLayout.addView(button2);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (state){
thread = new Thread(PicActivity.this);
thread.start();
state = false;
}else {
Toast.makeText(PicActivity.this, "图片已经被加载", Toast.LENGTH_SHORT).show();
}
}
});
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (thread != null){
thread.interrupt();
}
}
});
if (android.os.Build.VERSION.SDK_INT > 9) {
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
}
handler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
if (msg.what==0){
try {
loading();
} catch (IOException e) {
e.printStackTrace();
}
}
if (msg.what==1){
imageView.setVisibility(View.GONE);
}
}
};
}
@Override
public void run() {
Thread c = Thread.currentThread();
handler.sendEmptyMessage(0);
while (!c.isInterrupted()){
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
handler.sendEmptyMessage(1);
thread.interrupt();
}
}
}
private void loading() throws IOException {
String path = "https://pic2.zhimg.com/80/v2-fda7c13057f7182bd1c603b2d6b1fa00_720w.jpg";
Bitmap bitmap = null;
URL url;
url = new URL(path);
URLConnection conn = url.openConnection();
conn.connect();
InputStream is = conn.getInputStream();
bitmap=BitmapFactory.decodeStream(is);
imageView.setImageBitmap(bitmap);
}
}
在run中请求图片sendEmptyMessage的what为0,取消图片为1,new一个Handle对象来对what的判断来进行相应操作
4.循环播放图片
和上面的实例差不多
package com.thundersoft.session7;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class AdActivity extends Activity implements Runnable{
boolean state = true;
private Thread thread;
private ImageView imageView;
private Handler handler;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout linearLayout = new LinearLayout(this);
linearLayout.setOrientation(LinearLayout.VERTICAL);
setContentView(linearLayout);
imageView = new ImageView(this);
imageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
imageView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
imageView.setImageResource(R.drawable.ad1);
linearLayout.addView(imageView);
imageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (state){
thread = new Thread(AdActivity.this);
thread.start();
state = false;
}else {
if (thread != null){
thread.interrupt();
}
}
}
});
handler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
if (msg.what==0){
imageView.setImageResource(R.drawable.ad1);
}
if (msg.what==1){
imageView.setImageResource(R.drawable.ad2);
}
}
};
}
@Override
public void run() {
Thread c = Thread.currentThread();
while (!c.isInterrupted()){
try {
handler.sendEmptyMessage(0);
Thread.sleep(500);
handler.sendEmptyMessage(1);
Thread.sleep(500);
}catch (InterruptedException e){
e.printStackTrace();
handler.sendEmptyMessage(0);
thread.interrupt();
}
}
}
}
三、实践
1.要求
实现一个登录页面,提供记住密码和用户名功能,登陆后显示列表视图必须包含登录用户名,且点击列表会显示点击那一项
登录后主界面列表选择第一个选项后,显示一组图片,图片加载完成前需要显示进度条,加载完成后,长按某一个图片弹出提示框是否删除,如果确定就不再显示该图片
登录后主界面列表选择第二个选项后,跳转到另一个Activity,并且传过去一个字符串,在新的Activity中包含2个Fragment,左边的Fragment类似手机设置,含一个亮度设置选项,点击后右边的Fragment显示具体的亮度设置界面
登录后主界面列表选择第三个选项后,跳转到另一个Activity,并且传过去int/byte/Serializable等多种类型的数据,在新的Activity中显示传入的数据,检查是否所传所有类型的数据都正确接收,同时在Activity启动的时候开始监听android.net.conn.CONNECTIVITY_CHANGE,Activity退出后不再监听,当网络状态改变的时候提示用户网络状态改变情况。
登录后主界面列选择第四个选项后,跳转到另一个Activity,运用本章学习的资源、样式等知识,实现Activity中显示类似自己手机设置列表的效果(跟进到自己手机的设置界面一样)
登录后主界面列表选择第五个选项后,跳转到另一个Activity显示记账本,其含一个 记账项输入、金额输入、添加按钮和一个列表,添加记账功能往自定义Content Provider写入,数据可以存储在自定义数据库,或者xml文件里,或者最简单的存在列表变量里面;
(以上是Android 八 当中的实践要求)
登陆后主页面列表选择第六个选项后,通过输入网址下载MP3,显示下载进度,下载完成后立即播放,实现停止功能,并且显示播放的次数
2.代码实现
主程序代码改动:新加了字符串
if (index == 6){
startActivity(new Intent().setClass(MainActivity.this,MusicActivity.class));
}
MusicActivity.java
package com.thundersoft.login;
import android.app.Activity;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.util.Timer;
import java.util.TimerTask;
public class MusicActivity extends Activity{
private EditText editText = null;
private Button download,pause = null;
private ProgressBar schedule = null;
private ProgressBar schedule1 = null;
private TextView times = null;
private TextView result = null;
boolean state = true;
private Handler handler;
private static MediaPlayer mediaPlayer = null;
private int iTimes = 0;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.music);
editText = findViewById(R.id.url);
download = findViewById(R.id.download);
pause = findViewById(R.id.pause);
schedule = findViewById(R.id.schedule);
schedule1 = findViewById(R.id.schedule1);
times = findViewById(R.id.times);
result = findViewById(R.id.resultm);
String url = editText.getText().toString();
editText.setText("http://m10.music.126.net/20220101190407/e141e7a49dafae12790f300bd052255b/ymusic/0f59/5252/0353/834dc09348e89fa0d480496a7475eff8.mp3");
download.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (editText.length() != 0){
if (state){
new Thread(new Runnable() {
@Override
public void run() {
download();
}
}).start();
state = false;
}else {
Toast.makeText(MusicActivity.this, "正在下载", Toast.LENGTH_SHORT).show();
}
}else {
Toast.makeText(MusicActivity.this, "请输入下载地址", Toast.LENGTH_SHORT).show();
}
}
});
pause.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mediaPlayer.pause();
}
});
handler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
if (msg.what == 0x101){
schedule.setProgress(msg.arg1);
result.setText("已下载" + String.valueOf(msg.arg1) + "%");
if (msg.arg1 == 100){
result.setText("下载完成!");
}
}
if (msg.what == 0x102){
schedule1.setProgress(msg.arg1);
}
super.handleMessage(msg);
}
};
}
private void download() {
try {
URL url = new URL(editText.getText().toString());
URLConnection urlConnection = url.openConnection();
int length = urlConnection.getContentLength();
ReadableByteChannel c = Channels.newChannel(url.openStream());
File file = new File("data/data/com.thundersoft.login/test.mp3");
if (!file.exists()){
file.createNewFile();
}
FileOutputStream fileOutputStream = new FileOutputStream(file);
FileChannel channel = fileOutputStream.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
int len;
int sum = 0;
while ((len = c.read(byteBuffer)) != -1){
sum += len;
int progress = (sum*100)/length;
byteBuffer.flip();
while (byteBuffer.hasRemaining()){
channel.write(byteBuffer);
}
byteBuffer.clear();
Message m = handler.obtainMessage();
m.what = 0x101;
m.arg1 = progress;
handler.sendMessage(m);
}
display();
} catch (IOException e) {
e.printStackTrace();
}
}
private void display() {
mediaPlayer = new MediaPlayer();
mediaPlayer = MediaPlayer.create(this,Uri.parse("data/data/com.thundersoft.login/test.mp3"));
mediaPlayer.start();
schedule1.setMax(mediaPlayer.getDuration());
Timer timer = new Timer();
TimerTask timerTask = new TimerTask(){
@Override
public void run() {
schedule1.setProgress(mediaPlayer.getCurrentPosition());
}
};
timer.schedule(timerTask,0,10);
iTimes++;
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
times.setText(""+iTimes);
display();
}
});
}
}
点击下载按钮,创建线程下载,下载方法中,将文件的下载进度传递给message通过handler发送出去观察进度
3.实现效果
其实是有声音的
|