android面试2021

本人菜鸟一个,哪里有错误,欢迎指出啊!多谢!!!

阿里的android开发手册

一、activity

1、activity作用

前台运行页面,用户可见、可操作。不能进行耗时操作。5s后会产生ANR

2、activity生命周期

onCreate()-onStart()-onResume()-onPause()-onStop()-onDestroy()-onRestart()
onCreate:用户首次创建activity时
onStart:可见,无法与用户交互,没有焦点,用来做一些动画的初始化
onResume:当activity位于前台可供用户操作时,此时activity位于栈顶。可用来视频的继续播放
onPause:当另一个activity覆盖当前的activity时,可见,无法与用户交互。按回退键,再次执行onResume()
onStop:不可见,在系统内存紧张的情况下,有可能会被系统进行回收。所以可做资源回收工作。
onDestroy:可通过finish()销毁activity时
onRestart:app从后台返回前台时

3、activity四种启动模式

standard(默认模式):
无论当前要打开的activity实例是否在栈顶,总是new activity实例。各个实例可以属于不同的 task,一个 task 中也可以存在多个实例。
singleTop:
当前要打开的activity实例存在于栈顶时,会复用该activity实例。否则new activity实例。
singleTask:
当前要打开的activity实例只要存在该栈中,就会复用该activity实例,同时位于该activity实例上面的其他activity实例都会被移出该栈,这个时候按返回键,被移出的activity页面将不再显示,除非重新打开该activity页面创建实例。
singleInstance:
会单独放到一个栈中,一旦该模式的Activity实例已经存在于某个栈中,任何应用再激活该Activity时都会重用该栈中的实例。

4、activity启动流程

转载:详细解读activity启动流程中的各种进程的作用

5、如何加速启动actviity

1、减少onCreate()时间,减少初始化的操作,避免耗时操作
2、布局文件优化。减少层级结构、多使用include、merge等标签复用布局
3、在启动过程中有时会出现白屏、闪屏等情况,自定义一个背景透明主题在AndroidManifest.xml中设置上
4、使用jetpack里的App Startup来管理第三方sdk的初始化

问题:AActivity启动BActivity,此时B启动模式为singleTask或SingleInstance并且有实例存在栈中,此时的B的生命周期?

答:会走onNewIntent->onRestart—>onStart—>onResume

如果是singleTop(相当于自己打开自己)

onNewIntent—>onResume

只对startActivity有效,对仅仅从后台切换到前台而不再次启动的情形,不会触发onNewIntent

/***************************************************************************/

二、fragment

1、fragment作用

1、可以在程序运行时动态的切换(添加、替换、隐藏),并且比activity流畅,提高性能
2、可以动态创建不同尺寸的页面,也容易适配平板
3、依赖于activity,可以重用
4、可以传值也可以接受回传

FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.show(currentFragment);
transaction.hide(currentFragment);
transaction.add(R.id.frameLayout, currentFragment);
transaction.replace(R.id.frameLayout, currentFragment);
transaction.commitAllowingStateLoss();

2、fragment生命周期

onAttach()-onCreate()-onCreateView()-onActivityCreated()
-onStart()-onResume()-onPause()-onStop()
-onDestroyView()-onDestroy()-onDetach()-onRestart()

3、fragment的回退栈

如果你不是手动开启回退栈,它是直接销毁再重建,但如果将Fragment任务添加到回退栈,它就有了类似Activity的栈管理方式。
/***************************************************************************/

三、service

1、service作用

后台运行服务,不提供页面呈现。不能进行耗时操作。20s后会产生ANR

2、service的两种启动方式和生命周期

startService()和bindService()
在这里插入图片描述

3、service与activity的两种通信方式

1、BindService实现serviceConnection接口
重写onServiceConnected(判断是否连接上,需返回一个IBinder实例)和onServiceDisconnected(只在Service 被破坏了或者被杀死的时候调用)方法
2、广播+startService

4、IntentService

HandlerThread:轻量级异步类,继承Thread,内部封装了Looper。因为子线程中没有Looper需要自己手动创建。
IntentService:Service+HandlerThread,自动开启线程,可以执行一些耗时任务,用完自动停止。防止出现ANR错误

5、service保活

service的优先级
1、前台进程
2、可见进程
3、服务进程
4、后台进程
5、空进程
【1】提高优先级我们可以使用startForeground()方法将Service设置为前台进程。
【2】双进程守护
创建主服务——创建守护服务
在继承service重载onStartCommand方法中返回各个参数的意义
START_STICKY:在Service被关闭后,重新开启Service
START_NOT_STICKY:服务被异常杀掉后,系统将会被设置为started状态,系统不会重启该服务,直到startService(Intent intent)方法再次被调用。
START_REDELIVER_INTENT:重传Intent,使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统会自动重启该服务,并将Intent的值传入。
START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,但不保证服务被kill后一定能重启。
【3】继承JobService来实现应用退出后重启service,类似守护进程
【4】保证Service在开机后自动启动
注册广播——开机启动广播
在这里插入图片描述
/***************************************************************************/

四、ContentProvider(内容提供者)

主要用在多个进程间存储和读取数据
fileProvider继承自ContentProvider
https://blog.csdn.net/chunqiuwei/article/details/41675313
/***************************************************************************/

五、BroadcastReceiver(广播)

不能进行耗时操作。10s后会产生ANR

1、作用域

全局广播

多个进程都可以接收到

本地广播

只能在本进程内接收到

2、两种注册方式

静态注册

在AndroidManifest.xml文件中注册。
静态注册的广播,即使Activity销毁了,仍然可以收到广播。即使杀死进程,仍然可以收到广播

动态注册

java动态代码注册
动态注册的广播会受Activity的生命周期的影响, 当Activity销毁的时候,广播就失效了

3、广播类型

有序广播

Context.sendOrderedBroadcast()来发送一条有序的广播。从优先级高的开始,依次往下广播。获取上个广播返回的结果getResult()

无序广播

