中级Android研发,面试一般都问些什么?

android 运行是消息驱动,通过源码 可以看到 ViewRootImpl 中 是先将 TraversalRunnable添加到 Handler 中运行的 之后 才是 View.post()。

ViewRootImpl.class

final class TraversalRunnable implements Runnable {

@Override

public void run() {

doTraversal();

}

}

void doTraversal() {

if (mTraversalScheduled) {

mTraversalScheduled = false;

mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

// 该方法之后才有 view.post()

performTraversals();

}

}

因此,这个时候Handler正在执行着TraversalRunnable这个Runnable,而我们post的Runnable要等待TraversalRunnable执行完才会去执行,而TraversalRunnable这里面又会进行measure,layout和draw流程,所以等到执行我们的Runnable时,此时的View就已经被measure过了,所以获取到的宽高就是measure过后的宽高。

六、动画

================================================================

帧动画 :AnimationDrawable 实现,在资源文件中存放多张图片,占用内存多,容易OOM。

补间动画:作用对象只限于 View 视觉改变,并没有改变View 的 xy 坐标,支持 平移、缩放、旋转、透明度,但是移动后,响应时间的位置还在 原处,补间动画在执行的时候,直接导致了 View 执行 onDraw() 方法。补间动画的核心本质就是在一定的持续时间内,不断改变 Matrix 变换,并且不断刷新的过程。

属性动画 :ObjectAnimator、ValuetAnimator、AnimatorSet 可以是任何View,动画选择也比较多,其中包含 差速器,可以控制动画速度,节奏。类型估值器 可以根据当前属性改变的百分比计算改变后的属性值 。因为ViewGroup 在 getTransformedMotionEvent方法中通过子 View 的 hasIdentityMatrix() 来判断子 View 是否经过位移之类的属性动画。调用子 View 的 getInverseMatrix() 做「反平移」操作,然后判断处理后的触摸点是否在子 View 的边界范围内。

提升动画 可以打开 硬件加速,使GPU 承担一部分CPU的工作。

七、Android 进程通讯方式

============================================================================

a、bundle :由于Activity,Service,Receiver都是可以通过Intent来携带Bundle传输数据的,所以我们可以在一个进程中通过Intent将携带数据的Bundle发送到另一个进程的组件。(bundle只能传递三种类型,一是键值对的形式,二是键为String类型,三是值为Parcelable类型)

b、ContentProvider :ContentProvider是Android四大组件之一,以表格的方式来储存数据,提供给外界,即Content Provider可以跨进程访问其他应用程序中的数据。

c、文件 :两个进程可以到同一个文件去交换数据,我们不仅可以保存文本文件,还可以将对象持久化到文件,从另一个文件恢复。要注意的是,当并发读/写时可能会出现并发的问题。

d、Broadcast :Broadcast可以向android系统中所有应用程序发送广播,而需要跨进程通讯的应用程序可以监听这些广播。

e、AIDL :AIDL通过定义服务端暴露的接口,以提供给客户端来调用,AIDL使服务器可以并行处理。

f、Messager :Messenger封装了AIDL之后只能串行运行,所以Messenger一般用作消息传递。

g、Socket

八、Android 线程通信

==========================================================================

HandlerAsyncTask (AsyncTask:异步任务,内部封装了Handler)

Handler线程间通信

作用

线程之间的消息通信

流程

主线程默认实现了Looper (调用loop.prepare方法 向sThreadLocal中set一个新的looper对象, looper构造方法中又创建了MsgQueue) 手动创建Handler ,调用 sendMessage 或者 post (runable) 发送Message 到 msgQueue ,如果没有Msg 这添加到表头,有数据则判断when时间 循环next 放到合适的 msg的next 后。Looper.loop不断轮训Msg,将msg取出 并分发到Handler 或者 post提交的 Runable 中处理,并重置Msg 状态位。回到主线程中 重写 Handler 的 handlerMessage 回调的msg 进行主线程绘制逻辑。

