Android高级面试题汇总——Android篇(2)

作者: android 发布时间: 2021-01-14 浏览: 2020 次 编辑

三)常见的一些原理性问题

1、Handler机制和底层实现

机制:hanlder是android线程间通信的一种实现,以消息队列的方式实现线程间数据的共享,通过Looper不断的轮询消息队列来 获取数据
底层:MessageQueen、Looper以及Looper内部的ThreadLocal,我们在线程中Looper对消息队列的管理过程离不开数据储存ThreadLocal,ThreadLocal在多线程状态下数据是安全的,因为其独特的每个线程都保留一个副本,而我们Looper的核心原理就是对ThreadLocal为唯一的键进行put()和get()值得操作

在应用启动的时候,会执行程序的入口函数main(),main()里面会调用Looper.prepare()创建Looper并初始化Looper持有的消息队列MessageQueue,创建好后将Looper保存到ThreadLocal中方便Handler直接获取(提供了一个public的静态方法可以从ThreadLocal中取出Looper)

然后Looper.loop()开启循环,从MessageQueue里面取消息并调用handler的 dispatchMessage(msg) 方法处理消息。如果MessageQueue里没有消息,循环就会阻塞进入休眠状态,等有消息的时候被唤醒处理消息

new Handler()的时候,Handler构造方法中获取Looper并且拿到Looper的MessageQueue对象,然后Handler内部可以通过sendMessage直接往MessageQueue里面插入消息,这时候有消息了就会唤醒Looper循环去处理消息。处理消息就是调用dispatchMessage(msg) 方法,最终调用到我们重写的Handler的handleMessage()方法

2、Handler、Thread和HandlerThread的差别

Handler:在Android中负责发送和处理消息,实现其他子线程与主线程之间的消息通讯

Thread:普通的异步线程

HandlerThread:子线程与子线程之间的通信

差别:Handler是线程间进行消息处理的,每次使用都需要新建looper对象,在主线程不用新建,因为页面初始化时就为我们新建好了,所以handlder的使用除了发送信息和接受信息外还有一个looper新建、调用、回收过程,而HandlerThread包装了这个过程,HandlerThread继承了Thread类,实现了looper的初始化操作和解决了同步问题

注:HandlerThread适合在只需要一个工作线程(非UI线程)+任务的等待队列的形式,优点是不会有堵塞,减少了对性能的消耗,缺点是不能同时进行多任务的处理,需要等待进行处理。处理效率较低,可以当做一个轻量级的线程池来用

3、Handler发消息给子线程,looper怎么启动

对于发送消息的主线程,looper是由系统启动的。对于子线程,如果要接收hanlder发送的消息的话,必须创建自己的looper。调用looper.prepare() 初始化 looper,新建hanlder接收,调用looper.loop() 开始消息队列的轮询

4、关于Handler,在任何地方new Handler 都是什么线程下

一般而言new Handler是在主线程中,在子线程中不能直接new Handler,需要先新建子线程的looper

5、ThreadLocal原理,实现及如何保证Local属性

实现原理:

(1)定义了一个ThreadLocalMap内部类,使用的是Map的键值对方式来存取数据,key是ThreadLocal类的实例对象,value为传值

(2)创建新的ThreadLocal对象,调用set()或get()方法时,也就是调用了ThreadLocalMap来进行操作

(3)使用ThreadLocal时,线程所使用的变量是独享的(私有的变量副本),其他线程无法访问,在使用过后(线程结束),这些变量会被GC回收

ThreadLocal相当于线程内的内存,一个局部变量。每次可以对线程自身的数据读取和操作,并不需要通过缓冲区与主内存中的变量进行交互。并不会像Synchronized那样修改主内存的数据,再将主内存的数据复制到线程内的工作内存。ThreadLocal可以让线程独占资源,存储于线程内部,避免线程堵塞造成CPU吞吐下降

6、请描述一下View事件传递分发机制

(1)调用Activity的dispatchTouchEvent方法,事件会通过Activity附属的Window进行分发(Window的实现类PhoneWindow调用了DecorView的superDispatchTouchEvent方法,这个DecorView就是Window的顶级View,通过setContentView设置的View是它的子View),如果分发下去的事件,没有view可以响应,则调用Activity自己的onTouchEvent方法结束消费事件