用Context.sendBroadcast()来发送一条无序广播。没有getResult()方法

通知事例Notification
		Intent intent = new Intent(Intent.ACTION_MAIN);
        ComponentName cn =new ComponentName("com.xxx.xxx","com.xxx.xxx.MainActivity");
        intent.setComponent(cn);
        PendingIntent pi=PendingIntent.getActivity(this,0,intent,0);
        //后面一个参数为渠道参数,Android8.0新增要求
        Notification notification=new NotificationCompat.Builder(this,"dance")
                .setContentTitle("舞蹈教学App")
                .setContentText("明天有您的课呦!")
                .setWhen(System.currentTimeMillis())
                .setSmallIcon(com.wxcy.dance.tab1.R.mipmap.icon_logo)
                .setContentIntent(pi)//设置点击事件
//                .setSound(Uri.fromFile(new File("/system/media/audio/ringtones/Luna.ogg"))) //设置通知提示音
//                .setVibrate(new long[]{0,1000,1000,1000}) //设置振动, 需要添加权限  <uses-permission android:name="android.permission.VIBRATE"/>
                .setLights(Color.GREEN,1000,1000)//设置前置LED灯进行闪烁, 第一个为颜色值  第二个为亮的时长  第三个为暗的时长
                .setPriority(NotificationCompat.PRIORITY_MAX)//设置通知优先级
                .build();
        NotificationManager notificationManager= (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        NotificationChannel channel;
        //Android8.0要求设置通知渠道
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            channel = new NotificationChannel("dance", "danceName", NotificationManager.IMPORTANCE_HIGH);
            assert notificationManager != null;
            notificationManager.createNotificationChannel(channel);
        }
        assert notificationManager != null;
        notificationManager.notify(1,notification);
//        startForeground(1,notification);//还使该服务在前台运行,提供持续的在此状态下向用户显示的通知。

/***************************************************************************/

六、http协议流程

1、域名解析
2、TCP三次握手
3、建立连接后发起请求
4、服务端响应请求,返回给浏览器数据
5、浏览器解析html代码,同时请求资源(图片)
6、浏览器进行渲染
7、TCP四次挥手

1、TCP为什么要三次握手?

三次握手(一种情况正常,二种情况由于网络原因C第一次发的短信S没收到)
C:“你看得到我给你发的短信吗?”
S:“我看得到呀,你看得到我给你发的短信吗?”
C:“我能看到,balabalabala”

C:“你看得到我给你发的短信吗?”
C:“你看得到我给你发的短信吗?”//重新发送
S:“我看得到呀”
C:“balabalabala”
C说完断开连接,这时S看到之前的短信,又发了过去,但不能重新建立起连接【因为三次握手需要C的确认,而C认为第一次发送的短信已经失效了,所以不会确认】
S:“我看得到呀,我们可以重新建立连接吗?”
C没有回应,没有重新建立连接

两次握手(一种情况正常,二种情况由于网络原因C第一次发的短信S没收到)
C:“你看得到我给你发的短信吗?”
S:“我看得到呀”
C:“balabalabala”

C:“你看得到我给你发的短信吗?”
C:“你看得到我给你发的短信吗?”//重新发送
S:“我看得到呀”
C:“balabalabala”
C说完断开连接,这时S看到之前的短信,又发了过去,重新建立起连接【因为两次握手不需要C的确认】
S:“我看得到呀,我们重新建立连接吧”
C:“…”

四次握手
C:“你看得到我给你发的短信吗?”
S:“我看得到呀,你看得到我给你发的短信吗?”
C:“我能看到,你看得到我给你发的短信吗?”
C:“…不想跟傻逼说话”

三次握手主要防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误。

假设有这样一种场景,客户端发送了第一个请求连接并且没有丢失,只是因为在网络结点中滞留的时间太长了,由于TCP的客户端迟迟没有收到确认报文,以为服务器没有收到,此时重新向服务器发送这条报文,此后客户端和服务器经过两次握手完成连接,传输数据,然后关闭连接。此时此前滞留的那一次请求连接,网络通畅了到达了服务器,这个报文本该是失效的,但是,两次握手的机制将会让客户端和服务器再次建立连接,这将导致不必要的错误和资源的浪费。

四次握手会显得多余

2、TCP为什么要4次挥手
C:“我要断开连接了”
S:“等一下,等我把话说完着”
S:“我说完了” //此时服务端断开
C:“好,那我断开连接了” //如果服务端没有回复表明服务端确实断开了
C说完又等了一会没有再收到S的消息,也断开了连接

详细可以看这篇文章https://blog.csdn.net/dreamispossible/article/details/91345391?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2alltop_click~default-2-91345391.nonecase&utm_term=%E5%9B%9B%E6%AC%A1%E6%8C%A5%E6%89%8B&spm=1000.2123.3001.4430

/***************************************************************************/

七、网络请求框架

关于 HTTP 请求报文和响应报文的格式这里就不再过多介绍了,简单说,HTTP 协议是以 ASCII 码传输,建立在 TCP/IP 协议之上的应用层规范。规范把 HTTP 请求分为三个部分:请求行、请求头、请求体。类似于:

<空格> <协议版本号> <回车> <换行符>
<请求头>
<请求体>

Volley VS OkHttp
Volley 的优势在于封装的更好,而使用 OkHttp 你需要有足够的能力再进行一次封装。而 OkHttp 的优势在于性能更高,因为 OkHttp 基于 NIO 和 Okio ,所以性能上要比 Volley更快。
Retrofit是在okHttp基础上封装好的,当然Retrofit更好一些。
主流网络架构
Retrofit + OkHttp + RxJava + Dagger2
/***************************************************************************/

八、数据解析框架

