Android 知识点

421 阅读32分钟

1. Android中为什么主线程不会因为Looper.loop()里的死循环卡死?

Android 应用程序在主线程进入消息循环之前,也就是ActivityThread中的main函数中调用Looper.loop()之前,内部底层的linux会先创建一个管道,这个管道的作用使得Android应用程序的主线程在消息队列为空的情况下,可以进入等待空闲的状态,当消息队列中有新的消息时,再唤醒应用程序的主线线程。 在Lopper.loop()中通过一个死循环来处理分发消息,本身是不会卡死的,正在会引起卡死的是在处理消息的回调中,如 onCreate(),onStart()...中。

2. 线程与进程

进程: app启动时,是通过系统的zygote进程fork了一个进程,每个app运行在单独的一个进程中,除非在AndroidManifest.xml中配置Android:process属性,或通过native代码fork进程。进程直接的资源是不共享的。

前台进程>科技进程>服务进程>后台进程>空进程


**线程** 比如每次通过new Thread().start会创建一个新的线程,该线程与所在进程之间的资源是共享的。从linux角度来说线程与进程之间没有本质的区别。

3. binder

binder通常用于不同进程间的通信,hander通常用于同一进程的不同线程间的通信

Android 中app进程间是隔离的,数据不共享。

一个进程空间包含用户空间,和内核空间,用户空间在进程间无法共享数据,内核空间可以共享数据。

android 中的binder机制是一种跨进程的ipc通信,采用client,server模式。

Binder将client进程,service进程,serviceManager连接起来,实现进程间的通信。在内核空间中创建一个数据缓存区域,将client进程与service进程,以及内核空间建立映射关系。 开始由server端注册服务到Binder驱动,再由clienth获取服务,调用方法,传递数据到binder中,通过servicemanger建立client到server的连接关系,最后有server执行,将结果再返回到client.

进程间通信 AIDL

server:

  • 新建aidl文件,定义要通信的方法,编译工程
  • 自定义service继承service,
  • 自定义binder继承stub 实现AIDL接口方法,并实现生命周期方法
  • 注册service,暴露服务

client

  • 将server端aidl文件复制过来
  • 使用Stub.asInterface接口获取服务器的Binder;
  • 建立service连接,获取到AIDL中的接口
  • 调用方法,返回结果

4. Handler

Handler 通常是处理不同线程间的通信问题

  • Looper 能够保持线程持续存活并不断的从任务队列中获取任务并执行
  • MessageQuenue 使用intent ,message ,runnable做为任务的载体在不同的线程中进行传递
  • Handler 实现任务队列的管理

我们可以通过Handler发送一个Message对象,或者Runnable对象,到MessageQuenue,然后通过Lopper.loop(),用Handler.dispatchMessage()循环发送消息到指定的handleMessage()回调,或者Callback回调。

发送message通常可以new Message(),或者Message.obtain()来获取Message,通常使用后者,复用Message,避免创建更多的Message对象。

Handler在初始化时,变创建了自己的MessageQuenue对象,并和当前的Thread绑定,一个handler只能绑定一个Thread,且一个Thread只能有一个Lopper对象。

可以通过Looper.prepare()来创建一个Lopper.UI线程中不需要,在UI线程初始化时,会自动创建,可以通过getMainLopper()来获取主线程中的Lopper.

通常用Handler来处理消息,并更新UI操作。发送一些延时消息等。

 *  class LooperThread extends Thread {
  *      public Handler mHandler;
  *
  *      public void run() {
  *          Looper.prepare();
  *
  *          mHandler = new Handler() {
  *              public void handleMessage(Message msg) {
  *                  // process incoming messages here
  *              }
  *          };
  *
  *          Looper.loop();
  *      }
  *  }
  */

5.如何避免多线程产生的问题

使用场景

  • Android中在初始化Application,或初始化界面时,可以采用多线程来提高应用程序的并发执行性能,如需要初始化一些第三方的sdk,或数据库操作,等。
  • 采用多线程将一些复杂任务耗时操作放到子线程中去执行,执行完成后更新界面UI,提高界面的流畅性
  • 大文件下载时可以采用多线程
  • 批量处理数据,如压缩100张图片,可以创建多线程来处理,提高并行效率

使用多线程产生的问题

  • 会触发内存的消耗,使得内存消耗过多
  • 可能会产生死锁的想象
  • 可能会出现内存泄漏

怎么来使用多线程

  • AsyncTask 为UI线程和工作线程提供快速的切换操作,处理一些生命周期短的操作。
  • HandlerThread 为某些回调方法或者耗时任务的执行设置单独的线程任务,并提供线程任务的调度机制
  • ThreadPool 多个执行单元同时执行,处理一些并发操作
  • IntentService 通过UI线程出发一些后台执行的任务操作,并通过一定的机制将结果返回到UI线程。
  • Rxjava 处理线程的调度

