当前位置:   article > 正文

Android面试题_onstartcommand intent参数什么情况下会被缓存

onstartcommand intent参数什么情况下会被缓存

参考文章:

2019Android面试题及答案
2019年Android面试题含答案–适合中高级(下)
Android(2017-2018)BAT面试题整理(java篇,含答案)
Android(2017-2018)BAT面试题整理(Android篇,含答案)
2019年为android开发准备的面试题(含答案)
Android面试之Java基础笔试题

------------优雅的分割线----------

Activity

1.什么是Activity?

Activity是四大组件之一,提供一个界面与用户进行交互,让用户可以进行点击、滑动等操作。

2.生命周期:

在这里插入图片描述
正常操作Activity生命周期有以下四种场景

1)Activity正常启动

onCreate() —> onStart() —> onResume() —>此时Activity处于完全可见状态

onCreate() :Activity开始创建时调用,是Activity生命周期的第一个方法,我们一般都会重写这个方法,进行一些初始化操作。

onStart() :当该方法调用的时候,Activity已经处于可见状态,但是还不能进行交互。

onResume() : 此时Activity已处于完全可见状态,可与用户进行交互。此时Activity处于running状态。

2)点击Home键

onPause() —> onStop() —>此时Activity完全不可见。

onPause() : 此时Activity处于paused状态,无法与用户进行交互。

onStop() : 此时Activity处于stoped状态,Activity完全不可见。

3)点击Home键后再次回到Activity

onRestart() —> onStart() —> onResume() —> 此时Activity从不可见变为可见

onRestart() : 表明Activity正在重新启动,由不可见状态变为可见状态。

4)退出当前Activity

onPause() —> onStop() —> onDestroy() —> Activity被销毁

onDestroy() :表明Activity正在被销毁,Activity处于Killed状态,这是生命周期最后一个方法,可以做一些资源释放的操作。

3.Activity的四种状态?

running / paused / stoped / killed

1).running:表明Activity正处于完全可见状态,用户可以与屏幕进行交互,此时Activity位于栈顶。

2).paused:表明Activity正处于失去焦点的状态,(例如:透明Activity)此时用户无法与Activity进行交互。

3).stoped:表明Activity此时处于完全不可见状态(例如,被另一个Activity覆盖)。

4).killed:表明Activity已经被系统回收。

4.Activity的启动模式

1).standard 模式
这是默认模式,无需设置,每次激活Activity时都会创建新的Activity实例,并放入任务栈中。相当于入栈,按back键返回到前一个Activity相当于退栈。

2).singleTop 模式
栈顶复用模式,如果在任务的栈顶正好存在该Activity的实例,就重用该实例(回调Activity的onNewIntent()方法),否则就会重新创建新的实例并存入栈顶,即使栈内已经存在该实例,只要不在栈顶,都会重新创建,可以用来解决栈顶多个重复Activity实例的问题。

3).singleTask 模式
栈内复用模式,如果在任务的栈内正好存在该Activity的实例,就会将该实例移到栈顶,位于该栈上面的实例都会被移出栈,如果栈内不存在该实例,就会重新创建一个新的实例放入栈顶,singleTask可以用来退出整个应用,将主Activity设置为singleTask模式,然后在要退出的Activity中跳转到主Activity,在主Activity中重写onNewIntent()方法,在方法中加上finish()。

4).singleInstance 模式
单例模式,在新栈中创建该Activity的实例,并让多个应用共享该栈中的该Activity实例,一旦该模式的Activity实例存在栈中,任何应用在激活该Activity时都会调用该栈内的实例(会调用实例的onNewIntent()方法)。

5.Activity切换横竖屏的生命周期

横竖屏切换设计到android:configChanges属性。

android:configChanges有三个可以设置的属性值:

orientation:消除横竖屏的影响

keyboardHidden:消除键盘的影响

screenSize:消除屏幕大小的影响

如果没有设置configChangs属性,Activity会走onCreate() —> onStart() —>onResume() —> onPause() —> onStop() —> onDestroy() —> onCreate() —> onStart() —>onResume()

如果设置了configChanges为orentation,Activity切换横竖屏只会走onCreate() —> onStart() —>onResume()

如果想要屏蔽横竖屏操作,可以使用android:screenOrientation属性进行设置屏幕始终为横屏或者竖屏(portrait始终为屏,landscape始终为屏)

6.Activity状态保存和恢复

1).需要保存状态的场景

a.点击了返回键
b.点击了Home键
c.锁屏
d.有其他App进入前台(例如打电话)
e.打开其他Activity
f.切换横竖屏
h.App被杀死

2).为什么会数据丢失?

举个切换横竖屏的例子,当前没有设置android:configChanges属性的情况下,切换横竖屏时,Activity会从最开始的onCreate()方法走到onDestroy(),再重新走onCreate(),onStart(),onResume()方法,在前后两次onCreate()中打印当前Activity的hashCode就能很清楚的看到,这是两个不同的Activity,所以新的Activity就不会再存在已经被销毁的Activity的数据了。

3).如何保存?
先重写onSavaInstanceState()方法,将数据保存到Bundle中,然后在重写onRestoreInstanceState()方法,将数据取出来。

7.两个Activity之间跳转必定会执行的方法?

一般情况下比如说有两个activity,分别叫A,B。

当在A 里面激活B 组件的时候, A会调用onPause()方法,然后B调用onCreate() ,onStart(), onResume()。

这个时候B覆盖了A的窗体, A会调用onStop()方法。

如果B是个透明的窗口,或者是对话框的样式, 就不会调用A的onStop()方法。

如果B已经存在于Activity栈中,B就不会调用onCreate()方法。

8.Activity之间传值

1)通过Intent方式传递参数跳转

2)通过广播方式

3)通过接口回调方式

4)借助类的静态变量或全局变量

5)借助SharedPreference或是外部存储,如数据库或本地文件

6) EventBus

9.Android的任务栈

用于存储Activity,当用户打开或者销毁Activity的时候,都会在栈内做对应的添加和移除操作。

10.Android 进程优先级

前台进程 / 可见进程 / 服务进程 / 后台进程 / 空进程

前台进程:包含正处于前台与用户进行交互的Activity或者是前台Activity绑定了的Service。
可见进程:Activity处于可见状态但是不可交互的状态。
服务进程:包含一个Service服务的进程。
后台进程:处于不可见的状态下的进程,比如按下Home键。
空进程:没有活跃的组件,只是为了缓存的目的而存在的,随时都可能被系统杀掉。

11.Dialog弹起Activity的生命周期?

Dialog弹起对Activity的生命周期没有影响
.
.
.
.

Fragment

1.Fragment和Activity的生命周期对比

因为Fragment是依附在Activity上的,所以Fragment的生命周期会受Activity的影响
在这里插入图片描述
onAttach():Fragment和Activity建立关联的时候调用
onCreateView():为Fragment创建视图,加载布局时调用
onActivityCreate():当Activity的onCreate()方法执行完后调用
onDestroyView():Fragmemt布局被移除时调用
onDetach():Fragment和Activity解除关联时调用

2.Fragment生命周期解析

1). 当一个Fragment被创建的时候
onAttach() —> onCreate() —> onCreateView() —> onActivityCreated()

2). 当这个Fragment对用户可见时,它会走
onStart() —> onResume()

ps:**从创建到显示,经历了以上生命周期**
  • 1

3). 当Fragment进入后台模式的时候
onPause() —> onStop()

4). 当Fragment被销毁了(获取持有它的Activity被销毁了)
onPause() —> onStop() —> onDestroyView() —> onDestroy() —> onDetach()

5). 锁屏
onPause() —> onStop();

6). 屏幕解锁
onStart() —> onResume()

7). 按下Home键
onPause() —> onStop()

8). 切换到其他Fragment
onPause() —> onStop() —> onDestroyView()

9). 切换到原来的Fragment
onCreateView() —> onActivityCreate() —> onStart() —> onResume()

3.什么是Fragment?

Fragment是Activity界面中的一部分,可以理解为模块化的Activity。
1). Fragment不能单独存在,必须嵌入在Activity上。
2). Fragment具有自己的生命周期,接收它自己的事件,并在Activity运行被添加或者被删除。
3). Fragment的生命周期直接受Activity生命周期的影响,如果Activity处于暂停状态,它拥有的Fragment都会处于暂停状态。

4.Fragment的作用

支持动态、灵活的界面设计

5.具体使用

在Activity使用Fragment时,需要考虑版本兼容问题
1). Android 3.0后,Activity可直接继承自Activity,并且在其中嵌入使用Fragment
2). Android 3.0前,Activity需FragmentActivity(其也继承自Activity),同时需要导入android-support-v4.jar兼容包,这样在Activity中才能嵌入Fragment

Fragment一般是添加到Activity中的,一般有两种添加方式

1). 静态添加
在布局文件中添加< fragment >控件,然后再新建一个类继承Fragment,设置fragment的布局和事件

2). 动态添加
在布局中添加< Fragment >控件,然后在java文件中获取FragmentManager和FragmentTransaction,通过add的方法将fragment添加到Activity的布局中来

