总结的Android面试题

  1. Android Studio JNI流程

    1. 首先在java代码声明本地方法 用到native关键字 本地方法不用去实现,最好声明在一个新类中(rebuild project)
    2. 项目根目录下创建jni文件夹(java文件夹同级)
    3. 在jni文件夹下创建.c文件
      • 本地函数命名规则: Java包名_类名_本地方法名
      • 也可以cd到当前项目的 build/intermediates/classes/debug/ 目录下执行javah native方法下的包名类名
      • 得到头文件拷贝到.c文件下
    4. System.loadLibrary("hello"),把so库加载进来(可以调用native本地方法了)
    5. local.properties 设置ndk.dir目录
    6. gradle.properties 添加一行 android.useDeprecatedNdk=true
    7. 在当前Module下的build.gradle 文件下的defaultConfig节添加
      ndk {
          moduleName "hello"
          abiFilter "x86"
          abiFilter "armeabi"
      }
      
  2. C调用JAVA

    1. 找到字节码对象
      • jclass (FindClass)(JNIEnv, const char*);
      • 第二个参数 要回调的java方法所在的类的路径 "com/itheima/callbackjava/JNI"
    2. 通过字节码对象找到方法对象
      • jmethodID (GetMethodID)(JNIEnv, jclass, const char, const char);
      • 第二个参数 字节码对象 第三个参数 要反射调用的java方法名 第四个参数 要反射调用的java方法签名
      • javap -s 要获取方法签名的类的全类名 项目/bin/classes 运行javap
    3. 通过字节码创建 java对象(可选) 如果本地方法和要回调的java方法在同一个类里可以直接用 jni传过来的java对象 调用创建的Method
      • jobject obj =(*env)->AllocObject(env,claz);
      • 当回调的方法跟本地方法不在一个类里 需要通过刚创建的字节码对象手动创建一个java对象
      • 再通过这个对象来回调java的方法
      • 需要注意的是 如果创建的是一个activity对象 回调的方法还包含上下文 这个方法行不通!!!回报空指针异常
    4. 反射调用java方法
      • void (CallVoidMethod)(JNIEnv, jobject, jmethodID, ...);
      • 第二个参数 调用java方法的对象 第三个参数 要调用的jmethodID对象 可选的参数 调用方法时接收的参数
  3. NDK的理解

    • NDK提供了一系列的工具,帮助开发者快速开发 C (或 C++ )的动态库,并能自动将 so 和 java 应用一起打包成 apk 。这些工具对开发者的帮助是巨大的
    • NDK 提供了一份稳定、功能有限的 API 头文件声明
    • Google明确声明该 API 是稳定的,在后续所有版本中都稳定支持当前发布的 API 。从该版本的 NDK 中看出,这些 API 支持的功能非常有限,包含有: C 标准库( libc )、标准数学库( libm )、压缩库( libz )、 Log 库( liblog )
  4. 四大组件相关

    • 四大组件之间通过Intent来传数据
    • Service启动Activity需要加上 intentv.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
    • Service如何弹出dialog
      1. show() 调用之前添加以下代码: dialog.getWindow().setType((WindowManager.LayoutParams.TYPE_SYSTEM_ALERT));
      2. 清单文件添加权限:android.permission.SYSTEM_ALERT_WINDOW
  5. Handler机制

    • 作用:将一个任务切换到某个指定的线程中去执行和分发消息,主要涉及类:Handler、Message、MessageQueue、Looper
    • 创建:Handler构造函数通过Looper.myLooper()会获取当前线程中的Looper对象(Looper通过prepare/prepareMainLooper创建的,通过ThreadLocal存储,每个线程只有一个),通过Looper的构造函数创建MessageQueue(内部是链表结构),从而三者关联起来
    • 发送:Handler通过sendMessage/post将Message(Message.obtain()复用)发送到Looer对应的MessageQueue,MessageQueue通过enqueueMessage()把当前消息加入到链表对应位置
    • 处理:Looper通过loop()不断从MessageQueue中取出Message queue.next()(没有消息则阻塞,异步阻塞IO),然后调用与Message绑定的Handler对象的dispatchMessage()交给发送该消息的Handler进行处理,如果是post的message会调用handleCallback(),直接处理事件,否则会调用我们复写handleMessage()处理,处理完成后调用Message.recycle()将其放入Message对象池中,最终三者形成一个循环
    • 高级:NativeMessageQueue、 Native Looper
      • 获取消息首先调用nativePollOnce,从底层epoll(异步阻塞IO),发送消息加入消息队列首先会调用nativeWake,向管道写入字符,唤醒IO,从而MessageQueue.next()可以返回一个Message
  6. 分析AsyncTask

    • 主要方法有:onPreExecute()、doInBackground()、 publishProgress()、onProgressUpdate()、onPostExecute()
    • 创建:AsyncTask(泛型参数:Params、Progress、Result)时会创建一个WorkerRunnable(实现Callable接口)对象mWorker,一个FutureTask对象mFuture,mWorker会保存到mFuture属性中
    • 运行:调用executeOnExecutor(),设置状态 -> 回调onPreExecute() -> 设置mWorker参数(doInBackground参数) -> sDefaultExecutor.execute(mFuture),有两个线程池(线性入队线程池、工作线程池)从线性线程池(offer()/poll()轮训任务)执行FutureTask的run()封装成Runnable并交到工作线程池中,此时是运行在子线程中)
    • FutureTask#run()result = c.call()-> postResult(doInBackground(mParams));,这个过程中doInBackground将会被调用,其中mFuture中result变量保存了doInBackground返回值,通过postResult()传递给InternalHandler()(此时是主线程了...),handler中会调用mTask.finish(结果),最后正常结束会调用onPostExecute(result)FutureTask#finishCompletion()->done(),最后传到FutureTask复写的Done()->postResultIfNotInvoked(get())通过get()可以获取子线程传递过来的值
    • 如果复写的doInBackground方法中方调用了publishProgress方法,则通过InternalHandler实例sHandler发送一条Message,更新进度,sHandler处理消息onProgressUpdate方法将被调用
  7. ThreadLocal

    • ThreadLocal是一个线程内部的数据存储类(内部数据结构是map),通过它可以在指定的线程中存储数据,数据存储后,只有在指定的线程中获取存储的数据,其它的线程无法获取,实现了隔离多个线程的数据共享,主要用在Looper、ActivityThread以及AMS中
    • ThreadLocalMap类似HashMap,key为ThreadLocal,value为实际放入的值,采用线性探测法来解决散列冲突
  8. HandlerThread

    • HandlerThread继承了Thread,是可以使用Handler的Thread,内部run方法中通过Looper.prepare()创建消息队列,并通过Looper.loop()开启消息循环,具体使用场景有IntentService,它可以通过quit/quitSafely方法终止线程的执行(区别是quitSafely没有立即终止把当前时间之前的任务执行完才退出)
  9. IntentService

    • 当Service中执行耗时操作时,20s会产生ANR,而IntentService是一个特殊的Service,则可用于执行后台耗时的任务,无需处理多线程问题,IntentService封装了HandlerThread和Handler,通过复写onHandleIntent方法操作耗时操作,当任务执行完后会自动停止,无需调用stopSelf()方法停止Service,避免了Service的内存泄漏,Intentservice若未执行完成上一次的任务,将不会新开一个线程,是等待之前的任务完成后,再执行新的任务,等任务完成后再次调用stopSelf,适合执行一些高优先级的后台任务,因为不容易被杀死
  10. 保证后台进程不被kill

    • 提供进程优先级,降低进程被杀死的概率
      • 方法一:监控手机锁屏解锁事件,在屏幕锁屏时启动1个像素的 Activity,在用户解锁时将 Activity 销毁掉。
      • 方法二:启动前台service(setForeground)
      • 方法三:提升service优先级:
        在AndroidManifest.xml文件中对于intent-filter可以通过android:priority = "1000"这个属性设置最高优先级,1000是最高值,如果数字越小则优先级越低,(适用于广播)
    • 在进程被杀死后,进行拉活
      • 方法一:注册高频率广播接收器,唤起进程。如网络变化,解锁屏幕,开机等
      • 方法二:Native双进程相互唤起(fork+JNI)
      • 方法三:依靠系统唤起(账号同步机制)
      • 方法四:onDestroy方法里重启service:service +broadcast 方式,就是当service走ondestory的时候,发送一个自定义的广播,当收到广播的时候,重新启动service;
      • 方法五:手机QQ、微信、支付宝、UC浏览器等,以及友盟、信鸽、个推等 SDK,找出它们外发的广播,在应用中进行监听,这样当这些应用发出广播时,就会将我们的应用拉活
  11. 怎么保证 Service 不被杀死

    • 双进程守护(JNI,Fork)只有5.0以下有效
    • 在Service的onStartCommand方法里返回 STATR_STICK(kill 后会被重启(等待5秒左右),重传Intent,保持与重启前一样)(详情
    • 提升service 优先级 //android:priority="1000",1000是最高值,同时适用于广播
    • 提升service进程优先级(startForeground 将service放到前台进程)
    • 在 onDestory 中通过广播再次启动 Service(Service+Broadcast,发送一个自定义的广播,当收到广播的时候,重新启动service)
    • Application加上Persistent属性(保证应用的持久性,这种应用会顽固地运行于系统之中,从系统一启动,一直到系统关机)
    • root之后放到system/app变成系统级应用
  12. 广播的两种注册方法,有什么区别

    • 动态注册:动态注册的优先级高于静态注册,Activity关闭不能接受广播,无需在清单文件注册
    • 静态注册:Activity关闭还能接受广播,只要设备处于开启状态就能接收
  13. 服务的类型

    • 按照使用范围分类:本地服务、远程服务
    • 按照运行类别分类:前台服务、后台服务
  14. Android启动Service的两种方式是什么

    • startService:生命周期与调用者不同,启动后若调用者未调用stopService而直接退出,Service仍会运行
    • bindService:生命周期与调用者绑定(Service的生命周期依附于Context),调用者一旦全部退出,Service就会调用unBind->onDestroy
  15. Service两种启动方式的区别

    • startService只是启动Service,启动它的组件(如Activity)和Service并没有关联,只有当Service调用stopSelf或者其他组件调用stopService服务才会终止
    • bindService方法启动Service,其他组件可以通过回调获取Service的代理对象和Service交互,而这两方也进行了绑定,当启动方销毁时,Service也会自动进行unBind操作,当发现所有绑定都进行了unBind时才会销毁Service
  16. Android组件之间数据传递方法

    • 基于消息的通信机制 Intent--bundle,extra
    • 利用static静态数据,public static成员变量
    • 基于外部存储的传输 ,File/Preference/Sqlite
    • 基于ipc的通信机制
    • context与service之间的传输,如Activity与Service之间的通信
    • 基于Application Context
  17. Android怎么加速启动Activity(优化的细节)

    • 减少onCreate的时间,耗时操作
    • 移除背景
    • 在很多的应用一开始点击的时候总会出现黑屏或者白屏,甚至前段时间微信也有同样的问 题。其实白屏或者黑屏还是一些其他的东西,都是因为 Android 主题的问题,只要自己自 定义一个启动主题,问题完美解决
  18. View绘制流程

    • View的绘制是由ViewRootImpl来负责的,每个应用程序窗口的decorView都有一个与之关联的ViewRootImpl对象(ViewRootImpl是DecorView添加到Window中创建的)
    • View绘制的起点:requestLayout()->scheduleTraversals()->performTraversals()
    • performTraversals整个绘制流程可以分为以下三个阶段
      • performMeasure()->measure()->onMeasure: 判断是否需要重新计算View的大小,需要的话则计算
        • MeasureSpec:由SpecMode和SpecSize两部分组成EXACTLY(match_parent、数值)
          AT_MOST(wrap_content)
          UNSPECIFIED(不限制,ScrollView)
        • 具体自定义view复写onMeasure(),处理AT_MOST,setMeasuredDimension()设置测量的结果
      • performLayout()->layout()->onLayout(): 据上一阶段得到的View的测量宽高来确定View的最终显示位置,判断是否需要重新计算View的位置,ViewGroup类的onLayout()是abstract
      • performDraw()->draw()->onDraw()判断是否需要重新绘制View
        • drawBackground()->onDraw()-> dispatchDraw()-> onDrawForeground()
    • 重绘
      • requestLayout():调用performTraversals()但不执行draw() mPrivateFlags
      • invalidate():调用performTraversals()但不执行measure()、layout()
  19. 事件分发机制

  20. Android 长连接,怎么处理心跳机制

    • 维护任何一个长连接都需要心跳机制,客户端发送一个心跳给服务器,服务器给客户端一个心跳应答,这样就形成客户端服务器的一次完整的握手,这个握手是让双方都知道他们之间的连接是没有断开,客户端是在线的
    • 如果超过一个时间的阈值,客户端没有收到服务器的应答,或者服务器没有收到客户端的心跳,那么对客户端来说则断开与服务器的连接重新建立一个连接,对服务器来说只要断开这个连接即可
    • 推送的实现方式
      • 客户端不断的查询服务器,检索新内容,也就是所谓的pull 或者轮询方式
      • 客户端和服务器之间维持一个TCP/IP长连接,服务器向客户端push
      • 服务器有新内容时,发送一条类似短信的信令给客户端,客户端收到后从服务器中下载新内容,也就是SMS的推送方式
    • Android推送技术
    • 微信Android客户端后台保活经验分享
  21. Android 系统启动流程

    1. 通过打开adb shell 然后执行ps命令,我们可以看到首先执行的是0号进程init方法!然后我们找到init.c这个文件,
    2. 然后走init里面的main方法,在这main方法里面执行mkdir进行创建很多的文件夹,和挂载一些目录
    3. 然后回去初始化init.rc这个配置文件!在这个配置文件里面回去启动zygote这个服务,这个服务会去启动app_process这个文件夹,这个文件夹里面有个app_main.cpp这个文件!
    4. 然后在app_main.cpp这个c文件里面在main方法里面它会去启动安卓的虚拟机,然后安卓虚拟机会去启动os.zygoteinit这个服务!
    5. zygoteinit这是个java代码写的,然后我们找到了main方法,在这个方法里面我们看到他首先设置虚拟机的最小堆内存为5兆,然后走到preloadclasses()这个方法来加载安卓系统所有的2000多个类通过类加载器加载进来,比如activity,contentx,http,...(其实没有必要一下子全部加载下来,我们可以等用到的时候在加载也可以!)
    6. 然后又走preloadresources()这个方法来预加载安卓中定义好的资源比如颜色,图片,系统的id等等。。。都加载了!(其实这也是没必要的! )
    7. 然后又走startSystemServer(),这个方法来加载系统的服务!他会先使用natvieJNI去调用C去初始化界面和声音的服务,这就是我们为什么先听到声音和界面的原因!
    8. 最后等服务加载完成后也就启动起来了! 简之: linux启动->init进程启动(加载init.rc配置)->zygote启动->systemServer启动,systemServer会通过init1和init2启动navite世界和java世界
    版本2:
    1. BootLoder引导,然后加载Linux内核.
    2. 0号进程init启动.加载init.rc配置文件,启动了守护进程、MediaServer、zygote进程
    3. zygote开始fork出SystemServer进程
    4. SystemServer加载各种JNI库,然后init1出Native服务,init2方法,init2方法中开启了新线程ServerThread.
      在SystemServer中会创建一个socket客户端,创建出FrameWork层的服务,后续AMS(ActivityManagerService)会通过此客户端和zygote通信
    5. ServerThread的run方法中开启了AMS,还孵化新进程ServiceManager,加载注册了一溜的服务,最后一句话进入loop 死循环
    6. run方法的SystemReady调resumeTopActivityLocked打开锁屏界面
  22. Zygote 的启动过程

  23. Activity启动过程

    1. Activity调用ActivityManagerService启动应用
    2. ActivityManagerService调用Zygote孵化应用进程
    3. Zygote孵化应用进程
    4. 新进程启动ActivityThread
    5. 应用进程绑定到ActivityManagerService
    6. ActivityThread的Handler处理启动Activity的消息
    7. 概要:用户在Launcher程序里点击应用图标时,会通知ActivityManagerService启动应用的入口Activity,ActivityManagerService发现这个应用还未启动,则会通知Zygote进程孵化出应用进程,然后在这个dalvik应用进程里执行ActivityThread的main方法。应用进程接下来通知ActivityManagerService应用进程已启动,ActivityManagerService保存应用进程的一个代理对象,这样ActivityManagerService可以通过这个代理对象控制应用进程,然后ActivityManagerService通知应用进程创建入口Activity的实例,并执行它的生命周期方法
    8. 总体类图

  24. ListView的工作原理

    • ListView 针对每个item,要求 adapter “返回一个视图” (getView),也就是说ListView在开始绘制的时候,系统首先调用getCount()函数,根据他的返回值得到ListView的长度,然后根据这个长度,调用getView()一行一行的绘制ListView的每一项
    • 如果你的getCount()返回值是0的话,列表一行都不会显示,如果返回1,就只显示一行。返回几则显示几行。如果我们有几千几万甚至更多的item要显示怎么办?
    • 实际上Android早已经缓存了这些视图,Android中有个叫做Recycler的构件,下面加载的一条Item会复用最上一条滑动出去的Item,
  25. 优化ListView

    • 复用convertView,减少不必要的view的创建与销毁
    • 使用静态viewHolder,减少findViewById()操作(FB搜索是树形View的,有点耗时)
    • item条目层级尽量简单,避免布局太深或者不必要的重绘
    • 尽量能保证 Adapter 的 hasStableIds() 返回 true这样在 notifyDataSetChanged() 的时候,如果item内容并没有变化,ListView 将不会重新绘制这个 View,达到优化的目的
    • 避免在 getView 方法中做耗时的操作
    • 图片加载使用多级缓存或者使用成熟框架
    • 快速滑动时图片不加载
    • 分页加载
    • 将ListView的scrollingCache和animateCache这两个属性设置为false(默认是true)
    • 避免在Adapter中使用static来定义全局静态变量
    • 处理listview图片错位
  26. 优化RecyclerView

    • 通过布局管理器LayoutManager显示不同的显示方式
    • 默认Item是没有间隔的,想要控制Item间的间隔(可绘制),请通过ItemDecoration
    • 想要控制Item增删的动画,请通过ItemAnimator
    • 想要控制点击、长按事件,请自己写(在adapter里面绑定控件加)
  27. 优化自定义view

    • onDraw,需要特别注意不应该在这里做内存分配的事情,因为它会导致GC,从而导致卡顿。不要在动画正在执行的时候做内存分配的事情(耗时操作)
    • 尽可能的减少onDraw被调用的次数,大多数时候导致onDraw都是因为调用了invalidate()
    • 任何时候执行requestLayout(),会使得Android UI系统去遍历整个View的层级来计算出每一个view的大小。如果找到有冲突的值,它会需要重新计算好几次。另外需要尽量保持View的层级是扁平化的,这样对提高效率很有帮助
    • 复杂的UI,考虑自定义的ViewGroup来执行他的layout操作。与内置的view不同,自定义的view可以使得程序仅仅测量这一部分,这避免了遍历整个view的层级结构来计算大小
    • 如果是继承view或者viewGroup,让view支持wrap_content
    • View中如果有动画或者线程,要在onDetachedFromWindow中及时停止。当view的Activity退出或者当前view被remove时,调用它
    • View带有滑动嵌套时,处理好滑动冲突
  28. Android处理UI耗时操作的方法

    • Handler:主线程创建handler对象,当执行耗时操作时,新建一个线程通过sendMessage/post等方法,更新ui界面(优点是结构清晰,功能明确,但是代码过多)
    • AsyncTask:内部封装了线程和Handler,当需要操作ui界面时,会和工作线程通过handler传递消息(简单,快捷,但是占用资源有点多)
    • 子线程执行耗时操作,调用Activity的runOnUiThread()方法更新ui,这种方法需要把contex对象强制转换成activity后使用(简单好用,只是需要转递contex对象)
    • Loader/AsyncTaskLoader/CursorLoader
    • IntentService
  29. ANR

    • Application Not Responding,"应用无响应"对话框
    • 产生原因:Activity 5s、Service 10s、Broadcast 20s
    • 分析log,/data/anr/traces.txt,主要看main Thread CPU usage和是否block死锁
    • 避免ANR方法看上一节
  30. Android动画框架实现原理

    • Animation框架定义了透明度,旋转,缩放和位移几种常见的动画,而且控制的是整个View,实现原理是每次绘制视图时View所在的ViewGroup中的drawChild函数获取该View的Animation的Transformation值,然后调用canvas.concat(transformToApply.getMatrix()),通过矩阵运算完成动画帧,如果动画没有完成,继续调用invalidate()函数,启动下次绘制来驱动动画,动画过程中的帧之间间隙时间是绘制函数所消耗的时间,可能会导致动画消耗比较多的CPU资源,最重要的是,动画改变的只是显示,并不能相应事件
  31. 浅析LruCache原理

    • 用LruCache来取代原来强引用和软引用实现内存缓存
    • LruCache使用一个LinkedHashMap简单的实现内存的缓存,没有软引用,都是强引用。如果添加的数据大于设置的最大值,就删除最先缓存的数据来调整内存。
    • maxSize是通过构造方法初始化的值,他表示这个缓存能缓存的最大值是多少。size在添加和移除缓存都被更新值,他通过safeSizeOf这个方法更新值。safeSizeOf默认返回1,但一般我们会根据maxSize重写这个方法,比如认为maxSize代表是KB的话,那么就以KB为单位返回该项所占的内存大小。
    • 除异常外首先会判断size是否超过maxSize,,如果超过了就取出最先插入的缓存,如果不为空就删掉(一般来说只要map不为空都不会返回null,因为他是个双休链表),并把size减去该项所占的大小。这个操作将一直循环下去,直到size比maxSize小或者缓存为空。
      public void trimToSize(int maxSize) {
      while (true) {
          K key;
          V value;
          synchronized (this) {
              if (size < 0 || (map.isEmpty() && size != 0)) {
                  throw new IllegalStateException(getClass().getName() + ".sizeOf() is reporting inconsistent results!");
              }
              if (size <= maxSize) {
                  break;
              }
              Map.Entry<K, V> toEvict = map.eldest();
              if (toEvict == null) {
                  break;
              }
              key = toEvict.getKey();
              value = toEvict.getValue();
              map.remove(key);
              size -= safeSizeOf(key, value);
              evictionCount++;
          }
          entryRemoved(true, key, value, null);
        }
      }
      
  32. Service和Activity的交互方式

    • Server端将目前的下载进度,通过广播的方式发送出来,Client端注册此广播的监听器,当获取到该广播后,将广播中当前的下载进度解析出来并更新到界面上。
    • 共享文件指的是Activity和Service使用同一个文件来达到传递数据的目的。我们使用SharedPreferences来实现共享,当然也可以使用其它IO方法实现,通过这种方式实现交互时需要注意,对于文件的读写的时候,同一时间只能一方读一方写,不能两方同时写。
    • 在Server端与Client端之间通过一个Messenger对象来传递消息,该对象类似于信息中转站,所有信息通过该对象携带。
    • 自定义一个接口,该接口中有一个获取当前下载进度的空方法。Server端用一个类继承自Binder并实现该接口,覆写了其中获取当前下载进度的方法。Client端通过ServiceConnection获取到该类的对象,从而能够使用该获取当前下载进度的方法,最终实现实时交互。
    • AIDL属于Android的IPC机制,常用于跨进程通信,主要实现原理基于底层Binder机制。
  33. UI线程和非UI线程的交互方式

    • handler
    • Activity.runOnUIThread(Runnable)
    • View.Post(Runnable)
    • View.PostDelayed(Runnabe,long)
    • AsyncTask
  34. 如何避免ARN

    • UI线程尽量只做跟UI相关的工作
    • 耗时的工作(比如数据库操作,I/O,连接网络或者别的有可能阻碍UI线程的操作)把它放入单独的线程处理
    • 尽量用Handler来处理UIthread和别的thread之间的交互
  35. 获取LinearLayout的宽度和高度

    • 由于Android程序的运行机制决定了无法再组件类外部使用getWidth和getHeight方法获得高度和宽度(在自定义组件类中可以实现),必须使用View.getMeasuredWidth和View.getMeasureHeight方法获得当前组件的宽度和高度,在调用这两个方法之前,必须调用View.measure方法先测量组件宽度和高度。
    • 如果想直接获取在布局文件中定义的组件的宽度和高度,可以直接使用 View.getLayoutParams().width和View.getLayoutParams().height
      View view = getLayoutInflater().inflate(R.layout.activity_main, null);  
      LinearLayout linearlayout = (LinearLayout)view.findViewById(R.id.linearlayout);  
      //measure方法的参数值都设为0即可  
      linearlayout.measure(0,0);  
      //获取组件宽度  
      int width = linearlayout.getMeasuredWidth();  
      //获取组件高度  
      int height = linearlayout.getMeasuredHeight(); 
      
    • ViewTreeObserver
  36. Android 内存管理机制、异常、垃圾回收

    • 当 Android 应用程序退出时,并不清理其所占用的内存,Linux 内核进程也相应的继续存在,所谓“退出但不关闭”。从而使得用户调用程序时能够在第一时间得到响应。
    • Android 基于进程中运行的组件及其状态规定了默认的五个回收优先级
      • IMPORTANCE_FOREGROUND:
      • IMPORTANCE_VISIBLE:
      • IMPORTANCE_SERVICE:
      • IMPORTANCE_BACKGROUND:
      • IMPORTANCE_EMPTY:
  37. 内存泄漏场景

    • 单例造成的内存泄漏(Context持久引用)
    • 非静态内部类创建静态实例造成的内存泄漏(Context持久引用)
    • Handler造成的内存泄漏(Context持久引用、Message对象引用)
    • 线程、TimeTask造成的内存泄漏(Activity的隐式引用、合适的时候cancel)
    • 资源未关闭、监听器未反注册造成的内存泄漏(BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap)
    • 使用ListView时造成的内存泄漏(getView未复用convertView)
    • 集合造成内存泄漏(static引用造成大量垃圾回收不了)
    • WebView造成的泄露(长期占用的内存不能被回收)
  38. 避免内存泄露

    • 概念:指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光
    • 不要为Context长期保存引用(要引用Context就要使得引用对象和它本身的生命周期保持一致)
    • 如果要使用到Context,尽量使用ApplicationContext去代替Context,因为ApplicationContext的生命周期较长,引用情况下不会造成内存泄露问题
    • 在你不控制对象的生命周期的情况下避免在你的Activity中使用static变量。尽量使用WeakReference去代替一个static
    • 垃圾回收器并不保证能准确回收内存,这样在使用自己需要的内容时,主要生命周期和及时释放掉不需要的对象。尽量在Activity的生命周期结束时,在onDestroy中把我们做引用的其他对象做释放,比如:cursor.close()
  39. 横竖屏切换时候Activity的生命周期

    • 不设置Activity的android:configChanges时,切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行两次
    • 设置Activity的android:configChanges= orientation 时,切屏还是会重新调用各个生命周期,切横、竖屏时只会执行一次
    • 设置Activity的android:configChanges= orientation|keyboardHidden 时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法
  40. requestLayout在什么时候用呢

    • 当view确定自身已经不再适合现有的区域时,该view本身调用这个方法要求parent view(父类的视图)重新调用他的onMeasure onLayout来重新设置自己位置。特别是当view的layoutparameter发生改变,并且它的值还没能应用到view上时,这时候适合调用这个方法
    • 区别于invalidate与postInvalidate:重新mesure、layout、draw
  41. FragmentPagerAdapter与FragmentStatePagerAdapter使用详解与区别

    • FragmentPagerAdapter不会销毁Fragment, 适用于那些相对静态的页,数量也比较少的那种
    • 如果需要处理有很多页,并且数据动态性较大、占用内存较多的情况,应该使用FragmentStatePagerAdapter,会销毁不需要的Fragment,当拥有大量的页面时,不必在内存中占用大量的内存,更省内存
    • FragmentPagerAdapter详解
  42. 提升SQLite数据插入效率低、速度慢的方法

    • 慢速——最粗暴的方法(SQLite的API中直接执行SQL)
    • 中速——显式开启事务(就是指一组SQL命令,这些命令要么一起执行,要么都不被执行)
    • 高速——写同步(synchronous)
    • 极速——执行准备(一种是使用前文提到的函数sqlite3_exec(),该函数直接调用包含SQL语句的字符串;另一种方法就是“执行准备”(类似于存储过程)操作,即先将SQL语句编译好,然后再一步一步(或一行一行)地执行。)
    • SQLite插入数据效率最快的方式就是:事务+关闭写同步+执行准备(存储过程),如果对数据库安全性有要求的话,就开启写同步
  43. Activity与Fragment之间的区别

    • 区别:
      • fragment显得更加灵活。可以直接在XML文件中添加<fragment/>,Activity则不能
      • 可以在一个界面上灵活的替换一部分页面,activity不可以,做不到
    • 通信(也就是控件的相互操控):
      • fragment控制fragment:得到一个Activity,然后通过这个Activity的getFragmentManager()获得该Fragment的实例。
      • fragment控制Activity:这个很简单。每个Fragment都有getActivity()得到一个活动。MainActivity activity=getActivity();
      • Activity控制fragment:getFragmentManager().findFragmentById();
      • Activity控制Activity:这个显然是通过Intent活动之间的通信完成。别忘了在被打开的活动中创建Intent和得到Intent一起进行,写个静态的actionStart()。
  44. Activity与Fragment通信方案

    • handler:该方案存在的缺点:
      • Fragment对具体的Activity存在耦合,不利于Fragment复用
      • 不利于维护,若想删除相应的Activity,Fragment也得改动
      • 没法获取Activity的返回数据
      • handler的使用个人感觉就很不爽(不知大家是否有同感)
    • 广播方案缺点:
      • 用广播解决此问题有点大材小用了,个人感觉广播的意图是用在一对多,接收广播者是未知的情况
      • 广播性能肯定会差
      • 传播数据有限制(必须得实现序列化接口才可以)
    • EventBus方案:
      • EventBus是用反射机制实现的,性能上会有问题
      • EventBus难于维护代码
      • 没法获取Activity的返回数据
    • 接口方案
      • 假如项目很大了,Activity与Fragment的数量也会增加
  45. Activity与Service通信方案(区别于是否同个进程)

    • Aidl,回调(listener)
    • Messager
    • 广播 (推荐LocalBroadcastManager)
    • 开源组件(EventBus,otto)
  46. Android常见的内存缓存算法

    • LRU即Least RecentlyUsed,近期最少使用算法:当内存缓存达到设定的最大值时将内存缓存中近期最少使用的对象移除,有效的避免了OOM的出现
    • Least Frequently Used(LFU):
      对每个缓存对象计算他们被使用的频率。把最不常用的缓存对象换走
    • First in First out(FIFO):
      这是一个低负载的算法,并且对缓存对象的管理要不高。通过一个队列去跟踪所有的缓存对象,最近最常用的缓存对象放在后面,而更早的缓存对象放在前面,当缓存容量满时,排在前面的缓存对象会被踢走,然后把新的缓存对象加进去
    • LargestLimitedMemoryCache:超过指定缓存的话,每次移除栈最大内存的缓存的对象
  47. Context相关

    • Context直系子类有两个,一个是ContextWrapper,一个是ContextImpl。那么从名字上就可以看出,ContextWrapper是上下文功能的封装类,而ContextImpl则是上下文功能的实现类。而ContextWrapper又有三个直接的子类,ContextThemeWrapper、Service和Application。其中,ContextThemeWrapper是一个带主题的封装类,而它有一个直接子类就是Activity
    • Context类型:
    • Context获取:
      • getApplicationContext() 返回应用的上下文,生命周期是整个应用,应用摧毁它才摧毁
      • Activity.this的context 返回当前activity的上下文,属于activity ,activity 摧毁他就摧毁
      • getBaseContext() 返回由构造函数指定或setBaseContext()设置的上下文


        类结构图
  48. Service与Thread区别

    • Thread:Thread是程序执行的最小单元,它是分配CPU的基本单位。可以用 Thread 来执行一些异步的操作
    • Service:Service 是android的一种机制,当它运行的时候如果是Local Service,那么对应的 Service 是运行在主进程的 main 线程上的。如:onCreate,onStart 这些函数在被系统调用的时候都是在主进程的 main 线程上运行的。如果是Remote Service,那么对应的 Service 则是运行在独立进程的 main 线程上
    • Thread 的运行是独立于 Activity 的,也就是说当一个 Activity 被 finish 之后,如果你没有主动停止 Thread 或者 Thread 里的 run 方法没有执行完毕的话,Thread 也会一直执行。因此这里会出现一个问题:当 Activity 被 finish 之后,你不再持有该 Thread 的引用。另一方面,你没有办法在不同的 Activity 中对同一 Thread 进行控制
    • Android的后台就是指,它的运行是完全不依赖UI的。即使Activity被销毁,或者程序被关闭,只要进程还在,Service就可以继续运行。比如说一些应用程序,始终需要与服务器之间始终保持着心跳连接,就可以使用Service来实现
    • 在一个Activity中创建的子线程,另一个Activity无法对其进行操作。但是Service就不同了,所有的Activity都可以与Service进行关联,然后可以很方便地操作其中的方法,即使Activity被销毁了,之后只要重新与Service建立关联,就又能够获取到原有的Service中Binder的实例,因此,使用Service来处理后台任务,Activity就可以放心地finish,完全不需要担心无法对后台任务进行控制的情况。
  49. Apk打包过程

    • (aapt) 打包资源文件,生成R.java文件
      • 把xml文件编译成二进制文件,解析速度更快并减少文件大小
    • (aidl) 处理AIDL文件,生成相应的Java文件
    • (javac) 编译工程源代码,生成相应的class文件
    • (dex) 转换所有的class文件(包括3rd库),生成classes.dex文件
    • (apkbuilder) 将classes.dex、resources.arsc、res文件夹(res/raw不变)、Other Resources(assets文件夹)、AndroidManifest.xml打包成apk文件
    • (jarsigner) 对apk包进行签名
    • (zipalign) 对签名后的apk文件进行对对齐处理
  50. 如何退出Activity,如何安全退出已调用多个Activity的Application?

    • 抛异常强制退出,android.os.Process.killProcess(android.os.Process.myPid());
    • 在Appliction类中记录打开的Activity,每打开一个Activity,就记录下来。在需要退出时,关闭每一个Activity即可
    • 创建发送特定广播的Activity,在需要结束应用时,发送一个特定的广播,每个Activity收到广播后,关闭即可
    • 递归退出,在打开新的Activity时使用startActivityForResult,然后自己加标志,在onActivityResult中处理,递归关闭
    • 用intent.setFlag(FLAG_ACTIVITY_CLEAR_TOP)激活一个新的activity,然后在新的activity的oncreate方法里面 finish掉
  51. 自定义控件的生命周期

    • onFinishInflate() 当View中所有的子控件均被映射成xml后触发
    • onMeasure( int , int ) 确定所有子元素的大小
    • onLayout( boolean , int , int , int , int ) 当View分配所有的子元素的大小和位置时触发
    • onSizeChanged( int , int , int , int ) 当view的大小发生变化时触发,启动时间在onDraw之前
    • onDraw(Canvas) view渲染内容的细节
    • onKeyDown( int , KeyEvent) 有按键按下后触发
    • onKeyUp( int , KeyEvent) 有按键按下后弹起时触发
    • onTrackballEvent(MotionEvent) 轨迹球事件
    • onTouchEvent(MotionEvent) 触屏事件
    • onFocusChanged( boolean , int , Rect) 当View获取或失去焦点时触发
    • onWindowFocusChanged( boolean ) 当窗口包含的view获取或失去焦点时触发
    • onAttachedToWindow() 当view被附着到一个窗口时触发
    • onDetachedFromWindow() 当view离开附着的窗口时触发,Android123提示该方法和 onAttachedToWindow() 是相反的
    • onWindowVisibilityChanged( int ) 当窗口中包含的可见的view发生变化时触发
  52. Force Close相关

    • 比如空指针啦,类没有找到啦,资源没找到,就连Android API使用的顺序错误也可能导致比如setContentView()之前进行了findViewById()操作)
    • 如何避免弹出Force Close窗口 可以在Application类中实现Thread.UncaughtExceptionHandler接口的uncaughtException方法
  53. 缩减APK包大小

    • 保持良好的编程习惯,不要重复或者不用的代码,谨慎添加libs,移除使用不到的libs,使用proguard混淆代码,它会对不用的代码做优化,并且混淆后也能够减少安装包的大小
    • 使用Lint工具查找没有使用到的资源。去除不使用的图片,String,XML等等
    • 能用代码绘制实现的功能,尽量不要使用大量的图片
  54. android 关于安全的问题,你所知道的所有的安全问题

    • 错误导出组件
    • 参数校验不严
    • WebView引入各种安全问题
    • 不混淆、不防二次打包
    • 明文存储关键信息
    • 错误使用HTTPS
    • 山寨加密方法
  55. Android之安全机制

    • 代码安全:反编译,混淆proguard
    • 应用权限的设置:操作增加限制,防止恶意应用进行非法操作给用户造成敏感数据泄漏和设备被非法控制
    • 数字证书:只有同一包名且采用同一数字证书的应用才被认为是同一个应用
    • 网络安全:DES,RSA,SSL
    • 文件访问控制:采用的UGO权限机制
  56. 关于APK数字签名

    • 所有的应用程序都必须有数字证书,Android系统不会安装一个没有数字证书的应用程序
    • Android程序包使用的数字证书可以是自签名的,不需要一个权威的数字证书机构签名认证
    • 如果要正式发布一个Android ,必须使用一个合适的私钥生成的数字证书来给程序签名,而不能使用adt插件或者ant工具生成的调试证书来发布
    • 数字证书都是有有效期的,Android只是在应用程序安装的时候才会检查证书的有效期。如果程序已经安装在系统中,即使证书过期也不会影响程序的正常功能
  57. 新特性

    • Android5.0新特性
      • MaterialDesign设计风格
      • 支持多种设备
      • 支持64位ART虚拟机
    • Android6.0新特性
      • 运行时权限
      • 大量漂亮流畅的动画
      • 支持快速充电的切换
      • 支持文件夹拖拽应用
      • 相机新增专业模式
    • Android7.0新特性
      • 分屏多任务
      • 增强的Java8语言模式
      • 夜间模式
    • Android8.0新特性
      • 画中画模式
  58. 如何避免Overdraw

    • 合理选择控件容器:LinearLayout易用,效率高,表达能力有限。RelativeLayout复杂,表达能力强,效率稍逊
    • 去掉window的默认背景:可以在onCreate()中setContentView()之后调用getWindow().setBackgroundDrawable(null);或者在theme中添加android:windowbackground="null";
    • 去掉其他不必要的背景
    • ViewStu延时加载布局
    • Merge减少层级的嵌套
  59. SparseArray

    • 是稀疏数组,为了节省内存空间,并且不影响数组中原有的内容值,采用一种压缩的方式来表示稀疏数组的内容
    • SparseArray是android里为<Interger,Object>这样的Hashmap而专门写的类,目的是提高内存效率
    • 避免了自动装箱(auto-boxing)
    • 数据结构不会依赖于外部对象映射,用数组数据结构来保存映射,然后通过折半查找来找到对象
    • 一般来说,SparseArray执行效率比HashMap要慢一点,因为查找需要折半查找,而添加删除则需要在数组中执行,而HashMap都是通过外部映射。但相对来说影响不大,最主要是SparseArray不需要开辟内存空间来额外存储外部映射,从而节省内存
  60. Jar和Aar的区别

    • Jar包里面只有代码,aar里面不光有代码还包括代码还包括资源文件,比如 drawable 文件,xml 资源文件。对于一些不常变动的 Android Library,我们可以直接引用 aar,加快编译速度
  61. ART和Dalvik区别

    • Dalvik:
      • Dalvik是Google公司自己设计用于Android平台的Java虚拟机
      • .dex格式是专为Dalvik应用设计的一种压缩格式,工作在寄存器
      • Dalvik经过优化,允许在有限的内存中同时运行多个虚拟机的实例,并且每一个Dalvik应用作为独立的Linux进程执行
      • 独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭
    • ART:
      • Dalvik是依靠一个Just-In-Time(JIT)编译器去解释字节码,开发者编译后的应用代码需要通过一个解释器在用户的设备上运行,并不高效
      • ART在应用安装的时候就预编译字节码到机器语言,这一机制叫Ahead-Of-Time(AOT)编译,在移除解释代码这一过程后,应用程序执行将更有效率,启动更快
      • ART优点:1.系统性能的显著提升应用启动更快、2. 运行更快、3. 体验更流畅、触感反馈更及时、4. 更长的电池续航能力支持更低的硬件
      • ART缺点:1. 更大的存储空间占用、2. 可能会增加10%-20%更长的应用安装时间
    • 区别:
      • ART:Ahead of Time ,Dalvik: Just in Time
      • ART上应用启动快,运行快,但是耗费更多存储空间,安装时间长,总的来说ART的功效就是”空间换时间”
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,792评论 25 707
  • 1.什么是Activity?问的不太多,说点有深度的 四大组件之一,一般的,一个用户交互界面对应一个activit...
    JoonyLee阅读 5,729评论 2 51
  • 社保也是保险。 社会保险是一个现代的社会制度,由国家强制人民参与各种保险,预先为各种疾病、身心伤害、失能、失业、职...
    锐博Reborn阅读 1,746评论 1 0
  • 忙碌之间忘记停下.觉察自己。当我们听到一些触及让你不舒服的地方.那就是需要我们停下来的时候.望着它.看着这个念头....
    李艾青Alice阅读 303评论 0 1