攒了一个月的Android面试题及详细解答,年底准备起来,冲刺大厂单车变摩托

onTouchEvent(event);

}

} else {

onTouchEvent(event);

}

if (setOnClickListener) {

onClick();

}

}

解决滑动冲突的办法。

解决滑动冲突的根本就是要在适当的位置进行拦截,那么就有两种解决办法:

  • 外部拦截:从父view端处理,根据情况决定事件是否分发到子view

  • 内部拦截:从子view端处理,根据情况决定是否阻止父view进行拦截,其中的关键就是requestDisallowInterceptTouchEvent方法。

1)外部拦截法,其实就是在onInterceptTouchEvnet方法里面进行判断,是否拦截,见代码:

//外部拦截法:父view.java

@Override

public boolean onInterceptTouchEvent(MotionEvent ev) {

boolean intercepted = false;

//父view拦截条件

boolean parentCanIntercept;

switch (ev.getActionMasked()) {

case MotionEvent.ACTION_DOWN:

intercepted = false;

break;

case MotionEvent.ACTION_MOVE:

if (parentCanIntercept) {

intercepted = true;

} else {

intercepted = false;

}

break;

case MotionEvent.ACTION_UP:

intercepted = false;

break;

}

return intercepted;

}

还是比较简单的,直接判断拦截条件,然后返回true就代表拦截,false就不拦截,传到子view。注意的是ACTION_DOWN状态不要拦截,如果拦截,那么后续事件就直接交给父view处理了,也就没有拦截不拦截的问题了。

2)内部拦截法,就是通过requestDisallowInterceptTouchEvent方法让父view不要拦截。

//父view.java

@Override

public boolean onInterceptTouchEvent(MotionEvent ev) {

if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {

return false;

} else {

return true;

}

}

//子view.java

@Override

public boolean dispatchTouchEvent(MotionEvent event) {

//父view拦截条件

boolean parentCanIntercept;

switch (event.getActionMasked()) {

case MotionEvent.ACTION_DOWN:

getParent().requestDisallowInterceptTouchEvent(true);

break;

case MotionEvent.ACTION_MOVE:

if (parentCanIntercept) {

getParent().requestDisallowInterceptTouchEvent(false);

}

break;

case MotionEvent.ACTION_UP:

break;

}

return super.dispatchTouchEvent(event);

}

requestDisallowInterceptTouchEvent(true)的意思是阻止父view拦截事件,也就是传入true之后,父view就不会再调用onInterceptTouchEvent。反之,传入false就代表父view可以拦截,也就是会走到父view的onInterceptTouchEvent方法。所以需要父view拦截的时候,就传入flase,需要父view不拦截的时候就传入true。

Fragment生命周期,当hide,show,replace时候生命周期变化

1)生命周期:

  • onAttach():Fragment和Activity相关联时调用。可以通过该方法获取Activity引用,还可以通过getArguments()获取参数。

  • onCreate():Fragment被创建时调用。

  • onCreateView():创建Fragment的布局。

  • onActivityCreated():当Activity完成onCreate()时调用。

  • onStart():当Fragment可见时调用。

  • onResume():当Fragment可见且可交互时调用。

  • onPause():当Fragment不可交互但可见时调用。

  • onStop():当Fragment不可见时调用。

  • onDestroyView():当Fragment的UI从视图结构中移除时调用。

  • onDestroy():销毁Fragment时调用。

  • onDetach():当Fragment和Activity解除关联时调用。

每个调用方法对应的生命周期变化:

  • add(): onAttach()->…->onResume()。

  • remove(): onPause()->…->onDetach()。

  • replace(): 相当于旧Fragment调用remove(),新Fragment调用add()。remove()+add()的生命周期加起来

  • show(): 不调用任何生命周期方法,调用该方法的前提是要显示的 Fragment已经被添加到容器,只是纯粹把Fragment UI的setVisibility为true。

  • hide(): 不调用任何生命周期方法,调用该方法的前提是要显示的Fragment已经被添加到容器,只是纯粹把Fragment UI的setVisibility为false。

Activity 与 Fragment,Fragment 与 Fragment之间怎么交互通信。

  • Activity 与 Fragment通信

Activity有Fragment的实例,所以可以执行Fragment的方法,或者传入一个接口。同样,Fragment可以通过getActivity()获取Activity的实例,也是可以执行方法。

  • Fragment 与 Fragment之间通信