6.Fragment和Activity之间的通信

1). Fragment传递数据到Activity:
接口回调的方式传递,Activity调用Fragment的接口

2). Activity传递数据到Fragment:
Activity利用setArguments(Bundle),Fragment利用getArguments()接收

3). EventBus

7.FragmentManager中的add,remove和replace?

一般我们两种模式
一种是将fragment添加(add)到容器中,控制其隐藏和显示。这种模式不会销毁fragment。
另一种就是替换(replace)这种方法会把之前的fragment销毁掉。

8.Fragment遇到的坑

Fragment嵌套Fragment:
正确选择是使用getFragmentManager()还是getChildFragmentManager()。

9.Fragment的优点

1).Fragment可以将你的Activity分离成多个可重用的组件,每个都有它自己的生命周期和UI。
2).可以轻松地创建动态灵活的UI设计,可以使用于不同的屏幕尺寸,从手机到平板。
3).Fragment于Activity紧密关联在一起,可以在运行过程中动态地进行添加,移除,替换等。
4).解决Activity之间切换不流畅,轻量切换。
5).提供一个新的方式在不同的安卓设备上统一UI。
6).在Android4.2中新增的嵌套Fragment,能够生成更好的界面效果。
7).做局部更新更加方便

10.Fragment的add和replace方法的区别?

1).add:
用add切换Fragment,Fragment不会被重新创建,保持了原先Fragment的状态。
2).replace:
用replace切换Fragment,Fragment会被重新创建。

11.getChildFragmentManager和getsupportFragmentManager和getFragmentManager的区别

getFragmentManager()所得到的是所在fragment的父容器的管理器。

getChildFragmentManager()所得到的是所在fragment嵌套子容器的管理器。

getSupportFragmentManager()主要是用于支持Android 3.0以下的系统版本,3.0以上版本可以直接使用getFragmentManager()。

12.FragmentPagerAdapter和FragmentStatePagerAdapter的区别?

当需要加载的页面较少且页面数据变化较少的时候,应该使用FragmentPagerAdapter;
当需要加载的页面较多,且每个页面的数据量较大的时候或者数据经常发生变化的时候,应当使用FragmentStatePagerAdapter。

它们两个之间的本质区别在于:
当切换页面的时候,FragmentStatePagerAdapter会remove之前加载的fragment,从而将内存释放掉。
而FragmenPagerAdapter不会remove掉fragment,而只是detach,仅仅是在页面上让fragment的UI脱离Activity的UI,但是fragment仍然保存在内存里,并不会被回收内存。

13.Fragment 如何实现滑动?

Fragment +ViewPager + List< Fragment >

14.Fragment之间如何传递数据?

1)接口回调

2)在要传递数据的fragment中编写方法,在另一个fragment中创建其实例,然后调用方法。

3)以Activty为纽带,将数据传入Activity中,然后再传递给fragment

4)EventBus

.
.
.
.
Service

1.什么是service?

Service是Android 中实现程序后台运行的解决方案,它非常适合去执行那些不需要和用户交互而且还要求长期运行的任务,服务的运行不依赖于任何的用户界面,即使程序被切换到后台,或者用户打开了另外一个应用程序,服务仍然能够保持正常运行。服务同Activity一样,需要在清单中进行注册。
不过需要注意的是,服务并不是运行在一个独立的进程当中,而是依赖于创建服务时所在的应用程序进程,当某个应用程序进程被杀掉了,所有依赖于该进程的服务也会停止运行。
服务时运行在主线程的,所以说需要在服务的内部手动创建子线程,并在这里执行具体的任务,否则就有可能出现主线程被阻塞的情况。

2.启动方式

1). startService():
使用startService()启动的服务,生命周期是 onCreate() —> onStartCommand() —> onDestroy()。如果服务已经启动过了,则不过再运行onCreate()方法,只会执行onStartComand()方法。
使用这种启动方式启动的服务,生命周期与启动者没有联系,即使启动者已经被销毁,服务也不会停止,如果想要停止服务,需要在外部调用stopService()或者在服务内部调用stopSelf()。

2)bindService():
使用bindService启动的服务,生命周期是onCreate() —>onBind()—>onunbind()—>onDestory()。
使用这种方式启动的服务,生命周期会与启动它的组件绑定在一起,如果启动的它组件被销毁了,服务也会随着停止。
如果想要停止服务,可以在外部调用unBindService()来停止服务。

3.Activity如何调用Service中的方法?数据交互也是同样

1). 首先创建Service,然后实现内部类继承自Binder,然后在onBind()生命周期中返回内部类的实例。并在清单中注册服务。

2). 利用bindService()启动服务,然后在启动服务的Activity中,通过实现ServiceConnection接口来与Service取得连接。这样就能在回调中调用服务中的方法了。

数据交互还可以用广播的方式

4.生命周期

在这里插入图片描述

5. 粘性、非粘性服务

关于粘性服务,这里需要提到onStartCommand()的三个返回值。

1). START_STICKY : 粘性服务,如果使用这个返回值,我们启动的服务会和应用程序“粘”在一起,如果执行完onStartCommand()方法之后服务被异常kill掉,系统会自动重启该服务,当再次重启该服务时,传入的第一个参数(intent)为null。

2).START_NOT_STICKY: 非粘性服务,使用这个返回值,服务在执行onStartCommand()后被异常kill掉,系统不会自动重启该服务。

3). START_REDELIVER_INTENT : 重传intent,如果使用这个返回值,服务在执行onStartCommand()后被异常kill掉,系统会重新启动该服务,并将intent传入。

6.Activity和Service如何进行数据交互(通信方式)

1). binder + listener(接口回调ServiceConnection)

2). binder + Handler
主要思路:Service持有Activity的Handler对象,Service通过往Handler send Message的方式,达到通信的效果。

3).广播(推荐LocalBroadcastManager

4).EventBus

7.Service和线程的区别?为什么不用Service替代子线程?相应在什么情况下使用?

Service是Android 的一种机制,对应的Service是运行在主线程上的。

线程是程序执行的最小单位,它是分配CPU的基本单位,可以用线程来执行一些异步的操作。

区别:
Thread的运行是独立于Activity的,也就是说Activity被finish之后,如果你没有主动停止Thread或者Thread里面的run方法没有执行完成的话,Thread也会一直执行。因此这里会出现一个问题,当Activity被finish之后,你不再持有该Thread的应用,另一方面,你没有办法在不同的Activity中对同一个Thread进行操作,而Service可以被多个Activity共用。

我们不用服务代替线程的原因:
服务(子类IntentService则是在内部添加了子线程)也是运行在主线程上面,而不是子线程,相当于你还是需要新起线程来完成相应的操作,这又是何苦啦;并且一个类里面需要多线程操作的情况,服务是不是显得很无力。各有各的优点,下面来看使用情况。

使用情况:
1)在应用中,如果是长时间的在后台运行,而且不需要交互的情况下,使用服务。
同样是在后台运行,不需要交互的情况下,如果只是完成某个任务,之后就不需要运行,而且可能是多个任务,不需要长时间运行的情况下使用线程。

2)如果任务占用CPU时间多,资源大的情况下,要使用线程。

3)一般我们做下载任务都是在服务里面新起线程做异步任务来操作,或者直接使用IntentService。

.
.
.
.
BroadcastReceiver

1.什么是广播?

广播是Android 的四大组件之一,是一种广泛运用于应用程序之间传输信息的机制,广播有两种角色,广播发送者和广播接收者,Android 中的广播主要分为两种类型,标准广播和有序广播。

1).标准广播:
标准广播是一种完全异步执行的广播,在广播发布之后,所有的广播接收器几乎会在同一时刻接收到这条广播消息,因此它们之间没有任何先后顺序可言,这种广播的效率会比较高,但同时也意味着它是无法被截断的。

2).有序广播:
有序广播则是一种同步执行的广播,在广播发出之后,同一时刻只会有一个广播接收器接收到该条消息,当这个广播接收器中的逻辑执行完毕后,广播才会继续传递。所以此时的广播接收器是由先后顺序的,优先级高的广播接收器就可以先接收到广播消息,并且前面的广播接收器还可以截断正在传递的广播,这样后面的广播接收器就无法收到广播消息。

2.广播的生命周期?

1)动态注册:
存活周期在Context.registerReceiver() 和 Context.unregisterReceiver() 之间,BroadcastReceiver每次接收广播都是使用注册时传入的对象处理的。

2)静态注册:
进程在的情况下,receiver会正常接收到广播,调用onReceiver()方法,生命周期只存在onReceiver()函数当中,此方法结束,BroadcastReceiver就销毁了,onReceiver()只存在十几秒的时间,在onReceiver()中操作超过10s,就会报ANR。

进程不存在的情况下,广播相应的进程会被拉活,Application.onCreate()会被执行,再调用onReceiver()。

3.本地广播和系统全局广播的区别?

本地广播:LocalBroadcastReceiver
系统全局广播:正常使用的广播