(2)调用ViewGroup的dispatchTouchEvent方法,如果当前动作不是ACTION_DOWN或当前事件不是ViewGroup自身进行处理,调用onInterceptTouchEvent方法对事件进行拦截,否则不进行拦截。不拦截,则分发给子View进行处理,拦截,则调用ViewGroup自身的onTouchEvent方法。若ViewGroup自身消费掉,则结束掉消费事件,否则调用父View的onTouchEvent方法

注:子View可以通过requestDisallowInterceptTouchEvent方法干预父View的事件分发过程(ACTION_DOWN事件除外)

(3)重复步骤(2),知道子View不为ViewGroup

(4)调用View的dispatchTouchEvent方法,因为View没有拦截方法,顾调用自身的onTouchEvent方法。若View自身消费掉,则结束掉消费事件,否则调用父View的onTouchEvent方法

(5)如果View设置了OnTouchListener,并且onTouch()方法返回了true,则onTouchEvent()方法将不再执行。没有设置OnTouchListener或者设置了OnTouchListener但是onTouch方法返回false,则会调用View自己的onTouchEvent方法。当onTouchEvent()方法中的action==ACTION_UP的时候才会执行onClick()方法

7、事件分发中的onTouch 和onTouchEvent 有什么区别,又该如何使用

区别:

onTouch方法是View设置了触摸监听事件后,需要重写的方法,是onTouchListener接口中的方法

onTouchEvent方法是专门用来处理事件分发的,它一定存在Activity、View和ViewGroup这三者中

使用:

View绑定了onTouchListener后,当有Touch事件触发时,就会调用onTouch方法

View重写了Acitivity的onTouchEvent方法后,当屏幕有Touch事件时,此方法就会被调用

8、View和ViewGroup分别有哪些事件分发相关的回调方法

ViewGroup:onTouch,onTouchEvent,dispatchTouchEvent ,onInterceptTouchEvent

View:onTouch,onTouchEvent,dispatchTouchEvent

9、View刷新机制

(1)界面上任何一个View的刷新请求最终都会走到ViewRootImpl中的scheduleTraversals()里来安排一次遍历绘制View树的任务

(2)scheduleTraversals()会先过滤掉同一帧内的重复调用,确保同一帧内只需要安排一次遍历绘制View树的任务,遍历过程中会将所有需要刷新的View进行重绘

(3)scheduleTraversals()会往主线程的消息队列中发送一个同步屏障,拦截这个时刻之后所有的同步消息的执行,但不会拦截异步消息,以此来尽可能的保证当接收到屏幕刷新信号时可以尽可能第一时间处理遍历绘制View树的工作

(4)发完同步屏障后scheduleTraversals()将performTraversals()封装到Runnable里面,然后调用Choreographer的 postCallback()方法,将这个Runnable任务以当前时间戳放进一个待执行的队列里

(5)scheduleTraversals()方法中判断当前线程是否在主线程,如果是在主线程就会直接调用一个native层方法,如果不是在主线程,会发一个最高优先级的message到主线程,让主线程第一时间调用这个native层的方法

(6)调用一个native层方法向底层订阅下一个屏幕刷新信号Vsync,当下一个屏幕刷新信号发出时,底层就会回调Choreographer的onVsync()方法来通知上层app

(7)onVsync()方法被回调时,会往主线程的消息队列中发送一个执行doFrame()方法的异步消息

(8)doFrame()方法会去取出之前放进待执行队列里的任务来执行,取出来的这个任务实际上是ViewRootImpl的doTraversal()操作

(9)doTraversal()中首先移除同步屏障,再调用performTraversals()根据当前状态判断是否需要执行performMeasure()测量、perfromLayout()布局、performDraw()绘制流程,从而完成View树的刷新,在这几个流程中都会去遍历View树来刷新需要更新的View

(10)等到下一个Vsync信号到达,将上面计算好的数据渲染到屏幕上,同时如果有必要开始下一帧的数据处理

10、View绘制流程

onMeasure():测量视图大小。从顶层父View到子View递归调用measure方法,measure方法又回调onMeasure。(先调用子view的measure进行测量,完成后将其宽高记录下来,等所有子View测量完成后,再测量父View,因为父View的宽高会用到子View的测量结果)

onLayout():确定View位置,进行页面布局。从顶层父View向子View递归调用view.layout方法的过程,即父View根据上一步measure子View所得到的布局大小和布局参数,将子View放在合适的位置上(先对父View进行布局,然后调用onLayout方法对子View进行布局定位)

OnDraw():绘制视图。ViewRoot创建一个Canvas对象,然后调用onDraw()。

