Android--多线程的切换

石喜
2023-12-01

引言

Android为我们提供了消息循环的机制,我们可以利用这个机制来实现线程间的通信。那么,我们就可以在非UI线程发送消息到UI线程,最终让UI线程来进行UI的操作。

对于运算量较大的操作和IO操作或者网络请求,我们需要新开线程来处理这些繁重的工作,以免阻塞UI线程。

Thread+Handler

HandlerThread一般用于批处理,在需要切换对象较多的情况下。
使用:

  1. 创建Handler类并重写handleMessage方法

    Handler mHandler = new Handler(){
        @Override
        public void handeMessage(@NonNull Message msg){
            super.handlerMessage(msg);
        }
    }
    
  2. 将对UI操作的代码放进上面的HandelMessage里面,然后在子线程加入一句代码

    mHandler.sendEmptyMessage(1);//不带参数
    
  3. 此时子线程如果得到的数据要传回主线程并在UI上显示,就需要构造一个Message对象,将数据通过Message传给UI线程

    Message message = new Message();
    message.what = 1;
    Bundle bundle = new Bundle();
    bundle.putString("key","子线程得到的数据");
    message.setData(bundle);
    mHandler.senMessage(message);//带参数
    
  4. 然后就是在主线程接受子线程发过来的数据,并写到UI里面

    String s = msg.getData().getString("key");//子线程传过来的数据,通过key值获取
    textView.setText(s);//更新UI
    

原理:

首先是子线程发送消息给主线程,主线程有对应的回调方法去收到这个消息通知,并更新。

  • 创建的Handler在主线程,所以主线程和子线程可以拿到它的引用,并对它进行操作。
  • 它们之间的消息传递通过一个叫Message的对象,子线程通过sendMessage()这个方法,将这个对象放到一个叫MessageQueue的队列里面每次发送一个Message就放到这个队列的队尾。
  • 主线程有一个Looper来对这个队列进行操作,里面有一个loop()方法,通过循环操作将队列里面的Message对象以先进先出的顺序取出来
  • 取出来以后调用Handler的dispatchMessage()方法,这个方法里面调用了handleMessage方法,也就是回调handleMessage方法

AsyncTask

AsyncTask一般用于单个任务的线程切换。
核心方法:

  • execute(Params)(在主线程手动调用,执行异步任务)

  • onPreExecute() (在execute方法前自动调用,用于界面的初始化,如显示进度条等)

  • doInBackground(Params)(在onPreExecute方法之后自动调用,接受参数,执行耗时操作,返回一个执行结果)

  • onProgressUpdate(Progress)(在主线程调用publishProgress时自动调用显示线程任务执行的进度)

  • onPostExecute(Result)(任务结束时自动调用,接收结果并显示到UI组件)

  • onCancelled()(将异步任务设置为取消状态,在任务被取消时自动调用,此时onPostExecute()不会被调用)

创建子类继承AsyncTask抽象类,并重写上面的几个方法…

再创建这个类的对象实例,在主线程使用execute方法调用。

  1. AsyncTask不与任何组件绑定生命周期,另外最好在activity里面的onDistory()里面调用cancel()
  2. AsyncTask被声明为Activity的非静态内部类,当Activity需销毁时,会因AsyncTask保留对Activity的引用 而导致Activity无法被回收,最终引起内存泄露

**原理:**AsyncTask是对Handler和线程池(一个任务队列线程池,一个执行线程池)的封装,Handler用于给主线程发送一个通知去更新UI,线程池用于避免不必要的创建和销毁线程,在线程池内通过维护 固定数量的线程,使他们轮番上阵,避免不必要的开销。

它有几个局限性

  1. AsyncTask对象必须在主线程创建
  2. AsyncTask对象的execute方法必须在主线程调用
  3. 一个AsyncTask对象只能调用一次execute方法

AsyncTask的成员mWorker(派生子WorkRunnable类的对象,实现了Callable接口)包含了AsyncTask最终要执行的任务(mWorker的call方法)和mFuture(FutureTask的直接子类的对象,里面传入mWorker作为参数)。前者覆写call()后者覆写done()。

FutureTask类实现了FutureRunnable接口,通过这个接口可以方便的取消后台任务以及获取后台任务的执行结果

  • 执行任务前,通过 任务队列 线程池类(SerialExecutor)将任务按顺序放入到队列中;