1)直接获取另一个Fragmetn的实例

getActivity().getSupportFragmentManager().findFragmentByTag(“mainFragment”);

2)接口回调 一个Fragment里面去实现接口,另一个Fragment把接口实例传进去。

3)Eventbus等框架。

Fragment遇到viewpager遇到过什么问题吗。

  • 滑动的时候,调用setCurrentItem方法,要注意第二个参数smoothScroll。传false,就是直接跳到fragment,传true,就是平滑过去。一般主页切换页面都是用false。

  • 禁止预加载的话,调用setOffscreenPageLimit(0)是无效的,因为方法里面会判断是否小于1。需要重写setUserVisibleHint方法,判断fragment是否可见。

  • 不要使用getActivity()获取activity实例,容易造成空指针,因为如果fragment已经onDetach()了,那么就会报空指针。所以要在onAttach方法里面,就去获取activity的上下文。

  • FragmentStatePagerAdapter对limit外的Fragment销毁,生命周期为onPause->onStop->onDestoryView->onDestory->onDetach, onAttach->onCreate->onCreateView->onStart->onResume。也就是说切换fragment的时候有可能会多次onCreateView,所以需要注意处理数据。

  • 由于可能多次onCreateView,所以我们可以把view保存起来,如果为空再去初始化数据。见代码:

@Override

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

if (null == mFragmentView) {

mFragmentView = inflater.inflate(getContentViewLayoutID(), null);

ButterKnife.bind(this, mFragmentView);

isDestory = false;

initViewsAndEvents();

}

return mFragmentView;

}

ARouter的原理

首先,我们了解下ARouter是干嘛的?ARouter是阿里巴巴研发的一个用于解决组件间,模块间界面跳转问题的框架。所以简单的说,就是用来跳转界面的,不同于平时用到的显式或隐式跳转,只需要在对应的界面上添加注解,就可以实现跳转,看个案例:

@Route(path = “/test/activity”)

public class YourActivity extend Activity {

}

//跳转

ARouter.getInstance().build(“/test/activity”).navigation();

使用很方便,通过一个path就可以进行跳转了,那么原理是什么呢?

其实仔细思考下,就可以联想到,既然关键跳转过程是通过path跳转到具体的activity,那么原理无非就是把path和Activity一一对应起来就行了。没错,其实就是通过注释,通过apt技术,也就是注解处理工具,把path和activity关联起来了。主要有以下几个步骤:

  • 代码里加入的@Route注解,会在编译时期通过apt生成一些存储path和activity.class映射关系的类文件

  • app进程启动的时候会加载这些类文件,把保存这些映射关系的数据读到内存里(保存在map里)

  • 进行路由跳转的时候,通过build()方法传入要到达页面的路由地址,ARouter会通过它自己存储的路由表找到路由地址对应的Activity.class

  • 然后new Intent方法,如果有调用ARouter的withString()方法,就会调用intent.putExtra(String name, String value)方法添加参数

  • 最后调用navigation()方法,它的内部会调用startActivity(intent)进行跳转

ARouter怎么实现页面拦截

先说一个拦截器的案例,用作页面跳转时候检验是否登录,然后判断跳转到登录页面还是目标页面:

@Interceptor(name = “login”, priority = 6)

public class LoginInterceptorImpl implements IInterceptor {

@Override

public void process(Postcard postcard, InterceptorCallback callback) {

String path = postcard.getPath();

boolean isLogin = SPUtils.getInstance().getBoolean(ConfigConstants.SP_IS_LOGIN, false);

if (isLogin) {

// 如果已经登录不拦截

callback.onContinue(postcard);

} else {

// 如果没有登录,进行拦截

callback.onInterrupt(postcard);

}

}

@Override

public void init(Context context) {

LogUtils.v(“初始化成功”);

}

}

//使用

ARouter.getInstance().build(ConfigConstants.SECOND_PATH)

.withString(“msg”, “123”)

.navigation(this,new LoginNavigationCallbackImpl());

// 第二个参数是路由跳转的回调

// 拦截的回调