1、json-lib(原生)
需要依赖很多jar包,对于复杂类型的bean的转换也很容易出错,pass
2、jackson
优点:依赖的jar包较少,简单易用,性能较高,社区比较活跃,更新速度快。
缺点:全部解析。无论是json转bean,对于复杂的数据类型,容易出错。bean转json不是标准的json格式数据。依赖包较大
3、Goolge的Gson
优点:按需解析,无需jar包,直接导入依赖,前提是需要创建好对应的bean。无论是json转bean,还是bean转json都无可挑剔。
缺点:性能比fastJson差点
4、阿里的fastJson
优点:按需解析,无需jar包,直接导入依赖,性能最好的json解析框架
缺点:bean转json有时会出现一些问题。
总结
1、可以考虑Gson与fastJson一起使用
在解析(json转bean)时用fastJson,在需要bean转json时使用Gson
2、数据量小时采用Gson,大采用fastJson
3、依赖包体积由小到大:Gson<fastJson<jackson
/***************************************************************************/

九、三大图片加载框架对比

1、Glide
优点:1、可以绑定生命周期,不仅可以传入Context还可以传入Activity和Fragment。
2、Glide加载的是与控件大小一样尺寸的图片即RGB_565,减小内存的开销
缺点 :体积大
2、Picasso
优点:体积小
缺点:1、加载的是全尺寸即ARGB_8888,内存开销较大
2、生命周期只能传入Context
3、Fresco
优势:在5.0的系统下,它会将图片放在一个特别的内存区域(Ashmem区),当图片不显示的时候,占用的内存会自动释放,减少了oom情况的发生
缺点:在5.0系统以后,系统默认将图片储存在Ashmem区域,导致优势全没

图片的三级缓存

请跳转该链接进一步了解glide

Glide缓存机制大致分为三层:内存缓存、磁盘缓存、网络缓存
存的顺序是:网络、磁盘、内存
取的顺序是:内存、磁盘、网络

LruCache策略:在运行内存中通常会设置一个储存容量最大值,一旦达到这个容量,就要进行释放一些使用次数较少的图片,达到避免产生oom的特殊情况

总结

Picasso能做的Glide也都可以,并且Glide可以加载GIF动态图。Glide适用处理一些较大的图片流。

加载超长图

1、获取到图片的宽高,在使用SubsamplingScaleImageView框架(底层基于BitmapRegionDecoder)进行图片分段显示
2、压缩显示。设置固定宽高尺寸
/***************************************************************************/

十、屏幕适配

在这里插入图片描述
1、最简单的dp适配
描述:不能很好的适配平板之类的宽屏
2、宽高限定符
描述:按一定的宽高,分段适配,不是很精确,需要增加很多的文件
3、最小宽度限定符
描述:按一定的宽,分段适配,精确,需要增加很多的文件
4、鸿洋的按屏幕的宽高比,动态修改视图宽高
描述:如果是自定义的view也会有所影响
5、今日头条保持dp(设计图总宽)不变,动态改变density(1dp占当前多少像素)px = density * dp
描述:全局修改 density,对于第三方布局有所影响
今日头条的AndroidAutoSize
/***************************************************************************/

十一、RecyclerView优化

重点:不能在adapter中修改控件的一些属性,要从数据源上改变。因为RecyclerView的回收复用机制,取出来时会进行初始化属性

RecyclerView与ListVIew和GridView最大的区别:recyclerView可扩展性强,也就是自定义布局管理器(layoutManager)

设置recyclerView的item之间间距不一样,使用ItemDecoration(分割线:多组标题吸顶可以使用)

控制item的增删动画使用itemAnimator

想要设置点击或长点击事件,需要自己写

getChildCount()是当前屏幕看到的子项数,getItemCount()是该RecyclerView总共的子项数

1、RecyclerView设置横向滑动时,item居中,带有惯性
new LinearSnapHelper().attachToRecyclerView(mRecyclerView);
2、RecyclerView设置横向滑动时,item居中,效果类似ViewPager
new PagerSnapHelper().attachToRecyclerView(mRecyclerView);
3、ScrollView嵌套RecyclerView滑动冲突
1)代码设置
mRecyclerView.setHasFixedSize(true);确保每个item的宽高都是一定的,减少计算每个item的宽高
mRecyclerView.setNestedSrollingEnabled(false);阻止本身的滑动
2)业务设置
根据滑动的方向判断哪个事件消费,onInterceptTouthEvent()进行事件拦截
4、数据优化
1)分页加载,将数据缓存,提高二次加载的速度。
2)对于新增与删除的数据通过DiffUtil来进行局部刷新DiffUtil用来判断新数据与旧数据的差别,从而进行局部刷新
5、布局优化
1)减少item的绘制层级
2)共用RecycledViewPool线程池
3)RecyclerView数据预取
4)加大RecyclerView的缓存用空间换时间,提高流畅度
5)处理刷新时屏幕闪烁((DefaultItemAnimator)mRecyclerView.getItemAnimator()).setSupportsChangeAnimations(false);
/***************************************************************************/

十二、内存泄漏导致内存溢出(oom)

1、导致内存泄漏的情况及解决方法

1)没有使用的图片,没有进行释放
2)非静态内部类和匿名内部类持续持有外部类,导致外部类无法被GC回收
问题:比如Handler、AsycnTask等。
解决:1、改为静态内部类+软引用>弱引用>虚引用
2、Handler:removeCallbacksAndMessages(null)
3)静态变量引用内部类
问题:静态对象引用了方法内部类,方法内部类也是持有Activity实例的,会导致Activity泄漏
解决:就是通过在onDestory方法中置空static变量
4)使用较多的单例模式
问题:单例模式的生命周期与程序的生命周期一致,不能传入activity的生命周期
解决:传入application的生命周期getApplicationContext()
5)关闭activity时广播未取消注册
解决:在activity的onDestroy()方法中取消注册广unregisterReceiver()
6)弱网进行网络请求
问题:在弱网情况下就会导致接口回调缓慢,这时用户很可能就会退出Activity不在等待,但是这时网络请求还未结束,回调接口为内部类依然会持有Activity的对象,这时Activity就内存泄漏的,并且如果是在Fragment中这样使用不仅会内存泄漏还可能会导致奔溃。
解决:这类异常的原理和非静态内部类相同,所以可以通过【static内部类+弱引用进行处理。】由于本例是通过Retrofit进行,还可以【在onDestory进行call.cancel进行取消任务,】也可以避免内存泄漏。
7)Rxjava
问题:consumer这个为内部类,如果异步任务没有完成Activity依然是存在泄漏的风险的。
解决:RxJava有取消订阅的方法,在activity的onDestroy()方法中
if (disposable!=null && !disposable.isDisposed()){
disposable.dispose();
}
8)service用完也要停止服务
stopself();
9)webView造成的内存泄漏
application里的成员变量,持有了一个activity实例,而这个成员变量,在创建webView的时候又关联到了webview的实例,这个成员变量有注册和注销功能。

