Android开发高频口试题之——Android篇

打印 上一主题 下一主题

主题 665|帖子 665|积分 1995

Android开发高频口试题之——Android篇

Android开发高频口试题之——Java基础篇
Android开发高频口试题之——Kotlin基础篇
Android开发高频口试题之——Android基础篇
1. Activity启动模式



  • standard 标准模式,每次都是新建Activity实例。
  • singleTop 栈顶复用。如果要启动的Activity已经处于任务栈顶,则直接复用不会新建Activity实例,此时会调用onNewIntent方法。如果栈内不存在或者不在栈顶。则会新建Activity实例。
  • singleTask 栈内单例。如果任务栈内已经存在Activity实例,则直接复用。如果不在栈顶,则把该activity实例之上的全部出栈,让自身位于栈顶。此时会调用onNewIntent方法。
  • singleInstance 新建任务栈栈内唯一。应用场景:来电话界面,纵然来多个电话也只创建一个Activity;
2. Activity生命周期




  • 启动状态(Starting):Activity的启动状态很短暂,当Activity启动后便会进入运行状态(Running)。
  • 运行状态(Running):Activity在此状态时处于屏幕最前端,它是可见、有焦点的,可以与用户进行交互。如单击、长按等变乱。纵然出现内存不敷的情况,Android也会先销毁栈底的Activity,来确保当前的Activity正常运行。
  • 停息状态(Paused):在某些情况下,Activity对用户来说仍旧可见,但它无法获取焦点,用户对它操作没有没有相应,此时它处于停息状态。例如,当前Activity弹出Dialog,或者新启动Activity为透明的Activity等情况。
  • 制止状态(Stopped):当Activity完全不可见时,它处于制止状态,但仍旧生存着当前的状态和成员信息。如系统内存不敷,那么这种状态下的Activity很轻易被销毁。
  • 销毁状态(Destroyed):当Activity处于销毁状态时,将被清理出内存。
Activity的生命周期



  • onCreate() : 在Activity创建时调用,通常做一些初始化设置,不可以执行耗时操作。;
  • onNewIntent()*:留意 !!只有当 当前activity实例已经处于任务栈顶,并且使用启动模式为singleTop或者SingleTask再次启动Activity时才会调用此方法。此时不会走OnCreate(),而是会执行onNewIntent()。由于activity不需要创建而是直接复用。
  • onStart(): 在Activity即将可见时调用;可以做一些动画初始化的操作。
  • onRestoreInstanceState()*:留意 !!当app非常退出重建时才会调用此方法。可以在该方法中恢复以生存的数据。
  • onResume(): 在Activity已可见,获取焦点开始与用户交互时调用;当Activity第一次启动完成或者当前Activity被遮挡住一部分(进入了onPause())重新回到前台时调用,比如弹窗消散。当onResume()方法执行完毕之后Activity就进入了运行状态。根据官方的建议,此时可以做开启动画和独占设备的操作。
  • onPause(): 在当前Activity被其他Activity覆盖或锁屏时调用;Activity制止但是当前Activity还是处于用户可见状态,比如出现弹窗;在onPause()方法中不能进行耗时操作(当前Activity通过Intent启动另一个Activity时,会先执行当前Activity的onPause()方法,再去执行另一个Activity的生命周期)
  • onSaveInstanceState():留意 !! 只有当app大概会非常销毁时才会调用此方法生存activity数据。以便于activity重建时恢复数据
    Activity的onSaveInstanceState回调时机,取决于app的targetSdkVersion:
    targetSdkVersion低于11的app,onSaveInstanceState方法会在Activity.onPause之前回调;
    targetSdkVersion低于28的app,则会在onStop之前回调;
    28之后,onSaveInstanceState在onStop回调之后才回调。
  • onStop() : 在Activity完全被遮挡对用户不可见时调用(在onStop()中做一些接纳资源的操作)
  • onDestroy() :在Activity销毁时调用;
  • onRestart() : 在Activity从制止状态再次启动时调用;处于stop()状态也就是完全不可见的Activity重新回到前台时调用(重新回到前台不会调用onCreate()方法,由于此时Activity还未销毁)
Activity横竖屏切换生命周期
横竖屏切换涉及到的是Activity的android:configChanges属性;
android:configChanges可以设置的属性值有:
orientation:消除横竖屏的影响
keyboardHidden:消除键盘的影响
screenSize:消除屏幕大小的影响


  • 设置Activity的android:configChanges属性为orientation或者orientation|keyboardHidden或者不设置这个属性的时间,横竖屏切换会重新调用各个生命周期方法,切横屏时会执行1次,切竖屏时会执行1次;
  • 设置Activity的属性为 android:configChanges=“orientation|keyboardHidden|screenSize” 时,横竖屏切换不会重新调用各个生命周期方法,只会执行onConfigurationChanged方法;
