2023暑期实习Android端---阿里蚂蚁一面

蚂蚁一面

个人语录:时间不负有心人,星光不问赶路人,以顶级好的态度写一篇博客

今天又是被面试官吊打的一天,什么时候能吊打面试官呢,哈哈哈

  1. 为什么使用Gilde

    1.使用方便,API简洁。with、load、into 三步就可以加载图片

    2.生命周期自动绑定,根据绑定的Activity或Fragment生命周期管理图片请求

    3.支持多级配置:应用、单独页面(Activity/Fragment)、单个请求进行独立配置。

    4.高效缓存策略,两级内存 ,两级文件。

    5.支持多种图片格式(Gif、WebP、Video), 扩展灵活

    参考:面试官:我们来聊聊Glide吧……- 掘金 (juejin.cn)

  2. 有调研过其他的图片的加载框架

    参考:Carson带你学Android:主流开源图片加载库对比(UIL、Picasso、Glide、Fresco) - 简书 (jianshu.com)

  3. Activity的生面周期,ActivityA—>跳转ActivityB

    A.Onpause->B.Oncreate->B.Onstart->B.OnResume->A.Onstop

  4. 虚拟机的内存是根据什么原则销毁的

    我感觉这个面试官是想问对象销毁原则

    扫描二维码关注公众号,回复: 14262350 查看本文章

    一个对象被当作垃圾回收的情况主要如下两种:

    • 对象引用超过其作用范围

      {
               
               
          Object o = new Object();  // 对象o的作用范围,超过这个范围对象将被视为垃圾
      }
      
    • 对象被赋值为NULL

      {
               
               
          Object o = new Object();
          o = null;    // 对象被赋值为null将被视为垃圾
      }
      
    • 在 Java 的 Object 类中还提供了一个 protected 类型的 finalize() 方法,因此任何 Java 类都可以覆盖这个方法,在这个方法中进行释放对象所占有的相关资源的操作。

      在 Java 虚拟机的堆区,每个对象都可能处于以下三种状态之一。

      1)可触及状态:当一个对象被创建后,只要程序中还有引用变量引用它,那么它就始终处于可触及状态。

      2)可复活状态:当程序`不再有任何引用变量引用该对象时,该对象就进入可复活状态。在这个状态下,垃圾回收器会准备释放它所占用的内存,在释放之前,会调用它及其他处于可复活状态的对象的 finalize() 方法,这些 finalize() 方法有可能使该对象重新转到可触及状态。

      3)不可触及状态:当 Java 虚拟机执行完所有可复活对象的 finalize() 方法后,如果这些方法都没有使该对象转到可触及状态,垃圾回收器才会真正回收它占用的内存。

  5. 内存不够了怎么选择销毁对象

PS : 这个题和T4没啥区别,这种题目的回答可以换一种思路即四种引用

  • 强引用
    强引用就是我们平常最基本的对象引用,如果是强引用,那回收器不会回收带有强引用的对象。即使内存不足抛出OutOfMemoryError异常也不会回收强引用对象,存在即合理吧。
  • 软引用
    一个对象只有软引用,如果内存空间足够情况下垃圾回收器就不会回收它,如果内存空间不够了就会对这些只有软引用的对象进行回收。只要垃圾回收器没有回收,该软引用对象就可以继续被程序使用。
  • 弱引用
    弱引用的对象具有更短暂的生命周期,在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
  • 虚引用
    虚引用顾名思义就是形同虚设,虚引用并不决定对象的生命周期,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。主要用来跟踪对象被垃圾回收器回收的活动
引用类型 回收时间 用途
强引用 永不回收 普通对象引用
软引用 内在不足回收 缓存对象
弱引用 垃圾回收时 缓存对象
虚引用 不确定 不确定
  1. 前台进程会不会有对象被销毁

    虚引用和弱引用有可能前台也会销毁

  2. GC算法

JVM面试知识点合集 — Android 春招 2022_Liknananana的博客-CSDN博客

  1. 内存碎片的解决办法

  1. 少用动态内存分配的函数(尽量使用栈空间)
  2. 分配内存和释放的内存尽量在同一个函数中
  3. 尽量一次性申请较大的内存,而不要反复申请小内存(少进行内存的分割)
  4. 自己进行内存管理工作,设计内存池
  5. 也可以参考复制算法和标记压缩算法
  1. 内存碎片会带来什么问题,内存碎片为什么会导致浪费

  2. 什么是内存溢出

    程序在申请内存时,没有足够的内存空间供其使用,出现内存不够

  3. OOM的解决办法

    • 可以使用软,弱应用

    • 数据库使用完Cursor的时候就应该手动调用它的close方法关闭cursor.

    • ListView在getView方法里面对convertView进行判断后复用,还应该使用ViewHolder类来保存通过过findViewById得到的子控件地址值

    • 在activity中注册了广播,但是在activity退出的时候没有取消注册的话可能会造成内存溢出,需要手动的在相应的位置进行反注册.

    • 不关闭输入输出流的话就相当于在内存和硬盘一直存在着连接占用着资源,当其他操作需要资源时就会造成内存溢出.

    • 位图在安卓中占用的内存是很大的,使用后如果不及时回收的话会占用大量空间,所以针对位图的操作一般有如下解决方案:

      1)及时的调用resycle方法来手动的回收;

      2)设置采样率,有时候我们不一定要把图片完全显示出来,这时候就要按比例来缩放,在我们得到采样率的时候就可以将图片缩小后再进行加载,节省大量的内存;

      3)设置选择合适的压缩格式

      4)使用软引用

    • 应该尽量避免static成员变量引用资源耗费过多的实例,比如Context。

    • Context尽量使用Application Context,因为Application的Context的生命周期比较长,引用它不会出现内存泄露的问题。

    • 使用静态内部类,不持有外部引用,从而不影响GC对外部类的回收。

  4. 什么是大对象,你以为多大

    需要占用大量连续内存空间的java对象是大对象,比如很长的字符串和数组,数据库查询结果不使用分页查询导致结果集很大

  5. 内存泄露的原因

    1.Service导致内存泄漏:Service执行完没有停止操作或者停止失败就会导致内存泄漏,针对这种情况官方推荐使用IntentService,IntentService的最大特点就是后台任务执行结束后会自动停止。
    2.使用依赖注入框架导致内存泄漏:依赖注入框架虽然简化了复杂的编码操作,但为了要搜寻代码中的注解,通常都需要经历较长的初始化过程,并且还可能将一些你用不到的对象也一并加载到内存当中。这些用不到的对象会一直占用着内存空间,可能要过很久之后才会得到释放。
    3.静态对象导致内存泄漏:静态变量的生命周期和类是息息相关的,它们分配在方法区上,垃圾回收一般不会回收这一块的内存。所以我们在代码中用到静态对象,在不用的时候如果不赋null值消除对象的引用的话,那么这些对象是很难被垃圾回收的。如果这些对象一多或者比较大的话,程序出现OOM的概率就比较大了。因为静态变量而出现内存泄漏是很常见的。
    4.Context导致内存泄漏: android 中很多地方都要用到context,连基本的Activty 和 Service都是从Context派生出来的,我们利用Context主要用来加载资源或者初始化组件,在Activity中有些地方需要用到Context的时候,我们经常会把context给传递过去了,将context传递出去就有可能延长了context的生命周期,最终导致了内存泄漏。例如 我们将activty context对象传递给一个后台线程去执行某些操作,如果在这个过程中因为屏幕旋转而导致activity重建,那么原先的activity对象不会被回收,因为它还被后台线程引用着,如果这个activity消耗了比较多的内存,那么新建activity或者后续操作可能因为旧的activity没有被回收而导致内存泄漏。所以,遇到需要用到context的时候,我们要合理选择不同的context,对于android应用来说还有一个单例的Application Context对象,该对象生命周期和应用的生命周期是绑定的。选择context应该考虑到它的生命周期,如果使用该context的组件的生命周期超过该context对象,那么我们就要考虑是否可以用application context。如果真的需要用到该context对象,可以考虑用弱引用来WeakReference来避免内存泄漏。
    5.非静态内部类导致的内存泄漏:非静态的内部类会持有外部类的一个引用,所以和前面context说到的一样,如果该内部类生命周期超过外部类的生命周期,就可能引起内存泄露了,如AsyncTask和Handler。因为在Activity中我们可能会用到匿名内部类,所以要小心管理其生命周期。 如果明确生命周期较外部类长的话,那么应该使用静态内部类。
    6.Drawable对象的回调隐含的内存泄漏:当我们为某一个view设置背景的时候,view会在drawable对象上注册一个回调,所以drawable对象就拥有了该view的引用了,进而对整个context都有了间接的引用了,如果该drawable对象没有管理好,例如设置为静态,那么就会导致内存泄漏。
    7.监听器注册没注销造成内存泄漏:在Android程序里面存在很多需要register与unregister的监听器,我们需要确保在合适的时候及时unregister那些监听器。自己手动add的listener,需要记得及时remove这个listener。
    8.资源对象没关闭造成内存泄漏:资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。它们的缓冲不仅存在于java虚拟机内,还存在于java虚拟机外。如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄露。因为有些资源性对象,比如SQLiteCursor(在析构函数finalize(),如果我们没有关闭它,它自己会调close()关闭),如果我们没有关闭它,系统在回收它时也会关闭它,但是这样的效率太低了。 因此对于资源性对象在不使用的时候,应该调用它的close()函数,将其关闭掉,然后才置为null.在我们的程序退出时一定要确保我们的资源性对象已经关闭。
    9.单例对象中不合理的持有造成内存泄漏:虽然单例模式简单实用,提供了很多便利性,但是因为单例的生命周期和应用保持一致,使用不合理很容易出现持有对象的泄漏。
    10.集合容器中的对象泄漏:当集合里面的对象属性被修改后,再调用remove()方法时不起作用。

    Set<Person> set = new HashSet<Person>();
    Person p1 = new Person("唐僧","pwd1",25);
    set.add(p1);
    p1.setAge(2); //修改p3的年龄,此时p3元素对应的hashcode值发生改变
    set.remove(p1); //此时remove不掉,造成内存泄漏 
    

    11.Webview导致内存泄漏:Android中的WebView存在很大的兼容性问题,不仅仅是Android系统版本的不同对WebView产生很大的差异,另外不同的厂商出货的ROM里面WebView也存在着很大的差异。更严重的是标准的WebView存在内存泄露的问题,看这里WebView causes memory leak - leaks the parent Activity。所以通常根治这个问题的办法是为WebView开启另外一个进程,通过AIDL与主进程进行通信,WebView所在的进程可以根据业务的需要选择合适的时机进行销毁,从而达到内存的完整释放。
    12.属性动画导致内存泄露:从Android3.0开始,Google提供了属性动画,属性动画中有一类无限循环的动画,如果在Activity中播放此类动画且没有在onDestroy中去停止动画,那么动画就会一直播放下去,尽管已经无法在界面上看到动画效果了,并且这个时候Activity的View会被动画持有,而View又持有了Activity,最终Activity无法释放。解决方法是在Activity的onDestroy中调用animator.cancle()来停止动画。

  6. SQL联合查询

  7. SQLite版本管理

    SQLite数据库版本升级的管理实现_谷哥的小弟的博客-CSDN博客

  8. Activity的启动过程

    1. Launcher进程通过Binder驱动向ActivityManagerService类发起startActivity请求;
    2. ActivityManagerService类接收到请求后,向ActivityStack类发送启动Activity的请求
    3. ActivityStack类记录需启动的Activity的信息 & 调整Activity栈 将其置于栈顶、通过 Binder 驱动 将 Activity 的启动信息传递到ApplicationThread线程中(即Binder线程)
    4. ApplicationThread线程通过HandlerActivity的启动信息发送到主线程ActivityThread
    5. 主线程ActivityThread类接收到该信息 & 请求后,通过ClassLoader机制加载相应的Activity类,最终调用ActivityonCreate(),最后 启动完毕

    img

  9. Retrofit是基于什么怎么实现的

    Carson带你学Android:手把手带你深入读懂Retrofit 2.0源码 - 简书 (jianshu.com)

  10. 自定义注解

    自定义注解详细介绍_cherry-peng的博客-CSDN博客_自定义注解

  11. 是为了解决什么问题而引入的泛型

    • 1 集合类型元素在运行期出现类型装换异常,增加编译时类型的检查,向上转型:其核心目的在于参数的统一上,根本不需要强制类型转换。向下转型:是为了操作子类定义的特殊功能,需要强制类型转换,可是现在存在的问题是:向下转型其实是一种非常不安全的操作,以为编译的时候,程序不会报错,而在运行的时候会报错,这就是传说中的—迷之报错。
    • 2 解决的时重复代码的编写,能够复用算法。下面通过例子来说明编译器的类型检查
  12. 基本数据类型所占字节

  13. String为什么不可改变,String怎么做到不可变的

    • String的源码里其实使用一个char数组来存储字符串的,String之所以说不可变,就是因为这个char数组它是private类型,而且String没有对外暴露和提供修改这个char数组的方法,因此我们无法更改这个char数组的值,所以String是不可变的

    java中String真的不可变吗?其实这样可以真的改变String字符串_开局一打酒。的博客-CSDN博客

  14. String能够扩容吗

    不能

  15. map的扩容机制,hashmap的数组是怎么实现

  16. 泛型擦除过程

    Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉。Java的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程成为类型擦除。类型检查就是针对引用的,会对这个引用调用的方法进行类型检测,而无关它真正引用的对象。

    泛型的类型擦除原则是:

    • 消除类型参数声明,即删除<>及其包围的部分。
    • 根据类型参数的上下界推断并替换所有的类型参数为原生态类型:如果类型参数是无限制通配符或没有上下界限定则替换为Object,如果存在上下界限定则根据子类替换原则取类型参数的最左边限定类型(即父类)。
    • 为了保证类型安全,必要时插入强制类型转换代码。
    • 自动产生“桥接方法”以保证擦除类型后的代码仍然具有泛型的“多态性”。

    参考:Java 泛型擦除 - hongdada - 博客园 (cnblogs.com)

  17. 泛型引入存在的问题

    1. 在 Java 中,像下面形式的引用传递是不允许的:
    ArrayList<String> arrayList1=new ArrayList<Object>();//编译错误
    ArrayList<Object> arrayList1=new ArrayList<String>();//编译错误
    

    第一种情况可视为:

    ArrayList<Object> arrayList1=new ArrayList<Object>(); 
    arrayList1.add(new Object());    
    arrayList1.add(new Object());    
    ArrayList<String> arrayList2=arrayList1;//编译错误 
    

    实际上在第4行就有编译错误,我们先假设他编译没错,那么当我们使用arrayList2引用get()方法取值的时候,返回的都是String类型,但是它里面实际已经被我们存放了Object类型。就会有 ClassCastException 了
    第二种情况可视为:

    ArrayList<String> arrayList1=new ArrayList<String>();    
    arrayList1.add(new String());    
    arrayList1.add(new String());    
    ArrayList<Object> arrayList2=arrayList1;//编译错误 
    

    在我们用 arrayList2 取值的时候不会出现ClassCastException ,但是这种做法没有意义,泛型出现的原因,就是为了解决类型转换的问题。我们使用了泛型,到头来,还是要自己强转,违背了泛型设计的初衷。所以 java 不允许这么干。

    1. 泛型类型变量不能是基本数据类型
      就比如,没有 ArrayList,只有 ArrayList。因为当类型擦除后,ArrayList 的原始类中的类型变量(T)替换为 Object,但 Object 类型不能存储 int 值。

    2. 泛型在静态方法和静态类中的问题
      泛型类中的静态方法和静态变量不可以使用泛型类所声明的泛型类型参数。泛型参数的实例化是在定义泛型类型对象和对象初始化过程中,而静态变量和静态方法不需要使用对象来调用。对象都没有创建,如何确定这个泛型参数是何种类型,所以当然是错误的。

    public class Test2<T> {
           
                 
        public static T one;   //编译错误      
        public static  T show(T one){
           
            //编译错误      
            return null;      
        }      
    } 
    

附:

  1. Java 中的泛型是什么 ? 使用泛型的好处是什么?
    泛型是一种参数化类型的机制。它可以使得代码适用于各种类型,从而编写更加通用的代码,例如集合框架。
    泛型是一种编译时类型确认机制。它提供了编译期的类型安全,确保在泛型类型(通常为泛型集合)上只能使用正确类型的对象,避免了在运行时出现 ClassCastException。

  2. Java 的泛型是如何工作的 ? 什么是类型擦除 ?
    泛型的正常工作是依赖编译器在编译源码的时候,先进行类型检查,然后进行类型擦除并且在类型参数出现的地方插入强制转换的相关指令实现的。
    编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。例如
    List在运行时仅用一个 List 类型来表示。为什么要进行擦除呢?这是为了避免类型膨胀。

  3. 什么是泛型中的限定通配符和非限定通配符 ?
    限定通配符对类型进行了限制。有两种限定通配符,一种是<? extends T>它通过确保类型必须是 T 的子类来设定类型的上界,另一种是<? super T>它通过确保类型必须是 T 的父类来设定类型的下界。泛型类型必须用限定内的类型来进行初始化,否则会导致编译错误。另一方面<?>表示了非限定通配符, 因为<?>可以用任意类型来替代。

  4. List<? extends T>和 List <? super T>之间有什么区别 ?
    这和上一个面试题有联系,有时面试官会用这个问题来评估你对泛型的理解,而不是直接问你什么是限定通配符和非限定通配符。这两个 List 的声明都是限定通配符的例子,List<? extends T>可以接受任何继承自 T 的类型的 List,而 List<? super T>可以接受任何 T 的父类构成的 List。例如 List<? extends Number>可以接受 List或 List。在本段出现的连接中可以找到更多信息。

  5. 如何编写一个泛型方法,让它能接受泛型参数并返回泛型类型?
    编写泛型方法并不困难,你需要用泛型类型来替代原始类型,比如使用 T, E or K,V 等被广泛认可的类型占位符。泛型方法的例子请参阅 Java 集合类框架。最简单的情况下,一个泛型方法可能会像这样:

public V put(K key, V value) {
    
      
    return cache.put(key, value);  
} 

猜你喜欢

转载自blog.csdn.net/weixin_45882303/article/details/124356441