面试理论安卓面试

Android面试题(1)

2022-01-23  本文已影响0人  小杨不想努力了

参考资料:《Android开发艺术探索》
https://lrh1993.gitbooks.io/android_interview_guide/content/android/basis.html
可能有部分图片加载不出,参考我的有道云笔记:
https://note.youdao.com/old-web/#/file/F04BF236EEA948698DEB7365A553D5A5/markdown/WEBfe1142377ad79cd8a2078b9b1c2d2318/

  1. Activity,Fragment,Service生命周期

    • Activity

      在正常情况下,一个Activity从启动到结束的生命周期如下:

      onCreate()->onStart()->onResume()->onPause()->onStop()->onDestory()(还有一个onRestart()没有调用)

      1. onCreate()

        当Activity第一次创建时会被调用,用来初始化一些工作,比如调用setContentView去加载界面布局资源,初始化Activity所需的数据。

      2. onRestart()

        表示Activity正在重新启动。一般情况下,当当前Activity从不可见重新变为可见状态时,onRestart()就会被调用。这种情形一般是用户行为导致的,比如用户按Home键切换到桌面或打开了另一个新的Activity,接着用户又回到了这个Actvity,就会出现这种情况。

      3. onStart()

        表示Activity正在被启动,即将开始,这时Activity已经出现了,但是还没有出现在前台,无法与用户交互。这个时候可以理解为Activity已经显示出来, 但是我们还看不到。

      4. onResume()

        表示Activity已经可见了,并且出现在前台并开始活动。需要和onStart()区分,onStart的时候 Activity还在后台,onResume的时候Activity才显示到前台。

      5. onPause()

        表示 Activity正在停止,仍可见,正常情况下,紧接着onStop就会被调用。在特殊情况下,如果这个时候快速地回到当前Activity,那么onResume就会被调用(极端情况)。onPause中不能进行耗时操作,会影响到新Activity的显示。因为onPause必须执行完,新的Activity的onResume才会执行。

      6. onStop()

        表示Activity即将停止,不可见,位于后台。可以做稍微重量级的回收工作,同样不能太耗时。

      7. onDestory()

        表示Activity即将销毁,这是Activity生命周期的最后一个回调,可以做一些回收工作和最终的资源回收。

      四种状态:

      1. 运行状态:
        Activity在此状态时处于屏幕最前端,它是可见、有焦点的,可以与用户进行交互。如单击、长按等事件。即使出现内存不足的情况,Android也会先销毁栈底的Activity,来确保当前的Activity正常运行。

      2. 暂停状态:

        当一个活动不再处于栈顶的位置,但仍然可见,弹出一个对话框或者一个不能占满屏幕的活动都会导致前一个活动处于暂停状态,系统继续维护其内部状态,它仍然可见,但它已经失去了焦点,故不可与用户交互。系统也不会轻易回收这样的活动,除非是内存极低的情况(回收可见的活动都会造成极不好的用户体验)。

      3. 停止状态:

        当一个活动不处于栈顶位置,且完全不可见的时候,就进入停止状态,但仍然保留着当前的状态和成员信息,当内存较低时系统会回收这样的活动。

      4. 销毁状态:

        当一个活动从栈中移除后就变成销毁状态,系统会回收这样的活动

      生命周期分析:

      1. Activity1打开Activity2

        旧的先pause,新的才创建

        https://note.youdao.com/yws/public/resource/3c8c773a3d105f8e5fa5b9d7a0ef31ad/xmlnote/2FD62DB1D60646B896BA300401802DAF/5097
      2. 资源相关的系统配置发生变化(屏幕翻转)

        在横竖屏切换的过程中,会发生Activity被销毁并重建的过程。

        在了解这种情况下的生命周期时,首先应该了解这两个回调:onSaveInstanceStateonRestoreInstanceState

        在Activity由于异常情况下终止时,系统会调用onSaveInstanceState来保存当前Activity的状态。这个方法的调用是在onStop之前,它和onPause没有既定的时序关系,该方法只在Activity被异常终止的情况下调用。当异常终止的Activity被重建以后,系统会调用onRestoreInstanceState,并且把Activity销毁时onSaveInstanceState方法所保存的Bundle对象参数同时传递给onRestoreInstanceState和onCreate方法。因此,可以通过onRestoreInstanceState方法来恢复Activity的状态,该方法的调用时机是在onStart之后。其中onCreate和onRestoreInstanceState方法来恢复Activity的状态的区别: onRestoreInstanceState回调则表明其中Bundle对象非空,不用加非空判断。onCreate需要非空判断。建议使用onRestoreInstanceState。

        img

        横竖屏切换的生命周期:

        onPause()->onSaveInstanceState()-> onStop()->onDestroy()->onCreate()->onStart()->onRestoreInstanceState()->onResume()

        可以通过在AndroidManifest文件的Activity中指定如下属性:

        android:configChanges = "orientation| screenSize"
        

        来避免横竖屏切换时,Activity的销毁和重建,而是回调了下面的方法:

        @Override
        public void onConfigurationChanged(Configuration newConfig) {
                super.onConfigurationChanged(newConfig);
        }
        

        Android 3.2 (API 级别 13)以前

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

        从 Android 3.2 (API级别 13)开始

        • 不设置Activity的android:configChanges,或设置Activity的android:configChanges="orientation"`,或设置Activity的android:configChanges="orientation|keyboardHidden",切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行一次。
        • 配置 android:configChanges="orientation|keyboardHidden|screenSize",才不会销毁 activity,且只调用 onConfigurationChanged方法。
      3. Activity A启动Activity B它们的生命周期变化(及设置Activity的透明度)

        1. 当Activity B 覆盖A导致A完全不可见时:

          //【1】部署程序
          D/MainActivity: onCreate------A
          D/MainActivity: onStart-------A
          D/MainActivity: onResume------A
          
          //【2】点击A中的按钮开始跳转到B
          D/MainActivity: onPause-------A
          D/SecondActivity: onCreate----B
          D/SecondActivity: onStart-----B
          D/SecondActivity: onResume----B
          D/MainActivity: onStop--------A
          
          //【3】然后点击返回键从B返回A
          D/SecondActivity: onPause-----B
          D/MainActivity: onRestart-----A
                          onStart-------A
          D/MainActivity: onResume------A
          D/SecondActivity: onStop------B
          D/SecondActivity: onDestroy---B
          
        2. 当Activity B背景被设置为透明(相当于发生跳转后,A部分可见)

          //【1】部署程序
          D/MainActivity: onCreate------A
          D/MainActivity: onStart-------A
          D/MainActivity: onResume------A
          
          //【2】点击A中的按钮开始跳转到B
          D/MainActivity: onPause-------A
          D/SecondActivity: onCreate----B
          D/SecondActivity: onStart-----B
          D/SecondActivity: onResume----B
          
          //【3】然后点击返回键从B返回A
          D/SecondActivity: onPause-----B
          D/MainActivity: onResume------A
          D/SecondActivity: onStop------B
          D/SecondActivity: onDestroy---B
          

          总结:当A启动B时,并且启动之后A还处于部分可见状态,当启动完B之后并不回调A的onStop()方法。

      资源内存不足导致活动被杀死:

      当系统内存不足的时候,系统就会按照优先级的大小去杀死一些活动,并在后续通过上面onSaveInstanceState和onRestoreInstanceState方法来恢复数据。优先级按照状态依次是:运行状态,暂停状态,停止状态。一般最有可能是停止状态的活动被杀了。所以一些后台工作就尽量放在service中,提高优先级,防止被杀死。脱离四大组件的活动容易被杀死。

      几个特殊的方法:

      1. onWindowFocusChanged方法:在Activity窗口获得或失去焦点时被调用,例如创建时首次呈现在用户面前;当前Activity被其他Activity覆盖;当前Activity转到其他Activity或按Home键回到主屏,自身退居后台;用户退出当前Activity。以上几种情况都会调用onWindowFocusChanged,并且当Activity被创建时是在onResume之后被调用,当Activity被覆盖或者退居后台或者当前Activity退出时,它是在onPause之后被调用。

      2. onSaveInstanceState:

        • 在Activity被覆盖或退居后台之后,系统资源不足将其杀死,此方法会被调用;

        • 在用户改变屏幕方向时,此方法会被调用;

        • 在当前Activity跳转到其他Activity或者按Home键回到主屏,自身退居后台时,此方法会被调用。

          第一种情况我们无法保证什么时候发生,系统根据资源紧张程度去调度;第二种是屏幕翻转方向时,系统先销毁当前的Activity,然后再重建一个新的,调用此方法时,我们可以保存一些临时数据;第三种情况系统调用此方法是为了保存当前窗口各个View组件的状态。这个方法会在 onStop() 前被触发,但系统并不保证是否在 onPause() 之前或者之后触发。

      3. onRestoreInstanceState:

        • 在Activity被覆盖或退居后台之后,系统资源不足将其杀死,然后用户又回到了此Activity,此方法会被调用;
        • 在用户改变屏幕方向时,重建的过程中,此方法会被调用。我们可以重写此方法,以便可以恢复一些临时数据。onRestoreInstanceState的调用顺序是在onStart之后。
    • Fragment

      可以简单的理解为,Fragment是显示在Activity中的Activity。它可以显示在Activity中,然后它也可以显示出一些内容。因为它拥有自己的生命周期,可以接受处理用户的事件,并且你可以在一个Activity中动态的添加,替换,移除不同的Fragment,因此对于信息的展示具有很大的便利性。

      因为Fragment是依附于Activity存在的,因此它的生命周期受到Activity的生命周期影响

      举个例子:如果Activity是暂停状态,其中所有的Fragment都是暂停状态;如果Activity是stopped状态,这个Activity中所有的Fragment都不能被启动;如果Activity被销毁,那么它其中的所有 Fragment都会被销毁。但是,当Activity在活动状态,可以独立控制Fragment的状态,比如加上或者移除 Fragment。 当这样进行fragment transaction(转换)的时候,可以把fragment放入Activity的back stack中,这样用户就可以进行返回操作。

      <img src="https://note.youdao.com/yws/public/resource/3c8c773a3d105f8e5fa5b9d7a0ef31ad/xmlnote/8A347307CD604C4BB8F665DF76D3A790/3439" alt="https://note.youdao.com/yws/public/resource/3c8c773a3d105f8e5fa5b9d7a0ef31ad/xmlnote/8A347307CD604C4BB8F665DF76D3A790/3439" style="zoom: 67%;" />

      Fragment比Activity多了几个生命周期的回调方法:

      • onAttach(Activity)

        当Fragment与Activity发生关联时调用。

      • onCreateView(LayoutInflater, ViewGroup,Bundle)

        创建该Fragment的视图

      • onActivityCreated(Bundle)

        当Activity的onCreate方法返回时调用

      • onDestoryView()

        与onCreateView对应,当该Fragment的视图被移除时调用

      • onDetach()

        与onAttach相对应,当Fragment与Activity关联被取消时调用

      注意:除了onCreateView,其他的所有方法如果你重写了,必须调用父类对于该方法的实现,就是Super。不能去掉。

    • service

      Service是Android程序中四大基础组件之一,它和Activity一样都是Context的子类, 只不过它没有UI界面,是在后台运行的组件。 Service是Android中实现程序后台运行的解决方案,它非常适用于去执行那些不需 要和用户交互而且还要求长期运行的任务。Service默认并不会运行在子线程中,它 也不运行在一个独立的进程中,它同样执行在UI线程中,因此,不要在Service中执行耗时的操作,除非你在Service中创建了子线程来完成耗时操作。

      1. OnCreate()

        系统在service第一次创建时执行此方法,来执行只运行一次的初始化工作。如果 service已经运行,这个方法不会被调用。

      2. onStartCommand()

        每次客户端调用startService()方法启动该Service都会回调该方法(多次调用)。一旦这个方法执行,service就启动并且在后台长期运行。通过调用stopSelf()或stopService()来停止服务。

      3. onBind()

        当组件调用bindService()想要绑定到service时(比如想要执行进程间通讯)系统调用此方法(一次调用,一旦绑定后,下次再调用bindService()不会回调该方法)。在实现中,你必须提供一个返回一个IBinder来以使客户端能够使用它与service通讯,你必须总是实现这个方法,但是如果你不允许绑定,那么你应返回null。

      4. onUnbind()

        当前组件调用unbindService(),想要解除与service的绑定时系统调用此方法(一次调用,一旦解除绑定后,下次再调用unbindService()会抛出异常)。

      5. onDestory()

        系统在service不再被使用并要销毁时调用此方法(一次调用)。service应在此方法中释放资源,比如线程,已注册的侦听器,接收器等等。这是service收到的最后一个调用。

        img

        三种启动情况下的生命周期:

        1. startService / stopService

          生命周期顺序:onCreate->onStartCommand->onDestroy

          如果一个Service被某个Activity 调用 Context.startService方法启动,那么不管是否有Activity使用bindService绑定或unbindService解除绑定到该Service,该Service都在后台运行,直到被调用stopService,或自身的stopSelf方法。当然如果系统资源不足,android系统也可能结束服务,还有一种方法可以关闭服务,在设置中,通过应用->找到自己应用->停止。

          注意点:

          • 第一次 startService 会触发 onCreate 和 onStartCommand,以后在服务运行过程中,每次 startService 都只会触发 onStartCommand
          • 不论 startService 多少次,stopService 一次就会停止服务
        2. bindService / unbindService

          生命周期顺序:onCreate->onBind->onUnBind->onDestroy

          如果一个Service在某个Activity中被调用bindService方法启动,不论bindService被调用几次,Service的onCreate方法只会执行一次,同时onStartCommand方法始终不会调用。

          当建立连接后,Service会一直运行,除非调用unbindService来接触绑定、断开连接或调用该Service的Context不存在了(如Activity被Finish——即通过bindService启动的Service的生命周期依附于启动它的Context),系统在这时会自动停止该Service。

          注意点:

          • 第一次 bindService 会触发 onCreate 和 onBind,以后在服务运行过程中,每次 bindService 都不会触发任何回调
        3. 混合型(上面两种方式的交互)

          当一个Service在被启动(startService)的同时又被绑定(bindService),该Service将会一直在后台运行,并且不管调用几次,onCreate方法始终只会调用一次,onStartCommand的调用次数与startService调用的次数一致(使用bindService方法不会调用onStartCommand)。同时,调用unBindService将不会停止Service,必须调用stopService或Service自身的stopSelf来停止服务。

        在什么情况下使用 startService 或 bindService 或 同时使用startService 和 bindService?

        • 如果你只是想要启动一个后台服务长期进行某项任务那么使用 startService 便可以了。
        • 如果你想要与正在运行的 Service 取得联系,那么有两种方法,一种是使用 broadcast ,另外是使用 bindService ,前者的缺点是如果交流较为频繁,容易造成性能上的问题,并且 BroadcastReceiver 本身执行代码的时间是很短的(也许执行到一半,后面的代码便不会执行),而后者则没有这些问题,因此我们肯定选择使用 bindService(这个时候你便同时在使用 startService 和 bindService 了,这在 Activity 中更新 Service 的某些运行状态是相当有用的)。
        • 如果你的服务只是公开一个远程接口,供连接上的客服端(android 的 Service 是C/S架构)远程调用执行方法。这个时候你可以不让服务一开始就运行,而只用 bindService ,这样在第一次 bindService 的时候才会创建服务的实例运行它,这会节约很多系统资源,特别是如果你的服务是Remote Service,那么该效果会越明显(当然在 Service 创建的时候会花去一定时间,你应当注意到这点)。

        注意:

        • Service不运行在一个独立的进程中,它同样执行在UI线程中,因此,在Service中创建了子线程来完成耗时操作。
        • 当Service关闭后,如果在onDestory()方法中不关闭线程,你会发现我们的子线程进行的耗时操作是一直存在的,此时关闭该子线程的方法需要直接关闭该应用程序。因此,在onDestory()方法中要进行必要的清理工作。
  2. Activity启动模式,应用场景

    Android提供了四种Activity启动方式:

    1. 标准模式(standard)

    2. 栈顶复用模式(singleTop)

    3. 栈内复用模式(singleTask)

    4. 单例模式(singleInstance)

      Activity的管理是采用任务栈的形式,任务栈采用“后进先出”的栈结构。

      标准模式:

      该模式也是系统的默认模式,当我们多次创建Activity时,系统会将创建的实例一一放入任务栈,不管它是否已经被创建过。该模式下,谁启动了这个Activity,它就运行在启动的它的Activity所在的栈中。

      例如:Activity A启动了Activity B,则就会在A所在的栈顶压入一个新的Activity。
      特殊情况,如果在Service或Application中启动一个Activity,其并没有所谓的任务栈,可以使用标记位Flag来解决。解决办法:为待启动的Activity指定FLAG_ACTIVITY_NEW_TASK标记位,创建一个新栈。

      应用场景: 绝大多数Activity。如果以这种方式启动的Activity被跨进程调用,在5.0之前新启动的Activity实例会放入发送Intent的Task的栈的顶部,尽管它们属于不同的程序,这似乎有点费解看起来也不是那么合理,所以在5.0之后,上述情景会创建一个新的Task,新启动的Activity就会放入刚创建的Task中,这样就合理的多了。

      栈顶复用模式:

      顾名思义,在这种模式下,如果有新的Activity已经存在任务栈的栈顶,那么此Activity就不会被重新创建新实例,而是复用已存在任务栈栈顶的Activity,并回调如下方法:

      @Override
      protected void onNewIntent(Intent intent) {
          super.onNewIntent(intent);
      }
      //通过此方法的参数,我们可以获取当前请求的相关信息,此时Activity的onCreate、onStart方法不会被调用,因为Activity并没有被重建。
      

      ps:如果栈顶不是新建的Activity,就会创建该Activity新的实例,并放入栈顶。

      应用场景:这种模式通常比较适用于接收到消息后显示的界面,比如在通知栏点击收到的通知,然后需要启动一个Activity,这个Activity就可以用singleTop,否则每次点击都会新建一个Activity。当然实际的开发过程中,测试妹纸没准给你提过这样的bug:某个场景下连续快速点击,启动了两个Activity。如果这个时候待启动的Activity使用singleTop模式也是可以避免这个Bug的。同standard模式,如果是外部程序启动singleTop的Activity,在Android 5.0之前新创建的Activity会位于调用者的Task中,5.0及以后会放入新的Task中。

      栈内复用模式:
      该模式是一种单例模式,即一个栈内只有一个该Activity实例。该模式,可以通过在AndroidManifest文件的Activity中指定该Activity需要加载到那个栈中,即singleTask的Activity可以指定想要加载的目标栈。singleTask和taskAffinity配合使用,指定开启的Activity加入到哪个栈中。

      <activity android:name=".Activity1"
          android:launchMode="singleTask"
          android:taskAffinity="com.lvr.task"
          android:label="@string/app_name">
      </activity>
      

      关于taskAffinity的值: 每个Activity都有taskAffinity属性,这个属性指出了它希望进入的Task。如果一个Activity没有显式的指明该Activity的taskAffinity,默认为应用的包名。

      执行逻辑:

      在这种模式下,如果Activity指定的栈不存在,则创建一个栈,并把创建的Activity压入栈内。如果Activity指定的栈存在,如果其中没有该Activity实例,则会创建Activity并压入栈顶,如果其中有该Activity实例,则把该Activity实例之上的Activity杀死清除出栈,重用并让该Activity实例处在栈顶,然后调用 onNewIntent()方法。

      应用场景: 大多数App的主页(频繁使用的主架构)。对于大部分应用,当我们在主界面点击回退按钮的时候都是退出应用,那么当我们第一次进入主界面之后,主界面位于栈底,以后不管我们打开了多少个Activity,只要我们再次回到主界面,都应该使用将主界面Activity上所有的Activity移除的方式来让主界面Activity处于栈顶,而不是往栈顶新加一个主界面Activity的实例,通过这种方式能够保证退出应用时所有的Activity都能报销毁。在跨应用Intent传递时,如果系统中不存在singleTask Activity的实例,那么将创建一个新的Task,然后创建SingleTask Activity的实例,将其放入新的Task中。

      单实例模式:

      作为栈内复用模式的加强版,打开该Activity时,直接创建一个新的任务栈,并创建该Activity实例放入新栈中。一旦该模式的Activity实例已经存在于某个栈中,任何应用再激活该Activity时都会重用该栈中的实例。

      应用场景:这个经常使用于系统中的应用,比如Launch、锁屏键、来电显示等等,整个系统中仅仅有一个呼叫来电界面。这种模式的使用情况比较罕见,在Launcher中可能使用。或者你确定你需要使Activity只有一个实例。

      启动模式的使用方式

      1. 在 Manifest.xml中指定Activity启动模式

        一种静态的指定方法,在Manifest.xml文件里声明Activity的同一时候指定它的启动模式,这样在代码中跳转时会依照指定的模式来创建Activity。样例以下:

        <activity
        android:launchMode="启动模式"
        //属性
        //standard:标准模式
        //singleTop:栈顶复用模式
        //singleTask:栈内复用模式
        //singleInstance:单例模式
        //如不设置,Activity的启动模式默认为**标准模式(standard)**
        </activity>
        
      2. 启动Activity时。在Intent中指定启动模式去创建Activity一种动态的启动模式,在new 一个Intent后,通过Intent的addFlags方法去动态指定一个启动模式。样例以下:

        Intent intent = new Intent();      
        intent.setClass(context, MainActivity.class);       intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);  
        startActivity(intent);     
        

      注意:以上两种方式都能够为Activity指定启动模式,可是二者还是有差别的。

      • 优先级:动态指定方式即第二种比第一种优先级要高,若两者同一时候存在,以第二种方式为准。
      • 限定范围:第一种方式无法为Activity直接指定 FLAG_ACTIVITY_CLEAR_TOP 标识,第二种方式无法为 Activity指定 singleInstance 模式。

      Activity 的 Flags

      标记位既能够设定Activity的启动模式,如同上面介绍的,在动态指定启动模式:比方 FLAG_ACTIVITY_NEW_TASK 和 FLAG_ACTIVITY_SINGLE_TOP 等。它还能够影响Activity 的运行状态 ,比方 FLAG_ACTIVITY_CLEAN_TOP 和 FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS 等。

      1. FLAG_ACTIVITY_NEW_TASK

        作用是为Activity指定 “SingleTask”启动模式。跟在AndroidMainfest.xml指定效果同样。

      2. FLAG_ACTIVITY_SINGLE_TOP

        作用是为Activity指定 “SingleTop”启动模式,跟在AndroidMainfest.xml指定效果同样。

      3. FLAG_ACTIVITY_CLEAN_TOP

        具有此标记位的Activity,当它启动时,在同一个任务栈中所有位于它上面的Activity都要出栈。如果和singleTask模式一起出现,若被启动的Activity已经存在栈中,则清除其之上的Activity,并调用该Activity的onNewIntent()方法。如果被启动的Activity采用standard模式,那么该Activity连同之上的所有Activity出栈,然后创建新的Activity实例并压入栈中。

      4. FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS

        具有此标记位的Activity不会出如今历史Activity的列表中,使用场景:当某些情况下我们不希望用户通过历史列表回到Activity时,此标记位便体现了它的效果。它等同于在xml中指定Activity的属性:

        android:excludeFromRecents=“trure”

  3. Fragment和Activity怎么通信

    1. 接口回调

      Fragmet

          private showMsgListener msgListener;
          public interface showMsgListener{
              void showMsg(String str);
          }
          @Override
          public void onAttach(Activity activity) {
              super.onAttach(activity);
              try {
                  if(activity!=null){
                      msgListener=(MainActivity)activity;
                  }
              } catch (ClassCastException e) {
                  throw new ClassCastException(activity.toString()
                          + " must implement showMsgListener");
              }
          }
      
              mButton.setOnClickListener(new View.OnClickListener() {
                  @Override
                  public void onClick(View view) {
                      msgListener.showMsg("Hello Android!(接口)");
                  }
              });
      

      在Activity实现接口

      //MainActivity实现MainFragment开放的接口 
      public class MainActivity extends FragmentActivity implements FragmentListener{ 
          @Override
          public void showMsg(String str) {
              Toast.makeText(MainActivity.this, str, Toast.LENGTH_SHORT).show();
          }
      } 
      

      这种方案应该是既能达到复用,又能达到很好的可维护性,并且性能也是杠杠的。但是唯一的一个遗憾是假如项目很大了,Activity与Fragment的数量也会增加,这时候为每对Activity与Fragment交互定义交互接口就是一个很头疼的问题(包括为接口的命名,新定义的接口相应的Activity还得实现,相应的Fragment还得进行强制转换)。

    2. setArguments(Bundle args)

      给Fragment添加newInstance方法,将需要的参数传入,设置到bundle中,然后setArguments(bundle),最后在Activity中getArguments()进行获取;

      Fragment

      public static Fragment getInstance(String msg){
              Bundle bundle = new Bundle();
              bundle.putString("msg",msg);
              MainFragment fragment=new MainFragment();
              fragment.setArguments(bundle);
              return fragment;
          }
      

      Activity

       public void initDatas() {
              Bundle bundle=getArguments();
              if(bundle!=null){
                  String msg=bundle.getString("msg");
                  Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT).show();
              }
          }
      
    3. 广播

      在Fragment中注册广播接收器

      public class Fragment3 extends BaseFragment {
          private MyReceiver myReceiver;
       
          @Override
          public void onResume() {
              super.onResume();
              myReceiver = new MyReceiver();
              IntentFilter intentFilter = new IntentFilter();
              intentFilter.addAction(activity.ACTION_NAME);
              //注册广播
              activity.registerReceiver(myReceiver, intentFilter);
          }
          //创建广播接收实例
          class MyReceiver extends BroadcastReceiver {
              @Override
              public void onReceive(Context context, Intent intent) {
                  //获取数据
                  if (action.equals(ACTION_NAME)) {
                  String number1 = intent.getStringExtra(activity.SaveData1);
                  String number2 = intent.getStringExtra(activity.SaveData2);
                  //进行数据操作
                  textResult.setText(doAddition(number1, number2));
                  }
              }
          }
       
          @Override
          public void onPause() {
              super.onPause();
              //取消注册广播
              activity.unregisterReceiver(myReceiver);
          }
      

      在Activity中发送广播

      //使用广播传递数据
      Intent intent = new Intent();
      intent.setAction(ACTION_NAME);
      intent.putExtra(SaveData1, number1);
      intent.putExtra(SaveData2, number2);
      sendBroadcast(intent);
      //使用本地广播更加安全
      

      用广播解决此问题有点大材小用了,个人感觉广播的意图是用在一对多,接收广播者是未知的情况

      • 广播性能肯定会差(不要和我说性能不是问题,对于手机来说性能是大问题)
      • 传播数据有限制(必须得实现序列化接口才可以)
    4. Handler

      比如Activity向Fragment传递数据,首先,保证进行通信的Activity和Fragment使用同一个Handler实例对象。Activity中设置全局 变量hander,并给出其Setter方法。在响应事件中,封装数据到message中,然后进行发送。

      //使用handler传递数据,在handler发送数据之前,注意需要先开启fragment,否则handler变量一直为空,因为此handler变量是fragment调用setHandler()传来的。
      if (handler == null) {
          return;
      }
      Message message = new Message();
       
      bundle.putString(SaveData1, number1);
      bundle.putString(SaveData2, number2);
      message.setData(bundle);
      handler.sendMessage(message);
      

      Activity中暴露的setter方法:

      //设置handler对象  由fragment调用,将hander设置进来
      public void setHandler(Handler handler) {
          this.handler = handler;
      }
      

      Fragment中要接收数据,所以要注册一个Handler来接收Message。同时,还要将handler对象设置给Activity。

      onAttach中的activity是在基类BaseFragment中设置的,子类直接用了。

      @Override
      public void onAttach(Context context) {
          super.onAttach(context);
          //设置Activity的handler对象
          //为的是activity和fragment使用同一个handler对象 才能进行handler通信
          FrgmentActivity activity = (FrgmentActivity) context;//防止getActivity()空指针
          activity.setHandler(handler);
      }
       
      //定义handler 用于接收消息 接收activity传递来的数据
      private Handler handler = new Handler() {
          @Override
          public void handleMessage(Message msg) {
              super.handleMessage(msg);
              Bundle bundle = msg.getData();
              String number1 = bundle.getString("number1");
              String number2 = bundle.getString("number2");
              //进行数据操作
              textResult.setText(doAddition(number1, number2));
          }
      

      因为要在Fragment的生命周期onAttach中设置Activity的handler对象,所以传递数据发送消息,一定要在Fragment加载之后,即生命周期从onAttach走到onResume。否则Activity中的handler对象始终为null。

      该方案存在的缺点:

      • Fragment对具体的Activity存在耦合,不利于Fragment复
      • 不利于维护,若想删除相应的Activity,Fragment也得改动
      • 没法获取Activity的返回数据
  4. Service的两种启动模式和区别

    • Service第一种启动方式

      生命周期:onCreate()--->onStartCommand()(onStart()方法已过时) ---> onDestory()

      使用步骤:

      1. 定义一个类继承Service

      2. 在Manifest.xml文件中配置该Service

      3. 使用Context的startService(Intent)方法启动该Service

      4. 不再使用时,调用stopService(Intent)方法停止该服务

        说明:如果服务已经开启,不会重复的执行onCreate(), 而是会调用onStart()和onStartCommand()。服务停止的时候调用onDestory()。服务只会被停止一次。

        特点:一旦服务开启跟调用者(开启者)就没有任何关系了。开启者退出了,服务还在后台长期的运行。在多次调用startService的时候,onCreate不重复执行,但是onStartCommand会执行。startService调用了这后,会一直存在,直到其调用了stopService。开启者不能调用服务里面的方法。

        <img src="https://note.youdao.com/yws/public/resource/3c8c773a3d105f8e5fa5b9d7a0ef31ad/xmlnote/484DF13C4E51456186A6BBBEAE9A2952/3462" alt="0" style="zoom: 50%;" />

        启动的步骤:

        1. Activity向AMS,即ActivityManagerService请求启动Service。
        2. AMS判断Service所在的进程是否已经创建,注意Service和Activity是可以同个进程的。
        3. 如果还没创建则通过Zygote进程fork一个进程。
        4. AMS请求Service所在进程的ActivityThread创建Service和启动,并回调Service的onCreate方
        5. Service创建完成之后,AMS再次请求ActivityThread进行相关操作并回调onStartCommand方法。
    • Service第二种启动方式

      生命周期:onCreate() --->onBind()--->onUnbind()--->onDestory()

      使用步骤:

      1. 定义一个类继承Service

      2. 在Manifest.xml文件中配置该Service

      3. 使用Context的bindService(Intent, ServiceConnection, int)方法启动该Service

      4. 不再使用时,调用unbindService(ServiceConnection)方法停止该服务

        注意:绑定服务不会调用onstart()或者onstartcommand()方法

        特点:bind的方式开启服务,绑定服务,调用者挂了,服务也会跟着挂掉。多次调用bindService,onCreate及onBind都只执行一次。它生命周期跟随其调用者,调用者释放的时候,必须对该Service解绑,当所有绑定全部取消后,系统即会销毁该服务。绑定者可以调用服务里面的方法。bindService 的方式通过onServiceConnected方法,获取到Service对象,通过该对象可以直接操作到Service内部的方法,从而实现的Service 与调用者之间的交互。

        <img src="https://note.youdao.com/yws/public/resource/3c8c773a3d105f8e5fa5b9d7a0ef31ad/xmlnote/165EC320DCE54868A7F442CCF2C2DD41/3463" alt="0" style="zoom:50%;" />

        启动的步骤:

        1. Activity请求AMS绑定Service。

        2. AMS判断Service是否已经启动了。如果还没启动会先去启动Service。

        3. AMS调用Service的onBind方法获取Service的IBinder对象。

        4. Service把自己的IBinder对象发布到AMS中保存,这样下次绑定就不需要再调用onBind了。

        5. 把拿到的IBinder对象返回给Activity。

        6. Activity可以直接通过这个IBinder对象访问Service。

          使用场景:

          • 如果想要启动一个后台服务长期进行某项任务,那么使用startService

          • 如果只是短暂的使用,那么使用bindService。

          • 如果想启动一个后台服务长期进行任务,且这个过程中需要与调用者进行交互,那么可以两者同时使用,或者使用startService + BoardCast/ EventBus 等方法。

            对于既使用startService,又使用bindService的情况,结束服务时需要注意的事项:

            Service的终止,需要unbindService和stopService都调用才行;

          补充

          • 前台服务和后台服务

            <img src="https://note.youdao.com/yws/public/resource/328432cea4f2eeddc18f0ca0446558d9/xmlnote/AB97989FCD664966BBEB3239B29822D9/23458" alt="0" style="zoom: 67%;" />

          • 远程服务和本地服务

            远程服务与本地服务最大的区别是:远程Service与调用者不在同一个进程里(即远程Service是运行在另外一个进程);而本地服务则是与调用者运行在同一个进程里

            <img src="https://img.haomeiwen.com/i944365/843b2b4e2988ff66.png?imageMogr2/auto-orient/strip|imageView2/2/w/1000/format/webp" alt="img" style="zoom: 80%;" />

  5. 广播有哪几种,广播注册方式?(底层原理是什么?Binder机制)

    1. 定义

      • BroadcastReceiver(广播接收器),属于Android四大组件之一。这种组件本质上是一个全局监听器,用于监听系统全局的广播消息。
      • Android广播分为广播发送者、广播接收者
    2. 作用

      • 用于监听 / 接收 应用发出的广播消息,并做出响应

        应用场景:

        1. 不同组件之间通信(包括应用内 / 不同应用之间)
        2. 与 Android 系统在特定情况下的通信:电话呼入、网络可用时等
        3. 多线程通信
    3. 实现原理

      底层原理分析参考:https://www.jianshu.com/p/02085150339c

      • Android中的广播使用了设计模式中的观察者模式:基于消息的发布/订阅事件模型。

        因此,Android将广播的发送者 和 接收者 解耦,使得系统方便集成,更易扩展

      • 模型中有3个角色:

        1. 消息订阅者(广播接收者)
        2. 消息发布者(广播发布者)
        3. 消息中心(AMS)
      • 原理描述:

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

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

        3. AMS 根据 广播发送者 要求,在已注册列表中,寻找合适的广播接收者

          寻找依据:IntentFilter / Permission

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

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

        补充

        • 广播队列传送广播给Receiver的原理:其实就是将BroadcastReceiver和消息都包装到BroadcastRecord里面,然后通过Handler机制遍历BroadcastQueue里面的BroadcastRecord,将消息发送给BroadcastReceiver:

        • 广播发送者 和 广播接收者的执行 是 异步的,发出去的广播不会关心有无接收者接收,也不确定接收者到底是何时才能接收到;

        • 默认情况下,广播接收器运行在UI线程,因此,onReceive方法不能执行耗时操作,否则将导致ANR。

        • 广播接收器接收到相应广播后,会自动回调onReceive()方法

        • 一般情况下,onReceive方法会涉及与其他组件之间的交互,如发送Notification、启动service等

    4. 广播分类

      1. 标准广播(即自定义广播,通常调用sendBroadcast(Intent)(Intent, String)方法发送)

        完全执行异步的广播,广播发出后,所有的BroadcastReceiver几乎都可以同一时刻收到这个广播,没有先后顺序可言。

      2. 有序广播(sendOrderedBroadcast(Intent, String)方法发送)

        同步执行的广播,广播发出后,只有一个BroadcastReceiver能够收到广播,当这个接收者处理好逻辑后,才会继续传递,优先级高的可以先收到广播。

      3. 本地广播(调用LocalBroadcastManager.sendBroadcast(intent)方法发送)

        仅在自己的应用内发送接收广播,也就是只有自己的应用能收到,数据更加安全,效率更高,但只能采用动态注册的方式。

        LocalBroadcastManager部分源码解析:

        private static LocalBroadcastManager mInstance;
        
        public static LocalBroadcastManager getInstance(Context context) {
            synchronized (mLock) {
                if (mInstance == null) {
                    mInstance = new LocalBroadcastManager(context.getApplicationContext());
                }
                return mInstance;
            }
        }
        
        private LocalBroadcastManager(Context context) {
            mAppContext = context;
            mHandler = new Handler(context.getMainLooper()) {
        
                @Override
                public void handleMessage(Message msg) {
                    switch (msg.what) {
                        case MSG_EXEC_PENDING_BROADCASTS:
                            executePendingBroadcasts();
                            break;
                        default:
                            super.handleMessage(msg);
                    }
                }
            };
        } 
        
        • 在获取LocalBroadcastManager对象实例的时候,这里用了单例模式。并且把外部传进来的Context 转化成了ApplicationContext,有效的避免了当前Context的内存泄漏的问题。这一点我们在设计单例模式框架的时候是值得学习的,看源码可以学习到很多东西。
        • 在LocalBroadcastManager构造函数中创建了一个Handler.可见 LocalBroadcastManager 的本质上是通过Handler机制发送和接收消息的。
      4. 系统广播(发生各种事件时,系统自动发送)
        Android中内置了多个系统广播:只要涉及到手机的基本操作(如开机、网络状态变化、拍照等等),都会发出相应的广播。

      5. 粘性广播(Android5.0已失效)
        这种广播会一直滞留,当有匹配该广播的接收器被注册后,该接收器就会收到此条广播。

    5. 注册方式

      静态注册、动态注册

      1. 第一种是静态注册,也可成为常驻型广播,这种广播需要在 Androidmanifest.xml 中进行注册,这种方式注册的广播,不受页面生命周期的影响,即使退出了页面,也可以收到广播这种广播一般用于想开机自启动啊等等,由于这种注册的方式的广播是常驻型广播,所以会占用 CPU 的资源。

        <receiver
          android:enabled=["true" | "false"]
          //此broadcastReceiver能否接收其他App的发出的广播
          //默认值是由receiver中有无intent-filter决定的:如果有intent-filter,默认值为true,否则为false
          android:exported=["true" | "false"]
          android:icon="drawable resource"
          android:label="string resource"
          //继承BroadcastReceiver子类的类名
          android:name=".mBroadcastReceiver"
          //具有相应权限的广播发送者发送的广播才能被此BroadcastReceiver所接收;
          android:permission="string"
          //BroadcastReceiver运行所处的进程
          //默认为app的进程,可以指定独立的进程
          //注:Android四大基本组件都可以通过此属性指定自己的独立进程
          android:process="string" >
        
          //用于指定此广播接收器将接收的广播类型
          //本示例中给出的是用于接收网络状态改变时发出的广播
          <intent-filter>
            <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
          </intent-filter>
        </receiver>
        

        当此App首次启动时,系统会自动实例化mBroadcastReceiver类,并注册到系统中。

        适应场景:需要时刻监听广播

      2. 动态注册

        动态注册的话,是在代码中注册的,这种注册方式也叫非常驻型广播,收到生命周期的影响,退出页面后,就不会收到广播,我们通常运用在更新 UI 方面。这种注册方式优先级较高。最后需要解绑,否则会内存泄露。

        @Override
        protected void onResume() {
            super.onResume();
            //实例化BroadcastReceiver子类 &  IntentFilter
            mBroadcastReceiver mBroadcastReceiver = new mBroadcastReceiver();
            IntentFilter intentFilter = new IntentFilter();
            //设置接收广播的类型
            intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);
            //调用Context的registerReceiver()方法进行动态注册
            registerReceiver(mBroadcastReceiver, intentFilter);
        }
        //注册广播后,要在相应位置记得销毁广播
        //即在onPause() 中unregisterReceiver(mBroadcastReceiver)
        //当此Activity实例化时,会动态将MyBroadcastReceiver注册到系统中
        //当此Activity销毁时,动态注册的MyBroadcastReceiver将不再接收到相应的广播。
        @Override
        protected void onPause() {
            super.onPause();
            //销毁在onResume()方法中的广播
            unregisterReceiver(mBroadcastReceiver);
        }
        

        适应场景:需要特定时刻监听广播

        注意:

        • Android 8.0 后所有隐式广播不允许使用静态注册的方式来接收(隐式广播,没有具体指定发给哪个应用的广播)

        • 动态广播最好在Activity的onResume()注册、onPause()注销。

          原因: 对于动态广播,有注册就必然得有注销,否则会导致内存泄露

    6. 权限问题

      • 谁可以接收到我的广播

        1. 在发送者的Manifest中设置权限

          <permission android:name="com.example.broadcastreceiver.MY_BROADCAST"/>
          
        2. 在Sender app发送广播时将此权限作为参数传入

          sendOrderedBroadcast(intent,Manifest.permission.MY_BROADCAST,null,null,Activity.RESULT_OK,null,bundle);
          
        3. 接收者需要在Manifest中设置收听权限

          <uses-permission android:name="com.example.broadcastreceiver.MY_BROADCAST"/>
          
      • 谁可以发广播给我

        1. 需要在接收者的Manifest中设置权限

          <permission android:name="com.example.broadcastreceiver.MY_BROADCAST"/>
          
        2. <receiver android:name=".MyReceiver" 
           android:permission="com.example.broadcastreceiver.MY_BROADCAST">
           <intent-filter>
                <action android:name="com.android.XXX_ACTION" /> 
           </intent-filter>
          </receiver>
          
        3. 在广播发送者的Manifest中设置权限

          <uses-permission android:name="com.example.broadcastreceiver.MY_BROADCAST"/>
          
                                                                         广播队列
          
          img
                                                                                       广播机制
          
          img
  1. Binder机制

    参考资料:https://blog.csdn.net/carson_ho/article/details/73560642

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

    • Linux基础知识:

      <img src="https://note.youdao.com/yws/public/resource/328432cea4f2eeddc18f0ca0446558d9/xmlnote/15B6C91FB1FB4476A3B6B321172502D6/23493" alt="https://note.youdao.com/yws/public/resource/328432cea4f2eeddc18f0ca0446558d9/xmlnote/15B6C91FB1FB4476A3B6B321172502D6/23493" style="zoom: 80%;" />

      一个进程空间分为用户空间和内核空间,即把进程内 用户 和 内核 隔离开,用户空间的数据不可共享,内核空间的数据是可共享的,所有进程共用一个内核空间。用户空间与内核空间之间通过系统调用 copy_from_user()将用户空间的数据拷贝到内核空间, copy_to_user() 将内核空间的数据拷贝到用户空间。需要做2次数据的拷贝,才能实现进程间的一个通信。

    • Binder

      在 Android 系统的 Binder 机制中,是有Client,Service,Service Manager,Binder驱动程序组成的, 其中 Client,service,Service Manager 运行在用户空间,Binder 驱动程序是运行在内核空间的。而 Binder 就是把这 4 种组件粘合在一块的粘合剂,其中核心的组件就是 Binder 驱动程序,Binder 驱动的作用就是连接Service进程、Client进程和Service Manger的桥梁,具体实现原理是内存映射,即内部是调用了 mmap()函数。

      • 四个组件

        示意图
      • Binder驱动

        <img src="https://img-blog.csdnimg.cn/img_convert/8c0e36777260414f06c36fb0b312d423.png" alt="示意图" style="zoom:50%;" />

      • Binder 跨进程通信机制模型基于 Client - Server 模式

        <img src="https://img-blog.csdnimg.cn/img_convert/bd3121ba7ece9f2822b036e930d5124e.png" alt="示意图" style="zoom: 67%;" />

      • 通信过程

        Binder 通信模型
      • Binder实现原理

        示意图
        1. 首先Binder驱动创建一块 接收缓存区
        2. 实现地址映射关系,就是根据接收进程的信息,实现 内核缓存区 和 接收进程用户地址 同时映射到 同一个共享接收缓存区(就是第1步创建的接收缓存区)
        3. 发送进程通过系统调用copy_from_user()发送数据到虚拟内存映射(完成一次数据拷贝)
        4. 由于 内核缓存区 和 接收进程的用户空间地址 存在映射关系,就是同时映射到Binder创建的接收缓存区中,就相当于也发送到了接收进程的用户空间地址,从而实现跨进程通信。
      • 优点

        示意图

        补充:

        • PID是进程的身份标识,程序一旦运行,就会给应用分配一个独一无二的PID
        • UID在linux中就是用户的ID,因为Android为单用户系统,这时UID 便被赋予了新的使命,数据共享,Android为每个应用几乎都分配了不同的UID。(对于普通应用程序来说, GID=UID)
  2. ContentProvider是什么?(底层原理是什么?Binder机制+匿名共享内存)

    参考资料:https://blog.csdn.net/carson_ho/article/details/76101093

    内容提供者(ContentProvider)主要用于在不同的应用程序之间实现数据共享的功能,允许一个程序访问另一个程序中的数据,同时还能保证被访数据的安全性,底层是Binder实现的。它的使用过程相比AIDL简单很多,因为系统已经帮我们做了一次封装。对于系统应用的一些信息,只需要通过ContentResolver的CRUD就可以即可。对于自定义的ContentProvider,需要实现6个抽象方法,onCreate、CRUD、getType,onCreate代表ContentProvider的创建,由系统回调并运行在主线程里,其他方法是运行在Binder线程池之中的。ContentProvider主要以表格的形式来组织数据,并且可以包含多个表;还支持文件数据,比如图片、视频等,系统提供的MediaStore就是文件类型的ContentProvider;ContentProvider对底层的数据存储方式没有任何要求,可以是SQLite、文件,甚至是内存中的一个对象都行;要观察ContentProvider中的数据变化情况,可以通过ContentResolver的registerContentObserver方法来注册观察者,unregisterContentObserver来解除观察者。

    1. 统一资源标识符(URI)

      • 定义:Uniform Resource Identifier,即统一资源标识符

      • 作用:唯一标识 ContentProvider & 其中的数据,外界进程通过 URI 找到对应的 ContentProvider & 其中的数据,再进行数据操作

      • 具体使用:URI分为 系统预置 & 自定义,分别对应系统内置的数据(如通讯录、日程表等等)和自定义数据库

        示意图
        // 设置URI
        Uri uri = Uri.parse("content://com.carson.provider/User/1") 
        // 上述URI指向的资源是:名为 `com.carson.provider`的`ContentProvider` 中表名 为`User` 中的 `id`为1的数据
        
    2. MIME数据类型

      • 作用:指定某个扩展名的文件用某种应用程序来打开
        如指定 .html 文件采用 text 应用程序打开、指定 .pdf 文件采用 flash 应用程序打开

      • ContentProvider 根据 URI 返回 MIME 类型

        ContentProvider.geType(uri) ;
        
      • MIME组成

        由2部分组成 = 类型 + 子类型:text / html 类型 = text、子类型 = html

    3. ContentProvider是安卓四大组件之一,所以它和Activity一样也需要我们再xml文件中声明,声明如下:

      <provider       
      android:authorities="com.jrmf360.studentProvider"    
      android:name=".StudentContentProvider"  
      exported="true"  
      

      这里的authorities唯一标识该内容提供者,这样其它的应用才可以找到该内容提供者并操作它的数据;exported为true当前内容提供者可以被其它应用使用。

    4. ContentResolver类

      统一管理不同 ContentProvider 间的操作,外部进程通过 ContentResolver类 从而与ContentProvider类进行交互

      一般来说,一款应用要使用多个ContentProvider,若需要了解每个ContentProvider的不同实现从而再完成数据交互,操作成本高 & 难度大,所以再ContentProvider类上加多了一个 ContentResolver 类对所有的ContentProvider进行统一管理。

      使用:

      // 使用ContentResolver前,需要先获取ContentResolver
      // 可通过在所有继承Context的类中 通过调用getContentResolver()来获得ContentResolver
      ContentResolver resolver =  getContentResolver(); 
      
      // 设置ContentProvider的URI
      Uri uri = Uri.parse("content://cn.scu.myprovider/user"); 
       
      // 根据URI 操作 ContentProvider中的数据
      // 此处是获取ContentProvider中 user表的所有记录  insert delete update query
      Cursor cursor = resolver.query(uri, null, null, null, "userid desc"); 
      

      Android 提供了3个用于辅助ContentProvide的工具类:

      • ContentUris

        操作URI

        // withAppendedId()作用:向URI追加一个id
        Uri uri = Uri.parse("content://cn.scu.myprovider/user") 
        Uri resultUri = ContentUris.withAppendedId(uri, 7);  
        // 最终生成后的Uri为:content://cn.scu.myprovider/user/7
        
        // parseId()作用:从URL中获取ID
        Uri uri = Uri.parse("content://cn.scu.myprovider/user/7") 
        long personid = ContentUris.parseId(uri); 
        //获取的结果为:7
        
      • UriMatcher

        在 ContentProvider 中注册 URI,根据 URI 匹配 ContentProvider 中对应的数据表

        // 步骤1:初始化UriMatcher对象
            UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH); 
            //常量UriMatcher.NO_MATCH  = 不匹配任何路径的返回码
            // 即初始化时不匹配任何东西
        
        // 步骤2:在ContentProvider 中注册URI(addURI())
            int URI_CODE_a = 1;
            int URI_CODE_b = 2;
            matcher.addURI("cn.scu.myprovider", "user1", URI_CODE_a); 
            matcher.addURI("cn.scu.myprovider", "user2", URI_CODE_b); 
            // 若URI资源路径 = content://cn.scu.myprovider/user1 ,则返回注册码URI_CODE_a
            // 若URI资源路径 = content://cn.scu.myprovider/user2 ,则返回注册码URI_CODE_b
        
        // 步骤3:根据URI 匹配 URI_CODE,从而匹配ContentProvider中相应的资源(match())
        
        @Override   
            public String getType(Uri uri) {   
              Uri uri = Uri.parse(" content://cn.scu.myprovider/user1");   
        
              switch(matcher.match(uri)){   
             // 根据URI匹配的返回码是URI_CODE_a
             // 即matcher.match(uri) == URI_CODE_a
              case URI_CODE_a:   
                return tableNameUser1;   
                // 如果根据URI匹配的返回码是URI_CODE_a,则返回ContentProvider中的名为tableNameUser1的表
              case URI_CODE_b:   
                return tableNameUser2;
                // 如果根据URI匹配的返回码是URI_CODE_b,则返回ContentProvider中的名为tableNameUser2的表
            }   
        }
        
      • ContentObserver

        观察 URI 引起 ContentProvider 中的数据变化并通知外界(即访问该数据访问者)

        // 步骤1:注册内容观察者ContentObserver
            getContentResolver().registerContentObserver(uri);
            // 通过ContentResolver类进行注册,并指定需要观察的URI
        
        // 步骤2:当该URI的ContentProvider数据发生变化时,通知外界(即访问该ContentProvider数据的访问者)
            public class UserContentProvider extends ContentProvider { 
              public Uri insert(Uri uri, ContentValues values) { 
              db.insert("user", "userid", values); 
              getContext().getContentResolver().notifyChange(uri, null); 
              // 通知访问者
           } 
        }
        
        // 步骤3:解除观察者
         getContentResolver().unregisterContentObserver(uri);
        // 同样需要通过ContentResolver类进行解除
        
  3. 进程通信方式,进程与线程的区别?Android如何启动多线程

    1. 为什么进程间要通信

      • 数据传输:一个进程需要将它的数据发送给另一个进程。
      • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
      • 资源共享:多个进程之间共享同样的资源。为了做到这一点,需要内核提供互斥和同步机制。
      • 进程控制:有些进程希望完全控制另一个进程的执行(如 Debug 进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
    2. 进程通信方式

      1. 使用 Bundle/Intent传递数据

        四大组件的三大组件(Activity、Service、Receiver)都是支持在Intent中传递Bundle数据的,由于Bundle实现了Parcelable接口,所以它可以方便的支持在不同的进程间传输。

      2. 使用 文件共享

        两个进程通过读/写同一个文件来交换数据。Android系统是基于Linux,使得其并发读写文件可以没有限制的进行,甚至两个线程同时对一个文件进行操作都是可以的,所以说,文件共享方式适合在对数据同步要求不高的进程之间进行通信,并且需要妥善处理好并发读写问题。顺便一提:Windows下不支持并发读或写。

      3. 使用 Messenger(基于Binder)

        一种轻量级的IPC方案,它的底层实现就是AIDL。Messager是以串行的方式处理客户端发来的消息,如果有大量的消息同时发送给服务器,服务器只能一个个处理。双方用Messenger来发送数据,用Handler来处理数据。Messenger处理数据依靠Handler,所以是串行的,也就是说,Handler接到多个message时,就要排队依次处理。

        • 服务端进程

          Messenger是基于AIDL实现的,服务端(被动方)创建一个Service来处理客户端(主动方)连接,同时创建一个Handler并通过它来创建一个Messenger对象,然后在Service的 onBind() 时返回Messenger对象底层的Binder即。

        • 客户端进程

          首先需要绑定服务端的Service,绑定成功后用服务端返回的IBinder对象创建一个Messager,通过这个Messager就可以向服务端发送消息了。如果需要服务端能够回应客户端,就和服务端一样,我们还需要创建一个Handle并创建一个新的Messager,并把这个Messager对象通过Message的replyTo参数传递给服务端,服务端通过这个replyTo参数就可以回应客户端。
          Messager是以串行的方式处理客户端发来的消息,如果有大量的消息同时发送给服务器,服务器只能一个个处理。双方用Messenger来发送数据,用Handler来处理数据。Messenger处理数据依靠Handler,所以是串行的,也就是说,Handler接到多个message时,就要排队依次处理。

      4. 使用 AIDL(基于Binder)

        服务端首先要创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可,具体的实现是写在Stub中,用Binder对象传递给客户端。

        客户端首先需要绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转换为AIDL接口所属的类型,用asInterface的形式,接着就可以调用AIDL中的方法了。

        • AIDL支持的数据类型:基本数据类型、String和CharSequence、ArrayList、HashMap、Parcelable以及AIDL;
        • 某些类即使和AIDL文件在同一个包中也要显式import进来;
        • AIDL中除了基本数据类,其他类型的参数都要标上方向:in、out或者inout;
        • AIDL接口中支持方法,不支持声明静态变量;
        • 为了方便AIDL的开发,建议把所有和AIDL相关的类和文件全部放入同一个包中,这样做的好处是,当客户端是另一个应用的时候,可以直接把整个包复制到客户端工程中。
        • RemoteCallbackList是系统专门提供的用于删除跨进程Listener的接口。RemoteCallbackList是一个泛型,支持管理任意的AIDL接口,因为所有的AIDL接口都继承自IInterface接口。
      5. 使用 ContentProvider(基于Binder)

        系统四大组件之一,专门提供用于不同应用间进行数据共享的方式,底层也是Binder实现,但是它的使用过程比AIDL简单很多,因为系统已经帮我们做了一次封装。对于系统应用的一些信息,只需要通过ContentResolver的CRUD就可以即可。对于自定义的ContentProvider,需要实现6个抽象方法,onCreate、CRUD、getType,onCreate代表ContentProvider的创建,由系统回调并运行在主线程里,其他方法是运行在Binder线程池之中的。getType()用来返回一个Uri请求所对应的MIME类型,比如图片、视频等。 ContentProvider主要以表格的形式来组织数据,并且可以包含多个表;还支持文件数据,比如图片、视频等,系统提供的MediaStore就是文件类型的ContentProvider;ContentProvider对底层的数据存储方式没有任何要求,可以是SQLite、文件,甚至是内存中的一个对象都行;要观察ContentProvider中的数据变化情况,可以通过ContentResolver的registerContentObserver方法来注册观察者,unregisterContentObserver来解除观察者。

      6. 使用 Socket

        套接字,分为流式套接字和用户数据报套接字两种,分别对应于网络的传输控制层中TCP和UDP协议。需要注意,Android不允许在主线程中请求网络,而且请求网络必须要注意声明相应的permission。然后,在服务器中定义ServerSocket来监听端口,客户端使用Socket来请求端口,连通后就可以进行通信。

        • TCP协议是面向连接的协议,提供稳定的双向通信功能,TCP连接的建立需要经过"三次握手"才能完成,为了提供稳定的数据传输功能,其本身提供了超时重传功能,因此具有很高的稳定性。
        • UDP是无连接的,提供不稳定的单向通信功能,当然UDP也可以实现双向通信功能,在性能上,UDP具有更好的效率,其缺点是不保证数据能够正确传输,尤其是在网络拥塞的情况下。
      https://note.youdao.com/yws/public/resource/328432cea4f2eeddc18f0ca0446558d9/xmlnote/7D78A97B9AF24E6B94871208D4372247/23490

      补充:

      Binder连接池

      当项目规模很大的时候,创建很多个Service是不对的做法,因为service是系统资源,太多的service会使得应用看起来很重,所以最好是将所有的AIDL放在同一个Service中去管理。整个工作机制是:每个业务模块创建自己的AIDL接口并实现此接口,这个时候不同业务模块之间是不能有耦合的,所有实现细节我们要单独开来,然后向服务端提供自己的唯一标识和其对应的Binder对象;对于服务端来说,只需要一个Service,服务端提供一个queryBinder接口,这个接口能够根据业务模块的特征来返回相应的Binder对象给它们,不同的业务模块拿到所需的Binder对象后就可以进行远程方法调用了。

      Binder连接池的主要作用就是将每个业务模块的Binder请求统一转发到远程Service去执行,从而避免了重复创建Service的过程。

      建议在AIDL开发工作中引入BinderPool机制。当新业务模块加入新的AIDL,那么在它实现自己的AIDL接口后,只需要修改BinderPoolImpl中的queryBinder方法,给自己添加一个新的binderCode并返回相对应的Binder对象即可,不需要添加新的Service。

    3. 进程与线程的区别

      img
      • 进程是系统进行资源分配和调度的一个独立单位, 线程是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位,最小的 CPU 执行单元。
      • 地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。
      • 资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的资源是独立的。
      • 执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制
      • 进程切换时,消耗的资源大,效率不高。所以涉及到频繁的切换时,使用线程要好于进程。
      • 作用:进程,使多个程序并发执行,以提高系统的资源利用率和吞吐量;线程,减少程序在并发执行时所付出的时空开销,提高操作系统的并发性能。
      • 一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。

      如果是出于面试的角度,我们需要关注的点有哪些呢?

      进程的执行过程是线状的,尽管中间会发生中断或暂停,但该进程所拥有的资源只为该线状执行过程服务。一旦发生进程上下文切换,这些资源都是要被保护起来的。线程的改变只代表了 CPU 执行过程的改变,而没有发生进程所拥有的资源变化。计算机内的软硬件资源的分配与线程无关,线程只能共享它所属进程的资源。进程拥有一个完整的虚拟地址空间,不依赖于线程而独立存在;反之,线程是进程的一部分,没有自己的地址空间,与进程内的其他线程一起共享分配给该进程的所有资源。线程中执行时一般都要进行同步和互斥,因为他们共享同一进程的所有资源。

    4. Android如何启动多线程

      在Android中使用多进程只有一种方法,那就是在AndroidManifest中给四大组件指定 android:process 属性。除此之外没有其他的办法,也就是说我们无法给一个线程活一个实体类指定其运行时所在的进程。其实还有另一种非常规的多进程方法,那就是通过JNI在native层去fork一个新的进程,但这种方法属于特殊情况,并不是常用的创建多进程的方式,所以我们也暂不考虑这种情况。

    5. 进程优先级

      在安卓系统中:当系统内存不足时,Android系统将根据进程的优先级选择杀死一些不太重要的进程,优先级低的先杀死。进程优先级从高到低如下。

      1. 前台进程

        • 处于正在与用户交互的activity
        • 与前台activity绑定的service
        • 调用了startForeground()方法的service
        • 正在执行oncreate(),onstart(),ondestroy方法的 service。
        • 进程中包含正在执行onReceive()方法的BroadcastReceiver。

        系统中的前台进程并不会很多,而且一般前台进程都不会因为内存不足被杀死。特殊情况除外。当内存低到无法保证所有的前台进程同时运行时,才会选择杀死某个进程。

      2. 可视进程

        为处于前台,但仍然可见的activity(例如:调用了onpause()而还没调用onstop()的activity)。典型情况是:运行activity时,弹出对话框(dialog等),此时的activity虽然不是前台activity,但是仍然可见

        • 可见activity绑定的service。(处于上诉情况下的activity所绑定的service)

        可视进程一般也不会被系统杀死,除非为了保证前台进程的运行不得已而为之。

      3. 服务进程

        • 已经启动的service
      4. 后台进程

        • 不可见的activity(调用onstop()之后的activity)

        后台进程不会影响用户的体验,为了保证前台进程,可视进程,服务进程的运行,系统随时有可能杀死一个后台进程。当一个正确实现了生命周期的activity处于后台被杀死时,如果用户重新启动,会恢复之前的运行状态。

      5. 空进程

        • 任何没有活动的进程

        系统会杀死空进程,但这不会造成影响。空进程的存在无非为了一些缓存,以便于下次可以更快的启动。

  4. parcelable与serializable对比

    1. Serializable

      java自带,写到持久化存储单元里的。对象继承 Serializable 类即可实现序列化,就是这么简单,也是它最吸引我们的地方

    2. Parcelable

      Android专用,写到内存里的。它的实现原理是将一个完整的对象进行分解,分解后的每一部分都是 Intent 所支持的数据类型,这样实现传递对象的功能。

    Serializable 是 Java 中的序列化接口,其使用起来简单但是开销很大,因为它的序列化和反序列化过程需要大量I/O操作。而Parcelable 是 Android 中的序列化,因此更适合用在 Android 平台上,它的缺点就是使用起来稍微麻烦点,但是它的效率是非常高的,这也是 Android 推荐的序列化方式,因此我们要首选 Parcelable。 Parcelable主要用在内存序列上,当然通过 Parcelable 将对象序列化到存储设备中或者将对象序列化后通过网络传输也都是可以的,但是这个过程会稍显复杂,因此在这两种情况下建议使用 Serializable ,以上就是 Parcelahle和 Serializable 的区别。

    此外区别:

    1. Android 上尽量采用 Parcelable,因为Parcelable它是直接写道内存里的,效率远高于 Serializable 的
    2. Serializable 是写到持久化存储单元里的,本质上使用了反射,序列化过程慢。其次Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的 GC。
    3. Parcelable 不能使用在要将数据存储在磁盘上的情况,因为 Parcelable 在外界有变化的情况下,不能很好的保证数据的持续性。尽管 Serializable 效率低点,但此时还是建议使用 Serializable 。

    补充:

    • Serializable方法序列化有一个serialVersionUID,用来辅助序列化和反序列化过程的,原则上,被序列化后的数据中的serialVersionUID与当前类的serialVersionUID相同时,才能够正常的被反序列化。
    • Parcelable 实现序列化的重要方法:序列化功能是由 writeToParcel 完成,通过 Parcel 中 的 write 方法来完成;反序列化由 CREATOR 完成,内部标明了如何创建序列化对象及数组的, 通过 Parcel 的 read 方法完成反序列化过程;内容描述功能由 describeContents 方法完成,一般直接返回 0。
    • 静态成员变量属于类,不属于对象,固不会参与序列化的过程;
      用 transient 关键字编辑的成员变量不会参与序列化过程;
      可以通过重写 writeObject()和 readObject()方法来 重写系统默认的序列化和反序列化。
    • 序列化:将一个对象转换成可存储或可传输的状态,序列化后的对象可以在网络上传输,也可以存储到本地,或实现跨进程传输;
    • 为什么要进行序列化?
      开发过程中,我们需要将对象的引用传给其他 activity 或 fragment。使用时,需要将这些对象放到一个 Intent 或 Bundle 中,再进行传递,而 Intent 或 Bundle 只能识别基本数据类型和被序列化的类型。
上一篇下一篇

猜你喜欢

热点阅读