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移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
文末
当你打算跳槽的时候,应该把“跳槽成功后,我能学到什么东西?对我的未来发展有什么好处”放在第一位。这些东西才是真正引导你的关键。在跳槽之前尽量“物尽其用”,把手头上的工作做好,最好是完成了某个项目或是得到提升之后再走。跳槽不是目的,而是为了达到最终职业目标的手段
最后祝大家工作升职加薪,面试拿到心仪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)]