问题

  1. Handler 同步屏障机制:通过发送异步消息,在msg.next 中会优先处理异步消息,达到优先级的作用。

  2. Looper.loop 为什么不会卡死:为了app不挂掉,就要保证主线程一直运行存在,使用死循环代码阻塞在msgQueue.next()中的nativePollOnce()方法里 ,主线程就会挂起休眠释放cpu,线程就不会退出。Looper死循环之前,在ActivityThread.main()中就会创建一个 Binder 线程(ApplicationThread),接收系统服务AMS发送来的事件。当系统有消息产生(其实系统每 16ms 会发送一个刷新 UI 消息唤醒)会通过epoll机制 向pipe管道写端写入数据 就会发送消息给 looper 接收到消息后处理事件,保证主线程的一直存活。只有在主线程中处理超时才会让app崩溃 也就是ANR。

  3. Messaage复用:将使用完的Message清除附带的数据后, 添加到复用池中 ,当我们需要使用它时,直接在复用池中取出对象使用,而不需要重新new创建对象。复用池本质还是Message 为node 的单链表结构。所以推荐使用Message.obation获取 对象。

九、Android 和WebView 通信

=================================================================================

js调用android

// myObj 为在js中使用的对象名称 JavaScriptInterfaces 是我们自定义的一个类。

webView.addJavascriptInterface(new JavaScriptInterfaces(), “myObj”);

myObj.xx() //js方法

android 调用js

无参数:

mWebView.loadUrl(“javascript:wave()”);

有参数:

webView.evaluateJavascript(String.format(“javascript:callH5Re(‘测试数据’)”), new ValueCallback() {

@Override

public void onReceiveValue(String value) {

Log.e(“Test”, "onReceiveValue: "+value );

} });

十、app优化 (项目中处理的一些难点)

================================================================================

主要分为 启动优化,布局优化 ,打包优化 等。

启动优化

  1. 闪屏页 优化,设置theme 默认欢迎背景。

  2. 懒加载 第三方库,不要都放在application 中初始化。

  3. 如果项目中有 webview ,可以提前在app空闲时间加载 webview 的内核,如果多处使用 可以创建缓存池,缓存webview。

  4. 如果android 5.0- 在applicaton 的 attchbaseContext() 中加载MultiDex.install 会更加耗时,可以采用 子线程(子线程加载 需要担心ANR 和ContentProvider 未加载报错的问题)或者单独开一个进程B,进程B开启子线程运行MultiDex.install ,让applicaton 进入while 循环等待B进程加载结果。

MultiDex 优化,apk打包分为 android 5.0 + 使用 ART虚拟机 不用担心

布局UI优化

看过布局绘制源码流程后,可以知道 setContextView中 在ViewRootImpl 中使用 pull 的方法(这里可以扩展xml读取方式 SAX :逐行解析、dom:将整个文件加载到内存 然后解析,不推荐、pull:类似于 SAX 进行了android平台的优化,更加轻量级 方便)迭代读取 xml标签,然后对view 进行 measure,layout 和draw 的时候都存在耗时。通常优化方式有:

  1. 减少UI层级、使用merge、Viewstub标签 优化重复的布局。

  2. 优化 layout ,尽量多使用ConstraintLayout,因为 relalayout 和 linearlayout 比重的情况下都存在多次测量。

  3. recyclerView 缓存 。( 可扩展 说明 rv的缓存原理 )

  4. 比较极端的 将 measure 和 layout 放在子线程,在主线程进行draw。或者 子线程中 加载view 进行IO读取xml,通过Handler 回调主线程 加载view。(比如android 原生类 AsyncLayoutInflate )

  5. 将xml直接通过 第三方工具(原理 APT 注解 翻译xml)直接将xml 转为 java代码。

打包优化

Analyze APK 后可以发现代码 和 资源其实是 app包的主要内存。

1、res 文件夹下 分辨率下的图片 国内基本提供 xxhdpi 或者 xhdpi 即可,android 会分析手机分辨率到对应分辨率文件夹下加载资源。

2、res中的 png 图片 都可以转为 webg 或者 svg格式的 ,如果不能转 则可以通过 png压缩在减少内存。

3、通过在 build.gradle 中配置 minifyEnabled true(混淆)shrinkResources true 。(移除无用资源)