3. 相识Service吗

Service一般用于没有ui界面的长期服务。
Service有两种启动方式


  • StartService 这种方式启动的Service生命周期和启动实例无关。启动后会一直存在,直到app退出,或者调用stopService或者stopSelf。生命周期为onCreate-》onStartCommand-》onDestroyed。
         
    • 多次启动StartService。onStartCommand会调用多次  
      
  • bindService 这种方式启动的Service和生命周期会和调用者绑定。一旦调用者竣事,Service也会一起竣事。生命周期为onCreate-》onBind-》onUnbind-》onDestroyed
如何保证Service不被杀死


  • onStartCommand方式中,返回START_STICKY或者START_REDELIVER_INTENT
         
    • START_STICKY:如果返回START_STICKY,Service运行的进程被Android系统杀掉之后,Android系统会将该Service依然设置为started状态(即运行状态),会重新创建该Service。但是不再生存onStartCommand方法传入的intent对象   
    • START_NOT_STICKY:如果返回START_NOT_STICKY,表现当Service运行的进程被Android系统强制杀掉之后,不会重新创建该Service   
    • START_REDELIVER_INTENT:如果返回START_REDELIVER_INTENT,其返回情况与START_STICKY雷同,但差别的是系统会生存末了一次传入onStartCommand方法中的Intent再次生存下来并再次传入到重新创建后的Service onStartCommand方法中  
      
  • 提高Service的优先级: 在AndroidManifest.xml文件中对于intent-filter可以通过android:priority = "1000"这个属性设置最高优先级,1000是最高值,如果数字越小则优先级越低,同时实用于广播;
  • 在onDestroy方法里重启Service: 当service走到onDestroy()时,发送一个自界说广播,当收到广播 时,重新启动service;
  • 提升Service进程的优先级。 进程优先级由高到低:前台进程 一》 可视进程 一》 服务进程 一》 背景进程 一》 空进程
    可以使用 startForegroundservice放到前台状态,这样低内存时,被杀死的概率会低一些; 系统广播监听Service状态将APK安装到/system/app,变身为系统级应用。
4. 使用过broadcastReceiver吗?

可分为标准广播(无序广播)有序广播
按照作用范围可分为全局广播本地广播
按照注册方式可分为静态广播动态广播


  • 标准广播
    标准广播(normal broadcasts)是一种完全异步执行的广播,在广播发出之后,所有的BroadcastReceiver险些都会在同一时间接收到收到这条广播消息,因此它们之间没有任何先后次序可言。这种广播的效率会比较高,但同时也意味着它是无法被截断的。
  • 有序广播
    有序广播(ordered broadcasts)是一种同步执行的广播,在广播发出之后,同一时间只会有一个BroadcastReceiver可以或许收到这条广播消息,当这个BroadcastReceiver中的逻辑执行完毕后,广播才会继续传递。所以此时的BroadcastReceiver是有先后次序的,优先级高的BroadcastReceiver就可以先收到广播消息,并且前面的BroadcastReceiver还可以截断正在传递的广播,这样后面的BroadcastReceiver就无法收到广播消息了。
         
    • 清单文件里的android:priority属性的数字大小设置优先级 范围为(-1000到1000)数字越大,优先级越高   
    • setResultExtras(bundle) 向卑鄙广播接收器传递额外的键值对信息或者**setResultData(“”)**直接传送字符串   
    • 卑鄙广播通过getResultExtras方法接收信息, getResultData() 方法接收字符串信息   
    • abortBroadcast() 进行截断  

  1.         <receiver
  2.             android:name=".broadcast.MyHaveBroadcastReceiver02"
  3.             android:exported="true">
  4.             <intent-filter android:priority="003">
  5.                 <action android:name="my_have_broadcast_receiver"></action>
  6.             </intent-filter>
  7.         </receiver>
  8. /**
  9. * 有序广播
  10. */
  11. public class MyHaveBroadcastReceiver02 extends BroadcastReceiver {
  12.    
  13.     private static final String TAG = "MyHaveBroadcastReceiver02";
  14.     @Override
  15.     public void onReceive(Context context, Intent intent) {
  16.    
  17.         if (intent != null) {
  18.    
  19.             Bundle extras = intent.getExtras();
  20.             String data = "";
  21.             if (extras != null) {
  22.    
  23.                 data = extras.getString("name3");
  24.                 /**
  25.                  * 2.1有序广播可以通过setResultExtras向下游广播接收器传递数据
  26.                  */
  27.                 extras.putString("name2", "喊你速速到龙阳路集合");
  28.                 setResultExtras(extras);
  29.                 /**
  30.                  * 2.2有序广播可以通过setResultData向下游广播接收器传递字符串
  31.                  */
  32.                 setResultData("2号口的信息");
  33.             }
  34.             Log.d(TAG, "MyHaveBroadcastReceiver02 onReceive" + data);
  35.             /**
  36.              * 1.有序广播可以通过abortBroadcast();方法对广播进行截断
  37.              */
  38. //            abortBroadcast();
  39.         }
  40.     }
  41. }