AsyncTask为什么不用他?

在Activity内部创建一个AsyncTask,这是一个内部类,会持有外部对象activity的引用,若activity销毁时,asyncTask仍然执行,就会导致Activity无法完全释放,导致内存泄漏。所以不建议使用AsyncTask.

HandlerThread

  • 适合处理那些在子线程中执行,需要消耗时间的任务,在任务结束后,或者隔一段时间通知UI线程更新。
  • 内部自动创建了一个looper,并维持任务队列。不会阻碍UI线程
  • 每个任务会以队列的方式挨个执行,若其中的一个耗费较长时间,将会阻塞其它任务的执行。
  • 使用时要对不同的thread设置优先级调用Process.XXX数值越小优先级越高。cpu根据不同优先级对线程调度时会调优,优先处理一些前台线程。
  • 在onLooperPrepared中做一些初始化操作

ThreadPool

线程池适合将任务进行分解,在多个操作单元中执行,并发执行的场景。注意内存的使用开销。

IntentService

默认的service 是在主线程中执行的,会因为抢占主线程的资源,影响界面的绘制。IntentService 继承自普通的service,并在内部创建了一个HandlerThread 在onHandlerIntent中处理扔到service中的任务,所以IntentService不仅仅具备了异步线程的特性,还保留的service不受主页面生命周期的影响。不容易被后台杀死,介于前台线程与后台线程之间。

可以在IntentService间隔性的处理一些定时任务,如刷新数据,更新缓存等。内部维持了任务队列,所以某一个任务耗时将会影响到其它任务的执行。

6 UI卡顿分析

Android 中的UI绘制是在主线程中执行的,且大部分的任务操作都是主线中完成的,一旦我们在主线程出处理了一些耗时操作,可能就会阻碍UI线程的触摸事件,滑动事件,或者界面绘制。为了使界面的刷新帧率达到60FPS,我们要确保16ms内完成单次刷新操作,一旦主线程中的任务过于繁重就会阻碍主线程界面的绘制操作,当接收到刷新界面的信号时,因为资源被占用,就无法完成界面刷新操作,这样就会出现掉帧的现象,随之会影响帧率的下降就造成了界面卡顿。

原因?

  • 界面布局过于复杂,嵌套层数太多
  • UI线程中操作耗时任务,导致界面卡顿
  • 内存抖动,大量的对象被瞬间创建与释放会引起界面卡顿
  • 内存不足,频繁GC都会引起界面卡顿
  • 内存泄漏

如何解决?

  • 采用多线程将一些复杂任务耗时操作放到子线程中去执行,执行完成后更新界面UI,提高界面的流畅性
  • 视图布局减少嵌套层数,比如使用ConstraintLayout, 替换relativeLayout LinearLayout的嵌套,还有合理使用include ,viewStub,merge标签等。
  • 合理配置视图的背景色,重复设置父视图,子视图的背景色,会增加界面绘制的次数
  • 图片展示 合理的压缩质量 大小来展示
  • 避免内存泄漏引起的内存不足

检测工具

  • 手机上可以通过打开开发者选项中的显示界面布局,查看重绘程度
  • 手机上打开Gpu的过渡渲染来查看视图更新过程中花费的时间
  • android 打开调试工具栏的 android profile 查看memory 内存使用量,排查占用内存的操作

7.Executor线程池有哪几种,说一下区别,说说线程池特性是由什么决定的?

线程池的优点:

  1. 重用线程池中的线程对象,避免重复创建线程对象带来的开销。
  2. 有效的控制线程的并发问题,提高系统资源利用率,避免资源竞争,避免拥堵。
  3. 对多线程的管理比较简单,易用
  • Executors.newCachedThreadPool()
   public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

创建一个可缓存的线程池,但有任务执行时,若有线程可用,则复用线程池,若没有则新建一个线程,超过60s,没用的线程将会被回收。

  • Executors.newFixedThreadPool(int);
  public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

创建一个定长的线程池,可控制最大并发数,超出的线程会在等待队列等待。

  • Executors.newSingleThreadPool();
 public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

创建一个单线程化的线程池,它只会用单一的线程来处理工作队列,保证所有的工作队列,按照指定的顺序执行。

  • Executors.newScheduledThreadPool();
  public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

创建一个定长线程池,支持定时和周期性的任务。。