系统全局广播发出的广播可以被其他任何应用程序接收到,并且可以接收来自于其他任何应用程序的广播。这样就很容易引起安全性的问题,比如携带关键性数据的广播被其他的应用的所截获,或者其他应用程序不停地向我们的广播接收器里发送各种垃圾广播。

而为了解决广播的安全性问题,Android引入了一套本地广播机制,使用这个机制发出的广播只能够在应用程序的内部进行传递,并且广播接收器也只能接收来自本应用程序发出的广播,这样所有的安全性问题就不复存在了。

4.请描述一下广播BroadcastReceiver的理解(实现原理)

广播是Android 四大组件之一,是一个全局的监听器,它的作用是用来监听或者接收App或者系统发出的广播消息,并作出响应。广泛应用于Android 不同组件间的通信(含应用内不同应用)、多线程之间的通信,在Android 系统在特定情况下通信,例如电话呼入时,网络可用时。
Android中的广播使用了设计模式中的观察者模式,所以在整个广播系统中,存在三种角色,消息订阅者(广播接收者)、消息发布者(广播发布者)以及消息中心(AMS,即Activity Manage Sevice(作用:统一调度所有应用程序的Activity)),而它的原理是:

1). 广播接收者 通过Binder机制在AMS中进行注册

2). 广播发布者 通过Binder机制向AMS发送广播。

3). AMS根据 广播发送者 要求,在已注册的列表中,寻找合适的广播接收者。寻找的依据就是IntentFilter / permission。

4). AMS将广播发送到合适的广播接收者相应的消息循环队列中。

5). 广播接收者通过消息循环拿到该广播,并回调onReceive()方法。

注意:广播接收者和广播发布者是异步执行的,即广播发布者不关心有无接收者接收消息,也不关心何时接收到消息。

5.广播有几种创建方式?有什么区别?

两种,动态创建和静态创建(Android 8.0将不再支持大部分隐式广播,官方说法是为了省电)

区别:
1)动态注册广播永远要快于静态注册广播,不管静态广播的优先级有多高,不管动态注册广播的优先级有多低。(因为在AMS中优先处理动态注册广播)

2)动态注册的广播不是常驻型广播,也就是说广播跟随Activity的生命周期,需要在Activity的onPause()方法中移除广播,否则可能会内存泄漏;静态注册的广播是常驻性广播,也就是说应用程序关闭后,如果有广播信息发送过来,程序也会被系统调用自动运行。

6.广播的注册和注销时机

onResume()注册onPause()注销是因为onPause()在App死亡前一定会被执行,从而保证广播在App死亡前一定会被注销,从而防止内存泄露。

7.为什么要在onResume()&onPause()方法内进行注册和注销?不在onCreate() & onDestory() 或 onStart() & onStop()方法内进行呢?

因为当系统内存不足、优先级更高的应用程序需要内存、需要回收Activity占用的资源时,Activity在执行完onPause()后就会被销毁,不会再执行onStop()和onDestroy()方法,所以无法保证广播一定会被注销,但是onPause()是一定会执行的,从而保证Activity被销毁前广播一定会被注销,从而防止内存泄漏。

.
.
.
.
Content Provider

1.什么是Content Provider?
内容提供者,Android 四大组件之一,一般用法有两种,一种是使用现有的内容提供器来读取和操作相应程序中的数据,另外一种是创建自己的内容提供器给我们程序的数据提供外部访问接口。

2.Content Provider的生命周期?

和应用的生命周期一样,它属于系统应用,应用启动时,它会跟着初始化,应用关闭或者被杀,它会跟着结束。

3.说说ContentProvider、ContentResolver、ContentObserver 之间的关系

1)Content Provider 实现了各个应用程序间数据共享,用来提供内容给别的应用程序进行操作。

2)ContentResolver:内容解析者,用于获取内容提供者提供的数据,通过ContentResolver。nofityChange(uri)发出消息。

3)ContentObserver内容监听者,可以监听数据的改变状态,观察特定的Uri引起的数据库变化,继而做出一些相应的处理,类似于数据库种的触发器,当ContentObserver观察的Uri发生变化时便会触发它。

.
.
.
.
零碎问题

四大组件是什么?

Android 四大组件分别为Activity、Service、BroadcastReceiver、ContentProvider

Activity 是一个可操作的可视化界面,一个Activity通常就是一个界面,Activity需要再清单中进行注册,Activity之间通过Intent进行传值。

Service 是一个运行于后台,没有界面的组件,也需要在清单中进行注册,有自己的生命周期,有startServer()和bindServer()两种启动方式。

BroadcastReceiver 广播接收者,应用于应用程序之间传输信息,可以接收来自应用或者系统的广播信息并对其做出响应。

ContentProvider 内容提供者,是一个应用程序的指定数据集能够提供给其他应用程序,其他应用程序可以通过ContentResolver类从该内容提供者中获取或存入数据。

Application、Activity的Context对象的区别?

两者生命周期不同,Application的context对象是伴随应用生命周期的,Activity的context对象只跟随当前Activity的生命周期,Application的context对象不可以showDialog,startActivity,LayoutInflation,而与UI相关的操作,都得用Activty的context对象来处理。

讲解一下Context

Context是一个抽象基类,在Android中被翻译为上下文,也可以理解为环境,是提供一些程序运行的环境基础信息。
Context有两个子类,ContextWrapper是Context功能的封装类,ContextImpl是Context功能的实现类。
而ContextWrapper又有三个直接的子类,ContextThemeWrapper、Service、Application。
ContextThemeWrapper是一个带主题的封装类,它也有一个直接子类,就是Activity。
所以Activity和Service和Application的Context是不一样的,只有Activity需要主题,Service不需要。
所以Context有三种类型,分别是Activity、Service、Application,这三个类虽然分别各自承担不同的作用,但是他们的功能都是由ContextImpl去实现的,因此在绝大多数情况下,这三种Context是可以通用的,但是有几个场景比较特殊,例如弹起弹窗,启动Activity,出于安全的考虑,Android 不允许Activity或者Dialog凭空出现,一个Activity的启动必须建立在另一个Activity之上,也就是因此形成返回栈,而Dialog则必须是在一个Activity上弹出(除非系统的Dialog,例如电量不足)。因此我们只能使用Activity的Context。
getApplicationContext和getApplication得到的对象是一样的,只不过对象类型不一样。

一个应用的Context数量 = Activity数量 +Service数量 + 1(Application)。

HttpClient与HttpUrlConnection的区别

相同点:
两者都支持Https协议,都是以流的形式进行上传或下载数据,也可以说是以流的形式进行数据的传输,还有ipv6,已经连接池等功能。

区别:
HttpClient拥有特别多的api,所以很难在不破坏它兼容性的前提下进行扩展,也是因为这个原因,Google在Android 6.0的时候,直接就弃用了HttpClient。
HttpUrlConnection相对来说就比较轻量级,API比较少,容易扩展,并且能满Android 大部分数据传输。比如Volley框架,在2.3版本以前就是使用HttpClient,在2.3之后的版本就使用了HttpUrlConnection。

view的绘制流程

自定义控件:

1)组合控件:这种自定义不需要我们自己绘制,而是使用现有的原生控件进行组合,例如标题栏。

2)继承原有的控件:这种控件在原生控件提供的方法外,可以添加自己的方法,如制作圆角图片,圆形图片。

3)完全自定义控件:这种view上锁展示出来内容全部都是我们自己绘制出来的,例如水波纹。

view的绘制流程:onMeasure() —> onLayout() —> onDraw()

1)onMeasure() :测量view的宽高,从顶层父view到子view地递归调用measure()方法,measure()方法又回调onMeasure()方法。

2)onLayout():确定view位置,进行页面布局。这是一个从顶层父view向子view递归调用view.layout()方法的过程,根据上一步onMeasure()测量子view所得到的布局大小和布局参数,将子view放在合适的位置。

3)onDraw():绘制视图。

view的事件分发机制

这是一份全面 & 详细的事件分发学习指南

Android 中有几种动画?

Android 共有三种动画,分别是帧动画、补间动画和属性动画。

1)帧动画:通过指定每一帧的图片和播放时间,有序地进行播放而得到的动画效果。

2)补间动画:通过制定view的初始状态,变化时间、方式,通过一系列算法去进行图形变换,从而行成动画效果,主要由Alpha,Scale,Translate,Rotate四种效果。
值得注意的是,补间动画只是在视图层实现了动画,并没有真正改变view的属性。

3)属性动画:在Android 3.0的时候才支持,它是通过不断改变view的属性,不断重绘而形成的动画效果,相比于补间动画,view的属性是真正改变了。

Android 动画在使用时应该注意哪些点?

1)OOM问题(内存溢出):
这个情况主要出现在帧动画,当图片数量过大且图片较大时就容易出现OOM。解决方法?不用就好

2)内存泄漏:
属性动画在设置动画的时候可以设置其无限循环,这类动画应该在Activity退出时及时停止,否则将会导致Activty无法释放从而造成内存泄漏。(未尝试,不知道真假)

