需求已改活已加,加班通宵看朝霞。 终是上线已延期,bug还是改不完。
面试造火箭,工作拧螺丝,虽然我只想拧螺丝,可是我需要用造火箭的技术去寻找拧螺丝的工作,如何能在面试过程中让自己处于不败的地步呢,刷题是一个比较好的捷径,今天就汇总了一些比较经典的面试题进行了汇总,分享给大家。
从Bundle源码分析,说明我们的Bundle它实现的数据传递是通过我们的ArrayMap实现的,所以说实际上我们的这个Bundle为什么不用HashMap来替代。实际上就是说为什么我们的Android传递数据的时候要用ArrayMap,而不用HashMap。
Bundle的优势:
1). ArrayMap适合于小数据量操作,如果在数据量比较大的情况下,它的性能将退化。HashMap内部则是数组+链表结构,所以在数据量较少的时候,HashMap的Entry Array比ArrayMap占用更多的内存。而使用Bundle的场景大多数为小数据量。所以使用ArrayMap实现更合适。
2).Android中如果使用Intent来携带数据的话,需要数据是基本类型或者是可序列化类型,Bundle使用Parcelable进行序列化,而HashMap则是使用Serializable进行序列化。Parcelable它的性能是要优于Serializable的。
内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
产生的原因:一个长生命周期的对象持有一个短生命周期对象的引用。
通俗讲:就是该回收的对象,因为引用问题没有被回收,所以最终的结果,如果内存泄漏太多,那么我们内存会不断的变大,最后会产生一个OOM的过程。
Java是如何处理它的,Java本身并不会把我们处理的内存泄漏。
LRUCache是个泛型类,主要原理是:把最近使用的对象用强引用存储在LinkedHashMap中,当缓存满时,把最近很少使用的对象从内存中移除,并提供get/put方法完成缓存的获取和添加。LRUCache线程是安全的,因为使用了synchronized关键字。
当调用put方法时,将元素添加到链表头,如果链表头没有该元素,大小不变,如果没有,需调用trimToSize方法判断是否超过最大缓存量,trimToSize()方法中有一个while(true)死循环,如果缓存大小大于最大缓存值,会不断删除LinkedHashMap中队尾的元素,即最少访问的,直到缓存大小小于最大缓存值。当调用LRUCache的get方法时,LinkedHashMap会调用recordAccess方法将此元素添加到链表头部。
Synchronized是Java中的关键字,是一种同步锁,它修饰的对象有一下几种:
1). 修饰一个方法:被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象; 2). 修饰一个静态的方法:其作用的范围是整个静态的方法,作用的对象是这个类的所有对象; 3). 修饰一个代码块:被修饰的代码块称为同步块,起作用是范围的大括号{}起来的代码,作用的对象是调用这个代码块的对象; 4). 修饰一个类:其作用范围是Synchronized后面括号括起来的部分,作用主对象是这个类的所有对象。
Synchronized作用主要有三个:
1). 确保线程互斥的访问同步代码; 2). 保证共享变量的修改能够及时可见; 3). 有效解决重排序问题。
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰后,就具备了两层含义:
1).保证不同线程对该变量进行操作时的可见性,即一个线程修改了变量的值,新值对其他线程来说是立即可见的;
2). 禁止进行指令重排序。
volatile本职是告诉jvm当前变量在寄存器(内存)中的值是不确定的,需要从主存中读取。Synchronized 则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞。
1). volatile仅能使用在变量上;Synchronized 可以使用在变量、方法、类上;
2). volatile仅能实现变量的修改可见性,不能保证原子性;Synchronized 则可以保证变量的修改可见性和原子性;
3). volatile不会造成线程阻塞;Synchronized 可能会造成线程阻塞;
4). volatile标记的变量不会被编辑器优化,Synchronized 标记的变量可以被编辑器优化。
数组:是将元素在内存中连续存储的;
它的优点:因为数据是连续存储的,内存地址连续,所以在查找数据的时候效率比较高;
它的缺点:在存储之前,我们需要申请一块连续的内存空间,并且在编译的时候就必须确定好它的空间大小。在运行的时候空间大小是无法随着你的需要进行增加或者减少而改变的,当数据量比较大的时候,有可能会出现越界的情况,数据比较小的时候,又有可能会浪费掉内存空间。在改变整个数据个数时,增加、插入、删除数据效率比较低。
链表:是动态申请内存空间,不需要像数组需要提前申请好内存的大小,链表只需要在用的时候申请就可以,根据需要来动态申请或者删除内存空间,对于数据增加和删除以及插入比数组灵活。还有就是链表中数据在内存中可以在任意的为止,通过应用来关联数据(就是存在元素的指针来联系)。
Java 中有三种创建线程的方式,或者4种
1).继承Thread类实现多线程; 2).实现Runnable接口; 3).实现Callable接口; 4).通过线程池。
线程池的工作原理:
线程池可以减少创建和销毁线程的次数,从而减少系统资源的消耗,当一个任务提交 到线程池时
a. 首先判断核心线程池中的线程是否已经满了,如果没满,则创建一个核心线程执行任务,否则进入下一步
b. 判断工作队列是否已满,没有满则加入工作队列,否则执行下一步
c. 判断线程数是否达到了最大值,如果不是,则创建非核心线程执行任务,否则执行饱和策略,默认抛出异常。
1).非静态内部类的静态实例非静态内部类会持有外部类的引用,如果非静态内部类的实例是静态的,就会长期的维持着外部类的引用,组织被系统回收,解决办法是使用静态内部类。
2).多线程相关的匿名内部类和非静态内部类匿名内部类同样会持有外部类的引用,如果在线程中执行耗时操作就有可能发生内存泄漏,导致外部类无法被回收,直到耗时任务结束,解决办法是在页面退出时结束线程中的任务。
3).Handler内存泄漏Handler导致的内存泄漏也可以被归纳为非静态内部类导致的,Handler内部message是被存储在MessageQueue中的,有些message不能马上被处理,存在的时间会很长,导致handler无法被回收,如果handler是非静态的,就会导致它的外部类无法被回收,解决办法是1.使用静态handler,外部类引用使用弱引用处理2.在退出页面时移除消息队列中的消息。
4).Context导致内存泄漏根据场景确定使用Activity的Context还是Application的Context,因为二者生命周期不同,对于不必须使用Activity的Context的场景(Dialog),一律采用Application的Context,单例模式是最常见的发生此泄漏的场景,比如传入一个Activity的Context被静态类引用,导致无法回收。
5).静态View导致泄漏使用静态View可以避免每次启动Activity都去读取并渲染View,但是静态View会持有Activity的引用,导致无法回收,解决办法是在Activity销毁的时候将静态View设置为null(View一旦被加载到界面中将会持有一个Context对象的引用,在这个例子中,这个context对象是我们的Activity,声明一个静态变量引用这个View,也就引用了activity)。
6).WebView导致的内存泄漏WebView只要使用一次,内存就不会被释放,所以WebView都存在内存泄漏的问题,通常的解决办法是为WebView单开一个进程,使用AIDL进行通信,根据业务需求在合适的时机释放掉。
7).资源对象未关闭导致如Cursor,File等,内部往往都使用了缓冲,会造成内存泄漏,一定要确保关闭它并将引用置为null。
8).集合中的对象未清理集合用于保存对象,如果集合越来越大,不进行合理的清理,尤其是入股集合是静态的。
9).Bitmap导致内存泄漏bitmap是比较占内存的,所以一定要在不使用的时候及时进行清理,避免静态变量持有大的bitmap对象。
10).监听器未关闭 很多需要register和unregister的系统服务要在合适的时候进行unregister,手动添加的listener也需要及时移除。
当点击app的启动图标时,安卓系统会从Zygote进程中fork创建出一个新的进程分配给该应用,之后会依次创建和初始化Application类、创建MainActivity类、加载主题样式Theme中的windowBackground等属性设置给MainActivity以及配置Activity层级上的一些属性、再inflate布局、当onCreate/onStart/onResume方法都走完了后最后才进行contentView的measure/layout/draw显示在界面上
冷启动的生命周期简要流程:Application构造方法 –> attachBaseContext()–>onCreate –>Activity构造方法 –> onCreate() –> 配置主体中的背景等操作 –>onStart() –> onResume() –> 测量、布局、绘制显示。
冷启动的优化主要是视觉上的优化,解决白屏问题,提高用户体验,所以通过上面冷启动的过程。能做的优化如下:
a) 减少onCreate()方法工作量; b) 不要让Application参与业务的操作; c) 不要再Application进行耗时操作; d) 不要以静态变量的方式在Application保存数据; e) 减少布局的复杂度和层级; f) 减少主线程耗时。
AsyncTask、HandlerThread和IntentService。
AsyncTask原理:内部是Handler和两个线程池实现的,Handler用于将线程切换到主线程,两个线程池一个用于任务的排队,一个用于执行任务,当AsyncTask执行execute方法时会封装出一个FutureTask对象,将这个对象加入队列中,如果此时没有正在执行的任务,就执行它,执行完成之后继续执行队列中下一个任务,执行完成通过Handler将事件发送到主线程。AsyncTask必须在主线程初始化,因为内部的Handler是一个静态对象,在AsyncTask类加载的时候他就已经被初始化了。在Android3.0开始,execute方法串行执行任务的,一个一个来,3.0之前是并行执行的。如果要在3.0上执行并行任务,可以调用executeOnExecutor方法。
HandlerThread原理:继承自Thread,start开启线程后,会在其run方法中会通过Looper创建消息队列并开启消息循环,这个消息队列运行在子线程中,所以可以将HandlerThread中的Looper实例传递给一个Handler,从而保证这个Handler的handleMessage方法运行在子线程中,Android中使用HandlerThread的一个场景就是IntentService。
IntentService原理:继承自Service,它的内部封装了HandlerThread和Handler,可以执行耗时任务,同时因为它是一个服务,优先级比普通线程高很多,所以更适合执行一些高优先级的后台任务,HandlerThread底层通过Looper消息队列实现的,所以它是顺序的执行每一个任务。可以通过Intent的方式开启IntentService,IntentService通过handler将每一个intent加入HandlerThread子线程中的消息队列,通过looper按顺序一个个的取出并执行,执行完成后自动结束自己,不需要开发者手动关闭。
1).耗时的网络访问; 2).大量的数据读写; 3).数据库操作; 4).硬件操作例如camera; 5).调用thread的join()方法、sleep()方法、wait()方法或者等待线程锁的时候; 6).service binder的数量达到上限; 7).system server中发生WatchDog ANR; 8).service忙导致超时无响应; 9).其他线程持有锁,导致主线程等待超时; 10). 其他线程终止或崩溃导致主线程一直等待。
Collection是集合框架的顶层接口,是存储对象的容器,Colloction定义了接口的公用方法如add remove clear等等,它的子接口有两个,List和Set,List的特点有元素有序,元素可以重复,元素都有索引(角标),典型的有Vector:内部是数组数据结构,是同步的(线程安全的)。增删查询都很慢。ArrayList:内部是数组数据结构,是不同步的(线程不安全的)。替代了Vector。查询速度快,增删比较慢。LinkedList:内部是链表数据结构,是不同步的(线程不安全的)。增删元素速度快。
而Set的是特点元素无序,元素不可以重复HashSet:内部数据结构是哈希表,是不同步的。Set集合中元素都必须是唯一的,HashSet作为其子类也需保证元素的唯一性。判断元素唯一性的方式:通过存储对象(元素)的hashCode和equals方法来完成对象唯一性的。如果对象的hashCode值不同,那么不用调用equals方法就会将对象直接存储到集合中;如果对象的hashCode值相同,那么需调用equals方法判断返回值是否为true,若为false, 则视为不同元素,就会直接存储;若为true, 则视为相同元素,不会存储。如果要使用HashSet集合存储元素,该元素的类必须覆盖hashCode方法和equals方法。一般情况下,如果定义的类会产生很多对象,通常都需要覆盖equals,hashCode方法。建立对象判断是否相同的依据。
TreeSet:保证元素唯一性的同时可以对内部元素进行排序,是不同步的。判断元素唯一性的方式:根据比较方法的返回结果是否为0,如果为0视为相同元素,不存;如果非0视为不同元素,则存。TreeSet对元素的排序有两种方式:方式一:使元素(对象)对应的类实现Comparable接口,覆盖compareTo方法。这样元素自身具有比较功能。方式二:使TreeSet集合自身具有比较功能,定义一个比较器Comparator,将该类对象作为参数传递给TreeSet集合的构造函数。
总结:
以上真题案例,并不是靠死记硬背,也不是应付技术面试,而是认识自己的过程,对自身技术的认知,方便加深记忆,不断归纳总结,建立技术知识体系,让自己的能力提升一个新的台阶。学无止境,贵在坚持。
接下来分享的系统学习资源以详解各大互联网公司的 Android 常见面试题为主线,从面试的角度带你介绍必备知识点,以及该知识点在项目中的实际应用。
帮你在现在的基础上,重新梳理和建立 Android 开发的知识体系。无论是你短期内想提升 Android 内功实力,突破自己工作中的能力瓶颈,还是准备参加 Android 面试,都会在这份资料中有所一些收获。
从架构基础开始,分了8个模块来逐步从基础进阶到架构师的环节:
多余的话就不讲了,接下来将分享面试的一个复习路线,如果你也在准备面试但是不知道怎么高效复习,可以参考一下我的复习路线,有任何问题也欢迎一起互相交流,加油吧!
首先是超级详细得不能再详细的Android开发学习思维导图,因为图片实在是太大了,所以我就只把二级目录的内容放出来。
接下来就需要梳理知识,提升储备了!(Android移动架构师七大专题学习资源)
知识梳理完之后,就需要进行查漏补缺,所以针对这些知识点,我手头上也准备了不少的电子书和笔记,这些笔记将各个知识点进行了完美的总结。
然后再是通过源码来系统性地学习
只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。
真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。
刷大厂面试题备战,增加大厂通过率
以上这些内容均免费分享给大家,需要完整版的朋友,点这里可以看到全部内容。或者点击 【这里】 查看获取方式。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。