乐趣区

关于android:史上最全的Android面试题集锦

Android 根本知识点

1、惯例知识点

1、Android 类加载器

在 Android 开发中,不论是插件化还是组件化,都是基于 Android 零碎的类加载器 ClassLoader 来设计的。只不过 Android 平台上虚拟机运行的是 Dex 字节码,一种对 class 文件优化的产物,传统 Class 文件是一个 Java 源码文件会生成一个.class 文件,而 Android 是把所有 Class 文件进行合并、优化,而后再生成一个最终的 class.dex,目标是把不同 class 文件反复的货色只需保留一份,在晚期的 Android 利用开发中,如果不对 Android 利用进行分 dex 解决,那么最初一个利用的 apk 只会有一个 dex 文件。

Android 中罕用的类加载器有两种,DexClassLoader 和 PathClassLoader,它们都继承于 BaseDexClassLoader。区别在于调用父类结构器时,DexClassLoader 多传了一个 optimizedDirectory 参数,这个目录必须是外部存储门路,用来缓存零碎创立的 Dex 文件。而 PathClassLoader 该参数为 null,只能加载外部存储目录的 Dex 文件。所以咱们能够用 DexClassLoader 去加载内部的 apk 文件,这也是很多插件化技术的根底。

2、Service

了解 Android 的 Service,能够从以下几个方面来了解:

  • Service 是在 main Thread 中执行,Service 中不能执行耗时操作(网络申请,拷贝数据库,大文件)。
  • 能够在 xml 中设置 Service 所在的过程,让 Service 在另外的过程中执行。
  • Service 执行的操作最多是 20s,BroadcastReceiver 是 10s,Activity 是 5s。
  • Activity 通过 bindService(Intent,ServiceConnection,flag)与 Service 绑定。
  • Activity 能够通过 startService 和 bindService 启动 Service。

IntentService

IntentService 是一个抽象类,继承自 Service,外部存在一个 ServiceHandler(Handler)和 HandlerThread(Thread)。IntentService 是解决异步申请的一个类,在 IntentService 中有一个工作线程(HandlerThread)来解决耗时操作,启动 IntentService 的形式和一般的一样,不过当执行完工作之后,IntentService 会主动进行。另外能够屡次启动 IntentService,每一个耗时操作都会以工作队列的模式在 IntentService 的 onHandleIntent 回调中执行,并且每次执行一个工作线程。IntentService 的实质是:封装了一个 HandlerThread 和 Handler 的异步框架。

2.1、生命周期示意图

Service 作为 Android 四大组件之一,利用十分宽泛。和 Activity 一样,Service 也有一系列的生命周期回调函数,具体如下图。

通常,启动 Service 有两种形式,startService 和 bindService 形式。

2.2、startService 生命周期

当咱们通过调用了 Context 的 startService 办法后,咱们便启动了 Service,通过 startService 办法启动的 Service 会始终无限期地运行上来,只有在内部调用 Context 的 stopService 或 Service 外部调用 Service 的 stopSelf 办法时,该 Service 才会进行运行并销毁。

onCreate

onCreate: 执行 startService 办法时,如果 Service 没有运行的时候会创立该 Service 并执行 Service 的 onCreate 回调办法;如果 Service 曾经处于运行中,那么执行 startService 办法不会执行 Service 的 onCreate 办法。也就是说如果屡次执行了 Context 的 startService 办法启动 Service,Service 办法的 onCreate 办法只会在第一次创立 Service 的时候调用一次,当前均不会再次调用。咱们能够在 onCreate 办法中实现一些 Service 初始化相干的操作。

onStartCommand

onStartCommand: 在执行了 startService 办法之后,有可能会调用 Service 的 onCreate 办法,在这之后肯定会执行 Service 的 onStartCommand 回调办法。也就是说,如果屡次执行了 Context 的 startService 办法,那么 Service 的 onStartCommand 办法也会相应的屡次调用。onStartCommand 办法很重要,咱们在该办法中依据传入的 Intent 参数进行理论的操作,比方会在此处创立一个线程用于下载数据或播放音乐等。

public @StartResult int onStartCommand(Intent intent, @StartArgFlags int flags, int startId) {}

当 Android 面临内存匮乏的时候,可能会销毁掉你以后运行的 Service,而后待内存短缺的时候能够从新创立 Service,Service 被 Android 零碎强制销毁并再次重建的行为依赖于 Service 中 onStartCommand 办法的返回值。咱们罕用的返回值有三种值,START_NOT_STICKYSTART_STICKYSTART_REDELIVER_INTENT,这三个值都是 Service 中的动态常量。

START_NOT_STICKY

如果返回 START\_NOT\_STICKY,示意当 Service 运行的过程被 Android 零碎强制杀掉之后,不会从新创立该 Service,当然如果在其被杀掉之后一段时间又调用了 startService,那么该 Service 又将被实例化。那什么情境下返回该值比拟失当呢?如果咱们某个 Service 执行的工作被中断几次无关紧要或者对 Android 内存缓和的状况下须要被杀掉且不会立刻从新创立这种行为也可承受,那么咱们便可将 onStartCommand 的返回值设置为 START\_NOT\_STICKY。举个例子,某个 Service 须要定时从服务器获取最新数据:通过一个定时器每隔指定的 N 分钟让定时器启动 Service 去获取服务端的最新数据。当执行到 Service 的 onStartCommand 时,在该办法内再布局一个 N 分钟后的定时器用于再次启动该 Service 并开拓一个新的线程去执行网络操作。假如 Service 在从服务器获取最新数据的过程中被 Android 零碎强制杀掉,Service 不会再从新创立,这也没关系,因为再过 N 分钟定时器就会再次启动该 Service 并从新获取数据。

START_STICKY

如果返回 START\_STICKY,示意 Service 运行的过程被 Android 零碎强制杀掉之后,Android 零碎会将该 Service 仍然设置为 started 状态(即运行状态),然而不再保留 onStartCommand 办法传入的 intent 对象,而后 Android 零碎会尝试再次从新创立该 Service,并执行 onStartCommand 回调办法,然而 onStartCommand 回调办法的 Intent 参数为 null,也就是 onStartCommand 办法尽管会执行然而获取不到 intent 信息。如果你的 Service 能够在任意时刻运行或完结都没什么问题,而且不须要 intent 信息,那么就能够在 onStartCommand 办法中返回 START\_STICKY,比方一个用来播放背景音乐性能的 Service 就适宜返回该值。

START_REDELIVER_INTENT

如果返回 START\_REDELIVER\_INTENT,示意 Service 运行的过程被 Android 零碎强制杀掉之后,与返回 START\_STICKY 的状况相似,Android 零碎会将再次从新创立该 Service,并执行 onStartCommand 回调办法,然而不同的是,Android 零碎会再次将 Service 在被杀掉之前最初一次传入 onStartCommand 办法中的 Intent 再次保留下来并再次传入到从新创立后的 Service 的 onStartCommand 办法中,这样咱们就能读取到 intent 参数。只有返回 START\_REDELIVER\_INTENT,那么 onStartCommand 重的 intent 肯定不是 null。如果咱们的 Service 须要依赖具体的 Intent 能力运行(须要从 Intent 中读取相干数据信息等),并且在强制销毁后有必要从新创立运行,那么这样的 Service 就适宜返回 START\_REDELIVER\_INTENT。

onBind

Service 中的 onBind 办法是形象办法,所以 Service 类自身就是抽象类,也就是 onBind 办法是必须重写的,即便咱们用不到。在通过 startService 应用 Service 时,咱们在重写 onBind 办法时,只须要将其返回 null 即可。onBind 办法次要是用于给 bindService 办法调用 Service 时才会应用到。

onDestroy

onDestroy: 通过 startService 办法启动的 Service 会无限期运行,只有当调用了 Context 的 stopService 或在 Service 外部调用 stopSelf 办法时,Service 才会进行运行并销毁,在销毁的时候会执行 Service 回调函数。

2.3、bindService 生命周期

bindService 形式启动 Service 次要有以下几个生命周期函数:

onCreate():

首次创立服务时,零碎将调用此办法。如果服务已在运行,则不会调用此办法,该办法只调用一次。

onStartCommand():

当另一个组件通过调用 startService()申请启动服务时,零碎将调用此办法。

onDestroy():

当服务不再应用且将被销毁时,零碎将调用此办法。

onBind():

当另一个组件通过调用 bindService()与服务绑定时,零碎将调用此办法。

onUnbind():

当另一个组件通过调用 unbindService()与服务解绑时,零碎将调用此办法。

onRebind():

当旧的组件与服务解绑后,另一个新的组件与服务绑定,onUnbind()返回 true 时,零碎将调用此办法。

3、fragemnt

3.1、创立形式

(1)动态创立

首先咱们须要创立一个 xml 文件,而后创立与之对应的 java 文件,通过 onCreatView()的返回办法进行关联,最初咱们须要在 Activity 中进行配置相干参数即在 Activity 的 xml 文件中放上 fragment 的地位。

 <fragment
        android:name="xxx.BlankFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </fragment>
(2)动态创建

动态创建 Fragment 次要有以下几个步骤:

  1. 创立待增加的 fragment 实例。
  2. 获取 FragmentManager,在 Activity 中能够间接通过调用 getSupportFragmentManager()办法失去。
  3. 开启一个事务,通过调用 beginTransaction()办法开启。
  4. 向容器内增加或替换 fragment,个别应用 repalce()办法实现,须要传入容器的 id 和待增加的 fragment 实例。
  5. 提交事务,调用 commit()办法来实现。

3.2、Adapter 比照

