写点什么

一次违反常规的大厂 OPPO 面试经历(文末有面试答案领取)

用户头像
Android架构
关注
发布于: 2021 年 11 月 05 日

/**??????Create?a?helper?object?to?create,?open,?and/or?manage?a?database.??????This?method?always?returns?very?quickly.??The?database?is?not?actually??????created?or?opened?until?one?of?{@link?#getWritableDatabase}?or??????{@link?#getReadableDatabase}?is?called.???????????@param?context?to?use?to?open?or?create?the?database??????@param?name?of?the?database?file,?or?null?for?an?in-memory?database??????@param?factory?to?use?for?creating?cursor?objects,?or?null?for?the?default??????@param?version?number?of?the?database?(starting?at?1);?if?the?database?is?older,??????????{@link?#onUpgrade}?will?be?used?to?upgrade?the?database;?if?the?database?is??????????newer,?{@link?#onDowngrade}?will?be?used?to?downgrade?the?database?????/????public?SQLiteOpenHelper(Context?context,?String?name,?CursorFactory?factory,?int?version)?{????????this(context,?name,?factory,?version,?null);????}????public?SQLiteDatabase?getWritableDatabase()?{????????synchronized?(this)?{????????????return?getDatabaseLocked(true);????????}????}??private?SQLiteDatabase?getDatabaseLocked(boolean?writable)?{??????.......??????db.beginTransaction();??????try?{??????????????if?(version?==?0)?{???????????????????onCreate(db);??????????????}?else?{???????????????????if?(version?>?mNewVersion)?{?????????????????????????onDowngrade(db,?version,?mNewVersion);???????????????????}?else?{?????????????????????????onUpgrade(db,?version,?mNewVersion);???????????????????}??????????????}???????????????db.setVersion(mNewVersion);????????????????db.setTransactionSuccessful();??????????????}?finally?{?????????????????db.endTransaction();??????????????}??}


在 SQLiteOpenHelper 的构造函数中,包含了一个 version 的参数。这个参数即是数据库的版本。 所以,我们可以通过修改 version 来实现数据库的升级。 当 version 大于原数据库版本时,onUpgrade()会被触发,可以在该方法中编写数据库升级逻辑。具体的数据库升级逻辑示例可参考这里。常用的 SQL 增删改查:


增:INSERT INTO table_name (列 1, 列 2,…) VALUES (值 1, 值 2,….)删: DELETE FROM 表名称 WHERE 列名称 = 值改:UPDATE 表名称 SET 列名称 = 新值 WHERE 列名称 = 某值查:SELECT 列名称(通配是*符号) FROM 表名称


ps:操作数据表是:ALTER TABLE。该语句用于在已有的表中添加、修改或删除列。ALTER TABLE table_name ADD column_name datatypeALTER TABLE table_name DROP COLUMN column_nameALTER TABLE table_name_old RENAME TO table_name_new

Android 方面

1.Broadcast 的分类?有序,无序?粘性,非粘性?本地广播?


  • 广播可以分为有序广播、无序广播、本地广播、粘性广播。其中无序广播通过 sendBroadcast(intent)发送,有序广播通过 sendOrderedBroadcast(intent)发送。

  • 有序广播。(1) 有序广播可以用 priority 来调整优先级 ? 取值范围-1000~+1000,默认为 0,数值越大优先级越高,优先级越高越优先获得广播响应。(2) abortBroadcast()可来终止该广播的传播,对更低优先级的屏蔽,注意只对有序广播生效。(3) 有序广播在传播数据中会发生比如 setResul


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


tData(),getResultData(),在传播过程中,可以从新设置数据


  • 关于本地广播,可以查看这篇文章。总的来说,本地广播是通过 LocalBroadcastManager 内置的 Handler 来实现的,只是利用了 IntentFilter 的 match 功能,至于 BroadcastReceiver 换成其他接口也无所谓,顺便利用了现成的类和概念而已。在 register()的时候保存 BroadcastReceiver 以及对应的 IntentFilter,在 sendBroadcast()的时候找到和 Intent 对应的 BroadcastReceiver,然后通过 Handler 发送消息,触发 executePendingBroadcasts()函数,再在后者中调用对应 BroadcastReceiver 的 onReceive()方法。

  • 粘性消息:粘性消息在发送后就一直存在于系统的消息容器里面,等待对应的处理器去处理,如果暂时没有处理器处理这个消息则一直在消息容器里面处于等待状态,粘性广播的 Receiver 如果被销毁,那么下次重建时会自动接收到消息数据。(在 android 5.0/api 21 中 deprecated,不再推荐使用,相应的还有粘性有序广播,同样已经 deprecated)


2.Android 中的事件传递机制?当我们的手指触碰到屏幕,事件是按照 Activity->ViewGroup->View 这样的流程到达最终响应触摸事件的 View 的。而在事件分发过程中,涉及到三个最重要的方法:dispatchTouchEvent()、onInterceptTouchEvent()、onTouchEvent。我们的手指触摸到屏幕的时候,会触发一个 Action_Down 类型的事件,当前页面的 Activity 会首先做出相应,也就是说会走到 Activity 的 dispatchTouchEvent()方法内。在这个方法内部有下面两个逻辑:


  • 调用 getWindow.superDispatchTouchEvent()。

  • 如果上一步返回 true,则直接返回 true;否则 return 自己的 onTouchEvent()。显然,当 getWindow.superDispatchTouchEvent()返回 true,表示当前事件已经被消费掉,无需调用 onTouchEvent;否则代表事件并没有被处理,因此需要调用 Activity 的 onTouchEvent 进行处理。我们都知道,getWindow()返回的是 PhoneWindow,因此这句代码本质上调用了 PhoneWindow 中的 superDispatchTouchEvent()。而后者实际上调用了 mDecor.superDispatchTouchEvent(event)。这个 mDecor 也就是 DecorView,它是 FrameLayout 的一个子类。在 DecorView 中的 superDispatchTouchEvent(event)中调用的是 super.dispatchTouchEvent()。因此,本质上调用的是 ViewGroup 的 dispatchTouchEvent()。


到这里,事件已经从 Activity 传递到 ViewGroup 了。接下来我们分析 ViewGroup。在 ViewGroup 的 dispatchTouchEvent()中逻辑大致如下:


  • 通过 onInterceptTouchEvent()判断当前 ViewGroup 是否拦截,默认的 ViewGroup 都是不拦截的;

  • 如果拦截,则 return 自己的 onTouchEvent();

  • 如果不拦截,则根据 child.dispatchTouchEvent()的返回值判断。如果返回 true,则 return true;否则 return 自身的 onTouchEvent(),在这里实现了未处理事件的向上传递。


通常情况下,ViewGroup 的 onInterceptTouchEvent()都返回 false,表示不拦截。这里需要注意的是事件序列,比如 Down 事件、Move 事件…Up 事件,从 Down 到 Up 是一个完整的事件序列,对应着手指从按下到抬起这一系列事件,如果 ViewGroup 拦截了 Down 事件,那么后续事件都会交给这个 ViewGroup 的 onTouchEvent。如果 ViewGroup 拦截的不是 Down 事件,那么会给之前处理这个 Down 事件的 View 发送一个 Action_Cancel 类型的事件,通知子 View 这个后续的事件序列已经被 ViewGroup 接管了,子 View 恢复之前的状态即可。这里举一个常见的例子:在一个 Recyclerview 中有很多的 Button,我们首先按下了一个 button,然后滑动一段距离再松开,这时候 Recyclerview 会跟着滑动,并不会触发这个 button 的点击事件。这个例子中,当我们按下 button 时,这个 button 接收到了 Action_Down 事件,正常情况下后续的事件序列应该由这个 button 处理。但我们滑动了一段距离,这时 ?Recyclerview 察觉到这是一个滑动操作,拦截了这个事件序列,走了自身的 onTouchEvent()方法,反映在屏幕上就是列表的滑动。而这时 button 仍然处于按下的状态,所以在拦截的时候需要发送一个 Action_Cancel 来通知 button 恢复之前状态。事件分发最终会走到 View 的 dispatchTouchEvent()中。在 View 的 dispatchTouchEvent()中没有 onInterceptTouchEvent(),这里很容易理解,View 没有 child,也就不存在拦截。View 的 dispatchTouchEvent()直接 return 了自己的 onTouchEvent()。如果 onTouchEvent()返回 true 代表事件被消费,否则未消费的事件会向上传递,直到有 View 处理了事件或一直没有消费,最终回到 Activity 的 onTouchEvent()终止。有时候会有人混淆 onTouchEvent 和 onTouch。首先,这两个方法都在 View 的 dispatchTouchEvent()中:


  • 如果 touchListener 不为 null,并且这个 View 是 enable 的,而且 onTouch 返回 true,都满足时直接 return true,走不到 onTouchEvent()方法。

  • 否则,就会触发 onTouchEvent()。因此 onTouch 优先于 onTouchEvent 获得事件处理权。


最后附上流程图总结:



参考:https://juejin.im/entry/58df5b33570c35005798493chttps://juejin.im/post/5b8f15e26fb9a01a031b12d9#heading-3


3.Handler 的原理?与 Handler 密切相关的还有 Message、MessageQueue、Looper。


  • Message。Message 有两个关键的成员变量:target、callback:(1) target。就是发送消息的 Handler(2) callback。调用 Handler.post(Runnable)时传入的 Runnable 类型的任务。post 事件的本质也是创建了一个 Message,将我们传入的这个 runnable 赋值给创建的 Message 的 callback 这个成员变量。

  • MessageQueue。消息队列用于存放消息,其中重点关注 next()方法,它会返回下一个待处理的消息。

  • Looper。Looper 消息轮询器其实是连接 Handler 和消息队列的核心。想要在一个线程中创建一个 Handler,首先要通过 Looper.prepare()创建 Looper,之后还得调用 Looper.loop()开启轮询。(1) prepare()。这个方法做了两件事:首先通过 ThreadLocal.get()获取当前线程中的 Looper,如果不为空则抛出 RuntimeException。否则创建 Looper,并通过 ThreadLocal.set(looper)将当前线程与刚刚创建的 Looper 绑定。值得注意的是,上面的消息队列的创建其实就是发生在 Looper 的构造函数中。(2)loop()。这个方法开启了整个事件机制的轮询。其本质是开启一个死循环,不断地通过 MessageQueue 的 next()方法获取消息 msg。拿到消息后会调用 msg.target.dispatchMessage()来做处理。综上也就是调用 handler.dispatchMessage()。

  • Handler。Handler 重点在于发送消息和处理消息。(1)发送消息。其实发送消息除了 sendMessage 之外还有 sendMessageDelayed 和 post 以及 postDelayed 等等不同的方式。但它们的本质都是调用了 sendMessageAtTime。在 sendMessageAtTime 这个方法中调用了 enqueueMessage。在 enqueueMessage 这个方法中做了两件事:通过 msg.target = this 实现了消息与当前 handler 的绑定。然后通过 queue.enqueueMessage 实现了消息入队。(2)处理消息。 消息处理的核心其实就是 dispatchMessage()这个方法。这个方法里面的逻辑很简单,先判断 msg.callback 是否为 null,如果不为空则执行这个 runnable。如果为空则会执行我们的 handleMessage 方法。


4.ANR 出现的情况有几种? 怎么分析解决 ANR 问题?ANR(Application Not responding)。Android 中,主线程(UI 线程)如果在规定时内没有处理完相应工作,就会出现 ANR。具体来说,ANR 会在以下几种情况中出现:(1) 输入事件(按键和触摸事件)5s 内没被处理(2) BroadcastReceiver 的事件(onRecieve 方法)在规定时间内没处理完(前台广播为 10s,后台广播为 60s)(3) service 前台 20s 后台 200s 未完成启动(4) ContentProvider 的 publish 在 10s 内没进行完


分析 ANR 问题,需要结合 Log 以及 trace 文件。具体分析流程,可参照以下两篇文章:https://www.jianshu.com/p/fa962a5fd939https://blog.csdn.net/droyon/article/details/51099826


5.内存泄露的场景有哪些?内存泄漏分析工具使用方法?常见的内存泄露有:


  • 单例模式引起的内存泄露。

  • 静态变量导致的内存泄露。

  • 非静态内部类引起的内存泄露。

  • 使用资源时,未及时关闭引起内存泄露。

  • 使用属性动画引起的内存泄露。

  • Webview 导致的内存泄露。


而对于内存泄露的检测,常用的工具有 LeakCanary、MAT(Memory Analyer Tools)、Android Studio 自带的 Profiler。关于用法,网上教程很多,可自行查阅,下面两个经供参考:

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
一次违反常规的大厂OPPO面试经历(文末有面试答案领取)