补:《Android面试题思考与解答》11月刊(一)

简介: 又来更新啦,Android面试题《思考与解答》11月刊奉上。

又来更新啦,Android面试题《思考与解答》11月刊奉上。


为了新朋友,老朋友方便查看,我把面试题《思考与解答》以往期刊整理成PDF了。大家到公众号主页回复消息"111"即可获得下载链接


说说View/ViewGroup的绘制流程


View的绘制流程是从ViewRootperformTraversals开始的,它经过measure,layout,draw三个过程最终将View绘制出来。performTraversals会依次调用performMeasure,performLayout,performDraw三个方法,他们会依次调用measure,layout,draw方法,然后又调用了onMeasure,onLayout,dispatchDraw


  • measure :


对于自定义的单一view的测量,只需要根据父 view 传递的MeasureSpec进行计算大小。


对于ViewGroup的测量,一般要重写onMeasure方法,在onMeasure方法中,父容器会对所有的子View进行Measure,子元素又会作为父容器,重复对它自己的子元素进行Measure,这样Measure过程就从DecorView一级一级传递下去了,也就是要遍历所有子View的的尺寸,最终得出出总的viewGroup的尺寸。Layout和Draw方法也是如此。


  • layout :根据 measure 子 View 所得到的布局大小和布局参数,将子View放在合适的位置上。


对于自定义的单一view,计算本身的位置即可。


对于ViewGroup来说,需要重写onlayout方法。除了计算自己View的位置,还需要确定每一个子View在父容器的位置以及子view的宽高(getMeasuredWidth和getMeasuredHeight),最后调用所有子view的layout方法来设定子view的位置。


  • draw :把 View 对象绘制到屏幕上。


draw()会依次调用四个方法:


1)drawBackground(),根据在 layout 过程中获取的 View 的位置参数,来设置背景的边界。2)onDraw(),绘制View本身的内容,一般自定义单一view会重写这个方法,实现一些绘制逻辑。3) dispatchDraw(),绘制子View 4)onDrawScrollBars(canvas),绘制装饰,如 滚动指示器、滚动条、和前景


说说你理解的MeasureSpec


MeasureSpec是由父View的MeasureSpec和子View的LayoutParams通过简单的计算得出一个针对子View的测量要求,这个测量要求就是MeasureSpec。


  • 首先,MeasureSpec是一个大小跟模式的组合值,MeasureSpec中的值是一个整型(32位)将size和mode打包成一个Int型,其中高两位是mode,后面30位存的是size


// 获取测量模式
    int specMode = MeasureSpec.getMode(measureSpec)
    // 获取测量大小
    int specSize = MeasureSpec.getSize(measureSpec)
    // 通过Mode 和 Size 生成新的SpecMode
    int measureSpec=MeasureSpec.makeMeasureSpec(size, mode);


  • 其次,每个子View的MeasureSpec值根据子View的布局参数和父容器的MeasureSpec值计算得来的,所以就有一个父布局测量模式,子视图布局参数,以及子view本身的MeasureSpec关系图:


30.jpg


其实也就是getChildMeasureSpec方法的源码逻辑,会根据子View的布局参数和父容器的MeasureSpec计算出来单个子view的MeasureSpec。


  • 最后是实际应用时:


对于自定义的单一view,一般可以不处理onMeasure方法,如果要对宽高进行自定义,就重写onMeasure方法,并将算好的宽高通过setMeasuredDimension方法传进去。对于自定义的ViewGroup,一般需要重写onMeasure方法,并且调用measureChildren方法遍历所有子View并进行测量(measureChild方法是测量具体某一个view的宽高),然后可以通过getMeasuredWidth/getMeasuredHeight获取宽高,最后通过setMeasuredDimension方法存储本身的总宽高。


Scroller是怎么实现View的弹性滑动?


  • MotionEvent.ACTION_UP事件触发时调用startScroll()方法,该方法并没有进行实际的滑动操作,而是记录滑动相关量(滑动距离、滑动时间)
  • 接着调用invalidate/postInvalidate()方法,请求View重绘,导致View.draw方法被执行
  • 当View重绘后会在draw方法中调用computeScroll方法,而computeScroll又会去向Scroller获取当前的scrollX和scrollY;然后通过scrollTo方法实现滑动;接着又调用postInvalidate方法来进行第二次重绘,和之前流程一样,如此反复导致View不断进行小幅度的滑动,而多次的小幅度滑动就组成了弹性滑动,直到整个滑动过成结束。


