在Java中我们已经学习过线程的使用,详情可参见文章《 Java中的多线程 》。下面我们围绕为什么和如何使用两个问题来学习一下线程。
为什么使用多线程
在Android中每一个界面都有一个主线程,我们通常称之为UI主线程,这个线程主要用于绘制我们的UI界面。如果我们在这个主线程中开启一个耗时操作,例如连接网络,主线程就会被阻塞一直等待直到我们连接网络成功。所以我们不能再主线程中进行耗时操作,并需开启一个子线程来完消耗时间的操作。
UI主线程
UI主线程用于绘制UI界面,当我们每次滑动触碰屏幕时,UI主线程都会一遍一遍的绘制界面。但是UI主线程又有一点小任性,他只允许自己绘制UI界面,不允许其他进程绘制。我们可以通过一个例子来看一下:
我们在布局中定义一个TextView和Button,TextView初始显示“HelloWorld”文字,当我们点击Button时,通过一个线程将TextView中的文字更改为“HelloAndroid”。(恩……目前听起来逻辑还是很对的嘛。)我们来看代码:
布局文件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.lishuang.administrator.servicedemo.Main2Activity">
<TextView
android:id="@+id/textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/hello_world"
android:textSize="20sp" />
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button" />
</LinearLayout>
Activity:
public class Main2Activity extends Activity {
private Button mButton;
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
mButton = (Button) findViewById(R.id.button);
mTextView = (TextView) findViewById(R.id.textview);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
new Thread(new Runnable() {
@Override
public void run() {
mTextView.setText("HelloAndroid");
}
});
}
});
}
}
看代码也是很符合逻辑的哈,运行一下看结果:
结果出错了,果真UI主线程比较任性不允许其他线程去操作UI界面。
那么我们来看一下Android中的多线程是如何使用的。
如何使用多线程
我们首先来回顾一下,Java中的线程如何创建的……
创建线程
创建线程有两种方式:
(1)继承Thread类
继承Thread类,重写其run()方法,通过其对象调用个start()方法来调用线程。但是继承Thread类的方式耦合性比较高,这违背了我们开发“高内聚,低耦合”的理念。所以应尽量采用实现Runnable接口的方式创建多线程。
public class MyThread extends Thread{
@Override
public void run() {
while(true){
System.out.println("我是一个线程我正在运行!");
}
}
}
(2)实现Runnable接口
实现Runnable接口,实现其run()方法,调用start()方法开启线程。
public class MyThread implements Runnable{
@Override
public void run() {
while(true){
System.out.println("我是一个线程我正在运行!");
}
}
}
通常为了使用方便,我们都是使用匿名内部类来实现。
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我是一个线程我正在运行!");
}).start();
线程异步处理机制
Android中的多线程通过异步机制处理,线程之间通过发送Message来进行通信。了解线程的异步处理机制,我们首先要了解几个概念。
1. Message
这个刚才我们就已经说过了,Message用于线程之间的通信,例如,我们要通过一个子线程更改UI界面,我们可以在这个子线程中发送一个Message,让UI主线程接收,然后实现对界面的更改。
2. Handler
顾名思义,就是操纵者的意思,Handler主要用于发送Message和处理Message。
3. Message Queen
是消息队列,用于存放Handler对象发送的Message消息。
4. Looper
这是一个循环的意思,Looper会一直循环检查Message Queen有没有新的Message。
我们通过一个流程图来看,这几个概念之间的关系:
我们首先在处理消息的线程中创建一个Handler的对象,然后再发送消息的线程中创建一个Message对象,然后将这条消息通过Handler对象发送消息将消息添加到Message Queen中。Looper循环会一直检查Message Queen有没有新的Message,如果有新的Message会将该条Message传送给处理消息的线程,让其处理,下面我们通过例子来加深印象:
子线程操作主线程
这里我们以接收验证码倒计时为例,我们见过这种用法,当我们注册一个账号时会向我们的手机发送验证码,点击发送验证码后会有一个倒计时,我们通过这个子线程操作主线程来完成这个功能。
1. 定义布局。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/textview1"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:hint="获取的验证码" />
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="获取验证码"/>
</LinearLayout>
</LinearLayout>
2. 在主线程中创建一个Handler类,重写handleMessage()方法,这儿重写的内容为对消息的操作,我们在这里先不做定义。
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
//接收消息,对消息进行处理
}
}
3. 在按钮的点击事件中发送一个消息。
public void onClick(View view) {
switch (view.getId()) {
case R.id.button1:
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
if (count > 0) {
count--;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
//将消息封装
Message message = handler.obtainMessage();
message.obj = count + "秒";
message.what = HANDLER;
//发送消息
handler.sendMessage(message);
}
}
}).start();
break;
default:
break;
}
}
4. 在Handler对象的handleMessage()方法中定义对消息的处理。
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
//接收消息
super.handleMessage(msg);
switch (msg.what) {
case HANDLER:
String time = (String) msg.obj;
mButton1.setText(time);
break;
default:
break;
}
}
};
结果显示:
附上MAinActivity中的整体代码:
public class MainActivity extends Activity implements View.OnClickListener {
private Button mButton1;
private int count = 60;
private static final int HANDLER = 0x11;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
//接收消息
super.handleMessage(msg);
switch (msg.what) {
case HANDLER:
String time = (String) msg.obj;
mButton1.setText(time);
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton1 = (Button) findViewById(R.id.button1);
mButton1.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.button1:
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
if (count > 0) {
count--;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
//将消息封装
Message message = handler.obtainMessage();
message.obj = count + "秒";
message.what = HANDLER;
handler.sendMessage(message);
}
}
}).start();
break;
default:
break;
}
}
}
主线程操作子线程
一般情况下都是子线程操作主线程很少有主线程操作子线程,不过我们这里也学习一下。主线程操作子线程的方法同上,唯一的不同点就是子线程中如果创建Handler对象时,必须创建Looper,为什么我们在主线程中没有创建呢?因为主线程是主线程啊,他不是一般…的线程啊……主线程自己封装了Looper,但是子线程是一般的线程,因此它在创建Handler时,必须自己创建Looper。我们这里直接看代码.
布局文件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.lishuang.administrator.threaddemo.MaintoChildActivity">
<Button
android:id="@+id/button_main_to_child"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="主线程给子线程发消息"/>
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button"/>
</LinearLayout>
Activity:
public class MaintoChildActivity extends Activity {
private Button mButton;
private Button mButtonSend;
private Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mainto_child);
mButton = (Button) findViewById(R.id.button_main_to_child);
mButtonSend = (Button) findViewById(R.id.button);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
ChildThread thread = new ChildThread();
thread.start();
}
});
mButtonSend.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//这里在另一个点击事件中发送消息的原因是线程创建和执行是一个耗时操作,如果在同一事件中可能hangdler对象还没有创建就执行发送消息方法
handler.sendEmptyMessage(0);
}
});
}
//创建一个子线程
class ChildThread extends Thread {
@Override
public void run() {
Looper.prepare();
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
Log.d("data", "接收到主线程打来的消息");
Toast.makeText(getApplicationContext(), "接收到主线程打来的消息", Toast.LENGTH_SHORT).show();
}
};
Looper.loop();
}
}
}
使用Async Task操作进程
创建一个类继承AsyncTask
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.lishuang.administrator.threaddemo.asynctack.AsyncTaskDemo">
<ProgressBar
android:id="@+id/progressbar_download"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/button_download"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="开始下载" />
</LinearLayout>
2. 创建一个MyAsyncTask类继承AsyncTask实现其三个方法。
public class AsyncTaskDemo extends Activity {
private Button mButtonDownload;
private ProgressBar mProgressBarDownload;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_async_task_demo);
mButtonDownload = (Button) findViewById(R.id.button_download);
mProgressBarDownload = (ProgressBar) findViewById(R.id.progressbar_download);
mButtonDownload.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
MyAsyncTask myAsyncTask = new MyAsyncTask();
myAsyncTask.execute();
}
});
}
private int count=0;
class MyAsyncTask extends AsyncTask<String, String , String >{
//开始执行的方法
@Override
protected String doInBackground(String... strings) {
while(count<101){
count++;
publishProgress(""+count);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return "执行结束";
}
//执行中的方法
@Override
protected void onProgressUpdate(String... values) {
int progress = Integer.parseInt(values[0]);
mProgressBarDownload.setProgress(progress);
}
//执行结束的方法
@Override
protected void onPostExecute(String s) {
mButtonDownload.setText(s);
}
}
}