通过同步锁 修饰execute()从而保证AsyncTask中的任务是串行执行的

  • 之后的线程任务执行是 通过任务线程池类(THREAD_POOL_EXECUTOR) 进行的。
  • 任务线程池类(THREAD_POOL_EXECUTOR)实际上是1个已配置好的可执行并行任务的线程池
  • 调用THREAD_POOL_EXECUTOR.execute()实际上是调用线程池的execute()去执行具体耗时任务
  • 而该耗时任务则是步骤2中初始化 WorkerRunnable实例对象时复写的call()内容
  • call()方法里,先调用 我们复写的doInBackground(mParams)执行耗时操作
  • 再调用postResult(result), 通过 InternalHandler 类 将任务消息传递到主线程;根据消息标识(MESSAGE_POST_RESULT)判断,最终通过finish()调用我们复写的onPostExecute(result),从而实现UI更新操作

InterService

  1. 自定义类继承InterService,重写onHandleIntent方法中写下载逻辑

  2. Activity中使用startService或bindService来启动职务

RxJava

这个也可以实现线程的切换…

什么是RxJava

它的自我介绍:一个在 Java VM 上使用可观测的序列来组成异步的、基于事件的程序的库

其实总结就是RxJava 是一个 基于事件流、实现异步操作的库

类似于 Android中的 AsyncTaskHandler作用

https://blog.csdn.net/qq_46526828/article/details/107456378

原理介绍

RxJava的异步实现,是通过一种扩展的观察者模式来实现的

观察者模式:定义对象之间的一种一对多的依赖关系,使得每当一个对象状态发生改变时其相关依赖对象都得到通知。(设计模式-刘伟)

举个例子:设计模式里面的例子,在一个红绿灯的问题里面,驾驶员是观察者,红绿灯是被观察者,在程序中就采用注册或者订阅的方式告诉被观察者:我需要你的某某状态,你要在它变化的时候通知我。

对应于Android里面就是点击监听器,对设置 OnClickListener 来说, View 是被观察者, OnClickListener 是观察者,二者通过 setOnClickListener() 方法达成订阅关系,概念转化:Button -> 被观察者、OnClickListener -> 观察者、setOnClickListener() -> 订阅,onClick() -> 事件)

所以RxJava有四个基本概念:

角色作用类比
观察者(Observer)接收事件,并给出响应动作OnClickListener
被观察者(Observable)产生事件button
订阅(Subscribe)连接 被观察者 & 观察者setOnClickListener
事件(Event)被观察者 & 观察者 沟通的载体onClick

事件回调除了onNext和onError外还有onCompleted(onCompleted()onError() 二者互斥)

基本实现

实现原理

  1. 创建Observer

    接口的实现方式:

    Observer observer = new Observer() {

    ​ //重写三个方法

    }

    抽象类实现方式:

    Subscriber subscriber = new Subscriber() {

    ​ //重写三个方法

    }

    Subscriber比Observer多了onStart和unsubscribe,前者用于在 subscribe 刚开始,而事件还未发送之前被调用,可以用于做一些准备工作,例如数据的清零或重置。后者用于取消订阅。在这个方法被调用后,Subscriber 将不再接收事件

  2. 创建Observeable

    使用create方法来创建一个Observable,并定义触发规则

    Observable observable = Observable.create(new Observable.OnSubscribe() {

    ​ @Override

    ​ public void call(Subscriber<? super String> subscriber) {

    ​ subscriber.onNext(“Hello”);

    ​ subscriber.onNext(“Hi”);

    ​ subscriber.onNext(“Aloha”);

    ​ subscriber.onCompleted();

    ​ }

    });

    Observable 被订阅的时候,OnSubscribecall() 方法会自动被调用,事件序列就会依照设定依次触发(对于上面的代码,就是观察者Subscriber 将会被调用三次 onNext() 和一次 onCompleted())。这样,由被观察者调用了观察者的回调方法,就实现了由被观察者向观察者的事件传递,基于create方法还提供

    • just(T…):将传入的参数依次发送出来

      Observable observable = Observable.just(“Hello”, “Hi”, “Aloha”);

    • **from(T[ ])/from(Iterable<? extends T>)*将传入的数组或 Iterable 拆分成具体对象后,依次发送出来

      String[] words = {“Hello”, “Hi”, “Aloha”};

      Observable observable = Observable.from(words);

  3. 创建Subscribe

    创建了 ObservableObserver 之后,再用 subscribe() 方法将它们联结起来,整条链子就可以工作了

    observable.subscribe(observer);

    //或者:

    Observable.subscribe(subscriber);

    public class MainActivity extends AppCompatActivity {
    
        TextView textView ;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            textView=findViewById(R.id.textview);
    
            Observer<String> observer = new Observer<String>() {
                @Override
                public void onNext(String s) {
                    Log.d("tag", "Item: " + s);
                }
    
                @Override
                public void onCompleted() {
                    Log.d("tag", "Completed!");
                }
    
                @Override
                public void onError(Throwable e) {
                    Log.d("tag", "Error!");
                }
            };
            Observable observable = Observable.create(new Observable.OnSubscribe<String>() {
                @Override
                public void call(Subscriber<? super String> subscriber) {
                    subscriber.onNext("Hello");
                    subscriber.onNext("Hi");
                    subscriber.onNext("Aloha");
                    subscriber.onCompleted();
                }
            });
            observable.subscribe(observer);
        }
    }
    