mScroller = new Scroller(context);
@Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_UP:
                // 滚动开始时X的坐标,滚动开始时Y的坐标,横向滚动的距离,纵向滚动的距离
                mScroller.startScroll(getScrollX(), 0, dx, 0);
                invalidate();
                break;
        }
        return super.onTouchEvent(event);
    }
@Override
    public void computeScroll() {
        // 重写computeScroll()方法,并在其内部完成平滑滚动的逻辑
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            invalidate();
        }
    }


OKHttp有哪些拦截器,分别起什么作用


OKHTTP的拦截器是把所有的拦截器放到一个list里,然后每次依次执行拦截器,并且在每个拦截器分成三部分:


  • 预处理拦截器内容
  • 通过proceed方法把请求交给下一个拦截器
  • 下一个拦截器处理完成并返回,后续处理工作。


这样依次下去就形成了一个链式调用,看看源码,具体有哪些拦截器:


Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(retryAndFollowUpInterceptor);
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));
    Interceptor.Chain chain = new RealInterceptorChain(
        interceptors, null, null, null, 0, originalRequest);
    return chain.proceed(originalRequest);
  }


根据源码可知,一共七个拦截器:


  • addInterceptor(Interceptor),这是由开发者设置的,会按照开发者的要求,在所有的拦截器处理之前进行最早的拦截处理,比如一些公共参数,Header都可以在这里添加。
  • RetryAndFollowUpInterceptor,这里会对连接做一些初始化工作,以及请求失败的充实工作,重定向的后续请求工作。跟他的名字一样,就是做重试工作还有一些连接跟踪工作。
  • BridgeInterceptor,这里会为用户构建一个能够进行网络访问的请求,同时后续工作将网络请求回来的响应Response转化为用户可用的Response,比如添加文件类型,content-length计算添加,gzip解包。
  • CacheInterceptor,这里主要是处理cache相关处理,会根据OkHttpClient对象的配置以及缓存策略对请求值进行缓存,而且如果本地有了可⽤的Cache,就可以在没有网络交互的情况下就返回缓存结果。
  • ConnectInterceptor,这里主要就是负责建立连接了,会建立TCP连接或者TLS连接,以及负责编码解码的HttpCodec
  • networkInterceptors,这里也是开发者自己设置的,所以本质上和第一个拦截器差不多,但是由于位置不同,所以用处也不同。这个位置添加的拦截器可以看到请求和响应的数据了,所以可以做一些网络调试。
  • CallServerInterceptor,这里就是进行网络数据的请求和响应了,也就是实际的网络I/O操作,通过socket读写数据。


OkHttp怎么实现连接池


  • 为什么需要连接池?


频繁的进行建立Sokcet连接和断开Socket是非常消耗网络资源和浪费时间的,所以HTTP中的keepalive连接对于降低延迟和提升速度有非常重要的作用。keepalive机制是什么呢?也就是可以在一次TCP连接中可以持续发送多份数据而不会断开连接。所以连接的多次使用,也就是复用就变得格外重要了,而复用连接就需要对连接进行管理,于是就有了连接池的概念。


OkHttp中使用ConectionPool实现连接池,默认支持5个并发KeepAlive,默认链路生命为5分钟。


  • 怎么实现的?


1)首先,ConectionPool中维护了一个双端队列Deque,也就是两端都可以进出的队列,用来存储连接。2)然后在ConnectInterceptor,也就是负责建立连接的拦截器中,首先会找可用连接,也就是从连接池中去获取连接,具体的就是会调用到ConectionPool的get方法。


RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
    assert (Thread.holdsLock(this));
    for (RealConnection connection : connections) {
      if (connection.isEligible(address, route)) {
        streamAllocation.acquire(connection, true);
        return connection;
      }
    }
    return null;
  }


也就是遍历了双端队列,如果连接有效,就会调用acquire方法计数并返回这个连接。


3)如果没找到可用连接,就会创建新连接,并会把这个建立的连接加入到双端队列中,同时开始运行线程池中的线程,其实就是调用了ConectionPool的put方法。


public final class ConnectionPool {
    void put(RealConnection connection) {
        if (!cleanupRunning) {
         //没有连接的时候调用
            cleanupRunning = true;
            executor.execute(cleanupRunnable);
        }
        connections.add(connection);
    }
}