复制代码


  • 本地广播:发送的广播变乱不被其他应用步伐获取,也不能相应其他应用步伐发送的广播变乱。本地广播只能被动态注册,不能静态注册。动态注册或发送时时需要用到LocalBroadcastManager
  • 全局广播:发送的广播变乱可被其他应用步伐获取,也能相应其他应用步伐发送的广播变乱(可以通过 exported–是否监听其他应用步伐发送的广播 在清单文件中控制) 全局广播既可以动态注册,也可以静态注册。
  • 静态广播
    静态广播在清单文件AndroidMainfest.xml中注册,生命周期随系统,不受Activity生命周期影响,纵然进程被杀死,仍旧能收到广播,因此也可以通过注册静态广播做一些拉起进程的事。随着Android版本的增大,Android系统对静态广播的限制也越来越严格,一般能用动态广播办理的问题就不要用静态广播。
  • 动态广播
    动态广播不需要在AndroidManifest.xml文件中进行注册,动态注册的广播受Activity声明周期的影响,Activity消亡,广播也就不复存在。动态广播在需要担当广播的Activity中进行注册和解注册。
5. 说说你对handler的理解

Handler是Android用来办理线程间通讯问题的消息机制。Handler消息机制分为四个部分。


  • Handler 消息的发送者处理者
  • Message 消息实体 消息的载体和携带者。
  • MessageQueen 消息队列,使用双向链表实现,是存放消息的队列。
  • Looper 消息循环器,不绝的从消息队列中中取出消息。
如何使用Handler?



  • 使用Handler需要一个Looper情况,在主线程直接新建Handler实例然后实现handleMessage方法,然后在需要发送消息的地方,使用handler.sendMessage等方法即可。
  1. private static class MyHandler extends Handler {
  2.    
  3.         private final WeakReference<MainActivity> mTarget;
  4.         public MyHandler(MainActivity activity) {
  5.    
  6.             mTarget = new WeakReference<MainActivity>(activity);
  7.         }
  8.         @Override
  9.         public void handleMessage(@NonNull Message msg) {
  10.    
  11.             super.handleMessage(msg);
  12.             HandlerActivity activity = weakReference.get();
  13.             super.handleMessage(msg);
  14.             if (null != activity) {
  15.    
  16.                 //执行业务逻辑
  17.                 if (msg.what == 0) {
  18.    
  19.                         Log.e("myhandler", "change textview");
  20.                         MainActivity ma = mTarget.get();
  21.                         ma.textView.setText("hahah");
  22.                     }
  23.                 Toast.makeText(activity,"handleMessage",Toast.LENGTH_SHORT).show();
  24.             }
  25.         }
  26. }
  27. private Handler handler1 = new MyHandler(this);
  28. new Thread(new Runnable() {
  29.    
  30.             @Override
  31.             public void run() {
  32.    
  33.                 handler1.sendEmptyMessage(0);
  34.             }
  35.         }).start();
复制代码


  • 在子线程使用需要先创建Looper情况,调用Looper.prepare(),然后再创建Handler。末了在调用Looper.loop()启动消息循环。子线程Handler不使用时要调用handler.getLooper().quitSafely()退出Looper否则会壅闭。
  1.         private static class MyHandler extends Handler {
  2.    
  3.                 @Override
  4.                 public void handleMessage(@NonNull Message msg) {
  5.    
  6.                     super.handleMessage(msg);
  7.                     if (msg.what == 0) {
  8.    
  9.                         Log.e("child thread", "receive msg from main thread");
  10.                     }
  11.                 }
  12.             }
  13.         private Handler handler1;
  14.    
  15.     @Override
  16.     protected void onCreate(@Nullable Bundle savedInstanceState) {
  17.         
  18.             new Thread(new Runnable() {
  19.    
  20.                     @Override
  21.                     public void run() {
  22.    
  23.                         Looper.prepare(); //准备Looper环境
  24.                         handler1 = new MyHandler();
  25.                         Looper.loop(); //启动Looper
  26.                         Log.e("child thread", "child thread end");
  27.                     }
  28.                 }).start();
  29.                 handler1.sendEmptyMessage(0);
  30.                 handler1.getLooper().quitSafely();//子线程Handler不用时,退出Looper
  31. }
