10.2 Android多线程编程
定义一个线程只需要新建一个类继承自Thread,然后重写父类的run方法,在里面编写耗时逻辑即可
class MyThread extends Thread{
@Override
public void run(){
// 处理具体的逻辑
}
}
那么如何启动呢
new Mythread().start()
这样继承的方式耦合性高,更多都会使用Runnable接口的方式定义一个线程,如下:
class MyThread implements Runnable{
@Override
public void run(){
// 具体逻辑
}
}
调用方式也要改变
MyThread myThread = new MyThread();
new Thread(myThread).start();
如果不想专门定义一个类去实现runnable,也可以用匿名类的方式:
new Thread(new Runnable(){
@Override
public void run(){
// 相应逻辑
}
}).start();
下面来看下Android多线程和java多线程不同的地方:
10.2.2 在子线程中更新UI
Android的UI也是线程不安全的,要想更新程序中的UI元素,必须在主线程中进行
下面测试下,首先加点组件
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/change_text"
android:text="Change text"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/text"
android:layout_centerInParent="true"
android:text="hello world"
android:textSize="20sp"></TextView>
</RelativeLayout>
下面代码直接运行的话会出错
public class MainActivity extends AppCompatActivity {
private TextView text;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = (TextView) findViewById(R.id.text);
Button changeText = (Button) findViewById(R.id.change_text);
changeText.setOnClickListener(this);
}
@Override
public void onClick(View v){
switch (v.getId()){
case R.id.change_text:
new Thread(new Runnable() {
@Override
public void run() {
text.setText("Nice to meet you");
}
}).start();
break;
default:
break;
}
}
}
因为是在子线程中更新的UI导致的,由此可见确实不能在子线程更新UI,但是有时候必须要在子线程中执行耗时任务,然后根据任务结果更新相应UI
对于这种情况,Android 提供了一套异步处理机制,先学习如何使用
public class MainActivity extends AppCompatActivity {
public static final int UPDATE_TEXT = 1;
private TextView text;
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case UPDATE_TEXT:
// 在这里进行UI操作
text.setText("nice to meet you");
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = (TextView) findViewById(R.id.text);
Button changeText = (Button) findViewById(R.id.change_text);
changeText.setOnClickListener(this);
}
@Override
public void onClick(View v){
switch (v.getId()){
case R.id.change_text:
new Thread(new Runnable() {
@Override
public void run() {
Message message = new Message();
message.what = UPDATE_TEXT;
handler.sendMessage(message); // 将message对象发送出去
}
}).start();
break;
default:
break;
}
}
}
首先定义了常量 UPDATE_TEXT 用来表示更新 textview 这个动作,然后新增了个 Handler 对象,并重写父类的 handleMessage() 方法。
10.2.3 解析异步消息处理机制
Android 异步消息处理主要由 4个部分组成:Message、Handler、MessageQueue 和 Looper
- Message:在线程之间传递的消息,前面我们用了what字段,还可以使用 arg1 和 arg2 带一些整型数据,obj 带对象
- Handler:主要用于发和处理消息,发消息一般用Handler的sendMessage() 方法,发出的消息最终会传递到 Handler 的 handleMessage() 方法中。
- MessageQueue:消息队列的意思,存放所有通过 Handler 发送的消息,这部分消息会一直在消息队列中等待被处理,每个线程中只会有一个 MessageQueue 对象。
- Looper:调用 Looper 的 loop() 方法后,会进入无限循环中,每当发现 MessageQueue 中存在一个消息就取出,并传递到 HandlerMessage() 方法中,每个线程中也只会有一个 Looper 对象。
在9.2.1中 用到的 runOnUiThread()方法其实就是对上述异步消息处理机制的接口封装。
10.2.4 使用 AsyncTask
AsyncTask是一个抽象类,我们必须要建一个子类继承它,继承时我们可以为 AsyncTask 类指定3个泛型参数,这3个参数用途如下:
- Params:执行 AsyncTask 时需要传入的参数,可用于在后台任务使用。
- Progress:后台任务执行时,如果需要在界面显示进度,则使用这里指定的泛型作为进度单位
- Result:任务完毕后,返回结果
则一个最简单的自定义 AsyncTask 就可以写成如下方式:
class DownloadTask extends AsyncTask<void, Integer, Boolean>
目前我们自定义的 DownloadTask 还是一个空任务,我们还需要去重写 AsyncTask 中的几个方法才能完成对任务的定制,主要有 4 个:
- onPreExecute() :这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框。
- doInBackground(Params…):这个方法中代码都会在子线程中允许,应该在这里处理耗时任务,任务完成的话就可以通过return语句来执行结果返回,如果 AsyncTask 的第三个泛型参数是 void 就可以不返回任务执行结果。这个方法中不可以进行 UI 操作,要更新UI的话 调用 publishProgress(Progress…)
- onProgressUpdate(Progress…):当在后台任务中调用 publishProgress (Progress… )方法后,onProgressUpdate 很快就会被调用,在这个方法中就可以对 UI 操作。
- onPostExecute(Result):后台任务执行完毕并通过return返回时,这个方法很快就会被调用
简单来说 doInBackground 中放具体任务, onProgressUpdate 中UI操作,onPostExecute 执行收尾工作。
想要启动任务 只需要 加上 execute() 下就行
10.3 服务的基本用法
public class MyService extends Service {
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
public void onCreate(){
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId){
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy(){
super.onDestroy();
}
}
通常我们希望服务一启动就立刻执行某个动作,就可以把逻辑写在 onStartCommand() 方法中,销毁时,在 onDestroy()方法中回收不再使用的资源。
新的服务也需要在 AndroidManifest。xml中声明
10.3.2 启动和停止服务
通过 Intent 来实现的
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_height="match_parent"
android:layout_width="match_parent">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/start_service"
android:text="Start Service"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/stop_service"
android:text="Stop Service"/>
</LinearLayout>
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button startService = (Button) findViewById(R.id.start_service);
Button stopService = (Button) findViewById(R.id.stop_service);
startService.setOnClickListener(this);
stopService.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.start_service:
Intent startIntent = new Intent(this, MyService.class);
startService(startIntent); // 启动服务
break;
case R.id.stop_service:
Intent stopIntent = new Intent(this, MyService.class);
stopService(stopIntent); // 停止服务
break;
default:
break;
}
}
}
注意,这里是完全由活动来决定服务什么时候停止的,不点stop的话服务会一直处于允许状态,那么就会一直在运行,在 MyService 的任何一个位置调用 stopSelf() 方法就能让服务停止。
onCreate 和 onStartCommand 区别:create 只在服务第一次创建的时候调用的,而 onStartCommand() 方法则在每次启动服务时都会调用。
10.3.3 活动和服务进行通信
通过 Binder 对象实现管理
对 MyService 类做出如下修改:
private DownloadBinder mBinder = new DownloadBinder();
class DownloadBinder extends Binder{
public void startDownload(){
Log.d("MyService", "start xiazai");
}
public int getProgress(){
Log.d("Myservice", "jindu");
return 0;
}
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return mBinder;
}
public MyService() {
}
下面添加两个按钮用来绑定和取消绑定活动与服务,然后修改主活动代码
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private MyService.DownloadBinder downloadBinder;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
downloadBinder = (MyService.DownloadBinder) service;
downloadBinder.startDownload();
downloadBinder.getProgress();
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button startService = (Button) findViewById(R.id.start_service);
Button stopService = (Button) findViewById(R.id.stop_service);
startService.setOnClickListener(this);
stopService.setOnClickListener(this);
Button bindService = (Button) findViewById(R.id.bind_service);
Button unbindService = (Button) findViewById(R.id.unbind_service);
bindService.setOnClickListener(this);
unbindService.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.start_service:
Intent startIntent = new Intent(this, MyService.class);
startService(startIntent); // 启动服务
break;
case R.id.stop_service:
Intent stopIntent = new Intent(this, MyService.class);
stopService(stopIntent); // 停止服务
break;
case R.id.bind_service:
Intent bindIntent = new Intent(this, MyService.class);
bindService(bindIntent, connection, BIND_AUTO_CREATE); //绑定服务
break;
case R.id.unbind_service:
unbindService(connection); // 取消绑定
break;
default:
break;
}
}
}
bindService 三个参数 第一个是新创建的 Intent 对象,第二个是前面的 ServiceConnection 的实例,第三个参数是个标志位,传入 BIND_AUTO_CREATE 表示活动和服务进行绑定后自动创建服务,会导致 服务的 onCreate 函数执行。
任何一个服务在整个应用程序都是通用的,一个服务可以绑定多个活动。
10.5 服务的更多技巧
10.5.1 使用前台服务
后台的服务可能会在内存不足时被回收,但是前台服务不会
public void onCreate(){
super.onCreate();
Log.d("Myservice", "execute onCreate");
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
Notification notification = new NotificationCompat.Builder(this).setContentText("this is a content text")
.setContentTitle("dadasda").setWhen(System.currentTimeMillis()).setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
.setContentIntent(pi).build();
startForeground(1, notification);
}
只修改了 onCreate 中的代码就可以了
10.5.2 使用 IntentService
服务中的代码都是在主线程中的,如果在其中运行一些耗时的逻辑,很容易出现 ANR (application not responding)
这个时候就需要多线程技术了,在服务类的里面开启一个子线程。
@Override
public int onStartCommand(Intent intent, int flags, int startId){
new Thread(new Runnable() {
@Override
public void run() {
// 处理具体的逻辑
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
但这种服务一旦启动,但会一直运行,必须调用 stopService()或者 stopSelf()才能停止