public class LoginNavigationCallbackImpl implements NavigationCallback{

@Override

public void onFound(Postcard postcard) {

}

@Override

public void onLost(Postcard postcard) {

}

@Override

public void onArrival(Postcard postcard) {

}

@Override

public void onInterrupt(Postcard postcard) {

//拦截并跳转到登录页

String path = postcard.getPath();

Bundle bundle = postcard.getExtras();

ARouter.getInstance().build(ConfigConstants.LOGIN_PATH)

.with(bundle)

.withString(ConfigConstants.PATH, path)

.navigation();

}

}

拦截器实现IInterceptor接口,使用注解@Interceptor,这个拦截器就会自动被注册了,同样是使用APT技术自动生成映射关系类。这里还有一个优先级参数priority,数值越小,就会越先执行。

说说你对协程的理解

在我看来,协程和线程一样都是用来解决并发任务(异步任务)的方案。所以协程和线程是属于一个层级的概念,但是对于kotlin中的协程,又与广义的协程有所不同。kotlin中的协程其实是对线程的一种封装,或者说是一种线程框架,为了让异步任务更好更方便使用。

说下协程具体的使用

比如在一个异步任务需要回调到主线程的情况,普通线程需要通过handler切换线程然后进行UI更新等,一旦多个任务需要顺序调用,那更是很不方便,比如以下情况:

//客户端顺序进行三次网络异步请求,并用最终结果更新UI

thread{

iotask1(parameter) { value1 ->

iotask1(value1) { value2 ->

iotask1(value2) { value3 ->

runOnUiThread{

updateUI(value3)

}

}

}

}

}

简直是魔鬼调用,如果不止3次,而是5次,6次,那还得了。。

而用协程就能很好解决这个问题:

//并发请求

GlobalScope.launch(Dispatchers.Main) {

//三次请求并发进行

val value1 = async { request1(parameter1) }

val value2 = async { request2(parameter2) }

val value3 = async { request3(parameter3) }

//所有结果全部返回后更新UI

updateUI(value1.await(), value2.await(), value3.await())

}

//切换到io线程

suspend fun request1(parameter : Parameter){withContext(Dispatcher.IO){}}

suspend fun request2(parameter : Parameter){withContext(Dispatcher.IO){}}

suspend fun request3(parameter : Parameter){withContext(Dispatcher.IO){}}

就像是同一个线程中顺序执行的效果一样,再比如我要按顺序执行一次异步任务,然后完成后更新UI,一共三个异步任务。如果正常写应该怎么写?

thread{

iotask1() { value1 ->

runOnUiThread{

updateUI1(value1)

iotask2() { value2 ->

runOnUiThread{

updateUI2(value2)

iotask3() { value3 ->

runOnUiThread{

updateUI3(value3)

}

}

}

}

}

}

}

晕了晕了,不就是一次异步任务,一次UI更新吗。怎么这么麻烦,来,用协程看看怎么写:

GlobalScope.launch (Dispatchers.Main) {

ioTask1()

ioTask1()

ioTask1()

updateUI1()

updateUI2()

updateUI3()

}

suspend fun ioTask1(){

withContext(Dispatchers.IO){}

}

suspend fun ioTask2(){

withContext(Dispatchers.IO){}

}

suspend fun ioTask3(){

withContext(Dispatchers.IO){}

}

fun updateUI1(){

}

fun updateUI2(){

}

fun updateUI3(){

}

协程怎么取消

取消协程作用域将取消它的所有子协程。

// 协程作用域 scope

val job1 = scope.launch { … }

val job2 = scope.launch { … }

scope.cancel()

取消子协程

// 协程作用域 scope

val job1 = scope.launch { … }

val job2 = scope.launch { … }

job1.cancel()

但是调用了cancel并不代表协程内的工作会马上停止,他并不会组织代码运行。比如上述的job1,正常情况处于active状态,调用了cancel方法后,协程会变成Cancelling状态,工作完成之后会变成Cancelled 状态,所以可以通过判断协程的状态来停止工作。

Jetpack 中定义的协程作用域(viewModelScope 和 lifecycleScope)可以帮助你自动取消任务,下次再详细说明,其他情况就需要自行进行绑定和取消了。

之前大家应该看过我写的启动流程分析了吧,那篇文章里我说过分析源码的目的一直都不是为了学知识而学,而是理解了这些基础,我们才能更好的解决问题。所以今天就来看看通过分析app启动流程,我们该怎么具体进行启动优化。

  • App启动流程中我们能进行优化的地方有哪些?

  • 具体有哪些优化方法?

  • 分析启动耗时的方法