3)其实这个线程池中只有一个线程,是用来清理连接的,也就是上述的cleanupRunnable


private final Runnable cleanupRunnable = new Runnable() {
        @Override
        public void run() {
            while (true) {
                //执行清理,并返回下次需要清理的时间。
                long waitNanos = cleanup(System.nanoTime());
                if (waitNanos == -1) return;
                if (waitNanos > 0) {
                    long waitMillis = waitNanos / 1000000L;
                    waitNanos -= (waitMillis * 1000000L);
                    synchronized (ConnectionPool.this) {
                        //在timeout时间内释放锁
                        try {
                            ConnectionPool.this.wait(waitMillis, (int) waitNanos);
                        } catch (InterruptedException ignored) {
                        }
                    }
                }
            }
        }
    };


这个runnable会不停的调用cleanup方法清理线程池,并返回下一次清理的时间间隔,然后进入wait等待。


怎么清理的呢?看看源码:


long cleanup(long now) {
    synchronized (this) {
      //遍历连接
      for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
        RealConnection connection = i.next();
        //检查连接是否是空闲状态,
        //不是,则inUseConnectionCount + 1
        //是 ,则idleConnectionCount + 1
        if (pruneAndGetAllocationCount(connection, now) > 0) {
          inUseConnectionCount++;
          continue;
        }
        idleConnectionCount++;
        // If the connection is ready to be evicted, we're done.
        long idleDurationNs = now - connection.idleAtNanos;
        if (idleDurationNs > longestIdleDurationNs) {
          longestIdleDurationNs = idleDurationNs;
          longestIdleConnection = connection;
        }
      }
      //如果超过keepAliveDurationNs或maxIdleConnections,
      //从双端队列connections中移除
      if (longestIdleDurationNs >= this.keepAliveDurationNs
          || idleConnectionCount > this.maxIdleConnections) {      
        connections.remove(longestIdleConnection);
      } else if (idleConnectionCount > 0) {      //如果空闲连接次数>0,返回将要到期的时间
        // A connection will be ready to evict soon.
        return keepAliveDurationNs - longestIdleDurationNs;
      } else if (inUseConnectionCount > 0) {
        // 连接依然在使用中,返回保持连接的周期5分钟
        return keepAliveDurationNs;
      } else {
        // No connections, idle or in use.
        cleanupRunning = false;
        return -1;
      }
    }
    closeQuietly(longestIdleConnection.socket());
    // Cleanup again immediately.
    return 0;
  }


也就是当如果空闲连接maxIdleConnections超过5个或者keepalive时间大于5分钟,则将该连接清理掉。


4)这里有个问题,怎样属于空闲连接?


其实就是有关刚才说到的一个方法acquire计数方法:


public void acquire(RealConnection connection, boolean reportedAcquired) {
    assert (Thread.holdsLock(connectionPool));
    if (this.connection != null) throw new IllegalStateException();
    this.connection = connection;
    this.reportedAcquired = reportedAcquired;
    connection.allocations.add(new StreamAllocationReference(this, callStackTrace));
  }


RealConnection中,有一个StreamAllocation虚引用列表allocations。每创建一个连接,就会把连接对应的StreamAllocationReference添加进该列表中,如果连接关闭以后就将该对象移除。


5)连接池的工作就这么多,并不负责,主要就是管理双端队列Deque<RealConnection>,可以用的连接就直接用,然后定期清理连接,同时通过对StreamAllocation的引用计数实现自动回收。


OkHttp里面用到了什么设计模式


  • 责任链模式


这个不要太明显,可以说是okhttp的精髓所在了,主要体现就是拦截器的使用,具体代码可以看看上述的拦截器介绍。


  • 建造者模式


在Okhttp中,建造者模式也是用的挺多的,主要用处是将对象的创建与表示相分离,用Builder组装各项配置。比如Request:


public class Request {
  public static class Builder {
    @Nullable HttpUrl url;
    String method;
    Headers.Builder headers;
    @Nullable RequestBody body;
    public Request build() {
      return new Request(this);
    }
  }
}


  • 工厂模式


工厂模式和建造者模式类似,区别就在于工厂模式侧重点在于对象的生成过程,而建造者模式主要是侧重对象的各个参数配置。例子有CacheInterceptor拦截器中又个CacheStrategy对象:


CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    public Factory(long nowMillis, Request request, Response cacheResponse) {
      this.nowMillis = nowMillis;
      this.request = request;
      this.cacheResponse = cacheResponse;
      if (cacheResponse != null) {
        this.sentRequestMillis = cacheResponse.sentRequestAtMillis();
        this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();
        Headers headers = cacheResponse.headers();
        for (int i = 0, size = headers.size(); i < size; i++) {
          String fieldName = headers.name(i);
          String value = headers.value(i);
          if ("Date".equalsIgnoreCase(fieldName)) {
            servedDate = HttpDate.parse(value);
            servedDateString = value;
          } else if ("Expires".equalsIgnoreCase(fieldName)) {
            expires = HttpDate.parse(value);
          } else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
            lastModified = HttpDate.parse(value);
            lastModifiedString = value;
          } else if ("ETag".equalsIgnoreCase(fieldName)) {
            etag = value;
          } else if ("Age".equalsIgnoreCase(fieldName)) {
            ageSeconds = HttpHeaders.parseSeconds(value, -1);
          }
        }
      }
    }


  • 观察者模式


之前我写过一篇文章,是关于Okhttp中websocket的使用,由于webSocket属于长连接,所以需要进行监听,这里是用到了观察者模式:


final WebSocketListener listener;
  @Override public void onReadMessage(String text) throws IOException {
    listener.onMessage(this, text);
  }


  • 单例模式


这个就不举例了,每个项目都会有


  • 另外有的博客还说到了策略模式,门面模式等,这些大家可以网上搜搜,毕竟每个人的想法看法都会不同,细心找找可能就会发现。


介绍一下你们之前做的项目的架构


这个问题大家就真实回答就好,重点是要说完后提出对自己项目架构的认同或不认同的观点,也就是要有自己的思考和想法。


MVP,MVVM,MVC 区别


MVC


  • 架构介绍


Model:数据模型,比如我们从数据库或者网络获取数据View:视图,也就是我们的xml布局文件Controller:控制器,也就是我们的Activity


  • 模型联系


View --> Controller,也就是反应View的一些用户事件(点击触摸事件)到Activity上。Controller --> Model, 也就是Activity去读写一些我们需要的数据。Controller --> View, 也就是Activity在获取数据之后,将更新内容反映到View上。


这样一个完整的项目架构就出来了,也是我们早期进行开发比较常用的项目架构。


  • 优缺点


这种缺点还是比较明显的,主要表现就是我们的Activity太重了,经常一写就是几百上千行了。造成这种问题的原因就是Controller层和View层的关系太过紧密,也就是Activity中有太多操作View的代码了。


但是!但是!其实Android这种并称不上传统的MVC结构,因为Activity又可以叫View层又可以叫Controller层,所以我觉得这种Android默认的开发结构,其实称不上什么MVC项目架构,因为他本身就是Android一开始默认的开发形式,所有东西都往Activity中丢,然后能封装的封装一下,根本分不出来这些层级。当然这是我个人看法,可以都来讨论下。


MVP


  • 架构介绍


之前不就是因为Activity中有操作view,又做Controller工作吗。所以其实MVP架构就是从原来的Activity层把viewController区分开,单独抽出来一层Presenter作为原来Controller的职位。然后最后演化成,将View层写成接口的形式,然后Activity去实现View接口,最后在Presenter类中去实现方法。


Model:数据模型,比如我们从数据库或者网络获取数据。View:视图,也就是我们的xml布局文件和Activity。Presenter:主持人,单独的类,只做调度工作。


  • 模型联系


View --> Presenter,反应View的一些用户事件到Presenter上。Presenter --> Model, Presenter去读写操作一些我们需要的数据。Controller --> View, Presenter在获取数据之后,将更新内容反馈给Activity,进行view更新。


  • 优缺点


这种的优点就是确实大大减少了Activity的负担,让Activity主要承担一个更新View的工作,然后把跟Model交互的工作转移给了Presenter,从而由Presenter方来控制和交互Model方以及View方。所以让项目更加明确简单,顺序性思维开发。


缺点也很明显:首先就是代码量大大增加了,每个页面或者说功能点,都要专门写一个Presenter类,并且由于是面向接口编程,需要增加大量接口,会有大量繁琐的回调。其次,由于Presenter里持有了Activity对象,所以可能会导致内存泄漏或者view空指针,这也是需要注意的地方。