FragmnetPageAdapter 在每次切换页面时,只是将 Fragment 进行拆散,适宜页面较少的 Fragment 应用以保留一些内存,对系统内存不会多大影响。

FragmentPageStateAdapter 在每次切换页面的时候,是将 Fragment 进行回收,适宜页面较多的 Fragment 应用,这样就不会耗费更多的内存

3.3、Activity 生命周期

Activity 的生命周期如下图:

(1)动静加载:

动静加载时,Activity 的 onCreate()调用完,才开始加载 fragment 并调用其生命周期办法,所以在第一个生命周期办法 onAttach()中便能获取 Activity 以及 Activity 的布局的组件;

(2)动态加载:

1. 动态加载时,Activity 的 onCreate()调用过程中,fragment 也在加载,所以 fragment 无奈获取到 Activity 的布局中的组件,但为什么能获取到 Activity 呢?

2. 原来在 fragment 调用 onAttach()之前其实还调用了一个办法 onInflate(),该办法被调用时 fragment 曾经是和 Activity 互相联合了,所以能够获取到对方,然而 Activity 的 onCreate()调用还未实现,故无奈获取 Activity 的组件;

3.Activity 的 onCreate()调用实现是,fragment 会调用 onActivityCreated()生命周期办法,因而在这儿开始便能获取到 Activity 的布局的组件;

3.4、与 Activity 通信

fragment 不通过构造函数进行传值的起因是因为横屏切换的时候获取不到值。

Activity 向 Fragment 传值:

Activity 向 Fragment 传值,要传的值放到 bundle 对象里;在 Activity 中创立该 Fragment 的对象 fragment,通过调用 setArguments()传递到 fragment 中;在该 Fragment 中通过调用 getArguments()失去 bundle 对象,就能失去外面的值。

Fragment 向 Activity 传值:
第一种:

在 Activity 中调用 getFragmentManager()失去 fragmentManager,,调用 findFragmentByTag(tag)或者通过 findFragmentById(id),例如:

FragmentManager fragmentManager = getFragmentManager();Fragment fragment = fragmentManager.findFragmentByTag(tag);
第二种:

通过回调的形式,定义一个接口(能够在 Fragment 类中定义),接口中有一个空的办法,在 fragment 中须要的时候调用接口的办法,值能够作为参数放在这个办法中,而后让 Activity 实现这个接口,必然会重写这个办法,这样值就传到了 Activity 中

Fragment 与 Fragment 之间是如何传值的:
第一种:

通过 findFragmentByTag 失去另一个的 Fragment 的对象,这样就能够调用另一个的办法了。

第二种:

通过接口回调的形式。

第三种:

通过 setArguments,getArguments 的形式。

3.5、api 区别

add

一种是 add 形式来进行 show 和 add,这种形式你切换 fragment 不会让 fragment 从新刷新,只会调用 onHiddenChanged(boolean isHidden)。

replace

而用 replace 形式会使 fragment 从新刷新,因为 add 形式是将 fragment 暗藏了而不是销毁再创立,replace 形式每次都是从新创立。

commit/commitAllowingStateLoss

两者都能够提交 fragment 的操作,惟一的不同是第二种办法,容许失落一些界面的状态和信息,简直所有的开发者都遇到过这样的谬误:无奈在 activity 调用了 onSaveInstanceState 之后再执行 commit(),这种异样时能够了解的,界面被零碎回收(界面曾经不存在),为了在下次关上的时候复原原来的样子,零碎为咱们保留界面的所有状态,这个时候咱们再去批改界面实践上必定是不容许的,所以为了防止这种异样,要应用第二种办法。

3. 懒加载

咱们常常在应用 fragment 时,经常会联合着 viewpager 应用,那么咱们就会遇到一个问题,就是初始化 fragment 的时候,会连同咱们写的网络申请一起执行,这样十分耗费性能,最现实的形式是,只有用户点开或滑动到以后 fragment 时,才进行申请网络的操作。因而,咱们就产生了懒加载这样一个说法。

Viewpager 配合 fragment 应用,默认加载前两个 fragment。很容易造成网络丢包、阻塞等问题。

在 Fragment 中有一个 setUserVisibleHint 这个办法,而且这个办法是优于 onCreate()办法的,它会通过 isVisibleToUser 通知咱们以后 Fragment 咱们是否可见,咱们能够在可见的时候再进行网络加载。

从 log 上看 setUserVisibleHint()的调用早于 onCreateView,所以如果在 setUserVisibleHint()要实现懒加载的话,就必须要确保 View 以及其余变量都曾经初始化完结,防止空指针。

应用步骤:

申明一个变量 isPrepare=false,isVisible=false, 表明以后页面是否被创立了 在 onViewCreated 周期内设置 isPrepare=true 在 setUserVisibleHint(boolean isVisible)判断是否显示,设置 isVisible=true 判断 isPrepare 和 isVisible,都为 true 开始加载数据,而后复原 isPrepare 和 isVisible 为 false,避免反复加载。

4、Activity

4.1、Activity 启动流程

用户从 Launcher 程序点击利用图标可启动利用的入口 Activity,Activity 启动时须要多个过程之间的交互,Android 零碎中有一个 zygote 过程专用于孵化 Android 框架层和应用层程序的过程。还有一个 system\_server 过程,该过程里运行了很多 binder service。例如 ActivityManagerService,PackageManagerService,WindowManagerService,这些 binder service 别离运行在不同的线程中,其中 ActivityManagerService 负责管理 Activity 栈,利用过程,task。

点击 Launcher 图标来启动 Activity

用户在 Launcher 程序里点击利用图标时,会告诉 ActivityManagerService 启动利用的入口 Activity,ActivityManagerService 发现这个利用还未启动,则会告诉 Zygote 过程孵化出利用过程,而后在这个 dalvik 利用过程里执行 ActivityThread 的 main 办法。利用过程接下来告诉 ActivityManagerService 利用过程已启动,ActivityManagerService 保留利用过程的一个代理对象,这样 ActivityManagerService 能够通过这个代理对象管制利用过程,而后 ActivityManagerService 告诉利用过程创立入口 Activity 的实例,并执行它的生命周期办法。

4.2、Activity 生命周期

(1)Activity 的状态

Active/Running:

Activity 处于活动状态,此时 Activity 处于栈顶,是可见状态,可与用户进行交互。

Paused:

当 Activity 失去焦点时,或被一个新的非全屏的 Activity,或被一个通明的 Activity 搁置在栈顶时,Activity 就转化为 Paused 状态。但咱们须要明确,此时 Activity 只是失去了与用户交互的能力,其所有的状态信息及其成员变量都还存在,只有在零碎内存缓和的状况下,才有可能被零碎回收掉。

Stopped:

当一个 Activity 被另一个 Activity 齐全笼罩时,被笼罩的 Activity 就会进入 Stopped 状态,此时它不再可见,然而跟 Paused 状态一样放弃着其所有状态信息及其成员变量。

Killed:

当 Activity 被零碎回收掉时,Activity 就处于 Killed 状态。

Activity 会在以上四种状态中互相切换,至于如何切换,这因用户的操作不同而异。理解了 Activity 的 4 种状态后,咱们就来聊聊 Activity 的生命周期。

Activity 的生命周期

所谓的典型的生命周期就是在有用户参加的状况下,Activity 经验从创立,运行,进行,销毁等失常的生命周期过程。

onCreate

该办法是在 Activity 被创立时回调,它是生命周期第一个调用的办法,咱们在创立 Activity 时个别都须要重写该办法,而后在该办法中做一些初始化的操作,如通过 setContentView 设置界面布局的资源,初始化所须要的组件信息等。

onStart

此办法被回调时示意 Activity 正在启动,此时 Activity 已处于可见状态,只是还没有在前台显示,因而无奈与用户进行交互。能够简略了解为 Activity 已显示而咱们无奈看见摆了。

onResume

当此办法回调时,则阐明 Activity 已在前台可见,可与用户交互了(处于后面所说的 Active/Running 状态),onResume 办法与 onStart 的相同点是两者都示意 Activity 可见,只不过 onStart 回调时 Activity 还是后盾无奈与用户交互,而 onResume 则已显示在前台,可与用户交互。当然从流程图,咱们也能够看出当 Activity 进行后(onPause 办法和 onStop 办法被调用),从新回到前台时也会调用 onResume 办法,因而咱们也能够在 onResume 办法中初始化一些资源,比方从新初始化在 onPause 或者 onStop 办法中开释的资源。

onPause

此办法被回调时则示意 Activity 正在进行(Paused 状态),个别状况下 onStop 办法会紧接着被回调。但通过流程图咱们还能够看到一种状况是 onPause 办法执行后间接执行了 onResume 办法,这属于比拟极其的景象了,这可能是用户操作使以后 Activity 退居后盾后又迅速地再回到到以后的 Activity,此时 onResume 办法就会被回调。当然,在 onPause 办法中咱们能够做一些数据存储或者动画进行或者资源回收的操作,然而不能太耗时,因为这可能会影响到新的 Activity 的显示——onPause 办法执行实现后,新 Activity 的 onResume 办法才会被执行。

onStop

个别在 onPause 办法执行实现间接执行,示意 Activity 行将进行或者齐全被笼罩(Stopped 状态),此时 Activity 不可见,仅在后盾运行。同样地,在 onStop 办法能够做一些资源开释的操作(不能太耗时)。

onRestart

示意 Activity 正在重新启动,当 Activity 由不可见变为可见状态时,该办法被回调。这种状况个别是用户关上了一个新的 Activity 时,以后的 Activity 就会被暂停(onPause 和 onStop 被执行了),接着又回到以后 Activity 页面时,onRestart 办法就会被回调。

onDestroy