场景示例

  • 由 id 取得图片并显示
     public class MainActivity extends AppCompatActivity {

    TextView textView ;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView=findViewById(R.id.textview);

        final int drawableRes = getResources().getIdentifier("a", "drawable","com.c201801020243.myapplication" );//以int类型获取图片的id
        final ImageView imageView = findViewById(R.id.image);
        Observable.create(new Observable.OnSubscribe<Drawable>() {
            @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)//过时操作注解
            @Override
            public void call(Subscriber<? super Drawable> subscriber) {
                Drawable drawable = getTheme().getDrawable(drawableRes);
                subscriber.onNext(drawable);
                subscriber.onCompleted();
            }
        }).subscribe(new Observer<Drawable>() {
            @Override
            public void onNext(Drawable drawable) {
                imageView.setImageDrawable(drawable);
            }

            @Override
            public void onCompleted() {
            }

            @Override
            public void onError(Throwable e) {
                Toast.makeText(MainActivity.this, "Error!", Toast.LENGTH_SHORT).show();
            }
        });
    }
}

然而,这个示例并没有什么卵用

在 RxJava 的默认规则中,事件的发出和消费都是在同一个线程的。也就是说,如果只用上面的方法,实现出来的只是一个同步的观察者模式。观察者模式本身的目的就是『后台处理,前台回调』的异步机制,因此异步对于 RxJava 是至关重要的。而要实现异步,则需要用到 RxJava 的另一个概念: Scheduler 。

线程控制:Scheduler (一)

默认原则:在哪个线程调用 subscribe(),就在哪个线程生产事件;在哪个线程生产事件,就在哪个线程消费事件。如果需要切换线程,就需要用到 Scheduler (调度器)

RxJava内置了几个Scheduler:

  • Schedulers.immediate(): 直接在当前线程运行,相当于不指定线程。这是默认的Scheduler
  • Schedulers.newThread(): 总是启用新线程,并在新线程执行操作。
  • Schedulers.io(): I/O 操作(读写文件、读写数据库、网络信息交互等)所使用的Scheduler。行为模式和 newThread() 差不多,区别在于 io() 的内部实现是是用一个无数量上限的线程池,可以重用空闲的线程,因此多数情况下 io()newThread() 更有效率。不要把计算工作放在 io() 中,可以避免创建不必要的线程。
  • Schedulers.computation(): 计算所使用的 Scheduler。这个计算指的是 CPU 密集型计算,即不会被 I/O 等操作限制性能的操作,例如图形的计算。这个 Scheduler 使用的固定的线程池,大小为 CPU 核数。不要把 I/O 操作放在 computation() 中,否则 I/O 操作的等待时间会浪费 CPU。
  • 另外, Android 还有一个专用的 AndroidSchedulers.mainThread(),它指定的操作将在 Android 主线程运行

然后

public class MainActivity extends AppCompatActivity {

    TextView textView ;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.textview);

        Observable.just(1, 2, 3, 4)//基于create方法提供的一个方法,功能上相当于onNext
                .subscribeOn(Schedulers.io()) // 指定 subscribe() 发生在 IO 线程
                .observeOn(AndroidSchedulers.mainThread()) // 指定 Subscriber 的回调发生在主线程
                .subscribe(new Action1<Integer>() {
                    @Override
                    public void call(Integer number) {
                        Log.d("tag", "number:" + number);
                    }
                });
    }
}