3)兼容性问题:
属性动画是android 3.0才加入的,因此在安卓3.0以下需要做好适配工作。

4)setVisibility(View.GONE)失效
在完成补间动画后,对view进行隐藏操作失效,这个时候只要调用view.clearAnimation()清除动画即可。

进程和线程的区别

进程:
具有一定独立功能的的程序,是系统进行资源分配和调度运行的基本单位,每个Android应用程序可以理解为是一个进程,但一个应用也可以设置多个进程。

线程:
线程是操作系统能够进行运算调度的最小单位,线程也被称做轻量级进程,线程是进程的执行单位,每个线程都是互相独立的。

关系:
线程是进程的组成部分,一个进程可以拥有多个线程,但是至少有一个线程,一个线程必须有一个父进程。

区别:
进程具有独立的地址空间,而多个线程共享内存,进程具有一个独立功能的程序,线程不能独立运行,必须依赖于应用程序之中。

线程间的通信方式

1)接口

2)Handler

3)观察者模式(EventBus)

4)Android 使用runonUiThread可以切换到UI线程

5)AsyncTask

6)Broadcast

7)SharedPreference

进程间的通信方式

1)Intent,调用其他应用程序的Activity或者调用系统的电话、短信

2)Content Provider 多应用数据共享

3)AIDL 未使用过

4)Broadcast

解析异步消息处理机制(Handler的原理)

Android 中的异步消息主要由4个部分组成:Message、Handler、MessageQueue和Looper

1). Message:
Message是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间交换信息,Message中可以使用what、arg1、arg2等字段来携带一些整形数据,使用obj字段可以携带一个Object对象。

2). Handler
Handler顾名思义也就是处理者的意思,它主要是用于发送和处理信息的,发送信息一般是使用Handler的sendMessage()方法,而发送出的消息经过一系列地辗转处理后,最终会传递到Handler的handleMessage()方法中。

3).MeaasgeQueue
MessageQueue是消息队列的意思,它主要用于存放所有通过Handler发送的信息,这部分消息会一直存在消息队列中,等待被处理,每个线程中只会由一个MessageQueue()对象。

4)Looper
Looper是每个线程中MessageQueue的管家,调用Looper的loop()方法后,就会进入到一个无限循环中,然后每当发现MessageQueue中存在一条信息,就会将它取出,并传递到Handler的handleMessage()方法中,每个线程中也只会有一个Looper对象。

整体流程:

首先需要在主线程当中创建一个Handler对象,并重写handleMessage()方法。
然后当子线程中需要进行UI操作的时候,就创建一个Message对象,并通过Handler将这条信息发送出去,之后这条信息就会被添加到MessageQueue的队列中等待被处理,而Looper则会一直尝试从MessageQueue中取出待处理的消息,所以此时handleMessage()方法中的代码也会在主线程中运行,于是便可操作UI了。

Binder机制的原理

在Android 系统的Binder机制中,是由Client、Service、ServiceManager、Binder驱动程序组成的,其中Client、Service、ServiceManager是运行在用户空间,而Binder驱动程序是运行在内核空间的,而Binder就是将这四个组件粘合在一起的粘合剂,其中核心的组件就是Binder驱动程序,ServiceManager提供辅助管理的功能,而Client和Service正是在Binder驱动程序和Service Manager提供的基础设施上实现C/S 之间的通信。其中Binder驱动程序提供设备文件/dev/binder与用户控件进行交互,Client、Service,Service Manager通过open和ioctl文件操作相应的方法与Binder驱动程序进行通信。而Client和Service之间的进程间通信是通过Binder驱动程序间接实现的。而Binder Manager是一个守护进程,用来管理Service,并向Client提供查询Service接口的能力。

AIDL

AIDL: 每一个进程都有自己的Dalvik VM实例,都有自己的一块独立的内存,都在自己的内存上存储自己的数据,执行着自己的操作,都在自己的那片狭小的空间里过完自己的一生。而aidl就类似与两个进程之间的桥梁,使得两个进程之间可以进行数据的传输,跨进程通信有多种选择,比如 BroadcastReceiver , Messenger 等,但是 BroadcastReceiver 占用的系统资源比较多,如果是频繁的跨进程通信的话显然是不可取的;Messenger 进行跨进程通信时请求队列是同步进行的,无法并发执行。

Android UI适配

字体使用sp,使用dp,多使用match_parent,wrap_content,weight
图片资源,不同图片的的分辨率,放在相应的文件夹下可使用百分比代替。

热修复的原理

我们知道Java虚拟机 —— JVM 是加载类的class文件的,而Android虚拟机——Dalvik/ART VM 是加载类的dex文件,
而他们加载类的时候都需要ClassLoader,ClassLoader有一个子类BaseDexClassLoader,而BaseDexClassLoader下有一个
数组——DexPathList,是用来存放dex文件,当BaseDexClassLoader通过调用findClass方法时,实际上就是遍历数组,
找到相应的dex文件,找到,则直接将它return。而热修复的解决方法就是将新的dex添加到该集合中,并且是在旧的dex的前面,
所以就会优先被取出来并且return返回。

Android内存泄露及管理

1)内存溢出(OOM)和内存泄露(对象无法被回收)的区别。
2)引起内存泄露的原因
3) 内存泄露检测工具 ------>LeakCanary

内存溢出 out of memory:是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。内存溢出通俗的讲就是内存不够用。
内存泄露 memory leak:是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光
内存泄露原因:
一、Handler 引起的内存泄漏。
解决:将Handler声明为静态内部类,就不会持有外部类SecondActivity的引用,其生命周期就和外部类无关,
如果Handler里面需要context的话,可以通过弱引用方式引用外部类
二、单例模式引起的内存泄漏。
解决:Context是ApplicationContext,由于ApplicationContext的生命周期是和app一致的,不会导致内存泄漏
三、非静态内部类创建静态实例引起的内存泄漏。
解决:把内部类修改为静态的就可以避免内存泄漏了
四、非静态匿名内部类引起的内存泄漏。
解决:将匿名内部类设置为静态的。
五、注册/反注册未成对使用引起的内存泄漏。
注册广播接受器、EventBus等,记得解绑。
六、资源对象没有关闭引起的内存泄漏。
在这些资源不使用的时候,记得调用相应的类似close()、destroy()、recycler()、release()等方法释放。
七、集合对象没有及时清理引起的内存泄漏。
通常会把一些对象装入到集合中,当不使用的时候一定要记得及时清理集合,让相关对象不再被引用。

ANR

ANR全名Application Not Responding, 也就是"应用无响应". 当操作在一段时间内系统无法处理时, 系统层面会弹出上图那样的ANR对话框.
产生原因:
(1)5s内无法响应用户输入事件(例如键盘输入, 触摸屏幕等).
(2)BroadcastReceiver在10s内无法结束
(3)Service 20s内无法结束(低概率)

解决方式:
(1)不要在主线程中做耗时的操作,而应放在子线程中来实现。如onCreate()和onResume()里尽可能少的去做创建操作。
(2)应用程序应该避免在BroadcastReceiver里做耗时的操作或计算。
(3)避免在Intent Receiver里启动一个Activity,因为它会创建一个新的画面,并从当前用户正在运行的程序上抢夺焦点。
(4)service是运行在主线程的,所以在service中做耗时操作,必须要放在子线程中。

MVP,MVC

此处延伸:手写mvp例子,与mvc之间的区别,mvp的优势

MVC中的M对应着model,是针对业务模型建立的数据结构和相关的类,model与view无关,而与业务相关。
V对应着view,即视图。
C对应着Controller,Android 的控制层一般在Activity、Fragment或者由他们控制的其他业务类中。
MVC简单来说就是通过Controller来操作Model的数据,并且返回给View层展示。
MVC的缺点:
1.Activity并不是一个标准的MVC模式中的Controller,它的首要职责是加载应用的布局和初始化用户界面,接受并处理来自用户的操作请求,进而做出响应,随着界面及其逻辑的复杂度不断提高,Activity类的职责不断增加,以致变得庞大臃肿。
2.view层和model层互相耦合,不易开发和维护。

为了解决以上问题,就产生了MVP模式。

MVP是MVC的演化版本,MVP的角色定义如下:
model:主要提供数据的存取功能,Presenter需要通过model层来存储、获取数据。
View:负责处理用户事件和视图部分的展示,在Android中,它可能是Activity、Fragment类或者某个view控件。
Presenter:作为View和Model之间沟通的桥梁,它从Model层检索数据后返回给View层,使得View和Model之间没有耦合。
在MVP中,Presenter完全将Model和View进行了分离,主要的程序逻辑在Presenter里实现。而且,Presenter与具体的View是没有直接关联的,而是通过定义好的接口进行交互,从而使得在变更View时可以保持Presenter的不变,这点符合面向接口编程的特点。
View只应该有简单的GET/SET方法,以及用户输入和设置界面显示的内容,除此之外就不用管有更多的内容,决不允许View直接访问Model。