此时 Activity 正在被销毁,也是生命周期最初一个执行的办法,个别咱们能够在此办法中做一些回收工作和最终的资源开释。

小结

到这里咱们来个小结,当 Activity 启动时,顺次会调用 onCreate(),onStart(),onResume(),而当 Activity 退居后盾时(不可见,点击 Home 或者被新的 Activity 齐全笼罩),onPause()和 onStop()会顺次被调用。当 Activity 从新回到前台(从桌面回到原 Activity 或者被笼罩后又回到原 Activity)时,onRestart(),onStart(),onResume()会顺次被调用。当 Activity 退出销毁时(点击 back 键),onPause(),onStop(),onDestroy()会顺次被调用,到此 Activity 的整个生命周期办法回调实现。当初咱们再回头看看之前的流程图,应该是相当清晰了吧。嗯,这就是 Activity 整个典型的生命周期过程。

2、View 局部知识点

Android 的 Activity、PhoneWindow 和 DecorView 的关系能够用上面的图示意:

2.1、DecorView 浅析

例如,有上面一个视图,DecorView 为整个 Window 界面的最顶层 View,它只有一个子元素 LinearLayout。代表整个 Window 界面,蕴含告诉栏、标题栏、内容显示栏三块区域。其中 LinearLayout 中有两个 FrameLayout 子元素。

DecorView 的作用

DecorView 是顶级 View,实质是一个 FrameLayout 它蕴含两局部,标题栏和内容栏,都是 FrameLayout。内容栏 id 是 content,也就是 activity 中设置 setContentView 的局部,最终将布局增加到 id 为 content 的 FrameLayout 中。获取 content:ViewGroup content=findViewById(android.id.content)获取设置的 View:getChildAt(0).

应用总结

每个 Activity 都蕴含一个 Window 对象,Window 对象通常是由 PhoneWindow 实现的。PhoneWindow:将 DecorView 设置为整个利用窗口的根 View,是 Window 的实现类。它是 Android 中的最根本的窗口零碎,每个 Activity 均会创立一个 PhoneWindow 对象,是 Activity 和整个 View 零碎交互的接口。DecorView:是顶层视图,将要显示的具体内容出现在 PhoneWindow 上,DecorView 是以后 Activity 所有 View 的先人,它并不会向用户出现任何货色。

2.2、View 的事件散发

View 的事件散发机制能够应用下图示意:

如上图,图分为 3 层,从上往下顺次是 Activity、ViewGroup、View。

  1. 事件从左上角那个红色箭头开始,由 Activity 的 dispatchTouchEvent 做散发
  2. 箭头的下面字代表办法返回值,(return true、return false、return super.xxxxx(),super 的意思是调用父类实现。
  3. dispatchTouchEvent 和 onTouchEvent 的框里有个【true—-> 生产】的字,示意的意思是如果办法返回 true,那么代表事件就此生产,不会持续往别的中央传了,事件终止。
  4. 目前所有的图的事件是针对 ACTION\_DOWN 的,对于 ACTION\_MOVE 和 ACTION\_UP 咱们最初做剖析。
  5. 之前图中的 Activity 的 dispatchTouchEvent 有误(图已修复),只有 return super.dispatchTouchEvent(ev) 才是往下走,返回 true 或者 false 事件就被生产了(终止传递)。

ViewGroup 事件散发

当一个点击事件产生后,它的传递过程将遵循如下程序:

Activity -> Window -> View

事件总是会传递给 Activity,之后 Activity 再传递给 Window,最初 Window 再传递给顶级的 View,顶级的 View 在接管到事件后就会依照事件散发机制去散发事件。如果一个 View 的 onTouchEvent 返回了 FALSE,那么它的父容器的 onTouchEvent 将会被调用,顺次类推,如果所有都不解决这个事件的话,那么 Activity 将会解决这个事件。

对于 ViewGroup 的事件散发过程,大略是这样的:如果顶级的 ViewGroup 拦挡事件即 onInterceptTouchEvent 返回 true 的话,则事件会交给 ViewGroup 解决,如果 ViewGroup 的 onTouchListener 被设置的话,则 onTouch 将会被调用,否则的话 onTouchEvent 将会被调用,也就是说:两者都设置的话,onTouch 将会屏蔽掉 onTouchEvent,在 onTouchEvent 中,如果设置了 onClickerListener 的话,那么 onClick 将会被调用。如果顶级 ViewGroup 不拦挡的话,那么事件将会被传递给它所在的点击事件的子 view,这时候子 view 的 dispatchTouchEvent 将会被调用

View 的事件散发

dispatchTouchEvent -> onTouch(setOnTouchListener) -> onTouchEvent -> onClick

onTouch 和 onTouchEvent 的区别 两者都是在 dispatchTouchEvent 中调用的,onTouch 优先于 onTouchEvent,如果 onTouch 返回 true,那么 onTouchEvent 则不执行,及 onClick 也不执行。

2.3、View 的绘制

在 xml 布局文件中,咱们的 layout\_width 和 layout\_height 参数能够不必写具体的尺寸,而是 wrap\_content 或者是 match\_parent。这两个设置并没有指定真正的大小,可是咱们绘制到屏幕上的 View 必须是要有具体的宽高的,正是因为这个起因,咱们必须本人去解决和设置尺寸。当然了,View 类给了默认的解决,然而如果 View 类的默认解决不满足咱们的要求,咱们就得重写 onMeasure 函数啦~。

onMeasure 函数是一个 int 整数,外面放了测量模式和尺寸大小。int 型数据占用 32 个 bit,而 google 实现的是,将 int 数据的后面 2 个 bit 用于辨别不同的布局模式,前面 30 个 bit 寄存的是尺寸的数据。onMeasure 函数的应用如下图:

MeasureSpec 有三种测量模式:

match\_parent—>EXACTLY。怎么了解呢?match\_parent 就是要利用父 View 给咱们提供的所有残余空间,而父 View 残余空间是确定的,也就是这个测量模式的整数外面寄存的尺寸。

wrap\_content—>AT\_MOST。怎么了解:就是咱们想要将大小设置为包裹咱们的 view 内容,那么尺寸大小就是父 View 给咱们作为参考的尺寸,只有不超过这个尺寸就能够啦,具体尺寸就依据咱们的需要去设定。

固定尺寸(如 100dp)—>EXACTLY。用户本人指定了尺寸大小,咱们就不必再去干预了,当然是以指定的大小为主啦。

2.4、ViewGroup 的绘制

自定义 ViewGroup 可就没那么简略啦~,因为它不仅要管好本人的,还要兼顾它的子 View。咱们都晓得 ViewGroup 是个 View 容器,它装纳 child View 并且负责把 child View 放入指定的地位。

  1. 首先,咱们得晓得各个子 View 的大小吧,只有先晓得子 View 的大小,咱们才晓得以后的 ViewGroup 该设置为多大去包容它们。
  2. 依据子 View 的大小,以及咱们的 ViewGroup 要实现的性能,决定出 ViewGroup 的大小
  3. ViewGroup 和子 View 的大小算进去了之后,接下来就是去摆放了吧,具体怎么去摆放呢?这得依据你定制的需要去摆放了,比方,你想让子 View 依照垂直程序一个挨着一个放,或者是依照先后顺序一个叠一个去放,这是你本人决定的。
  4. 曾经晓得怎么去摆放还不行啊,决定了怎么摆放就是相当于把已有的空间”宰割”成大大小小的空间,每个空间对应一个子 View,咱们接下来就是把子 View 对号入座了,把它们放进它们该放的中央去。
![image](https://upload-images.jianshu.io/upload_images/27242968-7977eef94a34965f.image?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

![image](https://upload-images.jianshu.io/upload_images/27242968-cd5ac364ad49b141.image?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)




自定义 ViewGroup 能够参考:Android 自定义 ViewGroup

3、零碎原理

3.1、打包原理

Android 的包文件 APK 分为两个局部:代码和资源,所以打包方面也分为资源打包和代码打包两个方面,这篇文章就来剖析资源和代码的编译打包原理。

具体说来:

  1. 通过 AAPT 工具进行资源文件(包含 AndroidManifest.xml、布局文件、各种 xml 资源等)的打包,生成 R.java 文件。
  2. 通过 AIDL 工具解决 AIDL 文件,生成相应的 Java 文件。
  3. 通过 Javac 工具编译我的项目源码,生成 Class 文件。
  4. 通过 DX 工具将所有的 Class 文件转换成 DEX 文件,该过程次要实现 Java 字节码转换成 Dalvik 字节码,压缩常量池以及革除冗余信息等工作。
  5. 通过 ApkBuilder 工具将资源文件、DEX 文件打包生成 APK 文件。
  6. 利用 KeyStore 对生成的 APK 文件进行签名。
  7. 如果是正式版的 APK,还会利用 ZipAlign 工具进行对齐解决,对齐的过程就是将 APK 文件中所有的资源文件举例文件的起始间隔都偏移 4 字节的整数倍,这样通过内存映射拜访 APK 文件的速度会更快。

3.2、装置流程

Android apk 的装置过程次要气氛以下几步:

  1. 复制 APK 到 /data/app 目录下,解压并扫描安装包。
  2. 资源管理器解析 APK 里的资源文件。
  3. 解析 AndroidManifest 文件,并在 /data/data/ 目录下创立对应的利用数据目录。
  4. 而后对 dex 文件进行优化,并保留在 dalvik-cache 目录下。
  5. 将 AndroidManifest 文件解析出的四大组件信息注册到 PackageManagerService 中。
  6. 装置实现后,发送播送。

能够应用上面的图示意:

4、第三方库解析

4.1、Retrofit 网络申请框架

概念:Retrofit 是一个基于 RESTful 的 HTTP 网络申请框架的封装,其中网络申请的实质是由 OKHttp 实现的,而 Retrofit 仅仅负责网络申请接口的封装。

原理:App 应用程序通过 Retrofit 申请网络,实际上是应用 Retrofit 接口层封装申请参数,Header、URL 等信息,之后由 OKHttp 实现后续的申请,在服务器返回数据之后,OKHttp 将原始的后果交给 Retrofit,最初依据用户的需要对后果进行解析。

retrofit 应用

1. 在 retrofit 中通过一个接口作为 http 申请的 api 接口

public interface NetApi {@GET("repos/{owner}/{repo}/contributors")
    Call<ResponseBody> contributorsBySimpleGetCall(@Path("owner") String owner, @Path("repo") String repo);
}

2. 创立一个 Retrofit 实例

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://api.github.com/")
        .build();

3. 调用 api 接口

NetApi repo = retrofit.create(NetApi.class);

// 第三步:调用网络申请的接口获取网络申请
retrofit2.Call<ResponseBody> call = repo.contributorsBySimpleGetCall("username", "path");
call.enqueue(new Callback<ResponseBody>() { // 进行异步申请
    @Override
    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {// 进行异步操作}

    @Override
    public void onFailure(Call<ResponseBody> call, Throwable t) {// 执行谬误回调办法}
});

retrofit 动静代理

retrofit 执行的原理如下:1. 首先,通过 method 把它转换成 ServiceMethod。2. 而后,通过 serviceMethod,args 获取到 okHttpCall 对象。3. 最初,再把 okHttpCall 进一步封装并返回 Call 对象。首先,创立 retrofit 对象的办法如下:

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://api.github.com/")
        .build();

在创立 retrofit 对象的时候用到了 build()办法,该办法的实现如下:

public Retrofit build() {if (baseUrl == null) {throw new IllegalStateException("Base URL required.");
  }

  okhttp3.Call.Factory callFactory = this.callFactory;
  if (callFactory == null) {callFactory = new OkHttpClient(); // 设置 kHttpClient
  }

  Executor callbackExecutor = this.callbackExecutor;
  if (callbackExecutor == null) {callbackExecutor = platform.defaultCallbackExecutor(); // 设置默认回调执行器
  }

  // Make a defensive copy of the adapters and add the default Call adapter.
  List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
  adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));

  // Make a defensive copy of the converters.
  List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);

  return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
      callbackExecutor, validateEagerly); // 返回新建的 Retrofit 对象
}

该办法返回了一个 Retrofit 对象,通过 retrofit 对象创立网络申请的接口的形式如下:

NetApi repo = retrofit.create(NetApi.class);

retrofit 对象的 create()办法的实现如下:‘

public <T> T create(final Class<T> service) {Utils.validateServiceInterface(service);
  if (validateEagerly) {eagerlyValidateMethods(service);
  }
  return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service},
      new InvocationHandler() {private final Platform platform = Platform.get();

        @Override public Object invoke(Object proxy, Method method, Object... args)
            throws Throwable {
          // If the method is a method from Object then defer to normal invocation.
          if (method.getDeclaringClass() == Object.class) {return method.invoke(this, args); // 间接调用该办法
          }
          if (platform.isDefaultMethod(method)) {return platform.invokeDefaultMethod(method, service, proxy, args); // 通过平台对象调用该办法
          }
          ServiceMethod serviceMethod = loadServiceMethod(method); // 获取 ServiceMethod 对象
          OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args); // 传入参数生成 okHttpCall 对象
          return serviceMethod.callAdapter.adapt(okHttpCall); // 执行 okHttpCall
        }
      });
}