具体有哪些启动优化方法?

障眼法之闪屏页

为了消除启动时的白屏/黑屏,可以通过设置android:windowBackground,让人感觉一点击icon就启动完毕了的感觉。

<activity android:name=“.ui.activity.启动activity”

android:theme=“@style/MyAppTheme”

android:screenOrientation=“portrait”>

预创建Activity

对象第一次创建的时候,java虚拟机首先检查类对应的Class 对象是否已经加载。如果没有加载,jvm会根据类名查找.class文件,将其Class对象载入。同一个类第二次new的时候就不需要加载类对象,而是直接实例化,创建时间就缩短了。

第三方库懒加载

很多第三方开源库都说在Application中进行初始化,所以可以把一些不是需要启动就初始化的三方库的初始化放到后面,按需初始化,这样就能让Application变得更轻。

WebView启动优化

webview第一次启动会非常耗时,具体优化方法可以看我之前的文章,关于webview的优化。

线程优化

线程是程序运行的基本单位,线程的频繁创建是耗性能的,所以大家应该都会用线程池。单个cpu情况下,即使是开多个线程,同时也只有一个线程可以工作,所以线程池的大小要根据cpu个数来确定。

分析启动耗时的方法

Systrace + 函数插桩

也就是通过在方法的入口和出口加入统计代码,从而统计方法耗时

class Trace{

public static void i(String tag){

android.os.Trace.beginSection(tag);

}

public static void o(){

android.os.Trace.endSection();

}

}

void test(){

Trace.i(“test”);

System.out.println(“doSomething”);

Trace.o();

}

BlockCanary BlockCanary 可以监听主线程耗时的方法,就是在主线程消息循环打出日志的地入手, 当一个消息操作时间超过阀值后, 记录系统各种资源的状态, 并展示出来。所以我们将阈值设置低一点,这样的话如果一个方法执行时间超过200毫秒,获取堆栈信息。

而记录时间的方法我们之前也说过,就是通过looper()方法中循环去从MessageQueue中去取msg的时候,在dispatchMessage方法前后会有logging日志打印,所以只需要自定义一个Printer,重写println(String x)方法即可实现耗时统计了。

SharedPreferences是如何保证线程安全的,其内部的实现用到了哪些锁

SharedPreferences的本质是用键值对的方式保存数据到xml文件,然后对文件进行读写操作。

对于读操作,加一把锁就够了:

public String getString(String key, @Nullable String defValue) {

synchronized (mLock) {

String v = (String)mMap.get(key);

return v != null ? v : defValue;

}

}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
img

文末

当你打算跳槽的时候,应该把“跳槽成功后,我能学到什么东西?对我的未来发展有什么好处”放在第一位。这些东西才是真正引导你的关键。在跳槽之前尽量“物尽其用”,把手头上的工作做好,最好是完成了某个项目或是得到提升之后再走。跳槽不是目的,而是为了达到最终职业目标的手段

最后祝大家工作升职加薪,面试拿到心仪Offer.
为此我在文末整理了一些关于移动开发者需要的资料,欢迎大家免费领取
领取方式:点击我的GitHub


null ? v : defValue;

}

}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-Cmr2rs8i-1711093362377)]
[外链图片转存中…(img-nvSLNb5E-1711093362377)]
[外链图片转存中…(img-AjqhLUsu-1711093362378)]
[外链图片转存中…(img-ACxKYAD7-1711093362378)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
[外链图片转存中…(img-i1Fxdzfc-1711093362379)]

文末

当你打算跳槽的时候,应该把“跳槽成功后,我能学到什么东西?对我的未来发展有什么好处”放在第一位。这些东西才是真正引导你的关键。在跳槽之前尽量“物尽其用”,把手头上的工作做好,最好是完成了某个项目或是得到提升之后再走。跳槽不是目的,而是为了达到最终职业目标的手段

最后祝大家工作升职加薪,面试拿到心仪Offer.
为此我在文末整理了一些关于移动开发者需要的资料,欢迎大家免费领取
领取方式:点击我的GitHub

[外链图片转存中…(img-ZP3aWS9Q-1711093362379)]
[外链图片转存中…(img-T1kmgBVk-1711093362380)]

  • 27
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值