适用于多数的 『后台线程取数据,主线程显示』的程序策略

应用于上面的示例(加载图片将会发生在 IO 线程,而设置图片则被设定在了主线程。这就意味着,即使加载图片耗费了几十甚至几百毫秒的时间,也不会造成丝毫界面的卡顿):

public class MainActivity extends AppCompatActivity {

    TextView textView ;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.textview);

        final int drawableRes = getResources().getIdentifier("a", "drawable","com.c201801020243.myapplication" );
        final ImageView imageView = findViewById(R.id.image);
        Observable.create(new Observable.OnSubscribe<Drawable>() {
            @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
            @Override
            public void call(Subscriber<? super Drawable> subscriber) {
                Drawable drawable = getTheme().getDrawable(drawableRes);
                subscriber.onNext(drawable);
                subscriber.onCompleted();
            }
        }).subscribeOn(Schedulers.io()) // 指定 subscribe() 发生在 IO 线程
                .observeOn(AndroidSchedulers.mainThread()) // 指定 Subscriber 的回调发生在主线程
                .subscribe(new Observer<Drawable>() {
                    @Override
                    public void onNext(Drawable drawable) {
                        imageView.setImageDrawable(drawable);
                    }

                    @Override
                    public void onCompleted() {
                    }

                    @Override
                    public void onError(Throwable e) {
                        Toast.makeText(MainActivity.this, "Error!", Toast.LENGTH_SHORT).show();
                    }
                });
    }
}

RxJava——变换

核心功能之一,所谓变换,就是将事件序列中的对象或整个序列进行加工处理,转换成不同的事件或事件序列

  • map(): 事件对象的直接变换,它是 RxJava 最常用的变换。

    一个数据结构『学生』,现在需要打印出一组学生的名字

public class MainActivity extends AppCompatActivity {

    TextView textView;
    private Object Bitmap;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.textview);

        Student student = new Student();
        List<Student> students = new ArrayList<>();
        List<String> course = new ArrayList<>();
        course.add("英语");
        course.add("数学");
        student.setName("张三");
        student.setCourse(course);
        students.add(student);
        Subscriber<String> subscriber = new Subscriber<String>() {
            @Override
            public void onCompleted() {

            }

            @Override
            public void onError(Throwable e) {

            }

            @Override
            public void onNext(String name) {
                Log.d("tag", name);
            }
        };
        Observable.from(students)
                .map(new Func1<Student, String>() {
                    @Override
                    public String call(Student student) {
                        return student.getName();
                    }
                })
                .subscribe(subscriber);
    }

}
  • map() 是一对一的转化,而我现在的要求是一对多的转化。那就要用 flatMap() 来将一个 Student 转化成多个 Course (每个学生只有一个名字,但却有多个课程)
public class MainActivity extends AppCompatActivity {

    TextView textView;
    private Object Bitmap;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.textview);

        Student student = new Student();
        List<Student> students = new ArrayList<>();
        Course corses1 =new Course();
        Course corses2 =new Course();
        List<Course> courses = new ArrayList<>();
        corses1.setId(0);
        corses1.setName("C语言");
        courses.add(corses1);
        corses2.setId(1);
        corses2.setName("数据结构");
        courses.add(corses2);
        student.setName("张三");
        student.setCourses(courses);
        students.add(student);
        Subscriber<Student> subscriber = new Subscriber<Student>() {
            @Override
            public void onCompleted() {

            }

            @Override
            public void onError(Throwable e) {

            }

            @Override
            public void onNext(Student student) {
                List<Course> courses = student.getCourses();
                for (int i = 0; i < courses.size(); i++) {
                    Course course = courses.get(i);
                    Log.d("tag", course.getName());
                }
            }
        };
        Observable.from(students)
                .subscribe(subscriber);
    }

}

flatMap() 的原理是这样的:

  1. 使用传入的事件对象创建一个 Observable 对象;
  2. 并不发送这个 Observable, 而是将它激活,于是它开始发送事件;
  3. 每一个创建出来的 Observable 发送的事件,都被汇入同一个 Observable ,而这个 Observable 负责将这些事件统一交给 Subscriber 的回调方法。