复制代码
主线程使用Handler为什么不消Looper.prepare()?

由于在app启动时,ActivityThread的Main方法里帮我们调用了Looper.prepareMainLooper()。并且末了调用了Looper.loop()
启动了主线程。
  1. public static void main(String[] args) {
  2.    
  3.         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
  4.         // Install selective syscall interception
  5.         AndroidOs.install();
  6.         // CloseGuard defaults to true and can be quite spammy.  We
  7.         // disable it here, but selectively enable it later (via
  8.         // StrictMode) on debug builds, but using DropBox, not logs.
  9.         CloseGuard.setEnabled(false);
  10.         Environment.initForCurrentUser();
  11.         // Make sure TrustedCertificateStore looks in the right place for CA certificates
  12.         final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
  13.         TrustedCertificateStore.setDefaultUserDirectory(configDir);
  14.         // Call per-process mainline module initialization.
  15.         initializeMainlineModules();
  16.         Process.setArgV0("<pre-initialized>");
  17.         Looper.prepareMainLooper();
  18.         // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
  19.         // It will be in the format "seq=114"
  20.         long startSeq = 0;
  21.         if (args != null) {
  22.    
  23.             for (int i = args.length - 1; i >= 0; --i) {
  24.    
  25.                 if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
  26.    
  27.                     startSeq = Long.parseLong(
  28.                             args[i].substring(PROC_START_SEQ_IDENT.length()));
  29.                 }
  30.             }
  31.         }
  32.         ActivityThread thread = new ActivityThread();
  33.         thread.attach(false, startSeq);
  34.         if (sMainThreadHandler == null) {
  35.    
  36.             sMainThreadHandler = thread.getHandler();
  37.         }
  38.         if (false) {
  39.    
  40.             Looper.myLooper().setMessageLogging(new
  41.                     LogPrinter(Log.DEBUG, "ActivityThread"));
  42.         }
  43.         // End of event ActivityThreadMain.
  44.         Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
  45.         Looper.loop();
  46.         throw new RuntimeException("Main thread loop unexpectedly exited");
  47.     }
复制代码
简述一下Handler的工作流程



  • Handler使用SendMessage或者post等方法。最终都会调用MessageQueue的enqueueMessage()方法。将消息按照执行时间先后次序入队。
  • Looper里面是个死循环,不绝地在队列中通过MessageQueue.next()方法取出消息,取出消息后,通过msg.target.dispatchMessage() 方法分发消息。先交给msg消息的runnable处理,再交给Handler的Callable处理,末了再交给Handler实现的handleMessage方法处理
  1.     public void dispatchMessage(@NonNull Message msg) {
  2.    
  3.         if (msg.callback != null) {
  4.    
  5.             handleCallback(msg);
  6.         } else {
  7.    
  8.             if (mCallback != null) {
  9.    
  10.                 if (mCallback.handleMessage(msg)) {
  11.    
  12.                     return;
  13.                 }
  14.             }
  15.             handleMessage(msg);
  16.         }
  17.     }
复制代码
一个线程中最多有多少个Handler,Looper,MessageQueue?



  • 一个线程可以有多个Handler
  • 一个handler只能有一个Looper和一个MessageQueen
    由于创建Handler必须有Looper情况,而Looper只能通过Looper.prepare和Looper.prepareMainLooper来创建。同时将Looper实例存放到线程局部变量sThreadLocal(ThreadLocal)中,也就是每个线程有本身的Looper。在创建Looper的时间也创建了该线程的消息队列,prepareMainLooper会判断sMainLooper是否有值,如果调用多次,就会抛出非常,所以主线程的Looper和MessageQueue只会有一个。同理子线程中调用Looper.prepare()时,会调用prepare(true)方法,如果多次调用,也会抛出每个线程只能由一个Looper的非常,总结起来就是每个线程中只有一个Looper和MessageQueue。
  1.     public static void prepare() {
  2.    
  3.         prepare(true);
  4.     }
  5.     private static void prepare(boolean quitAllowed) {
  6.    
  7.         if (sThreadLocal.get() != null) {
  8.    
  9.             throw new RuntimeException("Only one Looper may be created per thread");
  10.         }
  11.         sThreadLocal.set(new Looper(quitAllowed));
  12.     }