8.app启动优化

  • Application onCreate方法中尽量不要处理耗时操作
  • Application onCreate方法中经常会有一些第三方平台的初始化操作,会耗时,可以创建一个IntentService 在后台的子线程中来做初始化操作。
  • 首屏展示优化,可以设置定义的主题颜色,展示不同的颜色,或者不同的图片 windowBackgroud 设置color 或者 drawable,避免白屏黑屏
  • 可以创建一个不加载视图的activity做为启动屏

9. 开源框架如何选型?

  • 分析业务应用的各个场景,看看满足度如何,是否能满足需求
  • 分析性能满足度如何?稳定性如何
  • 是否易于测试
  • 集成的难以程度如何,代码侵入性要尽量的小,尽量解耦
  • 开源协议
  • api 开发文档是否齐全
  • 拓展新功能的能力如何,是否可以定制
  • 版本升级,维护更新如何

10. BlockingQueue

BlockingQueue 是一个先进先出的阻塞队列,为啥说是阻塞?是因为BlockingQueue支持获取当前队列中的元素,但队列为空的时候,会阻塞,等待队列中有元素时再返回;添加元素时,如果队列已满,那么等到队列可以放入新的元素时再放入。

  • 不接受null值的插入
  • 使用来设计实现生产者-消费者队列的。
  • 实现是线程安全的
  • 不支持close shutdown 等关闭操作
  • 是一个简单的线程安全容器

ArrayBlockingQueue 是BlockingQueue接口的有界队列实现类,底层采用数组来实现。其并发控制采用可重入锁来控制,不管是插入还是读取操作,都需要获取到锁才能进行操作。

  • 设置队列容量,在构造时传入元素个数
  • 指定独占锁是公平锁还是非公平锁,非公平锁的吞吐量高,公平锁保证每次都是等待最久的线程获取到锁。
  • 可以指定一个集合来初始化,在构造方法期间加入到队列。

LinkedBlockingQueue 底层基于单向链表实现的阻塞队列,可以当做无界队列或有界队列来使用,看构造方法。 无界队列 传 Interger.MAX_VALUE。

SynchronousQueue 同步队列,当一个线程往队列中写入一个元素时,写入操作不会立即返回,需要等待另一个线程来将这个元素拿走。同理当一个线程做读操作的时候,同样需要一个相匹配的写线程的写操作。读线程和写线程要同步,一个读线程匹配一个写线程。

  • 不提供任何空间来存储元素,数据必须从某个写线程交给某个读线程
  • 不能peek,不能迭代

PriorityBlockingQueue 带排序的BlockingQueue实现,无界队列,在开始时可以指定初始大小,插入元素时,空间不够可以自动扩容。初入队列的元素必须是可比较大小的(comparable ),否则报异常。

11. 项目中如何排查内存泄露?

容易发生内存泄露的地方:

  • 集合类

集合类 添加元素后,仍引用着 集合元素对象,导致该集合元素对象不可被回收,从而 导致内存泄漏

  • Static关键字修饰的成员变量 被 Static 关键字修饰的成员变量的生命周期 = 应用程序的生命周期

若使被 Static 关键字修饰的成员变量 引用耗费资源过多的实例(如Context),则容易出现该成员变量的生命周期 > 引用实例生命周期的情况,当引用实例需结束生命周期销毁时,会因静态变量的持有而无法被回收,从而出现内存泄露

解决方案

尽量避免 Static 成员变量引用资源耗费过多的实例(如 Context) 若需引用 Context,则尽量使用Applicaiton的Context 使用 弱引用(WeakReference) 代替 强引用 持有实例

常见的单例模式持有context的应用,单例模式 由于其静态特性,其生命周期的长度 = 应用程序的生命周期,尽量使用Applicaiton的Context

  • 非静态内部类 / 匿名类 非静态内部类 / 匿名类 默认持有 外部类的引用;而静态内部类则不会 非静态内部类默认持有外部类的引用 而导致外部类无法释放,最终 造成内存泄露

多线程的使用方法 = 非静态内部类 / 匿名类;即 线程类 属于 非静态内部类 / 匿名类

当 工作线程正在处理任务 & 外部类需销毁时, 由于 工作线程实例 持有外部类引用,将使得外部类无法被垃圾回收器(GC)回收,从而造成 内存泄露

  • 资源对象使用后未关闭

对于资源的使用(如 广播BraodcastReceiver、文件流File、数据库游标Cursor、图片资源Bitmap等),若在Activity销毁时无及时关闭 / 注销这些资源,则这些资源将不会被回收,从而造成内存泄漏

  • LeakCanary 可以直接在手机上查看内存泄漏的地方,方便,常用
  • Android profile中的memory 进行查看 手动gc抓取procf文件,用相应的工具查看分析。
  • Memory analyer tool MAT eclipse中使用