4.2、图片加载库比照

Picasso:120K

Glide:475K

Fresco:3.4M

Android-Universal-Image-Loader:162K

图片函数库的抉择须要依据 APP 的具体情况而定,对于重大依赖图片缓存的 APP,例如壁纸类,图片社交类 APP 来说,能够抉择最业余的 Fresco。对于个别的 APP,抉择 Fresco 会显得比拟重,毕竟 Fresco3.4M 的体量摆在这。依据 APP 对图片的显示和缓存的需要从低到高,咱们能够对以上函数库做一个排序。

Picasso < Android-Universal-Image-Loader < Glide < Fresco

2. 介绍:

Picasso:和 Square 的网络库一起能施展最大作用,因为 Picasso 能够抉择将网络申请的缓存局部交给了 okhttp 实现。

Glide:模拟了 Picasso 的 API,而且在他的根底上加了很多的扩大(比方 gif 等反对),Glide 默认的 Bitmap 格局是 RGB\_565,比 Picasso 默认的 ARGB\_8888 格局的内存开销要小一半;Picasso 缓存的是全尺寸的(只缓存一种),而 Glide 缓存的是跟 ImageView 尺寸雷同的(即 56_56 和 128_128 是两个缓存)。

FB 的图片加载框架 Fresco:最大的劣势在于 5.0 以下 (最低 2.3) 的 bitmap 加载。在 5.0 以下零碎,Fresco 将图片放到一个特地的内存区域(Ashmem 区)。当然,在图片不显示的时候,占用的内存会主动被开释。这会使得 APP 更加晦涩,缩小因图片内存占用而引发的 OOM。为什么说是 5.0 以下,因为在 5.0 当前零碎默认就是存储在 Ashmem 区了。

3. 总结:

Picasso 所能实现的性能,Glide 都能做,无非是所需的设置不同。然而 Picasso 体积比起 Glide 小太多如果我的项目中网络申请自身用的就是 okhttp 或者 retrofit(实质还是 okhttp),那么倡议用 Picasso,体积会小很多(Square 全家桶的干活)。Glide 的益处是大型的图片流,比方 gif、Video,如果你们是做美拍、爱拍这种视频类利用,倡议应用。

Fresco 在 5.0 以下的内存优化十分好,代价就是体积也十分的大,按体积算 Fresco>Glide>Picasso

不过在应用起来也有些不便(小倡议:他只能用内置的一个 ImageView 来实现这些性能,用起来比拟麻烦,咱们通常是依据 Fresco 本人改改,间接应用他的 Bitmap 层)

4.3、各种 json 解析库应用

参考链接:www.cnblogs.com/kunpengit/p…

(1)Google 的 Gson

Gson 是目前性能最全的 Json 解析神器,Gson 当初是为因应 Google 公司外部需要而由 Google 自行研发而来,但自从在 2008 年五月公开公布第一版后已被许多公司或用户利用。Gson 的利用次要为 toJson 与 fromJson 两个转换函数,无依赖,不须要例外额定的 jar,可能间接跑在 JDK 上。而在应用这种对象转换之前需先创立好对象的类型以及其成员能力胜利的将 JSON 字符串胜利转换成绝对应的对象。类外面只有有 get 和 set 办法,Gson 齐全能够将简单类型的 json 到 bean 或 bean 到 json 的转换,是 JSON 解析的神器。Gson 在性能下面无可挑剔,然而性能下面比 FastJson 有所差距。

(2)阿里巴巴的 FastJson

Fastjson 是一个 Java 语言编写的高性能的 JSON 处理器, 由阿里巴巴公司开发。

无依赖,不须要例外额定的 jar,可能间接跑在 JDK 上。FastJson 在简单类型的 Bean 转换 Json 上会呈现一些问题,可能会呈现援用的类型,导致 Json 转换出错,须要制订援用。FastJson 采纳独创的算法,将 parse 的速度晋升到极致,超过所有 json 库。

综上 Json 技术的比拟,在我的项目选型的时候能够应用 Google 的 Gson 和阿里巴巴的 FastJson 两种并行应用,如果只是性能要求,没有性能要求,能够应用 google 的 Gson,如果有性能下面的要求能够应用 Gson 将 bean 转换 json 确保数据的正确,应用 FastJson 将 Json 转换 Bean

5、热点技术

参考链接 - Android 组件化计划

5.1、组件化

(1)概念:

组件化:是将一个 APP 分成多个 module,每个 module 都是一个组件,也能够是一个根底库供组件依赖,开发中能够独自调试局部组件,组件中不须要相互依赖然而能够互相调用,最终公布的时候所有组件以 lib 的模式被主 APP 工程依赖打包成一个 apk。

(2)由来:

  1. APP 版本迭代,新性能一直减少,业务变得复杂,保护老本高
  2. 业务耦合度高,代码臃肿,团队外部多人合作开发艰难
  3. Android 编译代码卡顿,繁多工程下代码耦合重大,批改一处须要从新编译打包,耗时耗力。
  4. 不便单元测试,独自改一个业务模块,不须要着重关注其余模块。

(3)劣势:

  1. 组件化将通用模块独立进去,对立治理,以进步复用,将页面拆分为粒度更小的组件,组件外部出了蕴含 UI 实现,还能够蕴含数据层和逻辑层
  2. 每个组件度能够独立编译、放慢编译速度、独立打包。
  3. 每个工程外部的批改,不会影响其余工程。
  4. 业务库工程能够疾速拆分进去,集成到其余 App 中。
  5. 迭代频繁的业务模块采纳组件形式,业务线研发能够互不烦扰、晋升合作效率,并管制产品质量,增强稳定性。
  6. 并行开发,团队成员只关注本人的开发的小模块,升高耦合性,前期保护不便等。

(4)思考问题:

模式切换:如何使得 APP 在独自调试跟整体调试自在切换

组件化后的每一个业务的 module 都能够是一个独自的 APP(isModuleRun=false),release 包的时候各个业务 module 作为 lib 依赖,这里齐全由一个变量管制,在根我的项目 gradle.properties 外面 isModuleRun=true。isModuleRun 状态不同,加载 application 和 AndroidManifest 都不一样,以此来辨别是独立的 APK 还是 lib。