复制代码
Looper死循环为什么不会导致应用ANR、卡死,会耗费大量资源吗?

线程其实就是一段可执行的代码,当可执行的代码执行完成后,线程的生命周期便该终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间,本身就退出,那么如何保证能一直存活呢?简朴做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出


  • ANR 产生的缘故原由是主线程没有实时相应用户的操作。也就是主线程执行某个耗时操作来不及处理UI消息。
  • 而Looper一直循环,就是在不断的检索消息,与主线程无法相应用户操作没有任何冲突
  • Android是基于消息处理机制的,用户的行为都在这个Looper循环中,正是有了主线程Looper的不断循环,才有app的稳固运行。
  • 简朴来说looper的壅闭表明没有变乱输入,而ANR是由于有变乱没相应导致,所以looper的死循环并不会导致应用卡死。
主线程的死循环并不斲丧 CPU 资源,这里就涉及到 Linux pipe/epoll机制,简朴说就是在主线程的 MessageQueue 没有消息时,便壅闭在 loop 的 queue.next() 中的 nativePollOnce() 方法里,此时主线程会释放 CPU 资源进入休眠状态,直到下个消息到达或者有事务发生,通过往 pipe 管道写端写入数据来叫醒主线程工作。这里采用的 epoll 机制,是一种IO多路复用机制,可以同时监控多个形貌符,当某个形貌符就绪(读或写就绪),则立即通知相应步伐进行读或写操作,本质同步I/O,即读写是壅闭的。 所以说,主线程大多数时间都是处于休眠状态,并不会斲丧大量CPU资源。
Handler同步屏蔽相识吗

同步屏蔽是为了保证异步消息的优先执行,一般是用于UI绘制消息,避免主线程消息太多,无法实时处理UI绘制消息,导致卡顿。


  • 同步消息 一般的handler发送的消息都是同步消息
  • 异步消息 Message标记为异步的消息
         
    • 可以调用 Message#setAsynchronous() 直接设置为异步 Message   
    • 可以用异步 Handler 发送 使用 Handler.createAsync() 创建异步Handler  

  1.     public static Handler createAsync(@NonNull Looper looper) {
  2.    
  3.         if (looper == null) throw new NullPointerException("looper must not be null");
  4.         return new Handler(looper, null, true);
  5.     }
复制代码


  • 同步屏蔽 在 MessageQueue 的 某个位置放一个 target 属性为 null 的 Message ,确保今后的非异步 Message 无法执行,只能执行异步 Message。
当 Looper轮循MessageQueue 遍历 Message发现建立了同步屏蔽的时间,会去跳过其他Message,读取下个 async 的 Message 并执行,屏蔽移除之前同步 Message 都会被壅闭。
比如屏幕革新 Choreographer 就使用到了同步屏蔽 ,确保屏幕革新变乱不会由于队列负荷影响屏幕实时革新。
留意: 同步屏蔽的添加或移除 API MessageQueue.postSyncBarrier并未对外公开,App 需要使用的话需要依靠反射机制
  1.     try {
  2.    
  3.             MessageQueue queue=handler.getLooper().getQueue();
  4.             Method method=MessageQueue.class.getDeclaredMethod("postSyncBarrier");
  5.             method.setAccessible(true);
  6.             token= (int) method.invoke(queue);
  7.         } catch (Exception e) {
  8.    
  9.             e.printStackTrace();
  10.         }
  11.    try {
  12.    
  13.             MessageQueue queue=handler.getLooper().getQueue();
  14.             Method method=MessageQueue.class.getDeclaredMethod("removeSyncBarrier",int.class);
  15.             method.setAccessible(true);
  16.             method.invoke(queue,token);
  17.         } catch (Exception e) {
  18.    
  19.             e.printStackTrace();
  20.         }        
复制代码
Handler 为什么大概导致内存泄露?如何避免?