六个步骤:

(1)绘制视图的背景

(2)如果需要,保存画布的图层(Layer)

(3)绘制View的内容

(4)绘制子View,如果没有就不用

(5)如果需要,绘制边界,还原图层(Layer)

(6)绘制View的装饰(View除了背景、内容、子View的其余部分,比如滚动条)

11、AsyncTask原理及缺点

原理:

调用execute()方法后,其内部直接调用executeOnExecutor方法,接着onPreExecute()被调用方法,执行异步任务的WorkerRunnable对象(实质为Callable对象)最终被封装成FutureTask实例,FutureTask实例将由线程池Executor执行去执行,这个过程中doInBackground()将被调用(在WorkerRunnable对象的call方法中被调用),如果我们覆写的doInBackground()方法中调用了publishProgress()方法,则通过InternalHandler实例发送一条MESSAGE_POST_PROGRESS消息更新进度,Handler处理消息时onProgressUpdate()方法将被调用。最后如果FutureTask任务执行成功并返回结果,则通过postResult方法发送一条MESSAGE_POST_RESULT的消息去执行AsyncTask的finish方法,在finish方法内部onPostExecute(Result result)方法被调用,在onPostExecute方法中我们可以更新UI或者释放资源等。这既是AsyncTask内部的工作流程,可以说是Callable+FutureTask+Executor+Handler内部封装

缺点:

(1)线程池中已经有128个线程,缓冲队列已满。假设此时向线程提交任务,将会抛出RejectedExecutionException。

过多的线程会引起大量消耗系统资源和导致应用FC的风险

(2)AsyncTask不会随着Activity的销毁而销毁,直到doInBackground()方法运行完成。假设Activity销毁之前,没有取消 AsyncTask,这有可能让AsyncTask崩溃(crash)。由于它想要处理的view已经不存在了,所以,必须确保在销毁活动之前取消任务

(3)如果AsyncTask被声明为Activity的非静态的内部类,那么AsyncTask会保留一个对创建了AsyncTask的Activity的引用。当Activity被销毁时,AsyncTask的后台线程还在执行,它将继续在内存里保留这个引用,导致Activity无法被回收,引起内存泄露

(4)屏幕旋转或Activity在后台被系统杀掉等情况会导致Activity的重新创建,之前运行的AsyncTask会持有一个之前Activity的引用,这个引用已经无效,这时调用onPostExecute()再去更新界面将不再生效

(5)在Android1.6之前的版本,AsyncTask是串行的,在1.6至2.3的版本,改成了并行的,在2.3之后的版本又做了修改,可以支持并行和串行。当想要串行执行时,直接执行execute()方法,如果需要并行执行,则要执行executeOnExecutor(Executor)

12、如何取消AsyncTask

在AsyncTask中,没有办法直接停止掉异步任务,只能通过cancel方法来将AsyncTask标记为cancel状态,即cancel方法只是传递了一个信号量,而不是真的cancel了异步任务。所以如果希望cancel方法能直接取消掉异步任务,就需要在doInBackground中检测当前状态,当状态是cancel状态,则结束任务

13、为什么不能在子线程更新UI

Android的UI访问是没有加锁的,多个线程可以同时访问更新操作同一个UI控件。也就是说访问UI的时候,android系统当中的控件都不是线程安全的,这将导致在多线程模式下,当多个线程共同访问更新操作同一个UI控件时容易发生不可控的错误,而这是致命的。所以Android中规定只能在UI线程中访问UI,这相当于从另一个角度给Android的UI访问加上锁,一个伪锁

14、ANR产生的原因是什么

类型:

(1)主线程对输入事件在5秒内没有处理完毕。产生这种ANR的前提是要有输入事件,如果用户没有触发任何输入事件,即便是主线程阻塞了,也不会产生ANR,因为InputDispatcher没有分发事件给应用程序,所以不会检测处理超时和报告ANR

(2)主线程在执行BroadcastReceiver的onReceive函数时10秒内没有执行完毕,后台进程是60秒,这种情况的ANR系统不会显示对话框提示,仅是输出log

(3)主线程在执行Service的各个生命周期函数时20秒内没有执行完毕,后台进程为200秒,这种情况的ANR系统不会显示对话框提示,仅是输出log

(4)ContentProvider在10秒内没有完成发布,这种情况的ANR系统不会显示对话框提示,仅是输出log

原因:

(1)应用自身进程引起的问题,比如, 在onCreate ,onResume等生命周期中执行耗时操作,ui线程阻塞,挂起,死循环等

(2)其他进程引起的,比如:io操作导致cpu使用过高,导致当前应用进程无法抢占到cpu时间片

细分的话可以分为以下一些情况:

  • 耗时的网络访问
  • 大量的数据读写
  • 数据库操作
  • 硬件操作(比如camera)
  • 调用thread的join()方法、sleep()方法、wait()方法或者等待线程锁的时候
  • service binder的数量达到上限
  • system server中发生WatchDog ANR
  • service忙导致超时无响应
  • 其他线程持有锁,导致主线程等待超时
  • 其它线程终止或崩溃导致主线程一直等待

15、ANR定位和修正

定位:如果开发机器上出现ANR问题时,系统会生成一个traces.txt的文件放在/data/anr下,最新的ANR信息在最开始部分。通过adb命令将其导出到本地,输入以下字符:$adb pull data/anr/traces.txt

修正:

(1)耗时的工作,比如数据库操作,I/O,网络操作,采用单独的工作线程处理

(2)用Handler来处理UIthread和工作Thread的交互

(3)合理使用并遵循Android生命周期, 避免在onCreate()和onResume()做过多的事情

(4)BroadCastReceiver要进行复杂操作的的时候,可以在onReceive()方法中启动一个Service来处理

(5)避免在IntentReceiver里启动一个Activity,因为它会创建一个新的画面,并从当前用户正在运行的程序上抢夺焦点。如果应用程序在响应Intent广播时需要向用户展示什么,应该使用Notification Manager来实现

(6)在设计及代码编写阶段避免出现出现同步/死锁或者错误处理不恰当等情况

(7)避免加载大图片引起内存不足导致ANR

(8)避免内存泄露引起的 ANR

16、oom是什么

全称“Out Of Memory”,意思是内存用完了,当JVM因为没有足够的内存来为对象分配空间并且垃圾回收器也已经没有空间可回收时,就会抛出这个error

17、什么情况导致oom

(1)内存分配少了

比如虚拟机本身可使用的内存(一般通过启动时的VM参数指定)太少

(2)应用用的太多,并且用完没释放,浪费了

此时会造成内存泄露或者内存溢出

内存泄露:申请使用完的内存没有释放,导致虚拟机不能再次使用该内存,此时这段内存就泄露了(申请者不用了,而又不能被虚拟机分配给别人用)

内存溢出:申请的内存超出了JVM能提供的内存大小,此时称之为溢出

18、有什么解决方法可以避免OOM

(1)资源文件需要选择合适的文件夹进行存放

(2)优化布局层次,越扁平化的视图布局,占用的内存就越少,效率越高

(3)减小Bitmap对象的内存占用

(4)使用更小的图片

(5)复用系统自带的资源,比如字符串/颜色/图片/动画/样式以及简单布局等等,这些资源都可以在应用程序中直接引用

(6)注意在ListView/GridView等出现大量重复子组件的视图里面对ConvertView的复用

(7)类似onDraw等频繁调用的方法,一定需要注意避免在这里做创建对象的操作,因为他会迅速增加内存的使用,而且很容易引起频繁的gc,甚至是内存抖动(内存频繁的分配和回收)

(8)在有些时候,代码中会需要使用到大量的字符串拼接的操作,这种时候有必要考虑使用StringBuilder来替代频繁的“+”

(9)考虑使用Application Context而不是Activity Context

19、OOM是否可以try catch?为什么

可以,但是这通常不是合适的做法

在try语句中声明了很大的对象,导致OOM,并且可以确认OOM是由try语句中的对象声明导致的,那么在catch语句中,可以释放掉这些对象,解决OOM的问题,继续执行剩余语句

如果是大内存操作,应该想办法一点点加载,或者压缩资源来加载。如果是巨大数量的排序问题,可以选择外排序的方式进行。如果是内存泄漏,需要寻找程序自身的设计问题(OOM的原因不是try语句中的对象,在catch语句中会继续抛出OOM)

20、什么情况导致内存泄漏

(1)单例造成的内存泄漏

由于单例的静态特性使得其生命周期和应用的生命周期一样长,如果一个Activity已经不再需要使用了,而单例对象还持有该Activity的引用,就会使得该Activity不能被正常回收,从而导致了内存泄漏

解决方法:不使用Context或使用Application的Context