RecyclerView和ListView的区别

RecyclerView可以完成ListView,GridView的效果,还可以完成瀑布流的效果。同时还可以设置列表的滚动方向(垂直或者水平);
RecyclerView中view的复用不需要开发者自己写代码,系统已经帮封装完成了。
RecyclerView可以进行局部刷新。
RecyclerView提供了API来实现item的动画效果。
在性能上:
如果需要频繁的刷新数据,需要添加动画,则RecyclerView有较大的优势。
如果只是作为列表展示,则两者区别并不是很大。

平时开发中设计到哪些性能优化,你是从哪些地方来优化,你是通过什么工具来分析的?

笼统的说:就是让App反应更快,使用更稳,流量、电量更省,apk更小。

具体的说:省电优化、内存优化、网络优化、图片优化、UI优化。

更快:使用时避免出现卡顿,响应速度快,减少用户等待的时间,满足用户期望。

UI优化:

分析工具:Systrace

(1)减少层级,合理使用 RelativeLayout 和 LinerLayout,合理使用Merge,Include。

(2)提高显示速度,使用 ViewStub,它是一个看不见的、不占布局位置、占用资源非常小的视图对象。

(3)布局复用,可以通过标签来提高复用。

(4)尽可能少用wrap_content,wrap_content 会增加布局 measure 时计算成本,在已知宽高为固定值时,不用wrap_content 。

(5)删除控件中无用的属性。

更稳:减低 Crash 率和 ANR 率,不要在用户使用过程中崩溃和无响应。

(1)增加相应的判断,以及异常处理。

(2)避免在主线程做耗时操作。

更省:节省流量和耗电,节约内存,减少用户使用成本,避免使用时导致手机发烫。

耗电分析工具:Battery Historian

(1)避免浮点运算。

(2)根据客户端图片的大小要求叫UI做相应大小的图提供给服务器,避免过大消耗更多流量和电量。

(3)不用的广播,服务记得及时关闭。

内存分析工具:Memory Monitor

(1)对象引用:强引用、软引用、弱引用、虚引用四种引用类型,根据业务需求合理使用不同,选择不同的引用类型。

(2)减少不必要的内存开销:注意自动装箱,增加内存复用,比如有效利用系统自带的资源、视图复用、对象池、Bitmap对象的复用。

(3)使用最优的数据类型:比如针对数据类容器结构,可以使用ArrayMap数据结构,避免使用枚举类型,使用缓存Lrucache等。

(4)图片内存优化:点9图减少图片大小以及可以设置位图规格,根据采样因子做压缩,用一些图片缓存方式对图片进行管理等。

更小:安装包小可以降低用户的安装成本。

(1)做混淆优化代码。

(2)删除无用的代码及图片相应的本地库。

(3)Lint优化。

(4)zip压缩。

Activity的启动过程(不要回答生命周期)

app启动的过程有两种情况,第一种是从桌面launcher上点击相应的应用图标,第二种是在activity中通过调用startActivity来启动一个新的activity。

我们创建一个新的项目,默认的根activity都是MainActivity,而所有的activity都是保存在堆栈中的,我们启动一个新的activity就会放在上一个activity上面,而我们从桌面点击应用图标的时候,由于launcher本身也是一个应用,当我们点击图标的时候,系统就会调用startActivitySately(),一般情况下,我们所启动的activity的相关信息都会保存在intent中,比如action,category等等。我们在安装这个应用的时候,系统也会启动一个PackaManagerService的管理服务,这个管理服务会对AndroidManifest.xml文件进行解析,从而得到应用程序中的相关信息,比如service,activity,Broadcast等等,然后获得相关组件的信息。当我们点击应用图标的时候,就会调用startActivitySately()方法,而这个方法内部则是调用startActivty(),而startActivity()方法最终还是会调用startActivityForResult()这个方法。而在startActivityForResult()这个方法。因为startActivityForResult()方法是有返回结果的,所以系统就直接给一个-1,就表示不需要结果返回了。而startActivityForResult()这个方法实际是通过Instrumentation类中的execStartActivity()方法来启动activity,Instrumentation这个类主要作用就是监控程序和系统之间的交互。而在这个execStartActivity()方法中会获取ActivityManagerService的代理对象,通过这个代理对象进行启动activity。启动会就会调用一个checkStartActivityResult()方法,如果说没有在配置清单中配置有这个组件,就会在这个方法中抛出异常了。当然最后是调用的是Application.scheduleLaunchActivity()进行启动activity,而这个方法中通过获取得到一个ActivityClientRecord对象,而这个ActivityClientRecord通过handler来进行消息的发送,系统内部会将每一个activity组件使用ActivityClientRecord对象来进行描述,而ActivityClientRecord对象中保存有一个LoaderApk对象,通过这个对象调用handleLaunchActivity来启动activity组件,而页面的生命周期方法也就是在这个方法中进行调用。

谈谈对接口回调的理解?

原理:先创建一个对象,再创建一个控制器对象,将回调对象需要被调用的方法告诉控制器对象,控制器对象负责检查某个场景是否出现或某个条件是否满足,当满足时,自动调用回调对象方法。

Android各个版本API的区别?

主要记住一些大版本变化:

android3.0 代号Honeycomb, 引入Fragments, ActionBar,属性动画,硬件加速

android4.0 代号Ice Cream,API14:截图功能,人脸识别,虚拟按键,3D优化驱动

android5.0 代号Lollipop API21:调整桌面图标及部件透明度等

android6.0 代号M Marshmallow API23,软件权限管理,安卓支付,指纹支持,App关联,

android7.0 代号N Preview API24,多窗口支持(不影响Activity生命周期),增加了JIT编译器,引入了新的应用签名方案APK Signature Scheme v2(缩短应用安装时间和更多未授权APK文件更改保护),严格了权限访问

android8.0 代号O API26,取消静态广播注册,限制后台进程调用手机资源,桌面图标自适应

android9.0 代号P API27,加强电池管理,系统界面添加了Home虚拟键,提供人工智能API,支持免打扰模式

invalidate和postInvalidate的区别及使用?

invalidate:在ui线程刷新view

postInvalidate:在工作线程刷新view(底层还是handler)其实它的原理就是invalidate+handler

Activity-Window-View三者的差别?

Activity:是安卓四大组件之一,负责界面展示、用户交互与业务逻辑处理。

Window:就是负责界面展示以及交互的职能部门,就相当于Activity的下属,Activity的生命周期方法负责业务的处理。

View:就是放在Window容器的元素,Window是View的载体,View是Window的具体展示。

三者的关系: Activity通过Window来实现视图元素的展示,window可以理解为一个容器,盛放着一个个的view,用来执行具体的展示工作。

描述一次网络请求的流程

Android网络编程(一次网络请求)

.
.
.
.

设计模式

知道哪些设计模式?

使用过:单例模式、观察者模式、工厂模式、建造者模式(Builder)、适配器模式(RecyclerView、List等的Adapter)

单例模式

定义:
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

使用场景:
1)应用的Application。
2)数据请求管理类,自己封装的工具类。
3)图片加载框架,例如Glide。

实现的要点
1)构造函数不对外开放,必须为Private(就是不能用New的形式生成对象)
2)通过一个静态方法或者枚举返回单例对象
3)确保单例类的对象有且只有一个,尤其是在多线程环境下
4)确保单例类对象在反序列化时不会重新创建对象

常见的实现方式:

1)饿汉式

饿汉式的缺点
饿汉单例模式采用的是静态变量 + fianl关键字的方式来确保单例模式,应用启动的时候就生成单例对象,效率不高。因为无法控制单例对象的生成时间。

public class Singleton {
    private static final Singleton singleton = new Singleton();
    //构造函数私有化
    private Singleton() {
    }
    //公有的静态函数,对外暴露获取单例对象的接口
    public static Singleton getInstance() {
        return singleton;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

2)懒汉式:

懒汉式的缺点:
懒汉模式的主要问题在于由于加了synchronized关键字,每调用一次getInstance方法,都会进行同步,造成了不必要的开销

public class Singleton {
    private static Singleton singleton;
    //构造函数私有化
    private Singleton() {
    }
    //公有的静态函数,对外暴露获取单例对象的接口
    public static synchronized Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

3)Double Check Lock(DCL)模式(双重检查锁定模式)

DCL的优点:DCL模式是使用最多的单例模式,它不仅能保证线程安全,资源利用率高,第一次执行getInstance时单例对象才会实例化;同时,后续调用getInstance方法时又不会有懒汉模式的重复同步的问题,效率更高;在绝大多数情况下都能保证单例对象的唯一性。

DCL的缺点:
DCL模式的缺点是第一次加载时由于需要同步反应会稍慢;在低于JDK1.5的版本里由于Java内存模型的原因有可能会失效。

注意:
DCL模式需要注意要用volatile关键字,否则还是会导致创建多个实例。

public class Singleton {
    private volatile static Singleton singleton = null;
    //构造函数私有化
    private Singleton() {
    }
    //公有的静态函数,对外暴露获取单例对象的接口
    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

4)静态内部类单例模式

优点:
第一次加载Singleton类时不会初始化sInstance,只有在第一次调用getInstance方法时才会初始化sInstance,延迟了单例对象的实例化。

静态内部类单例模式不仅能保证线程安全也能保证单例对象的唯一性。

public class Singleton {
    private Singleton() {
    }
    