持有 Activity 实例的匿名内部类或内部类的 生命周期 应当和 Activity 保持同等,否则产生内存泄露的风险。
如果 Handler 使用不当,将造成差别等,表现为:匿名内部类或内部类写法的 Handler、Handler$Callback、Runnable,或者Activity 竣事时仍有活跃的 Thread 线程或 Looper 子线程
具体在于:异步任务仍旧活跃或通过发送的 Message 尚未处理完毕,将使得内部类实例的 生命周期被错误地延长 。造本钱该接纳的 Activity 实例 被别的 Thread 或 Main Looper 占据而无法实时接纳 (活跃的 Thread 或 静态属性 sMainLooper 是 GC Root 对象)
建议的做法:


  • 无论是 Handler、Handler$Callback 还是 Runnable,只管采用 静态内部类 + 弱引用 的写法,确保尽管发生不当引用的时间也可以由于弱引用能清楚持有关系
  • 另外在 Activity 销毁的时间实时地 终止 Thread、制止子线程的 Looper 或清空 Message ,确保彻底切断 Activity 经由 Message 抵达 GC Root 的引用源头(Message 清空后会其与 Handler 的引用关系,Thread 的终止将竣事其 GC Root 的源头)
Handler是如何实现线程间通讯的



  • handler是消息的发送者也是处理者。发送消息时,msg.target会标记为自身。插入MessageQueen后,被Looper取出后会通过msg.target.dispatchMessage去分发给对应的Handler去处理。
Handler消息处理的优先级

  1.     public void dispatchMessage(@NonNull Message msg) {
  2.    
  3.         if (msg.callback != null) {
  4.    
  5.             handleCallback(msg);
  6.         } else {
  7.    
  8.             if (mCallback != null) {
  9.    
  10.                 if (mCallback.handleMessage(msg)) {
  11.    
  12.                     return;
  13.                 }
  14.             }
  15.             handleMessage(msg);
  16.         }
  17.     }
复制代码
可以看出优先级是Message.CallBack->Handler.callback->Handler.handleMessage
有时间口试官也会问Runnable->Callable->handleMessage
post方法就是runnable
Handler构造传入Callback就是Callable
send方法是handleMessage
如何正确或Message实例



  • 通过 Message 的静态方法 Message.obtain() 获取;
  • 通过 Handler 的公有方法 handler.obtainMessage()
  • 默认大小是50
Message使用享元设计模式,里面有一个spool指向一个Message对象,另有一个next指向下一个Message,维护了一个链表实现的对象池,obtain的时间在表头头取Message,在Message接纳的时间在表头添加一个Message。
Android 为什么不允许并发访问 UI?

Android 中 UI 非线程安全,并发访问的话会造成数据和显示庞杂。
此限制的检查始于ViewRootImpl#checkThread(),其会在革新等多个访问 UI 的时机被调用,去检查当前线程,非主线程的话抛出非常。(现实上并不是检查主线程。而是检查UI的更新线程是否与UI的创建线程同等,由于UI是在主线程创建的,所以也只能在主线程更新)
而 ViewRootImpl 的创建在 onResume() 之后,也就是说如果在 onResume() 执行前启动线程访问 UI 的话是不会报错的。
相识ThreadLocal吗



  • Thread中会维护一个雷同HashMap的东西,然后用ThreadLocal对象作为key,value就是要存储的变量值,这样就保证了存储数据的唯一性)
  • ThreadLocal为每个线程都提供了变量的副本,使得每个线程在某一时间访问到的并非同一个对象,这样就隔离了多个线程对数据的数据共享。
  • ThreadLocal 内部通过 ThreadLocalMap 持有 Looper,key 为 ThreadLocal 实例本身,value 即为 Looper 实例
    每个 Thread 都有一个本身的 ThreadLocalMap,这样可以保证每个线程对应一个独立的 Looper 实例,进而保证 myLooper() 可以获得线程独有的 Looper。让每个线程方便程获取本身的 Looper 实例

ThreadLocal与内存泄漏



  • 在线程池中使用ThreadLocal大概会导致内存泄漏,缘故原由是线程池中线程的存活时间太长,往往和步伐都是同生共死的,这就意味着Thread持有的ThreadLocalMap一直都不会被接纳,再加上ThreadLocalMap中的Entry对ThreadLocal是弱引用,所以只要ThreadLocal竣事了本身的生命周期是可以被接纳掉的。但是Entry中的Value却是被Entry强引用的,所以即便Value的生命周期竣事了,Value也是无法被接纳的,从而导致内存泄漏。
  1. ExecutorService es;
  2. ThreadLocal tl;
  3. es.execute(()->{
  4.    
  5. //ThreadLocal增加变量
  6. tl.set(obj);
  7. try{
  8.    
  9. //业务代冯
  10. }finally{
  11.    
  12. //于动消ThreadLocal
  13. tl.remove();}
  14. });