4、Assests 中的 mp4 /3 可以在需要使用的时候从服务器上下载下来,字体文件 使用字体提取工具FontZip 删除不用的文字格式,毕竟几千个中文app中怎么可能都使用。

5、lib 包如果 适配机型大多为高通 RAM ,可以单独引用abiFilters “armeabi-v7a”。

6、build文件中 resConfigs “zh” 剔除掉 官方中或者第三方库中的 外国文字资源。

十一、第三方库 源码总结

========================================================================

LeakCanary 原理

通过 registerActivityLifecycleCallbacks 监听Activity或者Fragment 销毁时候的生命周期。(如果不想那个对象被监控则通过 AndroidExcludedRefs 枚举,避免被检测)

public void watch(Object watchedReference, String referenceName) {

if (this == DISABLED) {

return;

}

checkNotNull(watchedReference, “watchedReference”);

checkNotNull(referenceName, “referenceName”);

final long watchStartNanoTime = System.nanoTime();

String key = UUID.randomUUID().toString();

retainedKeys.add(key);

final KeyedWeakReference reference =

new KeyedWeakReference(watchedReference, key, referenceName, queue);

ensureGoneAsync(watchStartNanoTime, reference);

}

然后通过弱引用和引用队列监控对象是否被回收。(弱引用和引用队列ReferenceQueue联合使用时,如果弱引用持有的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。即 KeyedWeakReference持有的Activity对象如果被垃圾回收,该对象就会加入到引用队列queue)

void waitForIdle(final Retryable retryable, final int failedAttempts) {

// This needs to be called from the main thread.

Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {

@Override public boolean queueIdle() {

postToBackgroundWithDelay(retryable, failedAttempts);

return false;

}

});

}

IdleHandler,就是当主线程空闲的时候,如果设置了这个东西,就会执行它的queueIdle()方法,所以这个方法就是在onDestory以后,一旦主线程空闲了,就会执行一个延时五秒的子线程任务,任务:检测到未被回收则主动 gc ,然后继续监控,如果还是没有回收掉,就证明是内存泄漏了。通过抓取 dump文件,在使用 第三方 HAHA 库 分析文件,获取到到达泄露点最近的线路,通过 启动另一个进程的 DisplayLeakService 发送通知 进行消息的展示。

OkHttp

同步和异步 网络请求使用方法

// 同步get请求

OkHttpClient okHttpClient=new OkHttpClient();

final Request request=new Request.Builder().url(“xxx”).get().build();

final Call call = okHttpClient.newCall(request);

try {

Response response = call.execute();

} catch (IOException e) {

}

//异步get请求

OkHttpClient okHttpClient=new OkHttpClient();

final Request request=new Request.Builder().url(“xxx”).get().build();

final Call call = okHttpClient.newCall(request);

call.enqueue(new Callback() {

@Override

public void onFailure(Call call, IOException e) {

}

@Override

public void onResponse(Call call, Response response) throws IOException {

}

});

// 异步post 请求

OkHttpClient okHttpClient1 = new OkHttpClient();

RequestBody requestBody = new FormBody.Builder()

.add(“xxx”, “xxx”).build();

Request request1 = new Request.Builder().url(“xxx”).post(requestBody).build();

okHttpClient1.newCall(request1).enqueue(new Callback() {

@Override

public void onFailure(Call call, IOException e) {

}

@Override

public void onResponse(Call call, Response response) throws IOException {

}

});

同步请求流程:

通过OkHttpClient new生成call实例 Realcall。

Dispatcher.executed() 中 通过添加realcall到runningSyncCalls队列中。

通过 getResponseWithInterceptorChain() 对request层层拦截,生成Response。

通过Dispatcher.finished(),把call实例从队列中移除,返回最终的response。

异步请求流程:

生成一个AsyncCall(responseCallback)实例(实现了Runnable)。

AsyncCall通过调用Dispatcher.enqueue(),并判断maxRequests (最大请求数)maxRequestsPerHost(最大host请求数)是否满足条件,如果满足就把AsyncCall添加到runningAsyncCalls中,并放入线程池中执行;如果条件不满足,就添加到等待就绪的异步队列,当那些满足的条件的执行时 ,在Dispatcher.finifshed(this)中的promoteCalls();方法中 对等待就绪的异步队列进行遍历,生成对应的AsyncCall实例,并添加到runningAsyncCalls中,最后放入到线程池中执行,一直到所有请求都结束。