MVVM


  • 架构介绍


MVVM的特点就是双向绑定,并且有Google官方加持,更新了Jetpack中很多架构组件,比如ViewModel,Livedata,DataBinding等等,所以这个是现在的主流框架和官方推崇的框架。


Model:数据模型,比如我们从数据库或者网络获取数据。View:视图,也就是我们的xml布局文件和Activity。ViewModel:关联层,将Model和View绑定,使他们之间可以相互绑定实时更新


  • 模型联系


View --> ViewModel -->View,双向绑定,数据改动可以反映到界面,界面的修改可以反映到数据。ViewModel --> Model, 操作一些我们需要的数据。


  • 优缺点


优点就是官方大力支持,所以也更新了很多相关库,让MVVM架构更强更好用,而且双向绑定的特点可以让我们省去很多View和Model的交互。也基本解决了上面两个架构的问题。


具体说说你理解的MVVM


1)先说说MVVM是怎么解决了其他两个架构所在的缺陷和问题:


  • 解决了各个层级之间耦合度太高的问题,也就是更好的完成了解耦。MVP层中,Presenter还是会持有View的引用,但是在MVVM中,View和Model进行双向绑定,从而使viewModel基本只需要处理业务逻辑,无需关系界面相关的元素了。
  • 解决了代码量太多,或者模式化代码太多的问题。由于双向绑定,所以UI相关的代码就少了很多,这也是代码量少的关键。而这其中起到比较关键的组件就是DataBinding,使所有的UI变动都交给了被观察的数据模型。
  • 解决了可能会有的内存泄漏问题。MVVM架构组件中有一个组件是LiveData,它具有生命周期感知能力,可以感知到Activity等的生命周期,所以就可以在其关联的生命周期遭到销毁后自行清理,就大大减少了内存泄漏问题。
  • 解决了因为Activity停止而导致的View空指针问题。在MVVM中使用了LiveData,那么在需要更新View的时候,如果观察者的生命周期处于非活跃状态(如返回栈中的 Activity),则它不会接收任何 LiveData 事件。也就是他会保证在界面可见的时候才会进行响应,这样就解决了空指针问题。
  • 解决了生命周期管理问题。这主要得益于Lifecycle组件,它使得一些控件可以对生命周期进行观察,就能随时随地进行生命周期事件。


2)再说说响应式编程


响应式编程,说白了就是我先构建好事物之间的关系,然后就可以不用管了。他们之间会因为这层关系而互相驱动。其实也就是我们常说的观察者模式,或者说订阅发布模式。


为什么说这个呢,因为MVVM的本质思想就是类似这种。不管是双向绑定,还是生命周期感知,其实都是一种观察者模式,使所有事物变得可观察,那么我们只需要把这种观察关系给稳定住,那么项目也就稳健了。


3)最后再说说MVVM为什么这么强大?


我个人觉得,MVVM强大不是因为这个架构本身,而是因为这种响应式编程的优势比较大,再加上Google官方的大力支持,出了这么多支持的组件,来维系MVVM架构,其实也是官方想进行项目架构的统一。


优秀的架构思想+官方支持=强大


ViewModel 是什么,说说你所理解的ViewModel?


如果看过我上一篇文章的小伙伴应该都有所了解,ViewModel是MVVM架构的一个层级,用来联系View和model之间的关系。而我们今天要说的就是官方出的一个框架——ViewModel


ViewModel 类旨在以注重生命周期的方式存储和管理界面相关的数据


官方是这么介绍的,这里面有两个信息:


  • 注重生命周期的方式。由于ViewModel的生命周期是作用于整个Activity的,所以就节省了一些关于状态维护的工作,最明显的就是对于屏幕旋转这种情况,以前对数据进行保存读取,而ViewModel则不需要,他可以自动保留数据。


其次,由于ViewModel在生命周期内会保持局部单例,所以可以更方便Activity的多个Fragment之间通信,因为他们能获取到同一个ViewModel实例,也就是数据状态可以共享了。


  • 存储和管理界面相关的数据。


ViewModel层的根本职责,就是负责维护界面上UI的状态,其实就是维护对应的数据,因为数据会最终体现到UI界面上。所以ViewModel层其实就是对界面相关的数据进行管理,存储等操作。


ViewModel 为什么被设计出来,解决了什么问题?


  • ViewModel组件被设计出来之前,MVVM又是怎么实现ViewModel这一层级的呢?