这三个步骤,把事件拆成了两级,通过一组新创建的 Observable 将初始的对象『铺平』之后通过统一路径分发了下去。而这个『铺平』就是 flatMap() 所谓的 flat。

线程控制:Scheduler (二)

RxJava可以实现多次线程切换

Observable.just(1, 2, 3, 4) // IO 线程,由 subscribeOn() 指定
    .subscribeOn(Schedulers.io())
    .observeOn(Schedulers.newThread())
    .map(mapOperator) // 新线程,由 observeOn() 指定
    .observeOn(Schedulers.io())
    .map(mapOperator2) // IO 线程,由 observeOn() 指定
    .observeOn(AndroidSchedulers.mainThread) 
    .subscribe(subscriber);  // Android 主线程,由 observeOn() 指定

observeOn() 指定的是它之后的操作所在的线程。因此如果有多次切换线程的需求,只要在每个想要切换线程的位置调用一次 observeOn() 即可

应用场景举例

添加依赖:

// Android 支持 Rxjava
// 此处一定要注意使用RxJava2的版本
    compile 'io.reactivex.rxjava2:rxjava:2.0.1'
    compile 'io.reactivex.rxjava2:rxandroid:2.0.1'

// Android 支持 Retrofit
    compile 'com.squareup.retrofit2:retrofit:2.1.0'

// 衔接 Retrofit & RxJava
// 此处一定要注意使用RxJava2的版本
    compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'

// 支持Gson解析
    compile 'com.squareup.retrofit2:converter-gson:2.1.0'

添加权限:

<uses-permission android:name="android.permission.INTERNET"/>

创建实体类Translation接受返回的数据

public class Translation {
    private int status;

    private content content;
    private static class content {
        private String from;
        private String to;
        private String vendor;
        private String out;
        private int errNo;
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public Translation.content getContent() {
        return content;
    }

    public void setContent(Translation.content content) {
        this.content = content;
    }

    //定义 输出返回数据 的方法
    public void show() {
        System.out.println( "Rxjava翻译结果:" + status);
        System.out.println("Rxjava翻译结果:" + content.from);
        System.out.println("Rxjava翻译结果:" + content.to);
        System.out.println("Rxjava翻译结果:" + content.vendor);
        System.out.println("Rxjava翻译结果:" + content.out);
        System.out.println("Rxjava翻译结果:" + content.errNo);
    }

    @Override
    public String toString() {
        return "Translation{" +
                "status=" + status +
                ", content=" + content +
                '}';
    }
}

创建接口GetRequest_Interface描述网络请求;

public interface GetRequest_Interface {
    @GET("ajax.php?a=fy&f=auto&t=auto&w=hi%20world")//hi%20world
    Observable<Translation> getCall();
    // 注解里传入 网络请求 的部分URL地址
    // Retrofit把网络请求的URL分成了两部分:一部分放在Retrofit对象里,另一部分放在网络请求接口里
    // 如果接口里的url是一个完整的网址,那么放在Retrofit对象里的URL可以忽略
    // 采用Observable<...>接口
    // getCall()是接受网络请求数据的方法
}

创建Retrofit对象:

Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://fy.iciba.com/") // 设置 网络请求 Url
                .addConverterFactory(GsonConverterFactory.create()) //设置使用Gson解析(记得加入依赖)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 支持RxJava
                .build();

创建网络接口请求的实例:

GetRequest_Interface request = retrofit.create(GetRequest_Interface.class);

采用Observable<…>形式 对 网络请求 进行封装

Observable<Translation> observable = request.getCall();

发送网络请求

observable.subscribeOn(Schedulers.io())               // 在IO线程进行网络请求
                .observeOn(AndroidSchedulers.mainThread())  // 回到主线程 处理请求结果
                .subscribe(new Observer<Translation>() {
                    @Override
                    public void onSubscribe(Disposable d) {
                        Log.d(TAG, "开始采用subscribe连接");
                    }

                    @Override
                    public void onNext(Translation result) {
                        // 步骤8:对返回的数据进行处理
                        textView = findViewById(R.id.textview);
                        textView.setText(result.toString());//对UI的操作
                        result.show() ;
                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.d(TAG, "请求失败");
                    }

                    @Override
                    public void onComplete() {
                        Log.d(TAG, "请求成功");
                    }
                });
 类似资料: