#面试题总结#

Activity的四种启动模式与特点

 - standard 默认的启动模式
 - singleTop 可以有多个实例,但是不允许多个Activity叠加,如果Activity在栈顶启用相同的实例。不会创建新的实例,而会调用其onNewIntent方法。适合那种接受通知启动的页面,比如新闻客户端之类的,可能会给你推送好几次 ,但是每次都是打开同一张页面调用onNewIntent。
 - singleTask 只有一个实例。在同一个应用程序中启动他的时候,若Activity不存在,则会在当前task创建一个新的实例,若存在,则会把task中在其之上的其它Activity destory掉并调用它的onNewIntent方法。适合作为程序入口点,例如浏览器的主界面。不管从多少个应用启动浏览器,只会启动主界面一次,其余情况都会走onNewIntent,并且会清空浏览器主界面上面的其他页面。而之前打开过的页面不再新建
 - singleInstance 只有一个实例,并且这个实例独立运行在一个task中,这个task只有这个实例,不允许有别的Activity存在。适合需要与程序分离开的页面。例如闹铃,将闹铃提醒与设置分离,使得闹铃提醒成为系统范围内的唯一实例

Service的生命周期,两种启动方法,有什么区别

 - 第一种启动方式: 采用start的方式启动service 生命周期为onCreate()→onStartCommand()→onDestory();  特点:一旦服务开启就和调用者没有什么关系了,不管调用者是挂了还是退出了,服务还在后台运行,并且调用者不能调用服务里面的方法;
 - 第二种启动方式: 采用bind方式开启服务,生命周期为 onCreate()→onBind()→onUnbind→onDestory()  特点:bind方式开启的服务,如果调用者退出或者挂掉,服务也会终止,同时还可以调用service里面的方法。

绑定者调用service里面的方法代码如下

public class MyService extends Service {
public MyService() {

}
@Override
public IBinder onBind(Intent intent) {
    //返回MyBind对象
    return new MyBinder();
}
private void methodInMyService() {
    Toast.makeText(getApplicationContext(), "服务里的方法执行了。。。",
    Toast.LENGTH_SHORT).show();
}
/**
* 该类用于在onBind方法执行后返回的对象,
* 该对象对外提供了该服务里的方法
*/
private class MyBinder extends Binder implements IMyBinder {
@Override
    public void invokeMethodInMyService() {
    methodInMyService();
        }
    }
}

在Activity中绑定并调用服务里的方法

public class MainActivity extends Activity {
private MyConn conn;
private Intent intent;
private IMyBinder myBinder;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
//开启服务按钮的点击事件
public void start(View view) {
intent = new Intent(this, MyService.class);
conn = new MyConn();
//绑定服务,
// 第一个参数是intent对象,表面开启的服务。
// 第二个参数是绑定服务的监听器
// 第三个参数一般为BIND_AUTO_CREATE常量,表示自动创建bind
bindService(intent, conn, BIND_AUTO_CREATE);
}
//调用服务方法按钮的点击事件
public void invoke(View view) {
    myBinder.invokeMethodInMyService();
}
private class MyConn implements ServiceConnection {

    @Override
    public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
    //iBinder为服务里面onBind()方法返回的对象,所以可以强转为IMyBinder类型
    myBinder = (IMyBinder) iBinder;
    }

    @Override
    public void onServiceDisconnected(ComponentName componentName) {
        }
    }
}

Intent的使用方法,可以传递哪些数据类型

 - Serializable   将 Java 对象序列化为二进制文件的 Java 序列化技术是 Java系列技术中一个较为重要的技术点,在大部分情况下,开发人员只需要了解被序列化的类需要实现 Serializable 接口,使用ObjectInputStream 和 ObjectOutputStream 进行对象的读写。
 - Charsequence  在JDK1.4中,引入了CharSequence接口,实现了这个接口的类有:CharBuffer、String、StringBuffer、StringBuilder这个四个类。
 - Parcelable  
 - Bundle  Bundle是将数据传递到另一个上下文中或保存或回复你自己状态的数据存储方式。它的数据不是持久化状态。

ContentProvider使用方法

因为在Android系统里面,数据库是私有的。一般情况下外部应用程序是没有权限读取其他应用程序的数据。如果你想公开你自己的数据,你有两个选择:你可以创建你自己的内容提供器(一个ContentProvider子类)或者你可以给已有的提供器添加数据-如果存在一个控制同样类型数据的内容提供器且你拥有写的权限。而外界根本看不到,也不用看到这个应用暴露的数据在应用当中是如何存储的,或者是用数据库存储还是用文件存储,还是通过网上获得,这些一切都不重要,重要的是外界可以通过这一套标准及统一的接口和程序里的数据打交道,可以读取程序的数据,也可以删除程序的数据,当然,中间也会涉及一些权限的问题。

线程池的使用,优点和风险
线程池的使用

优点:
    一般情况下,如果使用子线程去执行一些任务,那么使用 new Thread 的方式会很方便的创建一个线程,如果涉及到主线程和子线程的通信,我们将使用 Handler(一般需要刷新 UI 的适合用到)。如果我们创建大量的(特别是在短时间内,持续的创建生命周期较长的线程)野生线程,往往会出现如下两方面的问题:每个线程的创建与销毁(特别是创建)的资源开销是非常大的;大量的子线程会分享主线程的系统资源,从而会使主线程因资源受限而导致应用性能降低。各位开发一线的前辈们为了解决这个问题,引入了线程池(ThreadPool)的概念,也就是把这些野生的线程圈养起来,统一的管理他们。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

风险:虽然线程池是构建多线程应用程序的强大机制,但使用它并不是没有风险的。用线程池构建的应用程序容易遭受任何其它多线程应用程序容易遭受的所有并发风险,诸如同步错误和死锁,它还容易遭受特定于线程池的少数其它风险,诸如与池有关的死锁、资源不足和线程泄漏。

 1. 死锁
 2. 资源不足
    线程池的一个优点在于:相对于其它替代调度机制(有些我们已经讨论过)而言,它们通常执行得很好。但只有恰当地调整了线程池大小时才是这样的。线程消耗包括内存和其它系统资源在内的大量资源。除了 Thread 对象所需的内存之外,每个线程都需要两个可能很大的执行调用堆栈。除此以外,JVM 可能会为每个 Java 线程创建一个本机线程,这些本机线程将消耗额外的系统资源。最后,虽然线程之间切换的调度开销很小,但如果有很多线程,环境切换也可能严重地影响程序的性能。
    如果线程池太大,那么被那些线程消耗的资源可能严重地影响系统性能。在线程之间进行切换将会浪费时间,而且使用超出比您实际需要的线程可能会引起资源匮乏问题,因为池线程正在消耗一些资源,而这些资源可能会被其它任务更有效地利用。除了线程自身所使用的资源以外,服务请求时所做的工作可能需要其它资源,例如 JDBC 连接、套接字或文件。这些也都是有限资源,有太多的并发请求也可能引起失效,例如不能分配 JDBC 连接。
 3. 并发错误
     线程池和其它排队机制依靠使用 wait() 和 notify() 方法,这两个方法都难于使用。如果编码不正确,那么可能 丢失通知,导致线程保持空闲状态,尽管队列中有工作要处理。使用这些方法时,必须格外小心。而最好使用现有的、已经知道能工作的实现,例如 util.concurrent 包。
 4. 线程泄漏
     各种类型的线程池中一个严重的风险是线程泄漏,当从池中除去一个线程以执行一项任务,而在任务完成后该线程却没有返回池时,会发生这种情况。发生线程泄漏的一种情形出现在任务抛出一个 RuntimeException 或一个 Error 时。如果池类没有捕捉到它们,那么线程只会退出而线程池的大小将会永久减少一个。当这种情况发生的次数足够多时,线程池最终就为空,而且系统将停止,因为没有可用的线程来处理任务。
     有些任务可能会永远等待某些资源或来自用户的输入,而这些资源又不能保证变得可用,用户可能也已经回家了,诸如此类的任务会永久停止,而这些停止的任务也会引起和线程泄漏同样的问题。如果某个线程被这样一个任务永久地消耗着,那么它实际上就被从池除去了。对于这样的任务,应该要么只给予它们自己的线程,要么只让它们等待有限的时间。
 5. 请求过载
     仅仅是请求就压垮了服务器,这种情况是可能的。在这种情形下,我们可能不想将每个到来的请求都排队到我们的工作队列,因为排在队列中等待执行的任务可能会消耗太多的系统资源并引起资源缺乏。在这种情形下决定如何做取决于您自己;在某些情况下,您可以简单地抛弃请求,依靠更高级别的协议稍后重试请求,您也可以用一个指出服务器暂时很忙的响应来拒绝请求。

死锁和重入锁
1、一般的死锁是指多个线程的执行必须同时拥有多个资源,由于不同的线程需要的资源被不同的线程占用,最终导致僵持的状态

package com.cxt.thread;  

public class TestDeadLock extends Thread{  
    boolean b;  
    DeadLock lock;  
    public TestDeadLock(boolean b, DeadLock lock) {  
        super();  
        this.b = b;  
        this.lock = lock;  
    }  
    public static void main(String[] args) {  
        DeadLock lock = new DeadLock();  
        TestDeadLock t1 = new TestDeadLock(true, lock);  
        TestDeadLock t2 = new TestDeadLock(false, lock);  
        t1.start();  
        t2.start();  
    }  
    @Override  
    public void run() {  
        if(this.b){  
            lock.m1();  
        }  
        else  
            lock.m2();  
    }  

}  

class DeadLock {  
    Object o1 = new Object();  
    Object o2 = new Object();  

    void m1(){  
        synchronized(o1){  
            System.out.println("m1 Lock o1 first");  
            synchronized(o2){  
                System.out.println("m1 Lock o2 second");  
            }  
        }  
    }  
    void m2(){  
        synchronized(o2){  
            System.out.println("m2 Lock o2 first");  
            synchronized(o1){  
                System.out.println("m2 Lock o1 second");  
            }  
        }  
    }  
}  

如代码所示我们可知:线程t1,t2都需要对象o1,o2才能正常地完成功能,但是由于他们所持的对象与要获得的对象刚好相反,使得两条线程一直僵持, 最终导致死锁,解决方法:等其中一条线程完全执行完之后再执行另外一条线程。 推广到多条线程,按一定的顺序执行多条线程。 另外一种方法就是设置优先级,如果运行多条线程出现死锁,优先级低的回退,优先级高的先执行这样即可解决死锁问题。

2、嵌套管程锁死

//线程1获得A对象的锁。
//线程1获得对象B的锁(同时持有对象A的锁)。
//线程1决定等待另一个线程的信号再继续。
//线程1调用B.wait(),从而释放了B对象上的锁,但仍然持有对象A的锁。

//线程2需要同时持有对象A和对象B的锁,才能向线程1发信号。
//线程2无法获得对象A上的锁,因为对象A上的锁当前正被线程1持有。
//线程2一直被阻塞,等待线程1释放对象A上的锁。

//线程1一直阻塞,等待线程2的信号,因此,不会释放对象A上的锁,
    //而线程2需要对象A上的锁才能给线程1发信号……

package com.cxt.Lock;  

import com.cxt.thread.Synchronizer;  
import com.cxt.thread.TestLock;  

//lock implementation with nested monitor lockout problem  
/** 
 * 一个坑爹的嵌套管程锁死,区别于死锁 
 */  
public class Lock {  
    protected MonitorObject monitorObject = new MonitorObject();  
    protected boolean isLocked = false;  

    public static void main(String[] args) {  
        Lock l = new Lock();  
        l.isLocked = true;  

        MyRunnable r1 = new MyRunnable(l, 0);  
        MyRunnable r2 = new MyRunnable(l, 0);  
        Thread t1 = new Thread(r1);  
        Thread t2 = new Thread(r2);  
        t1.start();  
        t2.start();  
        /* 
         * 時而鎖住,時而釋放,因為另外兩條線程沒有有时捕捉不到isLocked = false 
         */  
//      for (int i = 0; i < 100; i++) {  
//          l.isLocked = false;  
//          try {  
//              Thread.sleep(10);  
//          } catch (InterruptedException e) {  
//              e.printStackTrace();  
//          }  
//      }  
        //  

    }  