    public static Singleton getInstance() {
        return SingletonHolder.sInstance;
    }
    
    //静态内部类
    private static class SingletonHolder {
        private static final Singleton sInstance = new Singleton();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

5)枚举

优点:
默认枚举实例的创建是线程安全的,并且在任何情况下它都是一个单例。

其他的单例模式,在一种情况下会出现失效的情况——反序列化,但是枚举即使在反序列化情况下也不会失效。

为什么不推荐使用:
因为Android官网不建议使用枚举,占用内存多。

public enum Singleton {
    INSTANCE;
}
  • 1
  • 2
  • 3

总结:
单例模式是运用频率很高的模式,由于在客户端一般没有高并发的情况,现在的JDK版本也已经到了9了,一般推荐用DCL模式和静态内部类2种实现。
单例对象的生命周期很长,如果持有Context,很容易引发内存泄漏,所以传递给单例对象的Context最好是Application Context

建造者模式(Builder)

定义:

将一个复杂对象的构建和它的表示分离,使得同样的构建过程可以创建不同的表示

使用场景:

1)当初始化一个对象特别复杂时,如参数多,且很多参数都具有默认值时。
2)相同的方法,不同的执行顺序,产生不同的事件结果时。
3)多个部件或零件,都可以装配到一个对象中,但是产生的运行效果又不相同时。
4)产品类非常复杂,或者产品类中的调用顺序不同产生了不同的作用,这个时候使用建造者模式非常合适。

使用例子:

1)AlertDialog
2)Retrofit

实现的要点:

1)简言之,就是把需要通过set方法来设置的多个属性封装在一个配置类里面,每个属性都应该有默认值。

2)具体的set方法放在配置类的内部类Builder类中,并且每个set方法都返回自身,以便进行链式调用

总结:

1)在构建的对象需要很多配置的时候可以考虑Builder模式,可以避免过多的set方法,同时把配置过程从目标类里面隔离出来,代码结构更加清晰。

2)Builder模式比较常用的实现形式是通过链式调用实现,这样更简洁直观。

观察者模式

定义:

定义对象间的一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。

使用场景:

1)当一个对象的改变需要通知其它对象改变时,而且它不知道具体有多少个对象有待改变时。
2)当一个对象必须通知其它对象,而它又不能假定其它对象是谁
3)跨系统的消息交换场景,如消息队列、事件总线的处理机制。

使用例子:

1)常见的发布-订阅模式
2)ListView的Adapter的notifyDataSetChanged更新方法
3)BroadcastReceiver
4)开源库EventBus
5)RxJava
6)点击事件

四大角色

1)Subject(抽象主题):又叫抽象被观察者,把所有观察者对象的引用保存到一个集合里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。

2)ConcreteSubject(具体主题):又叫具体被观察者,将有关状态存入具体观察者对象;在具体主题内部状态改变时,给所有登记过的观察者发出通知。

3)Observer (抽象观察者):为所有的具体观察者定义一个接口,在得到主题通知时更新自己。

4)ConcrereObserver(具体观察者):实现抽象观察者定义的更新接口,当得到主题更改通知时更新自身的状态。

实现的要点在于把握好以上四个角色

实现方式:

1) 创建抽象观察者:定义一个接到通知的更新方法,即收件人收到通知后的反应。

2)创建具体的观察者:实现抽象观察者并重写其方法。

3)创建抽象主题:即抽象被观察者,定义添加,删除,通知等方法。

4)创建具体主题:即具体被观察者,实现抽象主题并重写其方法。

另外实现方法:

1)利用JDK中Observable类和Observer接口实现

2)JDK中有Observable类和Observer接口

3)观察者实现Observer接口,被观察者继承Observable类

4)被观察者通过Observable类的addObserver方法添加观察者

优点

1)解除观察者与主题之间的耦合。让耦合的双方都依赖于抽象,而不是依赖具体。从而使得各自的变化都不会影响另一边的变化。

2)易于扩展,对同一主题新增观察者时无需修改原有代码。

缺点

1)依赖关系并未完全解除,抽象主题仍然依赖抽象观察者。

2)使用观察者模式时需要考虑一下开发效率和运行效率的问题,程序中包括一个被观察者、多个观察者,开发、调试等内容会比较复杂,而且在Java中消息的通知一般是顺序执行,那么一个观察者卡顿,会影响整体的执行效率,在这种情况下,一般会采用异步实现。

3)可能会引起多余的数据通知。

工厂方法

Android的设计模式-工厂方法模式

简单工厂模式

Android的设计模式-简单工厂模式

抽象工厂模式

Android的设计模式-抽象工厂模式

.
.
.
.

JAVA知识

1.java中equals和==和hashCode的区别

1)= = 若是基本数据类型比较,是比较值,若是引用类型,则比较的是他们在内存中的存放地址。对象是存放在堆中,栈中存放的对象的引用,所以 == 是对栈中的值进行比较,若返回true代表变量的内存地址相等;

2)equals是Object类中的方法,Object类的equals方法用于判断对象的内存地址引用是不是同一个地址(是不是同一个对象)。若是类中覆盖了equals方法,就要根据具体代码来确定,一般覆盖后都是通过对象的内容是否相等来判断对象是否相等。

3)hashCode()计算出对象实例的哈希码,在对象进行散列时作为key存入。之所以有hashCode方法,因为在批量的对象比较中,hashCode比较要比equals快。在添加新元素时,先调用这个元素的hashCode方法,一下子能定位到它应该旋转的物理位置,若该位置没有元素,可直接存储;若该位置有元素,就调用它的equals方法与新元素进行比较,若相同则不存,不相同,就放到该位置的链表末端。

4)equals与hashCode方法关系:

hashCode()是一个本地方法,实现是根据本地机器上关的。equals()相等的对象,hashCode()也一定相等;hashCode()不等,equals()一定也不等;hashCode()相等,equals()可能相等,也可能不等。

所以在重写equals(Object obj)方法,有必要重写hashCode()方法,确保通过equals(Object obj)方法判断结果为true的两个对象具备相等的hashCode()返回值。

5)equals与==的关系:

Integer b1 = 127;在java编译时被编译成Integer b1 = Integer.valueOf(127);对于-128到127之间的Integer值,用的是原生数据类型int,会在内存里供重用,也就是这之间的Integer值进行= =比较时,只是进行int原生数据类型的数值进行比较。而超出-128〜127的范围,进行==比较时是进行地址及数值比较。

2.String、StringBuffer、StringBuilder的区别?

String:字符串常量, 每次修改都相当于生成一个新的对象,所以不适合经常变更值的场景

StringBuffer:字符串变量,线程安全适用于多线程

StringBuilder:字符串变量,线程不安全适用于单线程,效率略快于StringBuffer

运行速度:StringBuilder>StringBuffer>String

速度上面String不断的复制和更改是创建不同的对象来进行操作,这里涉及到GC垃圾回收机制,会影响速度;而StringBuffer和StringBuilder则处理同一个对象不存在JVM的GC回收。

线程安全与否:如果一个StringBuffer对象在字符串缓冲区被多个线程使用时,StringBuffer中很多方法可以带有synchronized关键字,所以可以保证线程是安全的,但StringBuilder的方法则没有该关键字,所以不能保证线程安全,不能同步的问题。所以如果要进行的操作是多线程的,那么就要使用StringBuffer,但是在单线程的情况下,还是建议使用速度比较快的StringBuilder。

3.什么是线程安全和线程不安全?

线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。

线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。

4.java8新特性有哪些?

Lamabda表达式
接口的默认方法和静态方法
重复注解
方法引用
更好的类型推断
拓宽注解的应用场景
访问局部变量
Filter过滤
Stream接口
sort排序
Map映射
DataApi等等

5.Java基本数据类型

byte:Java中最小的数据类型,在内存中占8位(bit),即1个字节,取值范围-128~127,默认值0

short:短整型,在内存中占16位,即2个字节,取值范围-32768~32717,默认值0

int:整型,用于存储整数,在内在中占32位,即4个字节,取值范围-2147483648~2147483647,默认值0

long:长整型,在内存中占64位,即8个字节-263~263-1,默认值0L

float:浮点型,在内存中占32位,即4个字节,用于存储带小数点的数字(与double的区别在于float类型有效小数点只有6~7位),默认值0

double:双精度浮点型,用于存储带有小数点的数字,在内存中占64位,即8个字节,默认值0

char:字符型,用于存储单个字符,占16位,即2个字节,取值范围0~65535,默认值为空

boolean:布尔类型,占1个字节,用于判断真或假(仅有两个值,即true、false),默认值false

6.抽象类和接口区别

抽象类在类前面须用abstract关键字修饰,一般至少包含一个抽象方法,抽象方法指只有声明,用关键字abstract修饰,没有具体的实现的方法。因抽象类中含有无具体实现的方法,固不能用抽象类创建对象。当然如果只是用abstract修饰类而无具体实现,也是抽象类。抽象类也可以有成员变量和普通的成员方法。抽象方法必须为public或protected(若为private,不能被子类继承,子类无法实现该方法)。若一个类继承一个抽象类,则必须实现父类中所有的抽象方法,若子类没有实现父类的抽象方法,则也应该定义为抽象类。

接口用关键字interface修饰,接口也可以含有变量和方法,接口中的变量会被隐式指定为public static final变量。方法会被隐式的指定为public abstract,接口中的所有方法均不能有具体的实现,即接口中的方法都必须为抽象方法。若一个非抽象类实现某个接口,必须实现该接口中所有的方法。

区别:

1)抽象类可以提供成员方法实现的细节,而接口只能存在抽象方法;

2)抽象类的成员变量可以是各种类型,而接口中成员变量只能是public static final类型;

3)接口中不能含有静态方法及静态代码块,而抽象类可以有静态方法和静态代码块;

4)一个类只能继承一个抽象类,用extends来继承,却可以实现多个接口,用implements来实现接口。

意义:

抽象类:抽象类是用来提供子类的通用性,用来创建继承层级里子类的模板,减少代码编写,有利于代码规范化。

接口:

1)有利于代码的规范,对于大型项目,对一些接口进行定义,可以给开发人员一个清晰的指示,防止开发人员随意命名和代码混乱,影响开发效率。

2)有利于代码维护和扩展,当前类不能满足要求时,不需要重新设计类,只需要重新写了个类实现对应的方法。

3)解耦作用,全局变量的定义,当发生需求变化时,只需改变接口中的值即可。

4)直接看接口,就可以清楚知道具体实现类间的关系,代码交给别人看,别人也能立马明白。

7.泛型中extends和super的区别

< ? extends T >限定参数类型的上界,参数类型必须是T或T的子类型,但对于List< ? extends T >,不能通过add()来加入元素,因为不知道< ? extends T >是T的哪一种子类。

< ? super T >限定参数类型的下界,参数类型必须是T或T的父类型,不能能过get()获取元素,因为不知道哪个超类;

8.父类的静态方法能否被子类重写?静态属性和静态方法是否可以被继承?

父类的静态方法和属性不能被子类重写,但子类可以继承父类静态方法和属性,如父类和子类都有同名同参同返回值的静态方法show(),声明的实例Father father = new Son(); (Son extends Father),会调用father对象的静态方法。静态是指在编译时就会分配内存且一直存在,跟对象实例无关。

9.final,finally,finalize的区别

final:变量、类、方法的修饰符,被final修饰的类不能被继承,变量或方法被final修饰则不能被修改和重写。

finally:异常处理时提供finally块来执行清除操作,不管有没有异常抛出,此处代码都会被执行。如果try语句块中包含return语句,finally语句块是在return之后运行;

finalize:Object类中定义的方法,若子类覆盖了finalize()方法,在在垃圾收集器将对象从内存中清除前,会执行该方法,确定对象是否会被回收。

10.序列化Serializable 和Parcelable 的区别

序列化:将一个对象转换成可存储或可传输的状态,序列化后的对象可以在网络上传输,也可以存储到本地,或实现跨进程传输;

为什么要进行序列化:开发过程中,我们需要将对象的引用传给其他activity或fragment使用时,需要将这些对象放到一个Intent或Bundle中,再进行传递,而Intent或Bundle只能识别基本数据类型和被序列化的类型。

Serializable:表示将一个对象转换成可存储或可传输的状态。

Parcelable:与Serializable实现的效果相同,也是将一个对象转换成可传输的状态,但它的实现原理是将一个完整的对象进行分解,分解后的每一部分都是Intent所支持的数据类型,这样实现传递对象的功能。

Parcelable实现序列化的重要方法:序列化功能是由writeToParcel完成,通过Parcel中的write方法来完成;反序列化由CREATOR完成,内部标明了如何创建序列化对象及数级,通过Parcel的read方法完成;内容描述功能由describeContents方法完成,一般直接返回0。

区别:Serializable在序列化时会产生大量临时变量,引起频繁GC。Serializable本质上使用了反射,序列化过程慢。Parcelable不能将数据存储在磁盘上,在外界变化时,它不能很好的保证数据的持续性。

选择原则:若仅在内存中使用,如activity\service间传递对象,优先使用Parcelable,它性能高。若是持久化操作,优先使用Serializable

注意:静态成员变量属于类,不属于对象,固不会参与序列化的过程;用transient关键字编辑的成员变量不会参与序列化过程;可以通过重写writeObject()和readObject()方法来重写系统默认的序列化和反序列化。

11.string 转换成 integer的方式及原理

1)parseInt(String s)内部调用parseInt(s, 10)默认为10进制 。

2)正常判断null\进制范围,length等。

3)判断第一个字符是否是符号位。

4)循环遍历确定每个字符的十进制值。

5)通过*=和-=进行计算拼接。

6)判断是否为负值返回结果。

12.哪些情况下的对象会被垃圾回收机制处理掉?

利用可达性分析算法,虚拟机会将一些对象定义为GC Roots,从GC Roots出发沿着引用链向下寻找,如果某个对象不能通过GC Roots寻找到,虚拟机就认为该对象可以被回收掉。

1.1 哪些对象可以被看做是GC Roots呢?
1)虚拟机栈(栈帧中的本地变量表)中引用的对象;

2)方法区中的类静态属性引用的对象,常量引用的对象;

3)本地方法栈中JNI(Native方法)引用的对象;

1.2 对象不可达,一定会被垃圾收集器回收么?
即使不可达,对象也不一定会被垃圾收集器回收,1)先判断对象是否有必要执行finalize()方法,对象必须重写finalize()方法且没有被运行过。2)若有必要执行,会把对象放到一个队列中,JVM会开一个线程去回收它们,这是对象最后一次可以逃逸清理的机会。

13.说说你对Java反射的理解

在运行状态中,对任意一个类,都能知道这个类的所有属性和方法,对任意一个对象,都能调用它的任意一个方法和属性。这种能动态获取信息及动态调用对象方法的功能称为java语言的反射机制。

反射的作用:开发过程中,经常会遇到某个类的某个成员变量、方法或属性是私有的,或只对系统应用开放,这里就可以利用java的反射机制通过反射来获取所需的私有成员或是方法。

1) 获取类的Class对象实例 Class clz = Class.forName(“com.zhenai.api.Apple”);

2)根据Class对象实例获取Constructor对象 Constructor appConstructor = clz.getConstructor();

3)使用Constructor对象的newInstance方法获取反射类对象 Object appleObj = appConstructor.newInstance();

4)获取方法的Method对象 Method setPriceMethod = clz.getMethod(“setPrice”, int.class);

5)利用invoke方法调用方法 setPriceMethod.invoke(appleObj, 14);

6)通过getFields()可以获取Class类的属性,但无法获取私有属性,而getDeclaredFields()可以获取到包括私有属性在内的所有属性。带有Declared修饰的方法可以反射到私有的方法,没有Declared修饰的只能用来反射公有的方法,其他如Annotation\Field\Constructor也是如此。

14.List,Set,Map的区别?

Set是一个无序的集合,不能包含重复的元素;

list是一个有序的集合可以包含重复的元素,提供了按索引访问的方式;

map包含了key-value对,map中key必须唯一,value可以重复。

15.对java多态的理解

面向对象的三大特性:封装、继承、多态。从一定角度来看,封装和继承几乎都是为多态而准备的。这是我们最后一个概念,也是最重要的知识点。

多态的定义:指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。(发送消息就是函数调用)

实现多态的技术称为:动态绑定(dynamic binding),是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。

多态的作用:消除类型之间的耦合关系。

现实中,关于多态的例子不胜枚举。比方说按下 F1 键这个动作,如果当前在 Flash 界面下弹出的就是 AS 3 的帮助文档;如果当前在 Word 下弹出的就是 Word 帮助;在 Windows 下弹出的就是 Windows 帮助和支持。同一个事件发生在不同的对象上会产生不同的结果。

下面是多态存在的三个必要条件,要求大家做梦时都能背出来!

多态存在的三个必要条件 一、要有继承; 二、要有重写; 三、父类引用指向子类对象。

16.JAVA GC原理

垃圾收集算法的核心思想是:对虚拟机可用内存空间,即堆空间中的对象进行识别,如果对象正在被引用,那么称其为存活对象
,反之,如果对象不再被引用,则为垃圾对象,可以回收其占据的空间,用于再分配。垃圾收集算法的选择和垃圾收集系统参数的合理调节直接影响着系统性能。

17.线程中sleep和wait的区别

(1)这两个方法来自不同的类,sleep是来自Thread,wait是来自Object。

(2)sleep方法没有释放锁,而wait方法释放了锁。

(3)wait,notify,notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用。