在 build.grade 外面配置:

资源抵触

当咱们创立了多个 Module 的时候,如何解决雷同资源文件名合并的抵触,业务 Module 和 BaseModule 资源文件名称反复会产生抵触,解决方案在于:

每个 module 都有 app\_name,为了不让资源名重名,在每个组件的 build.gradle 中减少 resourcePrefix“xxx\_强行查看资源名称前缀。固定每个组件的资源前缀。然而 resourcePrefix 这个值只能限定 xml 外面的资源,并不能限定图片资源。

依赖关系

多个 Module 之间如何援用一些独特的 library 以及工具类

组件通信

组件化之后,Module 之间是互相隔离的,如何进行 UI 跳转以及办法调用,具体能够应用阿里巴巴 ARouter 或者美团的 WMRouter 等路由框架。

各业务 Module 之前不须要任何依赖能够通过路由跳转,完满解决业务之间耦合。

入口参数

咱们晓得组件之间是有分割的,所以在独自调试的时候如何拿到其它的 Module 传递过去的参数

Application

当组件独自运行的时候,每个 Module 自成一个 APK,那么就意味着会有多个 Application,很显然咱们不违心反复写这么多代码,所以咱们只须要定义一个 BaseApplication 即可,其它的 Application 间接继承此 BaseApplication 就 OK 了,BaseApplication 外面还可定义专用的参数。

对于如何进行组件化,能够参考:安居客 Android 我的项目架构演进

5.2、插件化

参考链接 - 插件化入门

(1)概述

提到插件化,就不得不提起办法数超过 65535 的问题,咱们能够通过 Dex 分包来解决,同时也能够通过应用插件化开发来解决。插件化的概念就是由宿主 APP 去加载以及运行插件 APP。

(2 长处)

在一个大的我的项目外面,为了明确的分工,往往不同的团队负责不同的插件 APP,这样分工更加明确。各个模块封装成不同的插件 APK,不同模块能够独自编译,进步了开发效率。解决了上述的办法数超过限度的问题。能够通过上线新的插件来解决线上的 BUG,达到“热修复”的成果。减小了宿主 APK 的体积。

(3 毛病)

插件化开发的 APP 不能在 Google Play 上线,也就是没有海内市场。

6、屏幕适配

6.1、基本概念

屏幕尺寸

含意:手机对角线的物理尺寸 单位:英寸(inch),1 英寸 =2.54cm

Android 手机常见的尺寸有 5 寸、5.5 寸、6 寸,6.5 寸等等

屏幕分辨率

含意:手机在横向、纵向上的像素点数总和

个别形容成屏幕的”宽 x 高”=AxB 含意:屏幕在横向方向(宽度)上有 A 个像素点,在纵向方向

(高)有 B 个像素点 例子:1080×1920,即宽度方向上有 1080 个像素点,在高度方向上有 1920 个像素点

单位:px(pixel),1px= 1 像素点

UI 设计师的设计图会以 px 作为对立的计量单位

Android 手机常见的分辨率:320×480、480×800、720×1280、1080×1920

屏幕像素密度

含意:每英寸的像素点数 单位:dpi(dots per ich)

假如设施内每英寸有 160 个像素,那么该设施的屏幕像素密度 =160dpi

6.2、适配办法

1. 反对各种屏幕尺寸:应用 wrap\_content, match\_parent, weight. 要确保布局的灵活性并适应各种尺寸的屏幕,应应用“wrap\_content”、“match\_parent”管制某些视图组件的宽度和高度。

2. 应用绝对布局,禁用相对布局。

3. 应用 LinearLayout 的 weight 属性

如果咱们的宽度不是 0dp(wrap\_content 和 0dp 的成果雷同),则是 match\_parent 呢?

android:layout\_weight 的实在含意是: 如果 View 设置了该属性并且无效,那么该 View 的宽度等于原有宽度 (android:layout\_width) 加上残余空间的占比。

从这个角度咱们来解释一下下面的景象。在下面的代码中,咱们设置每个 Button 的宽度都是 match\_parent,假如屏幕宽度为 L,那么每个 Button 的宽度也应该都为 L,残余宽度就等于 L -(L+L)= -L。

Button1 的 weight=1,残余宽度占比为 1 /(1+2)= 1/3,所以最终宽度为 L +1/3*(-L)=2/3L,Button2 的计算相似,最终宽度为 L +2/3(-L)=1/3L。

4. 应用.9 图片

6.3、今日头条屏幕适配

参考链接:今日头条屏幕适配计划终极版

7、性能优化

参考链接:Android 性能监测工具,优化内存、卡顿、耗电、APK 大小的办法 Android 的性能优化,次要是从以下几个方面进行优化的:稳固(内存溢出、解体)晦涩(卡顿)耗损(耗电、流量)安装包(APK 瘦身)影响稳定性的起因很多,比方内存应用不合理、代码异样场景考虑不周全、代码逻辑不合理等,都会对利用的稳定性造成影响。其中最常见的两个场景是:Crash 和 ANR,这两个谬误将会使得程序无奈应用。所以做好 Crash 全局监控,解决闪退同时把解体信息、异样信息收集记录起来,以便后续剖析; 正当应用主线程解决业务,不要在主线程中做耗时操作,避免 ANR 程序无响应产生。

(一)稳固——内存优化

(1)Memory Monitor 工具:

它是 Android Studio 自带的一个内存监督工具,它能够很好地帮忙咱们进行内存实时剖析。通过点击 Android Studio 右下角的 Memory Monitor 标签,关上工具能够看见较浅蓝色代表 free 的内存,而深色的局部代表应用的内存从内存变换的走势图变换,能够判断对于内存的应用状态,例如当内存继续增高时,可能产生内存透露;当内存忽然缩小时,可能产生 GC 等,如下图所示。

LeakCanary 工具:LeakCanary 是 Square 公司基于 MAT 开发的一款监控 Android 内存透露的开源框架。其工作的原理是:监测机制利用了 Java 的 WeakReference 和 ReferenceQueue,通过将 Activity 包装到 WeakReference 中,被 WeakReference 包装过的 Activity 对象如果被回收,该 WeakReference 援用会被放到 ReferenceQueue 中,通过监测 ReferenceQueue 外面的内容就能查看到 Activity 是否可能被回收(在 ReferenceQueue 中阐明能够被回收,不存在透露;否则,可能存在透露,LeakCanary 是执行一遍 GC,若还未在 ReferenceQueue 中,就会认定为透露)。

如果 Activity 被认定为泄露了,就抓取内存 dump 文件 (Debug.dumpHprofData);之后通过 HeapAnalyzerService.runAnalysis 进行剖析内存文件剖析;接着通过 HeapAnalyzer (checkForLeak—findLeakingReference—findLeakTrace) 来进行内存透露剖析。最初通过 DisplayLeakService 进行内存透露的展现。

(3)Android Lint 工具:

Android Lint Tool 是 Android Sutido 种集成的一个 Android 代码提醒工具,它能够给你布局、代码提供十分弱小的帮忙。硬编码会提醒以级别正告,例如:在布局文件中写了三层冗余的 LinearLayout 布局、间接在 TextView 中写要显示的文字、字体大小应用 dp 而不是 sp 为单位,就会在编辑器左边看到提醒。

(二)晦涩——卡顿优化

卡顿的场景通常是产生在用户交互体验最间接的方面。影响卡顿的两大因素,别离是界面绘制和数据处理。

界面绘制:次要起因是绘制的层级深、页面简单、刷新不合理,因为这些起因导致卡顿的场景更多呈现在 UI 和启动后的初始界面以及跳转到页面的绘制上。

数据处理:导致这种卡顿场景的起因是数据处理量太大,个别分为三种状况,一是数据在解决 UI 线程,二是数据处理占用 CPU 高,导致主线程拿不到工夫片,三是内存减少导致 GC 频繁,从而引起卡顿。

(1)布局优化

在 Android 种系统对 View 进行测量、布局和绘制时,都是通过对 View 数的遍从来进行操作的。如果一个 View 数的高度太高就会重大影响测量、布局和绘制的速度。Google 也在其 API 文档中倡议 View 高度不宜哦过 10 层。当初版本种 Google 应用 RelativeLayout 代替 LineraLayout 作为默认根布局,目标就是升高 LineraLayout 嵌套产生布局树的高度,从而进步 UI 渲染的效率。

布局复用,应用标签重用 layout;进步显示速度,应用提早 View 加载;缩小层级,应用标签替换父级布局;留神应用 wrap\_content,会减少 measure 计算成本;删除控件中无用属性;

(2)绘制优化

适度绘制是指在屏幕上的某个像素在同一帧的工夫内被绘制了屡次。在多层次重叠的 UI 构造中,如果不可见的 UI 也在做绘制的操作,就会导致某些像素区域被绘制了屡次,从而节约了多余的 CPU 以及 GPU 资源。如何防止适度绘制?

布局上的优化。移除 XML 中非必须的背景,移除 Window 默认的背景、按需显示占位背景图片

自定义 View 优化。应用 canvas.clipRect() 帮忙零碎辨认那些可见的区域,只有在这个区域内才会被绘制。

(3)启动优化

利用个别都有闪屏页 SplashActivity,优化闪屏页的 UI 布局,能够通过 Profile GPU Rendering 检测丢帧状况。

(三)节俭——耗电优化

在 Android5.0 以前,对于应用电量耗费的测试即麻烦又不精确,而 5.0 之后 Google 专门引入了一个获取设施上电量耗费信息的 API—— Battery Historian。Battery Historian 是一款由 Google 提供的 Android 零碎电量剖析工具,直观地展现出手机的电量耗费过程,通过输出电量剖析文件,显示耗费状况。