(2)非静态内部类创建静态实例造成的内存泄漏

非静态内部类默认会持有外部类的引用,非静态内部类又创建了一个静态的实例,其实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,从而导致该Activity的内存资源不能被正常回收

解决方法:将内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,就使用Application的Context

(3)Handler造成的内存泄漏

当Activity结束时,未处理的消息持有handler的引用,而handler又持有它所属的外部类也就是Activity的引用。这条引用关系会一直保持直到消息得到处理,这样阻止了Activity被垃圾回收器回收,从而造成了内存泄漏

解决方法:

  • Activity销毁时,清空Handler中未执行或正在执行的Callback以及Message
  • 使用静态内部类+弱引用的方式

(4)线程造成的内存泄漏

AsyncTask和Runnable都使用了匿名内部类,那么它们将持有其所在Activity的隐式引用。如果任务在Activity销毁之前还未完成,那么将导致Activity的内存资源无法被回收,从而造成内存泄漏

解决方法:

  • Activity销毁时取消相应的任务
  • 使用静态内部类+弱引用的方式

(5)资源未关闭造成的内存泄漏

对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,从而造成内存泄漏

解决方法:

  • Activity中注册的BraodcastReceiver,在Activity结束后注销该BraodcastReceiver
  • 资源性对象比如Cursor,Stream、File文件等往往都用了一些缓冲,在不使用的时候应该调用它的close()函数将其关闭掉,然后再设置为null。在程序退出时一定要确保资源性对象已经关闭
  • Bitmap对象不在使用时调用recycle()释放内存。2.3以后的bitmap应该是不需要手动recycle了,内存已经在java层了

(6)使用ListView时造成的内存泄漏

构造Adapter时,没有使用缓存的convertView

解决方法:在构造Adapter时,使用缓存的convertView

(7)集合容器中的内存泄露

将一些对象的引用加入到了集合容器(比如ArrayList)中,当不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,情况会更严重

解决方法:在退出程序之前,将集合里的东西clear,然后置为null,再退出程序

(8)WebView造成的泄露

当不使用WebView对象时没有对其进行销毁,其长期占用的内存不能被回收,从而造成内存泄露

解决方法:

  • 避免在xml布局文件中直接嵌套webview控件,而是采用addview的方式new一个webview并加载到布局中,上下文变量使用ApplicationContext
  • 当activity生命周期结束时及时销毁/释放资源

21、LruCache默认缓存大小

基本上设置为手机内存的1/8

22、如何通过广播拦截和abort一条短信

在Manifest清单文件中对信号进行注册,当接收到Intent发出的广播和注册的信息匹配时,广播就会启动并截获信息

23、广播是否可以请求网络

不可以,广播默认在主线程中

24、Activity栈

Task任务栈也叫BackStack回退栈,是后进先出栈,在一个Task栈里一直按Back键,就会不断回退到上一个Activity(包括其他App的Activity),直到回退至Home。

对Task任务栈的管理,主要包括Activity启动模式和IntentFlag创建方式两部分

25、Android线程有没有上限

理论上是没有的,但是内存分配没了就不在创建线程了

Android系统会给每个应用分配一个内存空间(不同的系统分配的内存大小不同),这块内存空间大小是有限的。创建线程需要占用内存空间,不可能拿有限的内存空间创建无限的线程。如果应用创建线程的数量过多,而没有及时释放会导致OOM

26、线程池有没有上限

Android线程池有上限,可限根据需要分配

一般认为线程池的大小应该这样设置:(其中N为CPU的核数)

如果是CPU密集型应用,则线程池大小设置为N+1

如果是IO密集型应用,则线程池大小设置为2N+1

I/O bound指的是系统的CPU效能相对硬盘/内存的效能要好很多,此时,系统运作,大部分的状况是CPU在等I/O (硬盘/内存) 的读/写,此时CPU Loading不高

CPU bound指的是系统的硬盘/内存效能相对CPU的效能要好很多,此时,系统运作,大部分的状况是CPU Loading100%,CPU要读/写 I/O (硬盘/内存),I/O在很短的时间就可以完成,而CPU还有许多运算要处理,CPU Loading很高

如果一台设备上只部署这一个应用并且只有这一个线程池,这种估算或许合理,但是IO优化中,(线程等待时间与线程CPU时间之比+1)*CPU核数这种估算可能更合适

线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程

Android应用属于IO密集型应用,单应用单线程池一般设置为2N+1,多应用多线程池设置为(线程等待时间与线程CPU时间之比+1)*CPU