    public void lock() throws InterruptedException {  
        // 当执行这个方法时,isLocked=true时,其他方法无论执行lock方法还是执行Unlock方法都会导致管程死锁  
        // 只有手动将isLocked 设置为false才能解决死锁,设置为false时必须让其他线程检测到,所以必须设置时间长一点  
        synchronized (this) {  
            while (isLocked) {  
                synchronized (this.monitorObject) {  
                    this.monitorObject.wait();  
                }  
            }  
            isLocked = true;  
        }  
    }  

    public void unlock() {  
        synchronized (this) {  
            this.isLocked = false;  
            synchronized (this.monitorObject) {  
                this.monitorObject.notify();  
            }  
        }  
    }  

    static class MyRunnable implements Runnable {  
        Lock l = null;  
        int i;  

        public MyRunnable(Lock l, int i) {  
            this.l = l;  
            this.i = i;  
        }  

        @Override  
        public void run() {  
            try {  
                if (i % 2 == 0) {  
                    this.l.lock();  
                } else {  
                    this.l.unlock();  
                }  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }  

    }  
} 

我们观察lock()方法,执行lock()时,当isLocked 为true时,问题就来了,执行monitorObject的方法块, 但是monitorObject变成了等待状态,但是这是外面的this锁还是被此线程持有的,如果有其他线程要执行lock() 或者unLock(),此时都会产生无限等待的状态,此线程也因此永远处于无限带等待其他线程来唤醒monitorObject的状态, 最终就一直僵持着。 解决方法手动将isLocked设为false. 这一种较坑,编代码时别没事找事做。

3、重入锁死

package com.cxt.Lock;  

public class Lock2{  
    private boolean isLocked = false;  

    public static void main(String[] args) {  
        Lock2 lock = new Lock2();  

        MyRunnable r1 = new MyRunnable(lock, true);  
        MyRunnable r2 = new MyRunnable(lock, false);  
        Thread t1 = new Thread(r1);  
        Thread t2 = new Thread(r2);  
        t1.start();  

//      t2.start();  
    }  
    public synchronized void lock()  
        throws InterruptedException{  
        while(isLocked){  
            wait();  
        }  
        isLocked = true;  
    }  

    public synchronized void unlock(){  
        isLocked = false;  
        notify();  
    }  

    static class MyRunnable implements Runnable{  
        Lock2 l = null;  
        boolean flag = false;  
        public MyRunnable(Lock2 l, boolean flag) {  
            this.l = l;  
            this.flag = flag;  
        }  
        @Override  
        public void run() {  
            if(flag == true)  
                try {  
//                  如果连续执行两次lock(),就会产生系统无限等待的状态  
//                  解决方法就是在两次中间执行一次unLock()方法  
                    l.lock();  
                    System.out.println("Lock!");  
//                  l.unlock();  
//                  System.out.println("Unlock!");  
                    l.lock();  
                    System.out.println("Lock!");  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            else   
                l.unlock();  
        }  

    }  
}  

当连续执行两次lock()时会出现: 第一次,isLocked为false,执行完把isLocked设为true. 第二次,isLocked为true,此时就会处于无限等待的状态。 解决方法,两个lock()中间执行一次unLock方法,或者由另外一条线程来执行一次unLock()方法。 【重入锁】

package com.text;

public class Lock2{  
    private boolean isLocked = false;  

    public static void main(String[] args) {  
        Lock2 lock = new Lock2();  

        MyRunnable r1 = new MyRunnable(lock, true);  
        MyRunnable r2 = new MyRunnable(lock, false);  
        Thread t1 = new Thread(r1);  
        Thread t2 = new Thread(r2);  
        t1.start();  

//      t2.start();  
    }  
    public synchronized void lock()  
        throws InterruptedException{  
        while(isLocked){
            System.out.println("synchronized wait()!");  
            unlock();
            wait();  
        }  
        isLocked = true;  
        System.out.println("synchronized lock!");  
    }  

    public synchronized void unlock(){  
        isLocked = false;  
        notify();  
        System.out.println("synchronized unlock!");  
    }  

    static class MyRunnable implements Runnable{  
        Lock2 l = null;  
        boolean flag = false;  
        public MyRunnable(Lock2 l, boolean flag) {  
            this.l = l;  
            this.flag = flag;  
        }  
        @Override  
        public void run() {  
            if(flag == true)  
                try {  
//                  如果连续执行两次lock(),就会产生系统无限等待的状态  
//                  解决方法就是在两次中间执行一次unLock()方法  
                    l.lock();  
                    System.out.println("Lock!");  
                    l.lock(); 
//                  l.unlock(); //注释了
                    System.out.println("Lock!");  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            else   
                l.unlock();  
        }  

    }  
}  

输出:
synchronized lock!
Lock!
synchronized wait()!
synchronized unlock!
在一个synchronized方法/块的内部调用本类的其他synchronized方法/块时,是永远可以获得锁的。

package com.text;

public class Lock2{  
    private boolean isLocked = false;  

    public static void main(String[] args) {  
        Lock2 lock = new Lock2();  

        MyRunnable r1 = new MyRunnable(lock, true);  
        MyRunnable r2 = new MyRunnable(lock, false);  
        Thread t1 = new Thread(r1);  
        Thread t2 = new Thread(r2);  
        t1.start();  

//      t2.start();  
    }  
    public synchronized void lock()  
        throws InterruptedException{  
        while(isLocked){
            System.out.println("synchronized wait()!");  
            wait();  
        }  
        isLocked = true;  
        System.out.println("synchronized lock!");  
    }  

    public synchronized void unlock(){  
        isLocked = false;  
        notify();  
        System.out.println("synchronized unlock!");  
    }  

    static class MyRunnable implements Runnable{  
        Lock2 l = null;  
        boolean flag = false;  
        public MyRunnable(Lock2 l, boolean flag) {  
            this.l = l;  
            this.flag = flag;  
        }  
        @Override  
        public void run() {  
            if(flag == true)  
                try {  
//                  如果连续执行两次lock(),就会产生系统无限等待的状态  
//                  解决方法就是在两次中间执行一次unLock()方法  
                    l.lock();  
                    System.out.println("Lock!");  
                    l.lock(); 
                    l.unlock(); //取消注释了
                    System.out.println("Lock!");  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            else   
                l.unlock();  
        }  

    }  
}  

输出:
synchronized lock!
Lock!
synchronized wait()!
因为连续两个lock方法,导致在第二次时形成死锁,第三次的unlock由于不是在synchronized方法/块内调用的,所以无法获取锁。

MVC
1、MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写
#面试题总结#_第1张图片
2、MVC的工作原理: 当用户出发事件的时候,view层会发送指令到controller层,接着controller去通知model层更新数据,model层更新完数据以后直接显示在view层上;

3、大家都知道一个Android工程有什么对吧,有Java的class文件,有res文件夹,里面是各种资源,还有类似manifest文件等等。对于原生的Android项目来说,layout.xml里面的xml文件就对应于MVC的view层,里面都是一些view的布局代码,而各种Java bean,还有一些类似repository类就对应于model层,至于controller层嘛,当然就是各种activity咯。大家可以试着套用我上面说的MVC的工作原理是理解。比如你的界面有一个按钮,按下这个按钮去网络上下载一个文件,这个按钮是view层的,是使用xml来写的,而那些和网络连接相关的代码写在其他类里,比如你可以写一个专门的networkHelper类,这个就是model层,那怎么连接这两层呢?是通过button.setOnClickListener()这个函数,这个函数就写在了activity中,对应于controller层。是不是很清晰。

  1. M层:适合做一些业务逻辑处理,比如数据库存取操作,网络操作,复杂的算法,耗时的任务等都在model层处理。
  2. V层:应用层中处理数据显示的部分,XML布局可以视为V层,显示Model层的数据结果。
  3. .C层:在Android中,Activity处理用户交互问题,因此可以认为Activity是控制器,Activity读取V视图层的数据(eg.读取当前EditText控件的数据),控制用户输入(eg.EditText控件数据的输入),并向Model发送数据请求(eg.发起网络请求等)。

4、MVC的缺陷:
大家想过这样会有什么问题吗?显然是有的,不然为什么会有MVP和MVVM的诞生呢,是吧。问题就在于xml作为view层,控制能力实在太弱了,你想去动态的改变一个页面的背景,或者动态的隐藏/显示一个按钮,这些都没办法在xml中做,只能把代码写在activity中,造成了activity既是controller层,又是view层的这样一个窘境。大家回想一下自己写的代码,如果是一个逻辑很复杂的页面,activity或者fragment是不是动辄上千行呢?这样不仅写起来麻烦,维护起来更是噩梦。(当然看过Android源码的同学其实会发现上千行的代码不算啥,一个RecyclerView.class的代码都快上万行了呢。。)

MVC还有一个重要的缺陷,大家看上面那幅图,view层和model层是相互可知的,这意味着两层之间存在耦合,耦合对于一个大型程序来说是非常致命的,因为这表示开发,测试,维护都需要花大量的精力。

正因为MVC有这样那样的缺点,所以才演化出了MVP和MVVM这两种框架。

MVP
MVP代码实践
MVP 即是(model-处理业务逻辑(主要是数据读写,或者与后台通信(其实也是读写数据)),view-处理ui控件,presenter-主导器,操作model和view),MVP作为MVC的演化,解决了MVC不少的缺点,对于Android来说,MVP的model层相对于MVC是一样的,而activity和fragment不再是controller层,而是纯粹的view层,所有关于用户事件的转发全部交由presenter层处理。下面还是让我们看图
#面试题总结#_第2张图片
从图中就可以看出,最明显的差别就是view层和model层不再相互可知,完全的解耦,取而代之的presenter层充当了桥梁的作用,用于操作view层发出的事件传递到presenter层中,presenter层去操作model层,并且将数据返回给view层,整个过程中view层和model层完全没有联系。看到这里大家可能会问,虽然view层和model层解耦了,但是view层和presenter层不是耦合在一起了吗?其实不是的,对于view层和presenter层的通信,我们是可以通过接口实现的,具体的意思就是说我们的activity,fragment可以去实现实现定义好的接口,而在对应的presenter中通过接口调用方法。不仅如此,我们还可以编写测试用的View,模拟用户的各种操作,从而实现对Presenter的测试。这就解决了MVC模式中测试,维护难的问题。

当然,其实最好的方式是使用fragment作为view层,而activity则是用于创建view层(fragment)和presenter层(presenter)的一个控制器。

MVVM
MVVM最早是由微软提出的
#面试题总结#_第3张图片
MVVM是Model-View-ViewModel的简写. 它是有三个部分组成:Model、View、ViewModel。Model:数据模型层。包含业务逻辑和校验逻辑,View:屏幕上显示的UI界面(layout、views),ViewModel:View和Model之间的链接桥梁,处理视图逻辑。

当View有用户输入后,ViewModel通知Model更新数据,同理Model数据更新后,ViewModel通知View更新。

这里可以可以看到 ViewModel 承担了 Presenter 中与 view和 Model 交互的职责,与 MVP模式不同的是,VM与 V 之间是通过 Datebinding 实现的,而 P是持有 View 的对象,直接调用 View 中的一些接口方法来实现。ViewModel可以理解成是View的数据模型和Presenter的合体。它通过双向绑定(松耦合)解决了MVP中Presenter与View联系比较紧密的问题,然而Android中的Datebinding只能单向绑定,只能从ViewModel绑定到View中

IntentService的用法

1、IntentService简介

IntentService是Service的子类,比普通的Service增加了额外的功能。先看Service本身存在两个问题:

Service不会专门启动一条单独的进程,Service与它所在应用位于同一个进程中;

Service也不是专门一条新线程,因此不应该在Service中直接处理耗时的任务;

2、IntentService特征

会创建独立的worker线程来处理所有的Intent请求;

会创建独立的worker线程来处理onHandleIntent()方法实现的代码,无需处理多线程问题;

所有请求处理完成后,IntentService会自动停止,无需调用stopSelf()方法停止Service;

为Service的onBind()提供默认实现,返回null;

为Service的onStartCommand提供默认实现,将请求Intent添加到队列中;

IntentService不会阻塞UI线程,而普通Serveice会导致ANR异常
Intentservice若未执行完成上一次的任务,将不会新开一个线程,是等待之前的任务完成后,再执行新的任务,等任务完成后再次调用stopSelf

Handler的实现原理
Handler的实现原理参考

你可能感兴趣的:(android)