最初提供一些可供参考耗电优化的办法:

(1)计算优化。算法、for 循环优化、Switch..case 代替 if..else、避开浮点运算。

浮点运算:计算机里整数和小数模式就是按一般格局进行存储,例如 1024、3.1415926 等等,这个没什么特点,然而这样的数精度不高,表白也不够全面,为了可能有一种数的通用表示法,就创造了浮点数。浮点数的示意模式有点像迷信计数法(_._×10^_),它的示意模式是 0._×10^_,在计算机中的模式为 .*** e ±_**),其中后面的星号代表定点小数,也就是整数局部为 0 的纯小数,前面的指数局部是定点整数。利用这样的模式就能示意出任意一个整数和小数,例如 1024 就能示意成 0.1024×10^4,也就是 .1024e+004,3.1415926 就能示意成 0.31415926×10^1,也就是 .31415926e+001,这就是浮点数。浮点数进行的运算就是浮点运算。浮点运算比惯例运算更简单,因而计算机进行浮点运算速度要比进行惯例运算慢得多。

(2)防止 Wake Lock 使用不当。

Wake Lock 是一种锁的机制,次要是绝对零碎的休眠而言的,, 只有有人拿着这个锁,零碎就无奈进入休眠意思就是我的程序给 CPU 加了这个锁那零碎就不会休眠了,这样做的目标是为了全力配合咱们程序的运行。有的状况如果不这么做就会呈现一些问题,比方微信等及时通信的心跳包会在熄屏不久后进行网络拜访等问题。所以微信外面是有大量应用到了 Wake\_Lock 锁。零碎为了节俭电量,CPU 在没有工作忙的时候就会主动进入休眠。有工作须要唤醒 CPU 高效执行的时候,就会给 CPU 加 Wake\_Lock 锁。大家常常犯的谬误,咱们很容易去唤醒 CPU 来工作,然而很容易遗记开释 Wake\_Lock。

(3)应用 Job Scheduler 治理后台任务。

在 Android 5.0 API 21 中,google 提供了一个叫做 JobScheduler API 的组件,来解决当某个工夫点或者当满足某个特定的条件时执行一个工作的场景,例如当用户在夜间劳动时或设施接通电源适配器连贯 WiFi 启动下载更新的工作。这样能够在缩小资源耗费的同时晋升利用的效率。

(四)安装包——APK 瘦身

(1)安装包的组成构造

assets 文件夹。寄存一些配置文件、资源文件,assets 不会主动生成对应的 ID,而是通过 AssetManager 类的接口获取。

res。res 是 resource 的缩写,这个目录寄存资源文件,会主动生成对应的 ID 并映射到 .R 文件中,拜访间接应用资源 ID。

META-INF。保留利用的签名信息,签名信息能够验证 APK 文件的完整性。

AndroidManifest.xml。这个文件用来形容 Android 利用的配置信息,一些组件的注册信息、可应用权限等。

classes.dex。Dalvik 字节码程序,让 Dalvik 虚拟机可执行,个别状况下,Android 利用在打包时通过 Android SDK 中的 dx 工具将 Java 字节码转换为 Dalvik 字节码。

resources.arsc。记录着资源文件和资源 ID 之间的映射关系,用来依据资源 ID 寻找资源。

(2)缩小安装包大小

代码混同。应用 IDE 自带的 proGuard 代码混同器工具,它包含压缩、优化、混同等性能。资源优化。比方应用 Android Lint 删除冗余资源,资源文件起码化等。图片优化。比方利用 PNG 优化工具 对图片做压缩解决。举荐目前最先进的压缩工具 Googlek 开源库 zopfli。如果利用在 0 版本以上,举荐应用 WebP 图片格式。防止反复或无用性能的第三方库。例如,百度地图接入根底地图即可、讯飞语音无需接入离线、图片库 Glide\Picasso 等。插件化开发。比方功能模块放在服务器上,按需下载,能够缩小安装包大小。能够应用微信开源资源文件混同工具——AndResGuard。个别能够压缩 apk 的 1M 左右大。

7.1、冷启动与热启动

参考链接:www.jianshu.com/p/03c0fd3fc…

冷启动 在启动利用时,零碎中没有该利用的过程,这时零碎会创立一个新的过程调配给该利用;

热启动 在启动利用时,零碎中已有该利用的过程(例:按 back 键、home 键,利用尽管会退出,然而该利用的过程还是保留在后盾);

区别 冷启动:零碎没有该利用的过程,须要创立一个新的过程调配给利用,所以会先创立和初始化 Application 类,再创立和初始化 MainActivity 类(包含一系列的测量、布局、绘制),最初显示在界面上。热启动:从已有的过程中来启动,不会创立和初始化 Application 类,间接创立和初始化 MainActivity 类(包含一系列的测量、布局、绘制),最初显示在界面上。

冷启动流程 Zygote 过程中 fork 创立出一个新的过程;创立和初始化 Application 类、创立 MainActivity;inflate 布局、当 onCreate/onStart/onResume 办法都走完;contentView 的 measure/layout/draw 显示在界面上。

冷启动优化 缩小在 Application 和第一个 Activity 的 onCreate() 办法的工作量;不要让 Application 参加业务的操作;不要在 Application 进行耗时操作;不要以动态变量的形式在 Application 中保留数据;缩小布局的复杂性和深度;

8、MVP 模式架构

8.1、MVP 模式

MVP 架构由 MVC 倒退而来。在 MVP 中,M 代表 Model,V 代表 View,P 代表 Presenter。

模型层(Model): 次要是获取数据性能,业务逻辑和实体模型。

视图层(View):对应于 Activity 或 Fragment,负责视图的局部展现和业务逻辑用户交互

管制层(Presenter): 负责实现 View 层与 Model 层间的交互,通过 P 层来获取 M 层中数据后返回给 V 层,使得 V 层与 M 层间没有耦合。

在 MVP 中,Presenter 层齐全将 View 层和 Model 层进行了拆散,把次要程序逻辑放在 Presenter 层实现,Presenter 与具体的 View 层(Activity)是没有间接的关联,是通过定义接口来进行交互的,从而使得当 View 层(Activity)产生扭转时,Persenter 仍然能够放弃不变。View 层接口类只应该只有 set/get 办法,及一些界面显示内容和用户输出,除此之外不应该有多余的内容。绝不允许 View 层间接拜访 Model 层,这是与 MVC 最大区别之处,也是 MVP 外围长处。

9、虚拟机

9.1、Android Dalvik 虚拟机和 ART 虚拟机比照

Dalvik

Android4.4 及以前应用的都是 Dalvik 虚拟机,咱们晓得 Apk 在打包的过程中会先将 java 等源码通过 javac 编译成.class 文件,然而咱们的 Dalvik 虚拟机只会执行.dex 文件,这个时候 dx 会将.class 文件转换成 Dalvik 虚拟机执行的.dex 文件。Dalvik 虚拟机在启动的时候会先将.dex 文件转换成疾速运行的机器码,又因为 65535 这个问题,导致咱们在利用冷启动的时候有一个合包的过程,最初导致的一个后果就是咱们的 app 启动慢,这就是 Dalvik 虚拟机的 JIT 个性(Just In Time)。

ART

ART 虚拟机是在 Android5.0 才开始应用的 Android 虚拟机,ART 虚拟机必须要兼容 Dalvik 虚拟机的个性,然而 ART 有一个很好的个性 AOT(ahead of time),这个个性就是咱们在装置 APK 的时候就将 dex 间接解决成可间接供 ART 虚拟机应用的机器码,ART 虚拟机将.dex 文件转换成可间接运行的.oat 文件,ART 虚拟机天生反对多 dex,所以也不会有一个合包的过程,所以 ART 虚构机会很大的晋升 APP 冷启动速度。

ART 长处:

放慢 APP 冷启动速度

晋升 GC 速度

提供性能全面的 Debug 个性

ART 毛病:

APP 装置速度慢,因为在 APK 装置的时候要生成可运行.oat 文件

APK 占用空间大,因为在 APK 装置的时候要生成可运行.oat 文件

arm 处理器

对于 ART 更具体的介绍,能够参考 Android ART 详解

总结

相熟 Android 性能剖析工具、UI 卡顿、APP 启动、包瘦身和内存性能优化

相熟 Android APP 架构设计,模块化、组件化、插件化开发

熟练掌握 Java、设计模式、网络、多线程技术

Java 根本知识点

1、Java 的类加载过程

jvm 将.class 类文件信息加载到内存并解析成对应的 class 对象的过程,留神:jvm 并不是一开始就把所有的类加载进内存中,只是在第一次遇到某个须要运行的类才会加载,并且只加载一次

次要分为三局部:1、加载,2、链接(1. 验证,2. 筹备,3. 解析),3、初始化

1:加载

类加载器包含 BootClassLoader、ExtClassLoader、APPClassLoader

2:链接

验证:(验证 class 文件的字节流是否合乎 jvm 标准)

筹备:为类变量分配内存,并且进行赋初值

解析:将常量池外面的符号援用(变量名)替换成间接援用(内存地址)过程,在解析阶段,jvm 会把所有的类名、办法名、字段名、这些符号援用替换成具体的内存地址或者偏移量。

3:初始化

次要对类变量进行初始化,执行类结构器的过程,换句话说,只对 static 修试的变量或者语句进行初始化。

范例:Person person = new Person(); 为例进行阐明。

Java 编程思维中的类的初始化过程次要有以下几点:

  1. 找到 class 文件,将它加载到内存
  2. 在堆内存中调配内存地址
  3. 初始化
  4. 将堆内存地址指给栈内存中的 p 变量