18.Thread中的start()和run()方法有什么区别

start()方法是用来启动新创建的线程,而start()内部调用了run()方法,这和直接调用run()方法是不一样的,如果直接调用run()方法,则和普通的方法没有什么区别。

19.关键字final和static是怎么使用的。

final:

1、final变量即为常量,只能赋值一次。

2、final方法不能被子类重写。

3、final类不能被继承。

static:

1)static变量
对于静态变量在内存中只有一个拷贝(节省内存),JVM只为静态分配一次内存,在加载类的过程中完成静态变量的内存分配,可用类名直接访问(方便),当然也可以通过对象来访问(但是这是不推荐的)。

2)static代码块
static代码块是类加载时,初始化自动执行的。

3)static方法
static方法可以直接通过类名调用,任何的实例也都可以调用,因此static方法中不能用this和super关键字,
不能直接访问所属类的实例变量和实例方法(就是不带static的成员变量和成员成员方法),只能访问所属类的静态成员变量和成员方法。

20.Java中重载和重写的区别?

1、重载:一个类中可以有多个相同方法名的,但是参数类型和个数都不一样。这是重载。

2、重写:子类继承父类,则子类可以通过实现父类中的方法,从而新的方法把父类旧的方法覆盖。

21.java 常用的数据结构?

在这里插入图片描述
Collection接口

Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素。由Collection接口派生的两个接口是List和Set和Queue。

List接口:

1)List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。

2)用户能够使用索引来访问List中的元素。

3)List允许有相同的元素。

4)实现List接口的常用类有LinkedListArrayListVectorStack

Set接口

Set是一种不包含重复的元素的Collection,即任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。实现Set接口的常用类有LinkedHashSetHashSet

请注意:必须小心操作可变对象(Mutable Object)。如果一个Set中的可变元素改变了自身状态导致Object.equals(Object)=true将导致一些问题。

Queue
队列是一种先进先出的数据结构,元素在队列末尾添加,在队列头部删除。Queue接口扩展自Collection,并提供插入、提取、检验等操作。

Map接口

请注意,Map没有继承Collection接口,Map提供key到value的映射。一个Map中不能包含相同的key,每个key只能映射一个value。Map接口提供3种集合的视图,Map的内容可以被当作一组key集合,一组value集合,或者一组key-value映射。

Hashtable类

Hashtable继承Map接口,实现一个key-value映射的哈希表。任何非空(non-null)的对象都可作为key或者value。Hashtable是同步的。

HashMap类

HashMap和Hashtable类似,不同之处在于HashMap是非同步的,并且允许null,即null value和null key。

WeakHashMap类

WeakHashMap是一种改进的HashMap,它对key实行“弱引用”,如果一个key不再被外部所引用,那么该key可以被GC回收。

22.java IO流

按照数据流的方向不同可以分为:输入流和输出流。

按照处理数据单位不同可以分为:字节流和字符流。
字节流:一次读入或读出是8位二进制,后缀是Stream是字节流
字符流:一次读入或读出是16位二进制,后缀是Reader,Writer是字符流

按照实现功能不同可以分为:节点流和处理流。
节点流:直接与数据源相连,读入或读出
处理流:与节点流一块使用,在节点流的基础上,再套接一层,套接在节点流上的就是处理流

FileInputStream:字节输入流,属于节点流类型
FileOutputStream:字节输出流,属于节点流类型

23.“static”关键字是什么意思?Java中是否可以覆盖(override)一个private或者是static的方法?

“static”关键字表明一个成员变量或者是成员方法可以在没有所属的类的实例变量的情况下被访问。

不能。静态方法和私有方法在程序编译的解析阶段中就确定了唯一的调用版本,是运行期不可变的,不能被重写和重载

24.HashMap和Hashtable有什么区别?

HashMap和Hashtable都实现了Map接口,因此很多特性非常相似。

HashMap允许键和值是null,而Hashtable不允许键或者值是null。

Hashtable是同步的,而HashMap不是。因此,HashMap更适合于单线程环境,而Hashtable适合于多线程环境。

HashMap提供了可供应用迭代的键的集合,因此,HashMap是快速失败的。另一方面,Hashtable提供了对键的列举(Enumeration)。

25.ArrayList和LinkedList和Vector有什么区别?

1)都实现了List接口。

2)ArrayList和Vector都是基于索引的数据接口,它的底层是数组。它可以以O(1)时间复杂度对元素进行随机访问。与此对应,LinkedList是以元素链表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)。

3)vector是同步的,ArrayList不同步,vector效率低于ArrayList。

4)相对于ArrayList和Vector,LinkedList的插入,添加,删除操作速度更快。
LinkedList比ArrayList和Vector更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。

26.TCP 三次握手四次挥手

握手:

第一次握手:建立连接。客户端发送连接请求报文段,将SYN设置为1、seq为x。等待服务端确认。
第二次握手:服务端接收到客户端的SYN报文段,对SYN报文段进行确认,设置ACK为x+1,同时自己还要发送SYN请求信息,将SYN设置为1,seq为y,服务端将上述所有信息放到SYN+ACK报文段中,一并发送给客户端。
第三次握手:客户端收到服务端的SYN+ACK报文段,然后将ACK设置为y+1,想服务端发送ACK报文段,这个报文段发送完毕后,客户端和服务端都进入成功连接状态,完成TCP的三次握手。

挥手:

第一次挥手:客户端设置seq和ACK,向服务端发送一个FIN报文段,此时,客户端进入FIN_WAIT_1状态,表示客户端没有数据要发送给服务端了。
第二次挥手:服务端收到了客户端发送的FIN报文段,向客户端回了一个ACK报文段。
第三次挥手:服务端向客户端发送FIN报文段,请求关闭连接,同时服务端进入LAST_ACK状态。
第四次挥手:客户端收到服务端发送的FIN报文段,向服务端发送ACK报文段,然后客户端进入TIME_WAIT状态。服务端收到客户端的ACK报文段以后,就关闭连接,此时,客户端等到2MSL(最大报文段生存时间)后依然没有收到回复,则说明服务端已正常关闭,这样客户端也可以关闭连接了。

27.面向对象特征?

主流观点是封装,继承,多态;其实很多人把抽象也归类到面向对象的特征之一。

封装:把过程和数据隐藏起来,使得数据的访问只能通过对外公开的接口,保证了对象被访问的安全性。

继承:类的一种层次模型,提供了一种明确表述共性的方法,类可以获取并使用基类的属性且可以重写基类方法,同时可以修改基类方法或者增加新的方法使其更加适合现有场景的需求;在Java中只有单继承。

多态:指对象在不同时刻表现出来的多种状态,是一种编译期和运行期状态不一致的现象。

抽象:对一类事物的高度提炼以得到它们的共性,抽象不需要了解全部细节,而只是一种通用的描述。

28.匿名内部类引用方法局部变量时为什么需要声明为final?

首先我们来研究一下变量的生命周期的问题,局部变量的生命周期是当该方法被调用时,该方法中的局部变量在栈中被创建,当方法调用结束时(执行完毕)出栈,这些局部变量就会回收。但是内部类对象是创建在堆中的,其生命周期跟其它类一样,只有当jvm用可达性分析法发现这个对象通过GCRoots节点已经不可达,然后进行gc才会死亡。所以完全有可能存在的一个现象就是一个方法已经调用结束(局部变量已死亡),但该内部类的对象仍然活着,也就是说内部类维护的局部变量的引用还存在,那么当内部类调用局部变量时就会出错,出现非法引用的问题

再谈谈数据同步的问题,内部类不是直接引用局部变量,而是通过构造方法的参数将其拷贝到内部,那么一个基本数据类型如果没有加final修饰且修改了这些参数,并不会对局部变量产生影响,仅仅改变内部类中备份的参数。但是在外部调用时发现值并没有被修改,这种问题就会很尴尬,造成数据不同步

所以Java要求所有能被匿名内部类访问的变量都用final修饰

29.this super 区别?

this:作为当前类的引用,谁调用代表谁。
super:是父类存储空间标识,可以理解为父类对象引用,调用父类方法,变量。

切记this和super不能在static方法中使用。

30.三种集合的区别?

ArrayList:底层的数据结构是数组;在集合末尾删除或添加元素所用的时间
是一致的,但是在列表中间的部分添加或删除时所用时间就会大大增加,
但是它根据索引查找元素的时间速度很快。

LinkedList:底层的数据结构是双向链表;与ArrayList相反的是,
LinkedList在插入、删除集合中的任何位置的元素所花费的时间都是
一样的,但是它根据索引查询一个元素的时候却比较慢,特别是在
链表中间的元素。

对于随机访问的get和set方法,AarrayList要优于LinkedList,因为后者
要移动指针
对于新增和删除操作add和remove,LinkedList要比较占优势,
因为ArrayList要移动数据

Vector:与ArrayList类似,vertor是线程安全,ArrayList是线程不安全

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小惠珠哦/article/detail/855949
推荐阅读
相关标签
  

闽ICP备14008679号