26、ListView重用的是什么

ListView将可视的View和非可视的View保存在activeViews和scrapViews两个数据结构中,activeViews是指,当前屏幕上显示的显示的View。scrapView是指,屏幕上不显示的View,所有activeViews都会转换成scrapViews。重用是把scrapView传给Adapter.getView, 参数convertView不为空就是从scrapView拿出的View

27、Android为什么引入Parcelable

Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC,且在使用内存的时候,Parcelable比Serializable性能高,所以Android引入Parcelable来处理内存的序列化

28、尝试简化Parcelable的使用

(1)创建通过反射读写bean的工具类

  • parcel.getClass()获取类的字节码文件对象
  • xxx.getDeclaredFields()获取该类的所有成员变量
  • 遍历成员变量数组,并设置xxx.setAccessible(true)跳过安全访问
  • xxx.getType().toString()判断变量类型,根据变量类型进行转换
  • xxx.writeInt或xxx.set设置读写数据

(2)创建bean类

(3)实现baan的序列化和反序列化(美中不足就是每一个bean都要去实现工具类,没有达到使用一个父类去实现序列化)

(四)开发中常见的一些问题

1、ListView中图片错位的问题是如何产生的

ListView同时使用了convertview的复用机制和异步操作会产生图片错位的问题

假设一种场景,一个ListView一屏显示七个个item,那么在拉出第八个item的时候,事实上该item是重复使用了第一个item,也就是说在第一个item从网络中下载图片并最终要显示的时候,其实该item已经不在当前显示区域内了,此时显示的后果将可能在第八个item上输出图像,这就导致了图片错位的问题。

2、混合开发有了解吗

混合开发的App(Hybrid App)就是在一个App中内嵌一个轻量级的浏览器,一部分原生的功能改为Html 5来开发,这部分功能不仅能够在不升级App的情况下动态更新,而且可以在Android或iOS的App上同时运行,让用户体验更好又可以节省开发资源

优点:

(1)兼容多平台

(2)顺利访问手机的多种功能

(3)App Store中可下载(Wen应用套用原生应用的外壳)

(4)可线下使用

缺点:

(1)不确定上线时间

(2)用户体验不如本地应用

(3)性能稍慢(需要连接网络)

3、知道哪些混合开发的方式?说出它们的优缺点和各自使用场景

(1)React Native(facebook开源的基于reactJs的RN)

优点:

  • 能够在Javascript和React的基础上获得完全一致的开发体验,构建世界一流的原生APP
  • 仅需学习一次,即可在任何平台编写

缺点:

  • 初次学习成本高
  • 必须在不同平台下写两套代码,依赖暴露的接口

(2)阿里weex(阿里巴巴开源的基于Vue.js)

优点:

  • Weex的结构是解耦的,渲染引擎与语法层是分开的,也不依赖任何特定的前端框架
  • 目前主要支持Vue.jsRax这两个前端框架
  • 渲染Weex页面时和渲染原生页面一样
  • 一套基础的内置组件
  • 不需要安装复杂的环境,运行环境简洁,调试工具也还好,容易做降级处理,特别适合开发单个页面

缺点:

  • 文档更新不及时,资料不多
  • 坑比较多,前端

(3)谷歌Flutter(Flutter是谷歌的移动UI框架,Dart语言)

优点:

  • 快速开发,Flutter的热重载可帮助快速地进行测试、构建UI、添加功能并更快地修复错误。在iOS和Android模拟器或真机上可以在亚秒内重载,并且不会丢失状态
  • UI界面比较漂亮,使用Flutter内置的Material Design和Cupertino(iOS风格)widget、丰富的motion API、平滑而自然的滑动效果和平台感知,为用户带来全新体验
  • 原生性能比较好

缺点:

  • 脱离不开原生,开发人员需要具备原生(Android、iOS)基础开发能力
  • Widget的类型难以选择,糟糕的UI控件API
  • 代码可读性较差,对代码质量和管理要求较高

4、屏幕适配的处理技巧都有哪些

(1)图片适配

该方法不常用,当图片在不同屏幕手机上出现偏差、失侦等情况,让UI多切几套图,放在res目录下的drawable-hdpi、drawable-mdpi、drawable- xhdpi、drawable-xxhdpi、drawable-xxxhdpi中,这几个目录分别对应不同的屏幕,当不同屏幕启动App时候会优先去访问其对应的目录下的图片资源,如果对应下面没有才回去访问其他的目录