2、String、StringBuilder、StringBuffer

StringBuffer 外面的很多办法增加了 synchronized 关键字,是能够表征线程平安的,所以多线程状况下应用它。

执行速度:

StringBuilder > StringBuffer > String

StringBuilder 就义了性能来换取速度的,这两个是能够间接在原对象下面进行批改,省去了创立新对象和回收老对象的过程,而 String 是字符串常量(final)修试,另外两个是字符串变量,常量对象一旦创立就不能够批改,变量是能够进行批改的,所以对于 String 字符串的操作蕴含上面三个步骤:

  1. 创立一个新对象,名字和原来的一样
  2. 在新对象下面进行批改
  3. 原对象被垃圾回收掉

3、JVM 内存构造

Java 对象实例化过程中,次要应用到虚拟机栈、Java 堆和办法区。Java 文件通过编译之后首先会被加载到 jvm 办法区中,jvm 办法区中很重的一个局部是运行时常量池,用以存储 class 文件类的版本、字段、办法、接口等形容信息和编译期间的常量和动态常量。

3.1、JVM 根本构造

类加载器 classLoader,在 JVM 启动时或者类运行时将须要的.class 文件加载到内存中。执行引擎,负责执行 class 文件中蕴含的字节码指令。本地办法接口,次要是调用 C /C++ 实现的本地办法及返回后果。内存区域(运行时数据区),是在 JVM 运行的时候操作所调配的内存区,次要分为以下五个局部,如下图:

  • 办法区:用于存储类构造信息的中央,包含常量池、动态变量、构造函数等。
  • Java 堆(heap):存储 Java 实例或者对象的中央。这块是 gc 的次要区域。
  • Java 栈(stack):Java 栈总是和线程关联的,每当创立一个线程时,JVM 就会为这个线程创立一个对应的 Java 栈。在这个 java 栈中又会蕴含多个栈帧,每运行一个办法就创立一个栈帧,用于存储局部变量表、操作栈、办法返回值等。每一个办法从调用直至执行实现的过程,就对应一个栈帧在 java 栈中入栈到出栈的过程。所以 java 栈是线程公有的。
  • 程序计数器:用于保留以后线程执行的内存地址,因为 JVM 是多线程执行的,所以为了保障线程切换回来后还能复原到原先状态,就须要一个独立的计数器,记录之前中断的中央,可见程序计数器也是线程公有的。
  • 本地办法栈:和 Java 栈的作用差不多,只不过是为 JVM 应用到的 native 办法服务的。

3.2、JVM 源码剖析

www.jianshu.com/nb/12554212

4、GC 机制

垃圾收集器个别实现两件事

  1. 检测出垃圾;
  2. 回收垃圾;

4.1 Java 对象援用

通常,Java 对象的援用能够分为 4 类:强援用、软援用、弱援用和虚援用。强援用:通常能够认为是通过 new 进去的对象,即便内存不足,GC 进行垃圾收集的时候也不会被动回收。

Object obj = new Object();

软援用:在内存不足的时候,GC 进行垃圾收集的时候会被 GC 回收。

Object obj = new Object();
SoftReference<Object> softReference = new SoftReference<>(obj);

弱援用:无论内存是否短缺,GC 进行垃圾收集的时候都会回收。


Object obj = new Object();
WeakReference<Object> weakReference = new WeakReference<>(obj);

虚援用:和弱援用相似,次要区别在于虚援用必须和援用队列一起应用。

Object obj = new Object();
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
PhantomReference<Object> phantomReference = new PhantomReference<>(obj, referenceQueue);

援用队列:如果软援用和弱援用被 GC 回收,JVM 就会把这个援用加到援用队列里,如果是虚援用,在回收前就会被加到援用队列里。

垃圾检测办法:

援用计数法 :给每个对象增加援用计数器,每个中央援用它,计数器就 +1,生效时 -1。如果两个对象相互援用时,就导致无奈回收。 可达性剖析算法:以根集对象为起始点进行搜寻,如果对象不可达的话就是垃圾对象。根集(Java 栈中援用的对象、办法区中常量池中援用的对象、本地办法中援用的对象等。JVM 在垃圾回收的时候,会查看堆中所有对象是否被这些根集对象援用,不可能被援用的对象就会被垃圾回收器回收。)

垃圾回收算法:

常见的垃圾回收算法有:

标记 - 革除

标记:首先标记所有须要回收的对象,在标记实现之后统计回收所有被标记的对象,它的标记过程即为下面的可达性剖析算法。革除:革除所有被标记的对象 毛病:效率有余,标记和革除效率都不高 空间问题,标记革除之后会产生大量不间断的内存碎片,导致大对象调配无奈找到足够的空间,提前进行垃圾回收。

复制回收算法 将可用的内存按容量划分为大小相等的 2 块,每次只用一块,当这一块的内存用完了,就将存活的对象复制到另外一块下面,而后把已应用过的内存空间一次清理掉。

毛病:

将内存放大了本来的个别,代价比拟高 大部分对象是“朝生夕灭”的,所以不用依照 1:1 的比例划分。当初商业虚拟机采纳这种算法回收新生代,但不是按 1:1 的比例,而是将内存区域划分为 eden 空间、from 空间、to 空间 3 个局部。其中 from 空间和 to 空间能够视为用于复制的两块大小雷同、位置相等,且可进行角色调换的空间块。from 和 to 空间也称为 survivor 空间,即幸存者空间,用于寄存未被回收的对象。

在垃圾回收时,eden 空间中的存活对象会被复制到未应用的 survivor 空间中 (假如是 to),正在应用的 survivor 空间 (假如是 from) 中的年老对象也会被复制到 to 空间中 (大对象,或者老年对象会间接进入老年带,如果 to 空间已满,则对象也会间接进入老年代)。此时,eden 空间和 from 空间中的残余对象就是垃圾对象,能够间接清空,to 空间则寄存此次回收后的存活对象。这种改良的复制算法既保证了空间的连续性,又防止了大量的内存空间节约。

标记 - 整顿

在老年代的对象大都是存活对象,复制算法在对象存活率教高的时候,效率就会变得比拟低。依据老年代的特点,有人提出了“标记 - 压缩算法(Mark-Compact)”

标记过程与标记 - 革除的标记一样,但后续不是对可回收对象进行清理,而是让所有的对象都向一端挪动,而后间接清理掉端边界以外的内存。

这种办法既防止了碎片的产生,又不须要两块雷同的内存空间,因而,其性价比比拟高。

分带收集算法

依据对象存活的周期不同将内存划分为几块,个别是把 Java 堆分为老年代和新生代,这样依据各个年代的特点采纳适当的收集算法。

新生代每次收集都有大量对象死去,只有大量存活,那就选用复制算法,复制的对象数较少就可实现收集。老年代对象存活率高,应用标记 - 压缩算法,以进步垃圾回收效率。

5、类加载器

程序在启动的时候,并不会一次性加载程序所要用的所有 class 文件,而是依据程序的须要,通过 Java 的类加载机制(ClassLoader)来动静加载某个 class 文件到内存当中的,从而只有 class 文件被载入到了内存之后,能力被其它 class 所援用。所以 ClassLoader 就是用来动静加载 class 文件到内存当中用的。

5.1、双亲委派原理

每个 ClassLoader 实例都有一个父类加载器的援用(不是继承关系,是一个蕴含的关系),虚拟机内置的类加载器(Bootstrap ClassLoader)自身没有父类加载器,然而能够用做其余 ClassLoader 实例的父类加载器。

当一个 ClassLoader 实例须要加载某个类时,它会试图在亲自搜寻这个类之前先把这个工作委托给它的父类加载器,这个过程是由上而下顺次查看的,首先由顶层的类加载器 Bootstrap CLassLoader 进行加载,如果没有加载到,则把工作转交给 Extension CLassLoader 视图加载,如果也没有找到,则转交给 AppCLassLoader 进行加载,还是没有的话,则交给委托的发起者,由它到指定的文件系统或者网络等 URL 中进行加载类。还没有找到的话,则会抛出 CLassNotFoundException 异样。否则将这个类生成一个类的定义,并将它加载到内存中,最初返回这个类在内存中的 Class 实例对象。

5.2、为什么应用双亲委托模型

JVM 在判断两个 class 是否雷同时,不仅要判断两个类名是否雷同,还要判断是否是同一个类加载器加载的。

防止反复加载,父类曾经加载了,则子 CLassLoader 没有必要再次加载。思考平安因素,假如自定义一个 String 类,除非扭转 JDK 中 CLassLoader 的搜寻类的默认算法,否则用户自定义的 CLassLoader 如法加载一个本人写的 String 类,因为 String 类在启动时就被疏导类加载器 Bootstrap CLassLoader 加载了。

对于 Android 的双亲委托机制,能够参考 android classloader 双亲委托模式

6、汇合

Java 汇合类次要由两个接口派生出:Collection 和 Map,这两个接口是 Java 汇合的根接口。

Collection 接口是汇合类的根接口,Java 中没有提供这个接口的间接的实现类。然而却让其被继承产生了两个接口,就是 Set 和 List。Set 中不能蕴含反复的元素。List 是一个有序的汇合,能够蕴含反复的元素,提供了按索引拜访的形式。

Map 是 Java.util 包中的另一个接口,它和 Collection 接口没有关系,是互相独立的,然而都属于汇合类的一部分。Map 蕴含了 key-value 对。Map 不能蕴含反复的 key,然而能够蕴含雷同的 value。

6.1、区别