其实就是自己编写类,然后通过接口,内部依赖实现View和数据的双向绑定。所以Google出这个ViewModel组件,无非就是为了规范MVVM架构的实现,并尽量让ViewModel这一层级只触及到业务代码,不去关心VIew层级的引用等。然后配合其他的组件,包括livedata,databindingrang等让MVVM架构更加完善,规范,健硕。


  • 解决了什么问题呢?


其实上面已经说过一些了,比如:


1)不会因为屏幕旋转而销毁,减少了维护状态的工作 2)由于在作用域内单一实例的特性,使得多个fragment之间可以方便通信,并且维护同一个数据状态。3)完善了MVVM架构,使得解耦更加纯粹。


说说ViewModel原理。


  • 首先说说是怎么保存生命周期


ViewModel2.0之前呢,其实原理是在Activity上add一个HolderFragment,然后设置setRetainInstance(true)方法就能让这个Fragment在Activity重建时存活下来,也就保证了ViewModel的状态不会随Activity的状态所改变。


2.0之后,其实是用到了Activity的onRetainNonConfigurationInstance()getLastNonConfigurationInstance()这两个方法,相当于在横竖屏切的时候会保存ViewModel的实例,然后恢复,所以也就保证了ViewModel的数据。


  • 再说说怎么保证作用域内唯一实例


首先,ViewModel的实例是通过反射获取的,反射的时候带上application的上下文,这样就保证了不会持有Activity或者Fragment等View的引用。然后实例创建出来会保存到一个ViewModelStore容器里面,其实也就是一个集合类,这个ViewModelStore 类其实就是保存在界面上的那个实例,而我们的ViewModel就是里面的一个集合类的子元素。


所以我们每次获取的时候,首先看看这个集合里面有无我们的ViewModel,如果没有就去实例化,如果有就直接拿到实例使用,这样就保证了唯一实例。最后在界面销毁的时候,会去执行ViewModelStore的clear方法,去清除集合里面的ViewModel数据。一小段代码说明下:


public <T extends ViewModel> T get(Class<T> modelClass) {
      // 先从ViewModelStore容器中去找是否存在ViewModel的实例
      ViewModel viewModel = mViewModelStore.get(key);
      // 若ViewModel已经存在,就直接返回
      if (modelClass.isInstance(viewModel)) {
            return (T) viewModel;
      }
      // 若不存在,再通过反射的方式实例化ViewModel,并存储进ViewModelStore
      viewModel = modelClass.getConstructor(Application.class).newInstance(mApplication);
      mViewModelStore.put(key, viewModel);
      return (T) viewModel;
 }
public class ViewModelStore {
    private final HashMap<String, ViewModel> mMap = new HashMap<>();
     public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.onCleared();
        }
        mMap.clear();
    }
}
 @Override
protected void onDestroy() {
    super.onDestroy();
   if (mViewModelStore != null && !isChangingConfigurations()) {
        mViewModelStore.clear();
    }
}


目录
相关文章
|
7月前
|
XML Java Android开发
Android面试官最喜欢问15道面试题
Android面试官最喜欢问15道面试题
93 0
|
1月前
|
存储 安全 Java
Android 面试题及答案整理,最新面试题
Android 面试题及答案整理,最新面试题
91 2
|
7月前
|
XML 网络协议 Java
Android最新面经,面试题,附答案,赶紧收藏起来吧
Android最新面经,面试题,附答案,赶紧收藏起来吧
|
消息中间件 存储 IDE
Android体系课--Handler-Handler面试题
面试官:说说Handler基本使用原理
|
安全 API Android开发
Android 面试题:说一下 PendingIntent 和 Intent 的区别
Android 面试题:说一下 PendingIntent 和 Intent 的区别
393 0
Android 面试题:说一下 PendingIntent 和 Intent 的区别
|
前端开发 网络协议 安全
Android 面试题二
Android 面试题二
151 0
|
消息中间件 存储 设计模式
Android 面试题
Android 面试题
151 0
|
Android开发
关于Android Service服务的面试题
距离上一次面试的已经过去两个月了,想起来面试的题目,想给大家分享一下,希望大家能用到。
265 0
|
Android开发
Android面试题:bindService获取代理是同步还是异步
Android面试题:bindService获取代理是同步还是异步
376 0
Android面试题:bindService获取代理是同步还是异步