(2)布局适配

该方法不常用,其原理和图片适配差不多,就是当不同屏幕上同一个布局文件不能满足需求,比如大屏幕需要做特殊显示这种,此时就在res目录下新建一个文件夹layout-800x480(适配480*800的屏幕,其他屏幕同样方法新建即可),然后在里面去对进行指定的设置布局文件了

(3)尺寸适配

在一些需求中,不同屏幕需要设置不同的尺寸,此时可以新建一个values-(分辨率)这样的目录,在其下面有一个dimens文件(在原values下的dimens也要设置),在里面进行尺寸设置然后再布局里面调用即可,手机访问App当调用values里面的文件时会优先调优和自己对应的文件

(4)权重适配

LinearLayout和ConstraintLayout才能够设置权重。在里面的空间分配权重,这样不管在什么手机,都是按照权重比例来进行显示的

(5)代码适配

代码适配类似于权重适配,权重适配可以用代码适配来实现,但是代码适配就不一定能够用权重适配来实现,比如相对布局不能用权重,就可以代码来控制。实现原理是:计算屏幕宽高,动态的来设置控件宽高。一般根据比例去计算,比如侧滑菜单和主界面分别的比例,此时就可以这样动态去计算

5、服务器只提供数据接收接口,在多线程或多进程条件下,如何保证数据的有序到达

(1)多线程要使用同步,多进程要使用进程通信保障数据传输的顺序

(2)使用TCP可靠传输,在传输的包中加入顺序标识,保证服务器返回传输成功后才能传输下一个

6、怎么去除重复代码

(1)定义一个基Activity或Fragment

对于Activity或者Fragment,抽取基类BaseActivity、BaseFragment,在基类中抽取一些所有子类都需要用的方法,或者在基类里面做一些项目特色的事情。在一个基类里面改动总比把每个Activity或Fragment里面都改一遍要好的多

(2)抽取相同部分

  • 能够抽取基类的抽取基类,让所有的子类可以同时使用
  • 能够抽取工具类的抽取工具类,让整个项目的所有类都可以随时随地调用
  • 能够抽取方法的抽取方法,让本类中可以重复的调用,而不用在一个类中写大量重复的代码

(3)用include减少局部布局的重复

对于xml布局文件,在多个页面中同时出现的、可以重复利用的,就单独抽取出来,使用include标签引入,作用是把另外一个布局文件全部无修改式的嵌入到标签所在的位置

(4)用ViewStub减少整体的布局的重复

ViewStub是一个轻量级别的,不可见的View,当ViewStub被设为visible时,或者显示调用layout()时,才会去把它所指向的布局渲染出来,所以它非常适合处理整体相同,局部不同的情况

(5)多用引用而不是写死

在使用资源文件的时候,尽可能的使用引用,这样非常易于复用,修改和定制

7、Android的系统架构及各层介绍

(1)LINUX KERNEL(Linux内核)

  • Android是基于Linux内核开发的
  • Linux提供了安全管理、内存管理、进程管理等服务

(2)LIBRARIES和ANDROID RUNTIME(系统库和Android运行时)

  • 系统库是一个C/C++库的集合,包含OpenGL,SQlite等,在开发过程中,开发者通过框架层来调用这些库
  • Android虚拟机位于Android运行时

(3)APPLICATION FRAMEWORK(框架层)

  • 框架层提供了日常开发所用的API
  • 包管理器、内容提供者等位于此层

(4)APPLICATIONS(应用程序层)

包含了一些原生应用程序,如日历、短信等

8、Recycleview和ListView的区别

(1)ListView布局单一,RecycleView可以根据LayoutManger有横向,瀑布和表格布局

(2)自定义适配器中,ListView的适配器继承ArrayAdapter,RecycleView的适配器继承RecyclerAdapter,并将范类指定为子项对象类ViewHolder(内部类)

(3)ListView优化需要自定义ViewHolder和判断convertView是否为null, 而RecyclerView是存在规定好的ViewHolder

(4)绑定事件的方式不同,ListView是在setOnItemClickListener方法中注册事件,RecyclerView则是在子项具体的View中去注册事件

9、动态权限适配方案,权限组的概念

动态授权方案将系统权限进行了分级。普通权限的授权方式跟Android6.0版本之前一样,在Manifest文件中声明,用户在安装或者更新软件过程中一次性实现授权。危险权限授权方案需要在Manifest文件中声明,在需要使用的地方,通过调用官方提供的Api主动申请