在webView创建会注册一个接口回调,注销的时候会先判断当前webView是否销毁,如果销毁直接return,否则会注销该回调。

这就导致我们在调用mWebView.destroy();时不会注销该回调,最终导致内存泄漏

解决方法

override fun onDestroy() {
        super.onDestroy()
        val parent = mWebView?.parent as ViewGroup
        parent.removeView(mWebView)
        mWebView?.removeAllViews()
        mWebView?.destroy()
        mWebView = null
    }

1)引用计数法
这种方法是在对象的头处维护一个计数器Counter,当有一个引用指向对象的时候counter就加一,当不在引用此对象时就让counter减一。所以,当counter等于零的时候虚拟机就认为此对象时可以被回收的。看起来好像有点道理,但是这种方法存在一个致命的问题:外部对对象A有一个引用,对象A持有对象B,而对象B也持有一个对象C,对象C又持有对象A。如果对于对象A的引用r失效,按照引用计数方法,GC永远无法回收上面的三个对象。所以基于上面的存在内存泄漏的巨大缺陷,Java虚拟机(应该是大多数虚拟机)不采用此方法进行回收内存。
2)可达性分析算法
GC Roots基本思路就是通过一系列的称为“GC Roots”的对象作为起始点, 从这些节点开始向下搜索, 搜索所走过的路径称为引用链( Reference Chain),当一个对象到 GC Roots 没有任何引用链相连( 用图论的话来 说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。

3、垃圾回收算法

1)标记/清除算法
内存中的对象构成一棵树,当有效的内存被耗尽的时候,程序就会停止,做两件事,第一:标记,标记从树根可达的对象(途中水红色),第二:清除(清楚不可达的对象)。标记清除的时候有停止程序运行,如果不停止,此时如果存在新产生的对象,这个对象是树根可达的,但是没有被标记(标记已经完成了),会清除掉。
缺点:递归效率低性能低;释放空间不连续容易导致内存碎片;会停止整个程序运行;
2)标记/复制算法
把内存分成两块区域:空闲区域和活动区域,第一还是标记(标记谁是可达的对象),标记之后把可达的对象复制到空闲区,将空闲区变成活动区,同时把以前活动区对象1,4清除掉,变成空闲区。
优/缺点:速度快但耗费空间,假定活动区域全部是活动对象,这个时候进行交换的时候就相当于多占用了一倍空间,但是没啥用。
3)标记/整理算法
前面标记部分同标记清除部分,不同的地方在于,标记清除中只是把可回收的对象进行垃圾回收,不会对剩余的内存空间进行整理,而标记整理则会对存活的对象进行整理。
优缺点:避免了标记清除中的内存碎片的问题,也避免了复制算法中的内存浪费问题。但会比前2者的效率低。

现在的垃圾回收是分代收集算法,针对不同的分代区,选择不同的垃圾收集算法。

堆部分,年轻代是采用复制算法进行垃圾回收,因为年轻代一般都是存活时间不长的对象,在第一次进行垃圾回收的时候,会把大部分的对象清除掉,这种情况下使用复制算法,只需要把少量存活的对象放入到另一块闲置的内存块中即可。

老年代中,一般对象的存活比例会很高,这种情况下,使用复制算法不能很好的提高性能和效率,把存活的对象移到另一个内存块时,会由于对象存活多而消耗的时间多,从而影响效率,这种情况下,使用标记整理或者标记清除比较合适

内存抖动(频繁的创建对象分配内存与销毁对象回收内存)

卡顿原因:GC回收内存时会触发STW机制,停止一切工作,回收完,再开始工作
造成OOM原因:标记-清除算法会产生内存碎片,导致容易产生OOM
比如再onDraw方法中创建对象
/***************************************************************************/

十三、插件化与组件化

1、插件化

1、可以在一个APP内做到免安装运行另一个app内的activity
2、hook插桩动态将只注册没有具体类的activity替换为另一个app内的实体activity
3、可以做到类似热更新
4、子moduel可单独运行,也可插到宿主上运行

2、组件化

1、按业务分不同的library或module,可以设置参数true或false更改为module或library,module可以单独运行,library不可单独运行,但是最终发布的时候是将这些组件(library)合并统一成一个apk
2、组件之间的跳转用Router框架
/***************************************************************************/

十四、热更新与增量更新

1、热更新主要用于紧急修复bug

原理:在一个数组中存着.dex文件,将新类转换成.dex文件并在加载之前经信的.dex文件放在数组第一位,开始加载时会先加载新类,就不会再加载旧类,完成了修改更新
热更新框架对比

方案对比SophixTinkernuwaAndFixRobustAmigo
类替换yesyesyesnonoyes
So替换yesyesnononoyes
资源替换yesyesyesnonoyes
全平台支持yesyesyesnoyesyes
即时生效同时支持nonoyesyesno
性能损耗较少较小较大较小较小较小
补丁包大小较小较大一般一般较大
开发透明yesyesyesnonoyes
复杂度傻瓜式接入复杂较低复杂复杂较低
Rom体积较小Dalvik较大较小较小较小
成功率较高较高一般最高较高
热度
开源noyesyesyesyesyes
收费收费(设有免费阈值)收费(基础版免费,但有限制)免费免费免费免费
监控提供分发控制及监控提供分发控制及监控nononono