List,Set 都是继承自 Collection 接口,Map 则不是; List 特点:元素有放入程序,元素可反复; Set 特点:元素无放入程序,元素不可反复,反复元素会笼罩掉,(留神:元素尽管无放入程序,然而元素在 set 中的地位是有该元素的 HashCode 决定的,其地位其实是固定的,退出 Set 的 Object 必须定义 equals()办法; LinkedList、ArrayList、HashSet 是非线程平安的,Vector 是线程平安的; HashMap 是非线程平安的,HashTable 是线程平安的;

6.2、List 和 Vector 比拟

Vector 是多线程平安的,线程平安就是说多线程拜访同一代码,不会产生不确定的后果。而 ArrayList 不是,这个能够从源码中看出,Vector 类中的办法很多有 synchronized 进行润饰,这样就导致了 Vector 在效率上无奈与 ArrayList 相比;两个都是采纳的线性间断空间存储元素,然而当空间有余的时候,两个类的减少形式是不同。Vector 能够设置增长因子,而 ArrayList 不能够。Vector 是一种老的动静数组,是线程同步的,效率很低,个别不赞成应用。

6.3、HashSet 如何保障不反复

HashSet 底层通过 HashMap 来实现的,在往 HashSet 中增加元素是

public boolean add(E e) {return map.put(e, PRESENT)==null;
}


// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();

在 HashMap 中进行查找是否存在这个 key,value 始终是一样的,次要有以下几种状况:

  • 如果 hash 码值不雷同,阐明是一个新元素,存;
  • 如果 hash 码值雷同,且 equles 判断相等,阐明元素曾经存在,不存;
  • 如果 hash 码值雷同,且 equles 判断不相等,阐明元素不存在,存;
  • 如果有元素和传入对象的 hash 值相等,那么,持续进行 equles()判断,如果依然相等,那么就认为传入元素曾经存在,不再增加,完结,否则依然增加;

6.4、HashSet 与 Treeset 的实用场景

  • HashSet 是基于 Hash 算法实现的,其性能通常都优于 TreeSet。为疾速查找而设计的 Set,咱们通常都应该应用 HashSet,在咱们须要排序的性能时,咱们才应用 TreeSet。
  • TreeSet 是二叉树(红黑树的树据构造)实现的,Treeset 中的数据是主动排好序的,不容许放入 null 值
  • HashSet 是哈希表实现的,HashSet 中的数据是无序的,能够放入 null,但只能放入一个 null,两者中的值都不能反复,就如数据库中惟一束缚。
  • HashSet 是基于 Hash 算法实现的,其性能通常都优于 TreeSet。为疾速查找而设计的 Set,咱们通常都应该应用 HashSet,在咱们须要排序的性能时,咱们才应用 TreeSet。

6.5、HashMap 与 TreeMap、HashTable 的区别及实用场景

HashMap 非线程平安,基于哈希表 (散列表) 实现。应用 HashMap 要求增加的键类明确定义了 hashCode()和 equals()[能够重写 hashCode()和 equals()],为了优化 HashMap 空间的应用,您能够调优初始容量和负载因子。其中散列表的抵触解决次要分两种,一种是凋谢定址法,另一种是链表法。HashMap 的实现中采纳的是链表法。TreeMap:非线程平安基于红黑树实现,TreeMap 没有调优选项,因为该树总处于均衡状态

7、常量池

7.1、Interger 中的 128(-128~127)

当数值范畴为 -128~127 时:如果两个 new 进去 Integer 对象,即便值雷同,通过“==”比拟后果为 false,但两个对象间接赋值,则通过“==”比拟后果为“true,这一点与 String 十分类似。当数值不在 -128~127 时,无论通过哪种形式,即便两个对象的值相等,通过“==”比拟,其后果为 false;当一个 Integer 对象间接与一个 int 根本数据类型通过“==”比拟,其后果与第一点雷同;Integer 对象的 hash 值为数值自身;

@Override
public int hashCode() {return Integer.hashCode(value);
}

7.2、为什么是 -128-127?

在 Integer 类中有一个动态外部类 IntegerCache,在 IntegerCache 类中有一个 Integer 数组,用以缓存当数值范畴为 -128~127 时的 Integer 对象。

8、泛型

泛型是 Java SE 1.5 的新个性,泛型的实质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型能够用在类、接口和办法的创立中,别离称为泛型类、泛型接口、泛型办法。Java 语言引入泛型的益处是平安简略。

泛型的益处是在编译的时候查看类型平安,并且所有的强制转换都是主动和隐式的,进步代码的重用率。

它提供了编译期的类型平安,确保你只能把正确类型的对象放入 汇合中,防止了在运行时呈现 ClassCastException。

应用 Java 的泛型时应留神以下几点:

  • 泛型的类型参数只能是类类型(包含自定义类),不能是简略类型。
  • 同一种泛型能够对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。
  • 泛型的类型参数能够有多个。
  • 泛型的参数类型能够应用 extends 语句,例如。习惯上称为“有界类型”。
  • 泛型的参数类型还能够是通配符类型。例如 Class<?> classType = Class.forName(“java.lang.String”);

8.1 T 泛型和通配符泛型

  • ?示意不确定的 java 类型。
  • T 示意 java 类型。
  • K V 别离代表 java 键值中的 Key Value。
  • E 代表 Element。

8.2 泛型擦除

Java 中的泛型基本上都是在编译器这个档次来实现的。在生成的 Java 字节码中是不蕴含泛型中的类型信息的。应用泛型的时候加上的类型参数,会在编译器在编译的时候去掉。这个过程就称为类型擦除。

泛型是通过类型擦除来实现的,编译器在编译时擦除了所有类型相干的信息,所以在运行时不存在任何类型相干的信息。例如 List 在运行时仅用一个 List 来示意。这样做的目标,是确保能和 Java 5 之前的版本开发二进制类库进行兼容。你无奈在运行时拜访到类型参数,因为编译器曾经把泛型类型转换成了原始类型。

8.3 限定通配符

一种是 <? extends T> 它通过确保类型必须是 T 的子类来设定类型的上界,另一种是 <? super T> 它通过确保类型必须是 T 的父类来设定类型的下界。另一方面表 示了非限定通配符,因为能够用任意类型来代替。例如 List<? extends Number> 能够承受 List 或 List。

8.4 泛型面试题

你能够把 List 传递给一个承受 List 参数的办法吗?

对任何一个不太熟悉泛型的人来说,这个 Java 泛型题目看起来令人纳闷,因为乍看起来 String 是一种 Object,所以 List 该当能够用在须要 List 的中央,然而事实并非如此。真这样做的话会导致编译谬误。如 果你再深一步思考,你会发现 Java 这样做是有意义的,因为 List 能够存储任何类型的对象包含 String, Integer 等等,而 List 却只能用来存储 Strings。

Array 中能够用泛型吗?

Array 事实上并不反对泛型,这也是为什么 Joshua Bloch 在 Effective Java 一书中倡议应用 List 来代替 Array,因为 List 能够提供编译期的类型平安保障,而 Array 却不能。

9、反射

9.1、概念

JAVA 反射机制是在运行状态中,对于任意一个类,都可能晓得这个类的所有属性和办法;对于任意一个对象,都可能调用它的任意一个办法;这种动静获取的信息以及动静调用对象的办法的性能称为 java 语言的反射机制。

9.2、作用

Java 反射机制次要提供了以下性能:在运行时判断任意一个对象所属的类;在运行时结构任意一个类的对象;在运行时判断任意一个类所具备的成员变量和办法;在运行时调用任意一个对象的办法;生成动静代理。

数据结构与算法

1、排序

排序有外部排序和内部排序,外部排序是数据记录在内存中进行排序,而内部排序是因排序的数据很大,一次不能包容全副的排序记录,在排序过程中须要拜访外存。

1.1、间接插入排序

思维:

将第一个数和第二个数排序,而后形成一个有序序列 将第三个数插入进去,形成一个新的有序序列。对第四个数、第五个数……直到最初一个数,反复第二步。代码:

首先设定插入次数,即循环次数,for(int i=1;i<length;i++),1 个数的那次不必插入。设定插入数和失去曾经排好序列的最初一个数的位数。insertNum 和 j =i-1。

2、设计模式

参考:Android 开发中的一些设计模式

2.1、单例设计模式

单例次要分为:懒汉式单例、饿汉式单例、注销式单例。

特点:

  1. 单例类只有一个实例
  2. 单例类必须本人创立本人的惟一实例
  3. 单例类必须给所有其余对象提供这一实例。

在计算机系统中,像线程池,缓存、日志对象、对话框、打印机等常被设计成单例。

懒汉式单例:

Singleton 通过将构造方法限定为 private 防止了类在内部被实例化,在同一个虚拟机范畴内,Singleton 的惟一实例只能通过 getInstance()办法拜访。(事实上,通过 Java 反射机制是可能实例化构造方法为 private 的类的,那基本上会使所有的 Java 单例实现生效。

它是线程不平安的,并发状况下很有可能呈现多个 Singleton 实例,要实现线程平安,有以下三种形式:1. 在 getInstance 办法上加上同步

2. 双重查看锁定

3. 动态外部类

这种形式比照前两种,既实现了线程平安,又防止了同步带来的性能影响。

饿汉式单例:

饿汉式在创立类的同时就曾经创立好了一个动态的对象供零碎应用,当前不再扭转,所以天生是系统安全。

相干视频:

面试官的家常菜——Android 事件抵触起因与解决方案大解密!_哔哩哔哩_bilibili

金三银四面试高峰期必问 MVVM 技术之 databinding_哔哩哔哩_bilibili

金三银四大厂面试 JVM 必问技术_哔哩哔哩_bilibili

本文转自 https://juejin.cn/post/6844903891625050119,如有侵权,请分割删除。

退出移动版