复制代码
Message 的执行时间如何管理



  • 发送的 Message 都是按照执行时间 when 属性的先后管理在 MessageQueue 里
    延时 Message 的 when 等于调用的当前时间和 delay 之和
    非延时 Message 的 when 等于当前时间(delay 为 0)
  • 插队 Message 的 when 固定为 0,便于插入队列的 head之后 MessageQueue 会根据 读取的时间和 when 进行比较将 when 已抵达的出队,尚未抵达的计算出 当前时间和目标 when 的插值 ,交由 Native 等待对应的时长,时间到了主动叫醒继续进行 Message 的读取
  • 事实上,无论上述哪种 Message 都不能保证在其对应的 when 时间执行,往往都会延迟一些!由于必须等当前执行的 Message 处理完了才有时机读取队列的下一个 Message。
    比如发送了非延时 Message,when 即为发送的时间,可它们不会立即执行。都要等主线程现有的任务(Message)走完才气有时机出队,而当这些任务执行完 when 的时间已经过了。倘使队列的前面另有其他 Message 的话,延迟会更加显着!
Looper等待如何准确叫醒的?

读取符合 Message 的 MessageQueue#next() 会由于 Message 尚无或执行条件尚未满意进行两种等的等待:


  • 无穷等待
    尚无 Message(队列中没有 Message 或建立了同步屏蔽但尚无异步 Message)的时间,调用 Natvie 侧的 pollOnce() 会传入参数 -1 。
    Linux 执行 epoll_wait() 将进入无穷等待,其等待符合的 Message 插入后调用 Native 侧的 wake() 叫醒 fd 写入变乱触发叫醒 MessageQueue 读取的下一次循环

  • 有限等待
    有限等待的场合将下一个 Message 剩余时长作为参数 交给 epoll_wait(),epoll 将等待一段时间之后 主动返回 ,接着回到 MessageQueue 读取的下一次循环。
Handler机制原理



  • Looper 准备和开启轮循:
    尚无 Message 的话,调用 Native 侧的 pollOnce() 进入 无穷等待
    存在 Message,但执行时间 when 尚未满意的话,调用 pollOnce() 时传入剩余时长参数进入 有限等待
    Looper#prepare() 初始化线程独有的 Looper 以及 MessageQueue
    Looper#loop() 开启 死循环 读取 MessageQueue 中下一个满意执行时间的 Message

  • Message 发送、入队和出队:
    Native 侧如果处于无穷等待的话:恣意线程向 Handler 发送 Message 或 Runnable 后,Message 将按照 when 条件的先后,被插入 Handler 持有的 Looper 实例所对应的 MessageQueue 中 得当的位置 。MessageQueue 发现有符合的 Message 插入后将调用 Native 侧的 wake() 叫醒无穷等待的线程。这将促使 MessageQueue 的读取继续 进入下一次循环 ,现在 Queue 中已有满意条件的 Message 则出队返回给 Looper
    Native 侧如果处于有限等待的话:在等待指定时长后 epoll_wait 将返回。线程继续读取 MessageQueue,现在由于时长条件将满意将其出队

  • handler处理 Message 的实现:
    Looper 得到 Message 后回调 Message 的 callback 属性即 Runnable,或依据 target 属性即 Handler,去执行 Handler 的回调。存在 mCallback 属性的话回调 Handler$Callback反之,回调 handleMessage()
6. 相识View绘制流程吗?