Sophix与Tinker比较

阿里的Sophix收费的阈(yu`)值
同一个账号中多个app合计的月活跃设备在10万以下免费,对于项目初期够用了
1、Sophix比较简单,而且对代码无侵入,好维护好操作易上手;(采用新理念性能消耗低)
Tinker接入很复杂,代码入侵,性能消耗很高,要合成资源,而且不支持即时生效
我的建议
我建议用Sophix,性能消耗低,支持即时生效,最主要的是对代码无侵入,便于代码的维护,以后的版本迭代,新功能的接入都不收影响。而且免费阈值对项目初期够用了。

2、增量更新主要用于增加产品功能

原理:通过生成差分包的供下载,再合并达到更新的方式
详情:https://www.jianshu.com/p/f1f9d1c8bb4e
/***************************************************************************/

十五、Handler机制

1、handler的重要程度

Handler是构成整个Android系统的基础,正是Looper的死循环才让Android程序能够不退出。所有的类似于屏幕刷新,UI互动都是一种事件,通过Handler发送给了Looper来进行分发。整个Android程序可以说就是运行在这个死循环中。
在生成消息的时候,最好是用 Message.obtain() 来获取一个消息。obtain方法是将一个Message对象的所有数据清空,然后添加到链表头中。sPool就是个消息池,默认的缓存是50个。Looper在分发结束以后,会将用完的消息回收掉,并添加到回收池里。

2、handler流程

1、Handler.sendMessage()发送message到MessageQueue队列中【通过enqueueMessage()将message按时间的顺序加入到链表(先进先出)对应的位置并将发送message的handler标记赋值给target ,loop就可以识别】
2、Looper通过loop()开始不停的从MessageQueue中取出message【MessageQueue.next()不是消息阻塞,是异步阻塞IO】
3、调用message绑定的Handler对象(disptchMessage())交给发送该消息的Handler处理
4、如果是post发送的消息,直接在post()括号中new一个匿名Runnable处理事件。否则会调用我们复写的handlerMessage()进行处理
5、处理完成后会调用Message.recycle()将message放入对象池中,等待下次复用。

looper创建

主线程的looper是在app启动的时候在main方法中通过Looper.prepareMainLooper()【里面就是通过这个方法sThreadLocal.set(new Looper(quitAllowed));里的map对象将当前线程与looper绑定】来创建一个looper,一个线程里只能有一个looper

handler是如何切换线程的?

其实它根本没有切换线程,这个要看Looper处于哪个线程,loop就会将message回调到哪个线程,Looper不管你在哪个线程发送message。

原因:handler在Message的target中,Message又在MessageQueue队列中,MessageQueue又是在Looper中创建的,【调用Looper.prepare()创建Looper】Looper又在ThreadLocal中保存着,ThreadLocal又是当前线程初始化创建的。我们唯一可控的就是创建Looper的时候

Handler导致的内存泄露问题

发送了一个延时 1s 的消息,如果 HandlerActivity 在 1s 内退出了,由于 Handler 会被 Message 持有,保存在其 target 变量中,而 Message 又会被保存在消息队列中,这一系列关联,导致 HandlerActivity 在退出的时候,依然会被持有,因此不能被 GC 回收,这就是内存泄漏!当这个 1s 延时的消息被执行完以后,HandlerActivity 会被回收。

解决:
1、将MyHandler改为静态类,这样它将不再持有外部类的引用。
2、可以将HandlerActivity作为弱引用放到MyHandler中使用,页面退出的时候可以被及时回收。
3、页面退出的时候,在onDestroy中,调用Handler的removeMessages方法,将所有的消息remove掉,这样也能消除持有链。

looper死循环为什么不会出现ANR错误

阻塞与程序无响应没有必然关系,虽然主线程在没有消息可处理的时候是阻塞的,但是只要保证有消息的时候能够立刻处理,程序就不会出现ANR。

handler在c++层唤醒

Looper的休眠和唤醒都是在Native层实现的,实现的原理是Linux上的epoll机制,通过对文件的可读事件的监听来实现唤醒。
在整个消息机制中,MessageQueue是连接Java层和Native层的纽带,换言之,Java层可以向MessageQueue消息队列中添加消息,Native层也可以向MessageQueue消息队列中添加消息。

include <sys/epoll.h>

// 创建句柄 相当于初始化onClickListener
int epoll_create(int size);
// 添加/删除/修改 监听事件  相当于addOnClicklistener
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
// 进入等待 监听文件的可读性
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

超时了,或者文件发生了变化,可以读了就可以唤醒了。注意,这个超时就是在Java层设置的延时发送,也就是说Java的sendMessageDelayed方法最后是通过epoll设置超时的机制实现延迟发送的。

handler同步消息屏障

//MessageQueue.java
//省略部分代码
Message next() {

        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            nativePollOnce(ptr, nextPollTimeoutMillis);
            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                //1 这一行很关键,同步消息屏障的关键点所在
                if (msg != null && msg.target == null) {
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }

            }

        }
    }

针对同步消息还真的是所有的message都有handler,而这里是异步消息。满足msg != null &&target == null的消息就是异步消息。同步屏障是用来阻挡同步消息执行的。Android 系统中的 UI 更新相关的消息即为异步消息,需要优先处理。当第一次发现符合这样条件的消息时,会先暂停执行后面的同步消息,找到后面的异步消息继续执行,等异步消息执行完,再继续按顺序执行同步消息
/***************************************************************************/

十六、三种动画

这里我主要写一下插值器估值器

1、插值器(Interpolator)

根据时间流逝的百分比计算出当前属性值改变的百分比。确定了动画效果变化的模式,如匀速变化、加速变化等等。View动画和属性动画均可使用。常用的系统内置插值器:
1、线性插值器(LinearInterpolator):匀速动画
2、加速减速插值器(AccelerateDecelerateInterpolator):动画两头慢中间快
3、减速插值器(DecelerateInterpolator):动画越来越慢

2、类型估值器(TypeEvaluator)

根据当前属性改变的百分比计算出改变后的属性值。针对于属性动画,View动画不需要类型估值器。常用的系统内置的估值器:
1、整形估值器(IntEvaluator)
2、浮点型估值器(FloatEvaluator)
3、Color属性估值器(ArgbEvaluator)
/***************************************************************************/

十七、事件分发机制?

主要就是三个方法

public boolean dispatchTouchEvent(MotionEvent ev)
public boolean onInterceptTouchEvent(MotionEvent ev)
public boolean onTouchEvent(MotionEvent ev)

activity与view有dispatchTouchEvent和onTouchEvent,没有onInterceptTouchEvent
viewGroup三个都有

事件类型分为四种

在这里插入图片描述
事件类型也是可以拦截的,如果从ACTION_DOWN开始拦截,就会走自己的onTouchEvent,不会向下传递了

简单说一下Activity的组成,一个Activity包含了一个Window对象,Window是由PhoneWindow来实现的。PhoneWindow将DecorView作为整个应用窗口的根View,而这个DecorView又将屏幕划分为两个区域:一个是 TitleView,另一个是ContentView,而我们平时所写的就是展示在ContentView中的。

一般大家都知道view的分发事件是从activity的#dispatchTouchEven------view的Viewgroup(Parent)#dispatchTouchEvent------Viewgroup(Parent)#onInterceptTouchEvent------View#dispatchTouchEvent------view的onTouchEvent,最后又回到了Viewgroup(Parent)#onTouchEvent。如果大家记不住方法名,可以直接说先是parent的分发到拦截再到view的分发,再到view的消费,最后到parent的消费

onTouchListener里的onTouch级别 > onTouchEvent > onClickListener 。

viewGroup#dispatchTouchEvent源码
  @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
            ........
            // Check for interception.
            final boolean intercepted;  //是否拦截
            /*
            * mFirstTouchTarget ==null表示子view没有处理,
            * 当事件由ViewGroup的子元素处理时,mFirstTouchTarget会被赋值并指向子元素
            * 当没有子view处理并且actionMasked ==MotionEvent.ACTION_MOVE 或 MotionEvent.ACTION_UP时
            * 都会返回true自己进行处理
            */
            if (actionMasked == MotionEvent.ACTION_DOWN 
            			 || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }
    }
 

/***************************************************************************/

十八、Seralizable与Parcelable

区别SerializableParcelable
所属APIJAVA APIAndroid SDK API
原理序列化和反序列化过程需要大量的I/O操作序列化和反序列化过程不需要大量的I/O操作
开销开销大开销小
效率很高
使用场景序列化到本地或者通过网络传输内存序列化

Seralizable
Seralizable相对Parcelable而言,好处就是非常简单,只需对需要序列化的类class执行就可以,不需要手动去处理序列化和反序列化的过程,但需要进行大量的IO操作
Parcelable
1、Parcelable是android特有的序列化API,它的出现是为了解决Serializable在序列化的过程中消耗资源严重的问题,但是因为本身使用需要手动处理序列化和反序列化过程,会与具体的代码绑定,使用较为繁琐。
2、而Parcelable依赖于Parcel,Parcel的意思是包装,实现原理是在内存中建立一块共享数据块,序列化和反序列化均是操作这一块的数据,如此来实现。
/***************************************************************************/

十九、app性能优化

反应快、稳定、省电、apk体积小
1、反应快
1)布局优化:减少层级嵌套。避免页面结构复杂化。include、marge复用布局。
2)数据优化:避免在onCreate()中初始化大量数据。请求大量数据时分页加载
3)内存优化:避免在主线程中进行耗时操作,可以采用异步执行。减少内存的占用,将无用的图片、变量和对象进行释放,可以采用静态内部类+软引用/弱引用。
4)启动优化:
app启动流程:
1、Launcher所在的进程通过ActivityManagerProxy(代理对象)将工作交给ActivityManagerService,它来启动app并保存首先要启动activity
2、如果已经启动,就直接开启,否则就通过Zygote进程fork自身,启动一个新进程创建ActivityThread,启动其中的main函数(该线程相当于主线程)
3、在主线程中,将首先要启动activityd给到ActivityManagerService,
4、ActivityThread随后依次调用Looper.prepare()和Looper.loop()来开启消息循环.
5、通过ActivityThread把新建的进程和Application绑定,然后加载app的classes到内存中
6、启动 Activity。
【1】闪屏页优化
在AndroidManifest.xml中自定义一个全局主题,将闪屏那个页面替换自己的logo,通过属性android:windowBackground这样打开桌面图标会马上显示logo,不会出现黑/白屏,直到Activity启动完成,替换主题,logo消失,但是总的启动时间并没有改变。
【2】MultipDex优化
multiDexEnabled true:表示app内的方法数可以突破65536,一个dex装不下,用多个dex来装。MultipDex主要为了支持5.0以下的手机

Android 5.0以下,ClassLoader加载类的时候只会从class.dex(主dex)里加载,ClassLoader不认识其它的class2.dex、class3.dex、…,当访问到不在主dex中的类的时候,就会报错:Class NotFound xxx

如果虚拟机本身就支持加载多个dex文件,那就啥都不用做;如果是不支持加载多个dex(5.0以下是不支持的),则走到 doInstallation 方法。是比较耗时的。

一种是直接在闪屏页开个子线程去加载dex,难维护,不推荐;
ContentProvider初始化太早了,如果不在主dex中,还没启动闪屏页就已经crash了
一种是今日头条的方案,在单独一个进程加载dex,加载完,主进程再继续。
在另一个进程中加载dex,加个弹窗加载中,在视觉上会感觉启动变快了,其实时间没什么变化
【3】第三方库懒加载
第三方框架初始化可以在用到时在进行初始化,也就是懒加载。没有必要一定在application中进行加载。【异步进行加载】待确定
【4】卡顿优化
VSYNC 这个概念出来很久了,Vertical Synchronization,就是所谓的“垂直同步”。在 Android 中也沿用了这个概念,我们也可以把它理解为“帧同步”。为了保证 CPU、GPU 生成帧的速度和 Display 刷新的速度保持一致。

Android 系统每 16ms(更准确的是大概16.6ms) 就会发出一次 VSYNC信号触发 UI 渲染更新。大约屏幕一秒刷新60次,也就是说要求 CPU 和 GPU 每秒要有处理 60 帧的能力,一帧花费的时间在 16ms 内。如果超出这个时间就回出现卡顿。
卡顿产生的原因
1、布局页面复杂:渲染时间增加
2、过度绘制:绘制了多重背景或者绘制了不可见的UI元素.
3、主线程的复杂运算,耗时运算
4、频繁的GC:执行 GC 操作的时候,任何线程的任何操作都会需要暂停,等待 GC 操作完成之后,其他操作才能够继续运行, 故而如果程序频繁 GC, 自然会导致界面卡顿
解决:尽量不要在循环中大量的使用局部变量。防止同时创建大量的对象或变量

内存泄漏检测工具:LeakCanary
2、稳定
1、也就是不崩溃,可以进行try/catch进行捕获,最好是全局捕获线程异常,全局监控
2、避免产生ANR无响应,就是避免在主线程中执行耗时任务。
3、LMK:android的沙箱机制,每个应用程序都运行在一个独立的进程中,各自拥有独立的Dalvik虚拟机实例,系统默认分配给虚拟机的内存是有限度的。当系统内存太低依然会触发LMK(Low Memory Killer)机制,即出现闪退、崩溃现象。Android4.0以后,可以通过在application节点中设置属性android:largeHeap=”true”来设置最大可分配多少内存空间就可以突破一定限制。
3、省电
1、减少浮点运算
2、使用 Job Scheduler 管理后台任务。
4、apk体积小
1、开启混淆、压缩、去除多余的资源
2、压缩图片
3、避免引入无用的第三方库
/***************************************************************************/

二十、android保证网络安全

加密+Https

1、不可逆: MD5、sha1
概念:没有办法逆向回推(MD5目前可以暴力解开)
2、对称加密: DES、AES(建议使用)
概念:用同一个密钥对数据进行加密/解密
对比:DES的密钥长度是56位,不安全。AES的密钥长度最少是128位,建议使用256位
3、非对称加密: RSA
概念:用公钥加密,用私钥解密。注意密钥长度不要低于512位,建议使用2048位的密钥长度
结合使用
1、用AES对数据进行加密
2、用RSA对AES的密钥进行加密
3、用https://…进行请求
/***************************************************************************/

二十一、AIDL跨进程通信

AIDL是Android Interface Definition Language 的缩写。意思是Android接口定义语言。

1、服务端:服务端就是你要连接的进程。他提供给客户端一个Service,在这个Service中监听客户端的连接请求,然后创建一个AIDL接口文件,里面是将要实现的方法,注意这个方法是暴露给客户端的的。最后在Service中实现这个AIDL接口即可(这里是接口的具体实现)。服务端的职责是提供连接和自身

2、客户端:客户端首先需要绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转换成AIDL接口所属的类型,最后调用AIDL的方法就可以了。可以看到,客户端还是比较简单的,负责连接和调用。

想让自己的Aapp得到Bapp数据,就需要选定Bapp作为服务端,Aapp作为客户端,一个服务端可以对应多个客户端。

具体创建一个.aidl文件,通过AIDL 接口进行通讯。 但是这个 AIDL 接口和普通接口不一样,其内部仅支持六种数据类型:
1、基本数据类型
2、String和CharSequence
3、List 接口(会自动将List接口转为 ArrayList),且集合的每个元素都必须能够被 AIDL 支持
4、Map 接口(会自动将 Map 接口转为 HashMap),且每个元素的 key 和 value 都必须被 AIDL 支持
5、Parcelable 的实现类
6、AIDL 接口本身

binder与socket、管道、共享内存区别

为保护进程不受其他进程破坏和干扰,Linux中的进程都是相对独立的,而起一个进程的内存空间被分为用户空间和内核空间,并且两者也都是相对隔离的

两次拷贝:第一次拷贝是将数据从Aapp的用户空间拷贝到内核缓存区,第二次是从内核空间(包含着内核缓存区)拷贝到Bapp的用户空间
binder的一次拷贝:将数据从Aapp的用户空间拷贝到内核缓存区,内核缓存区与数据接收缓存区存在着映射关系,而数据接收缓存区与Bapp的用户空间存在着映射关系,从而实现传递数据
性能
socket和管道数据拷贝需要两次,binder只拷贝一次,内存共享不需要拷贝,采用映射,但实现起来难度高,复杂性大,安全性也不高。
安全性
传统的通讯方式对于通讯双方并没有做出严格的验证,比如socket和ip需要手动填写,很容易进行伪造。binder机制从协议本身就支持对通讯双方做身份校验,从而提高了安全性。
/***************************************************************************/

二十二、android版本6.0~10.0适配

v6.0:新增9个危险权限组,如果申请的权限属于其中的危险权限时,不仅需要静态注册,还需要动态申请,当用户同意其中一组中的一个权限,那么这个组内的其他权限也会默认同意。
v7.0
1、在7.0之后,不支持在项目中传递file://类型的uri
解决:使用FileProvider进行uri转换成content://类型
2、引入V2签名方式,更加安全,在7.0以下会显示未安装
v8.0(Oreo )
1、通知渠道:可以将自己不感兴趣的通知关掉,相当于分类
方法:先创建通知渠道,再创建通知,传入chnnelId,否则通知将不会显示
2、权限修改:修改了一下6.0的错误,只有当用户使用到已经同意的权限组中的权限时,才会默认同意当前一个权限,而不是刚开始将一组权限都默认立刻同意
3、静态广播无法正常接收:引入了新的广播接收器限制,静态广播将无法使用,只能使用动态广播
4、非全面屏透明页面无法设置方向
5、安装apk权限:需要声明一下添加未知应用的权限:

<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />

v9.0(pis):9.0以及以上版本采用androidx包,
1、9.0以后限制了明文流量的网络请求,最好采用https方式请求
在 res 下新建一个 xml 目录,然后创建一个名为:network_security_config.xml 文件 ,该文件内容如下:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>

然后再AndroidManifest,application标签中加入

android:networkSecurityConfig="@xml/network_security_config"

2、刘海屏的适配
v10.0(Q)
1、暗黑模式:Android Q 的暗黑模式和 Android Pie 的暗黑模式不同,在 Android Q 中,暗黑模式适用于任何地方,如果应用不支持暗黑模式,那么系统将自动设置一个暗黑模式。
2、隐私增强
3、超级锁定模式:开启飞行模式关闭所有的传感器
4、限制程序访问剪贴板:新的权限将阻止随机的后台应用程序访问剪贴板内容

<uses-permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND" />

5、应用程序降级:当对商店更新后的版本后悔时,可以“回到过去”即回滚到旧版。
6、存储权限

Android Q 在外部存储设备中为每个应用提供了一个“隔离存储沙盒”(例如 /sdcard)。任何其他应用都无法直接访问您应用的沙盒文件。由于文件是您应用的私有文件,因此您不再需要任何权限即可在外部存储设备中访问和保存自己的文件。此变更可让您更轻松地保证用户文件的隐私性,并有助于减少应用所需的权限数量。

关于分区存储,在Android10就已经推行了,简单的说,就是应用对于文件的读写只能在沙盒环境,也就是属于自己应用的目录里面读写。其他媒体文件可以通过MediaStore进行访问。
但是在android10的时候,Google还是为开发者考虑,留了一手。在targetSdkVersion = 29应用中,设置android:requestLegacyExternalStorage=“true”,就可以不启动分区存储,让以前的文件读取正常使用。但是targetSdkVersion = 30中不行了,强制开启分区存储。
当然,作为人性化的android,还是为开发者留了一小手,如果是覆盖安装呢,可以增加android:preserveLegacyExternalStorage=“true”,暂时关闭分区存储,好让开发者完成数据迁移的工作。为什么是暂时呢?因为只要卸载重装,就会失效了

以下将按访问的目标文件的地址介绍如何适配。

  1. 访问自己文件:Q中用更精细的媒体特定权限替换并取消了 READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE权限,并且无需特定权限,应用即可访问自己沙盒中的文件。

  2. 访问系统媒体文件:Q中引入了一个新定义媒体文件的共享集合,如果要访问沙盒外的媒体共享文件,比如照片,音乐,视频等,需要申请新的媒体权限:READ_MEDIA_IMAGES,READ_MEDIA_VIDEO,READ_MEDIA_AUDIO,申请方法同原来的存储权限。

  3. 访问系统下载文件:对于系统下载文件夹的访问,暂时没做限制,但是,要访问其中其他应用的文件,必须允许用户使用系统的文件选择器应用来选择文件。

  4. 访问其他应用沙盒文件:如果你的应用需要使用其他应用在沙盒内创建的文件,请点击使用其他应用的文件

1)应用专属目录
//分区存储空间
val file = File(context.filesDir, filename)

//应用专属外部存储空间
val appSpecificExternalDir = File(context.getExternalFilesDir(), filename)

复制代码

2)访问公共媒体目录文件
val cursor = contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, 
null, "${MediaStore.MediaColumns.DATE_ADDED} desc")
if (cursor != null) {
    while (cursor.moveToNext()) {
        val id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID))
        val uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id)
        println("image uri is $uri")
    }
    cursor.close()
}

二十三、android的多线程几种方式

第一种: HandlerThread
它是在一个线程中,把不同地方的任务,有序的放在消息队列中,Loop()循环取出任务,执行完当前任务才会继续执行下一个任务,HandlerThread就相当于一个线程,同步
第二种: AsyncTask
与第一种一样,但是多了任务执行进度的回调监听,AsyncTask 就相当于是一个线程,同步

class DownloadTask extends AsyncTask<Integer, Integer, String>//可以传入三个参数,重写方法

第三种: Executors是基于ThreadPoolExecutor的封装

  1. Executors.newFixedThreadPool()
    创建一个定长的线程池,每提交一个任务就创建一个线程,直到达到池的最大长度,这时线程池会保持长度不再变化

  2. Executors.newCachedThreadPool()
    创建一个可缓存的线程池,如果当前线程池的长度超过了处理的需要时,它可以灵活的回收空闲的线程,利用空闲的线程去执行其他任务,不会增加新的线程。当需要增加时,它可以灵活的添加新的线程,而不会对池的长度作任何限制

  3. Executors.newScheduledThreadPool()
    创建一个定长的线程池,而且支持定时的以及周期性的任务执行,类似于Timer

  4. Executors.newSingleThreadExecutor()
    创建一个单线程化的executor,它只创建唯一的worker线程来执行任务

    Executors适用于多任务异步执行

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
		for (int i = 0; i < 30; i++) {
			cachedThreadPool.execute(new Runnable() {
				@Override
				public void run() {
					System.out.println(Thread.currentThread().getName());
				}
			});
		}

第四种: IntentService
IntentService继承自Service,是一个经过包装的轻量级的Service,用来接收并处理通过Intent传递的异步请求。客户端通过调用startService(Intent)启动一个IntentService,利用一个work线程依次处理顺序过来的请求,处理完成后自动结束Service。

适用于后台执行的单个任务

说到多线程也要提一下加锁机制请看这篇文章
https://blog.csdn.net/weixin_43841938/article/details/118187896

二十四、自定义view

https://blog.csdn.net/weixin_43841938/article/details/108991620

二十五、webview

在这里插入图片描述

持续更新中…

  • 5
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值