普通权限:不会直接给用户隐私权带来风险。如果应用在其清单中列出了普通权限,系统将会自动授予该权限

危险权限:会授予应用访问用户机密数据的权限。如果应用在其清单中列出了危险权限,则用户必须明确批准应用使用这些权限

权限组:官方将危险权限根据涉及操作的不同进行了分组

  • 涉及读写联系人,访问账户
  • 涉及电话操作
  • 涉及日历信息的操作(用户日程安排)
  • 涉及相机操作
  • 涉及使用手机传感器操作
  • 涉及用户地理位置信息的操作
  • 涉及存储卡的读写操作
  • 涉及多媒体信息的操作
  • 涉及SMS卡的操作

动态权限适配方案

(1)检查权限:ContextCompat.checkSelfPermission()

如果应用中需要危险权限,则每次执行需要这一权限的操作时,都必须检查是否具有该权限

(2)申请权限:ContextCompat.requestPermissions()

如果应用需要Manifest清单中的危险权限,那么必须要求用户授予该权限

(3)处理申请结果:onRequestPermissionResult()

需要在Activity中复写该方法。并且对用户操作的反馈做处理

10、下拉状态栏是不是影响Activity的生命周期

下拉状态栏不会影响Activity的生命周期

Activity生命周期的变化,必须至少有一个Activity参与,状态栏不属于Activity

11、如果在onStop的时候做了网络请求,onResume的时候怎么恢复

网络请求暂停后恢复:

onStop的时候被暂停,onStart的时候重新检测恢复请求即可

恢复页面请求后刷新页面数据:

(1)Activity被销毁,那么使用onSaveInstanceState存储数据,onRestoreInstanceState()恢复数据

(2)Activity没有被销毁,那就不需要恢复

12、Bitmap使用时候注意什么

(1)要使用合适的图片规格(Bitmap类型)

(2)降低采样率

(3)复用内存

(4)及时回收

(5)压缩图片

(6)尽量不要使用setImageBitmap或setImageResource或BitmapFactory.decodeResource来设置一张大图,因为这些函数在完成decode后,最终都会通过java层调用createBitmap来完成,需要消耗更多的内存,可以通过BitmapFactory.decodeStream方法,创建出一个Bitmap,再将其设置成ImageView的source

13、Bitmap的recycler()

Android有自己的垃圾回收机制,如果只使用了少量的图片,回收与否关系不大。可是若有大量的bitmap需要垃圾回收,那么垃圾回收的次数过于频繁,会造成系统资源负荷,这时候还是调用recycler()比较好

14、Android中开启摄像头的主要步骤

(1)AndroidManifest中配置Camera权限

(2)检查是否有摄像头,并动态申请Camera权限

(3)调用open()获取Camera实例

(4)拍摄照片

(5)调用release()释放资源

15、ViewPager使用细节,如何设置成每次只初始化当前的Fragment,其他的不初始化

采用懒加载的方式。

在页面进入前台的时候,系统会调用setUserVisiableHint()方法,通过这个方法,可以获得当前页面是否对用户可见,并在对用户可见的时候进行初始化。setUserVisiableHint()方法的调用并不保证View等需要的东西已经初始化成功,所以需要再判断一下

16、点击事件被拦截,但是想传到下面的View,如何操作

(1)自定义重写父控件onInterceptTouchEvent()方法并返回false

(2)自定义重写子控件的dispatchTouchView()方法,在方法中调用requestDisallowInterceptTouchEvent()方法,即可阻止父控件对子控件点击事件的拦截,为子控件单独设置点击事件的响应

17、微信主页面的实现方式

Fragment嵌套

18、微信上消息小红点的原理

可以是UI设计是切出的带有小红点的图,也可以自定义View绘制小红点

当有新消息进入是,在特定消息位置刷新小红点UI

19、CAS介绍

CAS:比较并替换

CAS机制中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。

更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B(线程1想要将内存地址V中的值改为B,在线程1提交更新之前,另一个线程2将内存地址V中的变量值率先更新,线程1提交更新时,判断旧值A不等于V的实际值,提交请求失败)

CAS属于乐观锁,乐观地认为程序中的并发情况不那么严重,所以让线程不断去重试更新

缺点:

(1)CPU开销过大

(2)不能保证代码块的原子性

(3)ABA问题