12. FragmentPagerAdapter和FragmentPagerStateAdapter的区别?

  • PagerAdapter:缓存三个,通过重写instantiateItem和destroyItem达到创建和销毁view的目的。
  • FragmentPagerAdapter:内部通过FragmentManager来持久化每一个Fragment,在destroyItem方法调用时只是detach对应的Fragment,并没有真正移除!
  • FragmentPagerStateAdapter:内部通过FragmentManager来管理每一个Fragment,在destroyItem方法 调用时移除对应的Fragment。

所以,我们分情况使用这三个Adapter
PagerAdapter:当所要展示的视图比较简单时适用
FragmentPagerAdapter:当所要展示的视图是Fragment,并且数量比较少时适用
FragmentStatePagerAdapter:当所要展示的视图是Fragment,并且数量比较多时适用

13. 接口和抽象类的区别?

接口和抽象类的相似性

  • 接口和抽象类都不能被实例化,它们都位于继承树的顶端,用于被其他类实现和继承。
  • 接口和抽象类都可以包含抽象方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法。

接口和抽象类的区别

  • 接口里只能包含抽象方法,静态方法和默认方法,不能为普通方法提供方法实现,抽象类则完全可以包含普通方法。
  • 接口里只能定义静态常量,不能定义普通成员变量,抽象类里则既可以定义普通成员变量,也可以定义静态常量。
  • 接口不能包含构造器,抽象类可以包含构造器,抽象类里的构造器并不是用于创建对象,而是让其子类调用这些构造器来完成属于抽象类的初始化操作。
  • 接口里不能包含初始化块,但抽象类里完全可以包含初始化块。
  • 一个类最多只能有一个直接父类,包括抽象类,但一个类可以直接实现多个接口,通过实现多个接口可以弥补Java单继承不足。

14. 谈谈你对面向对象三大特性的理解?

封装,继承,多态

15. KMP算法

16. 时间复杂度为n的情况下,将两个有序数组合并成一个

17. 点击了桌面图标以后做了哪些事?

18. jvm内存

  • java堆内存

保存Java对象的实例, GC活动区域, 线程间共享内存,内存不够时发生oom

  • java虚拟机栈(栈内存)

保存java运行方法中的引用变量,参数类型等。 Java栈帧的入栈出栈,线程间内存不共享,能发生oom,与stackoverflow异常

  • 方法区

保存了jvm已经加载了类的信息,常量,静态变量,等数据。内存共享,能发生oom,内存回收,对常量池的处理

  • 本地虚拟机栈

同Java虚拟机栈,这里主要指向native方法

  • Java程序计数器

当前线程字节码的行号指示器,不会发生oom ,内存线程私有,一个线程一个计数器。

19. Android如何解决屏幕适配

  • 布局 现在推荐使用ConstraintLayout,LinearLayout ,FrameLayout来布局,宽高不要写死,使用weight属性,推荐使用的ContraintLayout一般可以解决布局视适配问题。
  • 图片 一般会用不同的分辨率的图片,尽量用drawable.xml矢量图,.9图片,现在主流屏幕在1080P左右,需采用xxhdpi下的图片
  • 版本控制 兼容不同版本,可能会创建不同的theme,style,string
  • 屏幕大小资源配置 对于超大屏可以单独配置不同layout,values文件属性

20. Android View事件传递机制

  • activity-->ViewGroup-->View

  • dispatchTouchEvent()->onInterceptEvent()->onTouchEvent();

  • viewGroup中有onIntercepterEvent()操作 view中没有,没有子view,不需要拦截操作。

  • true--消费 拦截
    false--不消费 继续传递

  • 控件enable时才能出来拦截,点击事件

  • onTouch()->onTouchEvent()->performClick()->onClick()

  • ActionDown出发后不一定能接受到ActionMove,ActionUp事件,如:父控件在move事件中做了拦截操作,会出发子View的ActionCancle操作,然后再执行父View的actionMove,actionUp事件

view事件传递

21.Android消息推送

  • MQTT

  • 轮询请求pull

  • XMPP

  • 手机厂商推送(小米,华为) 应用系统覆盖广,厂商不会杀死自己的推送服务

  • 第三方平台的推送 建议,推送抵达率高,延时低(友盟,极关,云巴(基于MQTT)) 阿里云移动推送、腾讯信鸽推送、百度云推送

    若多个app都使用了友盟推送,自己app在杀死情况下,若有其它使用友盟app存活,可以收到消息。 安全性不够高,别人的服务器

  • 自己搭建

看用户群体,渠道,规模,性能,可以集成多方推送,保证消息的到达率

通知栏消息,透传消息