责任链模式 和 拦截器

责任链:

源码跟进 execute() 进入到 getResponseWithInterceptorChain() 方法。

Response getResponseWithInterceptorChain() throws IOException {

//责任链 模式

List 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, this, eventListener, client.connectTimeoutMillis(),

client.readTimeoutMillis(), client.writeTimeoutMillis());

return chain.proceed(originalRequest);

}

chain.proceed() 方法核心代码。每个拦截器 intercept()方法中的chain,都在上一个 chain实例的 chain.proceed()中被初始化,并传递了拦截器List与 index,调用interceptor.intercept(next),直接最后一个 chain实例执行即停止。

//递归循环下一个 拦截器

RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,

connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,

writeTimeout);

Interceptor interceptor = interceptors.get(index);

Response response = interceptor.intercept(next);

//递归循环下一个 拦截器

RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,

connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,

writeTimeout);

Interceptor interceptor = interceptors.get(index);

Response response = interceptor.intercept(next);

@Override public Response intercept(Chain chain) throws IOException {

Request request = chain.request();

RealInterceptorChain realChain = (RealInterceptorChain) chain;

Call call = realChain.call();

EventListener eventListener = realChain.eventListener();

StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),

createAddress(request.url()), call, eventListener, callStackTrace);

while (true) {

// 循环中 再次调用了 chain 对象中的 proceed 方法,达到递归循环。

response = realChain.proceed(request, streamAllocation, null, null);

releaseConnection = false;

}

}

拦截器:

  1. RetryAndFollowUpInterceptor :重连并跟踪 拦截器。

  2. BridgeInterceptor : 将用户请求构建为网络请求(hander cooker content-type 等) 并发起请求 。

  3. CacheInterceptor :缓存拦截器 负责从缓存中返回响应和把网络请求响应写入缓存。

  4. ConnectInterceptor :与服务端 建立连接,并且获得通向服务端的输入和输出流对象。

OkHttp 流程

  1. 采用责任链方式的拦截器,实现分成处理网络请求,可更好的扩展自定义拦截器。(采用GZIP压缩,支持http缓存)

  2. 采用线程池(thread pool)和连接池(Socket pool)解决多并发问题,同时连接池支持多路复用。(http2才支持,可以让一个Socket同时发送多个网络请求,内部自动维持顺序.相比http只能一个一个发送,更能减少创建开销))

  3. 底层采用socket和服务器进行连接.采用okio实现高效的io流读写。

ButterKnife

butterKnife 使用的是 APT 技术 也就是编译时注解,不同于运行时注解(在运行过程中通过反射动态地获取相关类,方法,参数等信息,效率低耗时等缺点),编译时注解 则是在代码编译过程中对注解进行处理(annotationProcessor技术),通过注解获取相关类,方法,参数等信息,然后在项目中生成代码,运行时调用,其实和直接手写代码一样,没有性能问题,只有编辑时效率问题。

ButterKnife在Bind方法中 获取到DecorView,然后通过Activity和DecorView对象获取xx_ViewBinding类的构造对象,然后通过构造方法反射实例化了这个类 Constructor。

在编写完demo之后,需要先build一下项目,之后可以在build/generated/source/apt/debug/包名/下面找到 对应的xx_ViewBinding类,查看bk 帮我们做的事情。

xx_ViewBinding.java

@UiThread

public ViewActivity_ViewBinding(ViewActivity target, View source) {

this.target = target;

target.view = Utils.findRequiredView(source, R.id.view, “field ‘view’”);

}

Utils.java

public static View findRequiredView(View source, @IdRes int id, String who) {

View view = source.findViewById(id);

if (view != null) {

return view;

}

String name = getResourceEntryName(source, id);

throw new IllegalStateException(“Required view …”)

}

通过上述上述代码 可以看到 注解也是帮我们完成了 findviewbyid 的工作。