Activity启动走完onResume方法后,在ActivityThread#handleResumeActivity会进行window的添加。
window添加过程会调用 ViewRootImpl的setView() 方法,setView()方法会调用 requestLayout() 方法来哀求绘制布局,requestLayout()方法内部又会走到 scheduleTraversals() 方法,末了会走到 performTraversals() 方法,接着到了我们熟知的测量、布局、绘制三大流程了。


  • 所有UI的变革都是走到 ViewRootImpl的scheduleTraversals() 方法。
  1.     //ViewRootImpl.java
  2.     void scheduleTraversals() {
  3.    
  4.         if (!mTraversalScheduled) {
  5.    
  6.                 //此字段保证同时间多次更改只会刷新一次,例如TextView连续两次setText(),也只会走一次绘制流程
  7.             mTraversalScheduled = true;
  8.             //添加同步屏障,屏蔽同步消息,保证VSync到来立即执行绘制
  9.             mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
  10.             //mTraversalRunnable是TraversalRunnable实例,最终走到run(),也即doTraversal();
  11.             mChoreographer.postCallback(
  12.                     Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
  13.             if (!mUnbufferedInputDispatch) {
  14.    
  15.                 scheduleConsumeBatchedInput();
  16.             }
  17.             notifyRendererOfFramePending();
  18.             pokeDrawLockIfNeeded();
  19.         }
  20.     }
  21.     final class TraversalRunnable implements Runnable {
  22.    
  23.         @Override
  24.         public void run() {
  25.    
  26.             doTraversal();
  27.         }
  28.     }
  29.     final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
  30.     void doTraversal() {
  31.    
  32.         if (mTraversalScheduled) {
  33.    
  34.             mTraversalScheduled = false;
  35.             //移除同步屏障
  36.             mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
  37.             ...
  38.             //开始三大绘制流程
  39.             performTraversals();
  40.             ...
  41.         }
  42.     }
复制代码

  • 首先使用mTraversalScheduled字段保证同时间多次更改只会革新一次,例如TextView一连两次setText(),也只会走一次绘制流程。
  • 然后把当前线程的消息队列Queue添加了同步屏蔽,这样就屏蔽了正常的同步消息,保证VSync到来后立即执行绘制,而不是要等前面的同步消息。后面会具体分析同步屏蔽和异步消息的代码逻辑。
  • 调用了mChoreographer.postCallback()方法,发送一个会在下一帧执行的回调,即在下一个VSync到来时会执行TraversalRunnable–>doTraversal()—>performTraversals()–>绘制流程。
mChoreographer,是在ViewRootImpl的构造方法内使用Choreographer.getInstance()创建:
  1. Choreographer mChoreographer;
  2. //ViewRootImpl实例是在添加window时创建
  3. public ViewRootImpl(Context context, Display display) {
  4.    
  5.         ...
  6.         mChoreographer = Choreographer.getInstance();
  7.         ...
  8. }
复制代码
  1.     public static Choreographer getInstance() {
  2.    
  3.         return sThreadInstance.get();
  4.     }
  5.     private static final ThreadLocal<Choreographer> sThreadInstance =
  6.             new ThreadLocal<Choreographer>() {
  7.    
  8.         @Override
  9.         protected Choreographer initialValue() {
  10.    
  11.             Looper looper = Looper.myLooper();
  12.             if (looper == null) {
  13.    
  14.                     //当前线程要有looper,Choreographer实例需要传入
  15.                 throw new IllegalStateException("The current thread must have a looper!");
  16.             }
  17.             Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
  18.             if (looper == Looper.getMainLooper()) {
  19.    
  20.                 mMainInstance = choreographer;
  21.             }
  22.             return choreographer;
  23.         }
  24.     };
复制代码
  1.     private Choreographer(Looper looper, int vsyncSource) {
  2.    
  3.         mLooper = looper;
  4.         //使用当前线程looper创建 mHandler
  5.         mHandler = new FrameHandler(looper);
  6.         //USE_VSYNC 4.1以上默认是true,表示 具备接受VSync的能力,这个接受能力就是FrameDisplayEventReceiver
  7.         mDisplayEventReceiver = USE_VSYNC
  8.                 ? new FrameDisplayEventReceiver(looper, vsyncSource)
  9.                 : null;
  10.         mLastFrameTimeNanos = Long.MIN_VALUE;
  11.                 // 计算一帧的时间,Android手机屏幕是60Hz的刷新频率,就是16ms
  12.         mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
  13.                 // 创建一个链表类型CallbackQueue的数组,大小为5,
  14.                 //也就是数组中有五个链表,每个链表存相同类型的任务:输入、动画、遍历绘制等任务(CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL)
  15.         mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
  16.         for (int i = 0; i <= CALLBACK_LAST; i++) {
  17.    
  18.             mCallbackQueues[i] = new CallbackQueue();
  19.         }
  20.         // b/68769804: For low FPS experiments.
  21.         setFPSDivisor(SystemProperties.getInt(ThreadedRenderer.DEBUG_FPS_DIVISOR, 1));
  22.     }
复制代码
安排任务—postCallback
回头看mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null)方法,留意到第一个参数是CALLBACK_TRAVERSAL,表现回调任务的类型,共有以下5种类型:
  1. //输入事件,首先执行
  2. public static final int CALLBACK_INPUT = 0;
  3. //动画,第二执行
  4. public static final int CALLBACK_ANIMATION = 1;
  5. //插入更新的动画,第三执行
  6. public static final int CALLBACK_INSETS_ANIMATION = 2;
  7. //绘制,第四执行
  8. public static final int CALLBACK_TRAVERSAL
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

惊雷无声

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表