透传消息在整个消息传递过程中比通知栏消息多了一步-传递到App,因此透传消息就增加一些被系统限制的概率,给系统杀死的概率就高一些,所以说,通知栏消息比透传消息应该能提供更好的送达率。

通知栏消息的优点:送达率高
透传消息的优点:对消息操作程度高 & 自定义程度高

22.Android:XML 解析方式对比(DOM、SAX、PULL)

  • DOM: 加载整个文档,占用内存,耗时较长,操作文档效率高,可以修改,使用于,文档较小,需要频繁操作的场景。
  • SAX:

事件驱动型,解析速度快,灵活性高,占用内存小,无法修改文档,可拓展性差,解析方法复杂,适用于大型文档,要求解析效率高,不需要修改文档,不需要控制结束时机。

  • PULL:

同SAX,优点是可以控制访问结束的时机,使用简单些,推荐使用

23. Activity 生命周期

onCreate()->onStart()->onResume()->onPause()->onStop()->onDestory()

  • 启动Activity 系统会先调用onCreate方法,然后调用onStart方法,最后调用onResume,Activity进入运行状态。

  • 当前Activity被其他Activity覆盖一部分其上或被锁屏 系统会调用onPause方法,暂停当前Activity的执行

  • 当前Activity由被覆盖(一部分)状态回到前台或解锁屏 系统会调用onResume方法,再次进入运行状态。

  • 当前Activity转到新的Activity界面或按Home键回到主屏,自身退居后台 系统会先调用onPause方法,然后调用onStop方法,进入停滞状态

  • 用户后退回到此Activity 系统会先调用onRestart方法,然后调用onStart方法,最后调用onResume方法,再次进入运行状态。

  • 当前Activity处于被覆盖状态或者后台不可见状态,当更高优先级的apps需要内存,系统内存不足 系统就会杀死当前Activity

  • 而后用户退回当前Activity 再次调用onCreate方法、onStart方法、onResume方法,进入运行状态。

  • 用户退出当前Activity 系统先调用onPause方法,然后调用onStop方法,最后调用onDestory方法,结束当前Activity。

fragment

onAttach()->onCreate()->onCreateView()->onActivityCreated()->onStart()->onResume()->onPause()->onStop()->onDestoryView()->onDestory()->onDetach()

  • onAttach方法 Fragment和Activity建立关联的时候调用(获得activity的传递的值)

  • onCreateView方法 为Fragment创建视图(加载布局)时调用(给当前的fragment绘制UI布局,可以使用线程更新UI)

  • onActivityCreated方法 当Activity中的onCreate方法执行完后调用(表示activity执行oncreate方法完成了的时候会调用此方法)

  • onDestroyView方法 Fragment中的布局被移除时调用(表示fragment销毁相关联的UI布局)

  • onDetach方法 Fragment和Activity解除关联的时候调用(脱离activity)

  • 屏幕灭掉 onPause() onSaveInstanceState() onStop()

  • 屏幕解锁 onStart() onResume()

  • 切换到其他Fragment onPause() onStop() onDestroyView()

  • 切换回本身的Fragment onCreateView() onActivityCreated() onStart() onResume()

  • 回到桌面 onPause() onSaveInstanceState() onStop()

  • 回到应用 onStart() onResume()

  • 退出应用 onPause() onStop() onDestroyView() onDestroy() onDetach()

启动模式

  • standard 标准模式,后进先出

  • singletop 栈顶复用模式,栈顶存在activity,复用,调用onNewIntent(),否则新建,加入栈顶。

  • singleTask 栈内复用模式,不存在栈,新建栈,加入新的activity,存在栈,存在activity,复用,否则新建activity,加入

  • singleInstance 单例,创建单独的栈,存放单独的activity,多线程公用。

24. Service 生命周期

  • 手动调用startService() : onCreate()[第一次]->onStartCommond()[可能多次]->onStop()->onDestory();
  • 手动调用stopServie(): 若没有调用onStartCommond()直接结束。若已经调用onStartCommond(),看是否有bindservice(),有的话,看是否解绑,已经解绑unBindService(),ondestroy().没有绑定服务的话,直接onDestory
  • 手动调用bindservice():oncreat(是否已经调过),没有的话,oncreate()-onBind();已经调过,则看是否已经绑定服务,没有的话onBind(),否则结束。
  • 手动调用unBindService(): 若没有onbind(),直接结束。否则,看是否调用onStartCommond(),没调用过,unBind(),ondestroy(),调用过,unBind(),结束。

startService,stopService 只能开启,关闭服务,无法操作服务,startService之后,调用者退出后,service继续后台运行。
bindService,unBindService 除了能够绑定Service还能操作Service,调用者退出后,service销毁。

25. BrodcastReciver的使用

自定义广播接收者继承自BrodCastReciver,实现onRecive()在UI线程中执行,不能执行耗时操作,并注册广播到消息中心,当广播发起方发起广播时,消息中心更加注册的广播,安照一定的规则,通知接收者。

静态注册 常驻广播,耗电,耗内存,可以及时唤醒程序,为了是app体验流畅,Android O 对隐式广播做了部分限制,推荐使用动态注册。可能部分隐式广播在8.0上将无法起作用。

Activity生命周期的方法是成对出现的:

onCreate() & onDestory()

onStart() & onStop()

onResume() & onPause()

动态注册最好在Activity 的onResume()注册、onPause()注销。否则容易造成内存泄露,APP杀死回收前一定会走onPause所以保证能够注销。不能重复注册注销广播。

自定义广播 普通广播

系统广播 网络变动,电话来电,锁屏,电量低,飞行模式等

有序广播 设置的priority有序广播。

app内广播 LocalBroadcastManager 设置内部广播发送接收。

粘性广播 5.0 废弃

26. 一键退出app

  • 设置入口activity 为SingleTask ,退出时调用跳转到该activity,重写onNewIntent()杀死自己,若有多任务栈的activity 不可以退出,如singleIntance.
  • 设置入口activity 的标志位为 activity_clear_top,activity_single_top,重写onNewIntent()杀死自己,若有多任务栈的activity 不可以退出,如singleIntance.
  • 在Android5.0以上可以采用自身的activityManager,获取当前activity任务栈中的activity,逐个关闭。若有多任务栈的activity 不可以退出,如singleIntance.
  • 创建自己的activity栈,在application中注册activityLifecycleCallback监听,存储所有的activity,在退出时,一个个逐步退出。
  • 注册广播,rxbus
  • 辅助可以调用本地方法,android.os.Process.killProcess(),System.exit(0),来结束当前进程,activity。

27. RecycleView 相比ListView优点:

  • item复用性高,强制使用ViewHolder,规范ViewHolder的使用,把要展示的view设置给Viewholder,通过对Viewholder的复用,来复用View
  • 灵活,可定制,拓展性高,充分的解耦,通过布局管理器,来管理布局展示效果,方便的实现列表,网格,流式布局。
  • 控制item的间隔,可以绘制
  • 设置item增删的动画

缺点

item点击事件比较麻烦,需要自己实现

28.ContentProvider

为应用间的数据交互提供一个安全的环境,允许将自己应用的数据根据需求给其它应用进行增删改查,而不用担心开放数据库权限引发安全问题。

可以用于进程间通信,数据共享,内部通信等。

访问联系人通讯录,日历日程,音频,视频,图片文件等。

通过ContentRescover 与ContentProvider交换,操作简单,可靠。

29. 如何减少安装包的体积

lib,res,dex,

  • proguard 做代码混淆,在编译时会剔除无用的代码
  • 使用Android lint做检查,删除没有用到的String,图片,values,styles等。
  • 检查asset文件夹,看看有哪些不用的文件,删除掉
  • 尽量用drawable.xml,shape,layerlist替换一些图片,背景
  • 剔除一些ldpi,xxxhdpi下面不常用的图片
  • 在build.gradle中配置defaultConfig,提供了resConfig这个flavor来指定打包出只打包某些资源,比如字串、图片,so库等等
defaultConfig {
    // ...

    resConfigs "en", "de", "fr", "it"
    resConfigs "hdpi", "xhdpi", "xxhdpi"
    ndk {
            abiFilters 'armeabi-v7a'
        }
}
  • 对美工给的图片做一下压缩处理
  • 第三方jar包,对源码进行删减,提取我们有用的代码,打成jar来用
  • 大项目可以使用插件化

30. 熟练掌握常见数据库操作

sqlite,GreenDao,realm,AFinal,OrmLite GreenDao 体积小,速度快 推荐使用:realm,c++编写,速度快,跨平台

create TABLE people (id interger primary key autoincrement,name varchar(20)) select * from people select name from people select name from people by id desc select name from people group by select name frome people where insert into people(name,age) values(a,3) update people set name = aa where id = 0 delete from people where id = 9

31. 熟练应用常见的设计模式

原则:

  • 单一职责原则 单一功能
  • 里氏替换原则 父类出现的地方,可以用其子类替换
  • 开放关闭原则 对外修改开发,对内修改关闭
  • 依赖倒置原则 细节依赖于抽象,抽象不依赖于细节
  • 最小知识原则 单独模块尽量少的依赖其它对象功能
  • 接口隔离原则 多接口分类

* 工厂模式 * 单例模式 * builder模式 * 模板模式 * 装饰器 * 责任链模式 * 观察者模式 * 代理模式 起到中介作用,连接客户端和目标对象,播放器中使用到 * 外观模式 定义了一个高层、统一的接口,外部与通过这个统一的接口对子系统中的一群接口进行访问。 * 策略模式
### 32. 熟练掌握自定义view的操作,满足工作需求
  • 分析功能场景,简单的view,直接继承自View来实现
  • 再看是否能在现有的view上扩展功能实现
  • 能复用组合View实现复杂布局
  • 继承自ViewGroup

自定义View

  • 实现构造方法,1(代码中动态new的一般),2(xml中配置的调用),3,4个构造参数的,最少实现一个
  • 自定义属性 values 建attrs.xml
  • onMeasure()

MeasureSpec 将测量模式,和size封装打包,减少内存

子View的MeasureSpec值根据子View的布局参数(LayoutParams)和父容器的MeasureSpec值计算得来的,具体计算逻辑封装在getChildMeasureSpec()里。

测量模式(Mode)的类型有3种:

UNSPECIFIED (不常用到)

EXACTLY (match_parent,100dp) 父控件来决定其测量大小

AT_MOST (wrap_content) 子控件来实现自己的测量策略,来定大小

  • onLayout()

继承自ViewGroup需要遍历,测量每个子View的位置,调用layout(),传入位置,通过getMeasureWidth(),height(),获取宽高

  • onDraw() 绘制界面

33. 熟练掌握常见动画的实现

补间动画

  • 平移
  • 旋转
  • 透明度
  • 缩放 使用简单,不能改变view属性,视觉效果上的变化

逐帧动画

帧动画,大量引用图片,容易引起oom

属性动画

可以改变view的属性,如颜色,背景,位置,实现复杂的动画效果, 可以自定义属性差值器,时间差值器,已经提供了几种差值器,

34. 常见的排序

冒泡,快速,插入,选择,希尔排序。

    //冒泡
    private static void sortm(int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            for (int j = i + 1; j < arr.length; j++) {
                if (arr[i] > arr[j]) {
                    int temp = arr[i];
                    arr[i] = arr[j];
                    arr[j] = temp;
                }
            }
        }
    }
    //快速排序
    public static void sortk(int[] arr, int left, int right) {

        if (left > right) return;
        int i, j, temp;

        temp = arr[left];
        i = left;
        j = right;

        while (i < j) {
            while (arr[j] >= temp && i < j)
                j--;
            while (arr[i] <= temp && i < j)
                i++;
            if (i < j) {
                int t = arr[i];
                arr[i] = arr[j];
                arr[j] = t;
            }
        }
        arr[left] = arr[i];
        arr[i] = temp;
        sortk(arr, left, i - 1);
        sortk(arr, i + 1, right);
    }

    //选择排序
    public static void sort(int[] arr) {
        int k = 0;
        for (int i = 0; i < arr.length; i++) {
            k = i;
            for (int j = i; j < arr.length; j++) {
                if (arr[j] < arr[k]) {
                    k = j;
                }
            }
            int temp = arr[k];
            arr[k] = arr[i];
            arr[i] = temp;
        }
    }
   //插入排序
    private static void sortc(int[] arr){
        for (int i = 0; i < arr.length; i++) {
            for (int j=i;j>0&&arr[j]<arr[j-1];j--){
                int temp = arr[j];
                arr[j]= arr[j-1];
                arr[j-1]=temp;
            }
        }
    }
    //希尔排序 最坏情况时间复杂度为:O(n^1.5),平均时间复杂度为O(nlogn)。
    private static void sortX(int[] arr){
        int len = arr.length;
        int  temp = 0;
        while (len>=1){
            for (int i = len; i < arr.length; i++) {
                for (int j=i;j>=len&&arr[j]<arr[j-len];j-=len){
                     temp = arr[j];
                     arr[j]=arr[j-len];
                     arr[j-len] = temp;
                }
            }
            len/=arr.length-1;
        }
    }

35. 数据结构

36. 组件化开发,插件化开发,热修复

组件化

1:建module

2: config.gradle 配置 是否独立运行

3:统一配置 第三方库的版本

4:资源文件命名规范

5: library -application 切换

6: mainfest, lanchactivity切换

7: common-library,base-application,main-application

8: 组件间的通信 ARouter,Dagger

9: DataBinding

10: okhttp,retrofit,rxjava2,rxbus,fastjson,glide,lifeCycle,LeakCanary 内存泄露

11: mvp

插件化