butterknife 实现流程

  1. 扫描Java代码中所有的ButterKnife注解。

  2. 发现注解, ButterKnifeProcessor会帮你生成一个Java类,名字<类名>$$ViewBinding.java,这个新生成的类实现了Unbinder接口,类中的各个view 声明和添加事件都添加到Map中,遍历每个注解对应通过JavaPoet生成的代码。

未来

Gradle插件升级到5.0版本之后ButterKnife将无法再被使用,R文件中的 id将添加final标识符,虽然 jake大神通过生成R2文件的方式,尝试避开版本升级带来的影响。但是随着官方ViewBinding等技术的出现。

我们这里说下 被final修饰的基础类型和String类型为什么不能被反射?

答:由于JVM 内联优化的机制,编译器将指定的函数体插入并取代每一处调用该函数的地方(就是在方法编译前已经进行了赋值),从而节省了每次调用函数带来的额外时间开支。

最后

==============================================================

在这里我分享一份由多位大佬亲自收录整理的Android学习PDF+架构视频+面试文档+源码笔记高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料

这些都是我现在闲暇时还会反复翻阅的精品资料。里面对近几年的大厂面试高频知识点都有详细的讲解。相信可以有效地帮助大家掌握知识、理解原理,帮助大家在未来面试取得一份不错的答卷。

当然,你也可以拿去查漏补缺,提升自身的竞争力。

作者2013年从java开发,转做Android开发,在小厂待过,也去过华为,OPPO等大厂待过,18年四月份进了阿里一直到现在。

参与过不少面试,也当面试官 面试过很多人。深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长,而且极易碰到天花板技术停滞不前!

我整理了一份阿里P7级别的最系统的Android开发主流技术,特别适合有3-5年以上经验的小伙伴深入学习提升。

主要包括阿里,以及字节跳动,腾讯,华为,小米,等一线互联网公司主流架构技术。如果你想深入系统学习Android开发,成为一名合格的高级工程师,可以收藏一下这些Android进阶技术选型

我搜集整理过这几年阿里,以及腾讯,字节跳动,华为,小米等公司的面试题,把面试的要求和技术点梳理成一份大而全的“ Android架构师”面试 Xmind(实际上比预期多花了不少精力),包含知识脉络 + 分支细节。

Java语言与原理;
大厂,小厂。Android面试先看你熟不熟悉Java语言

高级UI与自定义view;
自定义view,Android开发的基本功。

性能调优;
数据结构算法,设计模式。都是这里面的关键基础和重点需要熟练的。

NDK开发;
未来的方向,高薪必会。

前沿技术;
组件化,热升级,热修复,框架设计

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

我在搭建这些技术框架的时候,还整理了系统的高级进阶教程,会比自己碎片化学习效果强太多,CodeChina上可见;

当然,想要深入学习并掌握这些能力,并不简单。关于如何学习,做程序员这一行什么工作强度大家都懂,但是不管工作多忙,每周也要雷打不动的抽出 2 小时用来学习。

不出半年,你就能看出变化!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

[外链图片转存中…(img-ADBY02Eb-1714993675475)]

Java语言与原理;
大厂,小厂。Android面试先看你熟不熟悉Java语言

[外链图片转存中…(img-2AOx4won-1714993675477)]

高级UI与自定义view;
自定义view,Android开发的基本功。

[外链图片转存中…(img-tcmzB8oY-1714993675478)]

性能调优;
数据结构算法,设计模式。都是这里面的关键基础和重点需要熟练的。

[外链图片转存中…(img-WAv61Ikc-1714993675479)]

NDK开发;
未来的方向,高薪必会。

[外链图片转存中…(img-bysMP06o-1714993675480)]

前沿技术;
组件化,热升级,热修复,框架设计

[外链图片转存中…(img-0aORzQMZ-1714993675481)]

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

我在搭建这些技术框架的时候,还整理了系统的高级进阶教程,会比自己碎片化学习效果强太多,CodeChina上可见;

当然,想要深入学习并掌握这些能力,并不简单。关于如何学习,做程序员这一行什么工作强度大家都懂,但是不管工作多忙,每周也要雷打不动的抽出 2 小时用来学习。

不出半年,你就能看出变化!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

  • 12
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值