1: 定义标准化接口 2: 插件app实现标准化接口 3: 宿主appactivity 代理activity,service,实现其生命周期相关方法,传递上下文Context,重写getClassLoader,getResource(); 4: 通过dexClassLoader 加载插件apk 5: 通过classloader,与传递过来的完整类名,获取构造方法,创建类的实例。调用相关方法。

要考虑兼容性 宿主activity与插件activity的通信 插件activiy,fragment相互之间的通信

热修复

Sophix 阿里云,tingker 微信,AndFix支付宝。

37. 常见第三方库的使用,EventBus,RxJava2,Retrofit,okhttp,glide,Dagger2,fastjson,DataBinding,

38. mvc,mvp,mvvp,mvvm

39. 强,软,弱,虚应用

强引用: 为null,或不在引用时,回收 软引用,内存空间足够,垃圾回收器就不会回收它; 弱引用,当JVM进行垃圾回收时,无论内存是否充足,回收。 虚引用,并不影响对象的生命周期,任何时候都会被回收

40. DVM 与 JVM的区别

  • JVM基于栈,jvm字节码中,变量压入栈进行计算
  • DVM基于寄存器,dalvik字节码复制给寄存器,直接访问寄存器,基于寄存器的编译要比栈更快
  • DVM将编译的class打包为一个dex,外加一些资源文件,最终打包成apk.
  • jvm 将编译的多个class打包为一个jar包,使用
  • DVM运行多个实例,每个实例做为单独的linux进程运行

41.class文件和dex文件有什么区别

class文件,通过将jvm语言(Java,kotlin,python等)编译得到的二进制文件,存储类的所有信息。 dex文件是有Android dvm将编译生成的多个class的文件打包生成的文件。 dex文件去除了class文件中的一些多余的信息,更加精简,运行效率高

42.http和https的区别,https是怎么认证的

http是基于tcp的一种网络传输协议,明文传输,不安全。Https是在http的基础上加了一层ssl协议,对数据进行加密处理,对需要加密传输,保证安全的数据,可以采用https.

https需要申请证书,http不需要 http默认端口80 https 443 http链接简单,不安全 https安全

1: 客户端通过https的url访问服务器,要求建立ssl连接。 2: 服务器将网站的证书和公钥返回给客户端 3: 客户端随机生成一个秘钥,用公钥对称加密发送给服务器 4: 服务器用自己的私钥解密,获取秘钥 5:服务器利用该秘钥对数据加密和客户端通讯

43.理解StackOverflowError与OutOfMemoryError

jvm线程操作方法是基于栈的实现,每个方法的执行时一个栈帧的入栈与出栈的过程。当方法过多,递归太深的话,栈内空间不足,无法继续入栈,就会发生栈溢出。

  private static void method() {  
        method();  
    }  

jvm不能分配给创建对象所需要的空间,并且gc回收的空间也不足以满足时,会发生OOM.

  List<Object> list = new ArrayList<Object>();  
        while(true){  
            int[] index = new int[20_0000_0000];  
            list.add(index);  
        }  

44.httpurlconnection,httpclient, 和okhttp的区别

httpclient : Apache开发的第三方网络框架,api多,稳定,用起来方便。封装的众多api改进起来比较困难,Android 6.0 已改为使用okhttp.

httpUrlConection: sun公司提供,api简单,小巧但它主要的 API使我们能轻易的使用和拓展它,2.2之前会有bug。

okttp square 家族的,square出品必属精品。 okhttp是专注于提升网络连接效率的http客户端。 1、它能实现同一ip和端口的请求重用一个socket,这种方式能大大降低网络连接的时间,和每次请求都建立socket,再断开socket的方式相比,降低了服务器的压力。 2、okhttp 对http和https都有良好的支持。 3、okhttp 不用担心android版本变换的困扰。 4、成熟的网络请求解决方案,比HttpURLConnection更好用。 5、缺点,okhttp请求网络切换回来是在线程里面的,不是在主线程,不能直接刷新UI,需要我们手动处理。

45.Java中有哪些线程安全的集合?

Vector,HashTable,concurrentMap

46. volatile synchronized

47. picasso glide

  • 加载图片的编码格式不一样 Picasso argb_8888,glide rgb_565,加载同样的一张图片,glide需要的内存空间要小很多。
  • 缓存策略不一样,Picasso 会缓存整个原图,glide会缓存最后加载的图(可以调整缓存策略)
  • glide可以加载gif,和解析视频第一帧图片加载,Picasso不可以
  • glide加载图片速度快于Picasso,占用内存较小,有效防止oom
  • glide 定义动画,对图片处理,展示更加灵活
  • glide 可以绑定Activity,fragment的生命周期,及时停止与恢复加载
  • Picasso 的方法数要远小于 glide ,可以减少655635的限制