Android 12 源码分析 —— 应用层 二(SystemUI大体组织和启动过程)

news2024/11/24 0:08:56

Android 12 源码分析 —— 应用层 二(SystemUI大体组织和启动过程)

在前一篇文章中,我们介绍了SystemUI怎么使用IDE进行编辑和调试。这是分析SystemUI的最基础,希望读者能尽量掌握。

本篇文章,将会介绍SystemUI的大概组织架构,以及它的启动过程。本篇文章读完,将会知道:

  1. SystemUI为什么选择使用Dagger2
  2. SystemUI怎么新建一个模块
  3. SystemUI的启动流程

在进行阅读之前,请跟着我思考如下的问题:

  1. SystemUI要完成哪些功能?
  2. 各个功能之间需要沟通吗?
  3. 倘若各个功能之间需要进行沟通,怎样组织他们之间的引用关系
  4. 各个功能需要与系统服务沟通吗?
  5. 倘若各个功能需要与系统服务沟通,怎么高效的组织他们之间的引用
  6. 各个功能之间有依赖关系吗?
  7. 各个功能之间启动有顺序要求吗?

针对上述1,2,3问题,我们可以从界面上看到,SystemUI包含:锁屏,状态栏,导航栏,Toast的显示,音量调节等等功能。这些功能之间可能会相互引用,如状态栏模块需要知道锁屏模块的情况,便于决定是否要隐藏一些状态栏图标。

这些功能多多少少会与系统之间进行交互,如,锁屏模块需要知道电源按钮的情况,便于决定是否要显示或者隐藏锁屏;再如,各个功能模块是否要显示夜间模式等等。

从这里我们已经知道,各个模块之间需要相互引用,且与系统之间也会有沟通,为此,需要设计好架构,让各个模块便于获得想要的对象,而这些对象的创建,可能又依赖于其他对象的创建。事实上SystemUI的各个对象之间依赖关系比较复杂,如果手动创建各个对象,需要写非常多的代码。为此,我们使用新的组件管理方式:DI(依赖注入)

依赖注入的核心思想就是:在编写代码的时候,不再由开发人员,显示的去编辑各个对象应该怎么创建,而是由依赖注入库去决定怎么创建对象。SystemUI选择了Dagger2作为其依赖注入库。

注意:这里需要说明什么叫做依赖?依赖就是一个组件A在完成功能的过程中,需要用到另外一个组件B,则叫做A依赖B。A,B可以是类,对象,模块等等。那么什么叫做注入呢?注入就是将依赖项提供给组件的过程。

现在设计好了各个模块之间怎么得到想要的对象——Dagger依赖注入。

下面想一想SystemUI和系统之间该怎么去沟通呢?这就涉及到应该怎么划分SystemUI,SystemUI作为整个系统的基础UI组成,在整个Android 系统中,占据非常重大的地位。为了能够适应更加多元的场景如Android TV,Android Car他们可能又不一样的SystemUI,同时又为了满足模块化设计。故将SystemUI设计成一个常驻内存中的apk。

既然已经将SystemUI设计成了一个apk,那么它就单独运行在另外一个进程中,要与系统进行沟通,那么只有通过Android的四大组件进行交互,以及使用Android的Binder进行交互。那么SystemUI就变成了各种Service,Activity,BroadcastReceiver,ContentProvider的一个集合了。然后在合适的时候启用这些组件。

结合前面的思考,各个功能模块,如锁屏,状态栏,导航栏只要放入合适的组件之中,就可以在正确的地方显示了。同时为了方便访问其他各个模块,我们还使用了Dagger进行辅助。

看上去似乎一切美好,但是真的美好吗?是否漏掉了一个重要问题?这些模块之间有先后顺序吗?为了能快速的进入系统,目前SystemUI不做这方面的要求,如果某一个模块需要等待另外一个模块准备好之后,才能正常工作,那么就需要调整设计逻辑了。

查看SystemUI的组件有哪些

从上面我们知道,SystemUI整体上被放入了四大组件之中,那么我们查看AndroidManifest.xml看看都有哪些组件被定义了。

在android-12.0.0_r34分支上,AndroidManifest.xml有38个Activity,11个Service,4个provider,11个receiver

接下来,我将给出各个组件的简单描述,再后续的章节中,我们将会细细介绍这些组件如何启动,完成哪些功能。

Activity篇

  1. LongScreenShotActivity:长截图使用的视图,用户决定长截图时,调起这个Activity。
  2. ScreenRecordDialog:录屏时弹出的选项框视图。
  3. TunerActivity:这是给研发人员用的微调界面。可以使用下面命令打开界面入口
adb shell pm enable com.android.systemui/com.android.systemui.tuner.TunerActivity

然后在设置->系统->System UI Tuner进入界面

  1. DemoMode:SystemUI的Demo模式,也是给研发人员用的,他是TunerActivity的功能补充,可在开发者选项中打开
  2. ForceReSizableInfoActivity:弹出应用无法在分屏模式,或者辅助屏幕下运行
  3. UsbPermissionActivity:确定USB权限弹框
  4. UsbResolverActivity:为USB设备选择一个应用弹框
  5. UsbConfirmActivity:弹出一个视图,用来确定是否要使用某个app,是UsbResolverActivity的后续视图
  6. SensorUseStartedActivity:当传感器在privacy mode下,欲使用传感器时的弹框
  7. TvUnblockSensorActivity:同SensorUseStartedActivity,只不过这个是运用在电视机上的视图
  8. UsbAccessoryUriActivity:弹出一个框,让你去下载这个USB设备对应的应用
  9. UsbContaminantActivity:弹出一个框,表示USB已经停用,停用的原因可能是usb端口处有赃物等。
  10. UsbDebuggingActivity:弹出是否允许USB调试
  11. UsbDebuggingActivityAlias:这个是UsbDebuggingActivity的别名
  12. WifiDebuggingActivity:弹出是否允许网络进行无线调试
  13. WifiDebuggingActivityAlias:是WifiDebuggingActivity的别名
  14. WifiDebuggingSecondaryUserActivity:弹出目前登录的用户无法开启无线调试功能,需要切换为主用户
  15. NetworkOverLimitActivity:弹出数据流量已经达到上限
  16. MediaProjectionPermissionActivity:多媒体投屏权限确认
  17. TvNotificationPanelActivity:TV专用,弹出一个消息框
  18. SlicePermissionActivity:Slice权限弹框
  19. DessertCase:彩蛋之一
  20. MLandActivity:彩蛋小游戏
  21. PeopleSpaceActivity:提示Pepole Space UI的位置,android 11新增功能
  22. LaunchConversationActivity:当会话被点击的时候,展开视图,Android 11 新增功能
  23. WorkLockActivity:解锁work profile的界面
  24. CreateUserActivity:创建用户视图
  25. Somnambulator:屏保
  26. BrightnessDialog:亮度弹框
  27. ForegroundServicesDialog:展示前台services的一个弹框
  28. ChooserActivity:弹出一个框,让用户选择打开哪一个应用,来处理当前的Intent
  29. ControlsProviderSelectorActivity 弹出“选择要添加控制器的应用”
  30. ControlsEditingActivity:编辑控制器,拖拽进行编辑
  31. ControlsFavoritingActivity:控制器,偏好设置
  32. ControlsActivity:列出设备控制器
  33. WalletActivity:电子钱包
  34. ControlsRequestDialog:control请求添加设备控制器弹框

注意:这里的Controls,是外部设备的控制器,如全屋智能中的控制器。

上面只是一个非常简单的概览,而一些常见组件逻辑和UI细节,将会在后续文章中出现。

看到这里,可能会有读者提问:上面的Activity似乎没有状态栏和锁屏呀,他们的视图,难道不在这些Activity里面吗?

欲探讨这个问题,还需要先看剩下的组件。

Services篇

  1. SystemUIService:哇哦,多么让人提神的名字,这个Service包含了SystemUI内部的大部分功能它也是我们SystemUI源码分析的重中之重。
  2. SystemUISecondaryUserService:多用户情况下,该service保证多用户的SystemUI功能正常
  3. SystemUIAuxiliaryDumpService:开发使用,dump出各个必要部件的信息并查看
  4. TakeScreenshotService:截屏相关的service
  5. RecordingService:录屏相关的service
  6. ImageWallpaper:壁纸相关的service
  7. PeopleBackupFollowUpJob:People service ui相关的服务
  8. DessertCaseDream:小彩蛋
  9. KeyguardService:锁屏相关的服务
  10. AuxiliaryPersistenceWrapper$DeletionJobService:外部设备控制器相关的服务
  11. DozeService:跟Doze相关的服务

ContentProvider篇

  1. FileProvider:提供文件
  2. KeyguardSliceProvider:提供锁屏Slice
  3. ClockOptionsProvider:为选择器程序提供时钟预览
  4. PeopleProvider:返回给定快捷方式的 People Tile 预览

BroadcastReceiver篇

  1. ScreenshotServiceErrorReceiver:截屏失败广播接收器
  2. SysuiRestartReceiver:重启SystemUI广播接收器
  3. ActionProxyReceiver:拦截share和edit intent,便于提前处理一些事情的广播接收器
  4. DeleteScreenshotReceiver:删除截屏广播接收器
  5. SmartActionsReceiver:用户点击通知中的smart action之后,用于接收对应的广播,并执行smart action
  6. ControlsRequestReciver:接收增加控制器请求的广播接收器
  7. TunerService$ClearReciver:用于调用TunerService的clear的广播接收器
  8. KeyboardShortcutsReceiver:展示或者隐藏键盘快捷键的广播接收器
  9. MediaOutputDialogReceiver:接收媒体输出Intent的广播接收器
  10. PeopleSpaceWidgetPinnedReceiver:当一个联系人Tile widget被添加之后,这个接收器被调用
  11. PeopleSpaceWidgetProvider:People Space widget 的实现

阅读到这个地方,读者依然会有疑问——SystemUI的锁屏和状态栏,到底在什么地方显示出来的?Service里面能显示UI吗?明显不合理呀。那么安卓的锁屏和状态栏,到底是怎么显示出来的呢?

小提示:SystemUI除了上面列出的组件来显示视图以外,还通过直接与WindowManager交互来显示视图。What~~ 是不是会感叹一句,android的设计架构还真是有些混乱。

此处不表,后续详解。接下来我们需要先处理前面提到的关于Dagger2,它是如何处理SystemUI中各个组件的引用关系的。

SystemUI内部组件设计

我们要让Dagger2来管理各个组件的依赖关系,那我我们必然要告诉Dagger2有怎样的依赖关系,应该使用什么样的方式呢?用xml文件来描述吗?还是用其他的方式呢?

Dagger2使用了java的注解来描述他们之间的依赖关系。同时为了提升性能,Dagger2会在编译的时候,根据注解生成不同的java对象,然后在生成的java对象中,安排好了一切的依赖,以及生命周期。

用来表示各种依赖关系的注解,叫做给Dagger2画一副图(graph).接下来的我们结合SystemUI中的实例,看看SystemUI如何给Dagger2画了一副图。

Dagger2 在SystemUI中的应用

在我们的设想中,需要一个最最顶部的对象比如RootManager.然后根据这个RootManager来获得我们需要的各个对象。

在SystemUI中,依然也有这么个RootManager。它就是:GlobalRootComponent。SystemUI的各个模块,想要拿到自己想要的对象,可以通过GlobalRootComponent获取。

注意:读者,看到这里,肯定会非常疑惑,为什么要叫Component,而不是Manager,毕竟Manager在Android中多么的常见。这是因为SystemUI使用了Dagger2的抽象。在Dagger2中,Component表示一个组件,事实上它是一个容器,里面包含有它可以提供的所有依赖。故此GlobalRootComponent则是可以提供给所有依赖的一个组件。

那么我们来看看如何给GlobalRootComponent画图的。

//@Singeton:告诉Dagger2所有带有@singtone注解的对象,生命周期一致。此处表示全局唯一
//@Component(xxx):告诉Dagger2,定义了一个Component组件
//modules={xxx}:告诉Dagger2,这个组件依赖这些模块。关于模块的概念,见后文
@Singleton
@Component(modules = {
        GlobalModule.class,
        SysUISubcomponentModule.class,
        WMModule.class})
//此处是interface接口定义,Dagger2会生成对应的实现类,并按照我们给Dagger2的注解图,管理好
//各个对象的依赖和创建
public interface GlobalRootComponent {

    //@Component.Builder:
    //告诉Dagger2,这个是创建GlobalRootComponent的Builder类
    //请务必要思考:为什么此处的对象创建要用Builder模式,而不是工厂模式?
    @Component.Builder
    interface Builder {
        @BindsInstance
        Builder context(Context context);

        GlobalRootComponent build();
    }

    //提供一个方法,这个方法的返回类型,就是这个组件可以提供的依赖,此处表示可以提供
    //WMComponent.Builder对象
    WMComponent.Builder getWMComponentBuilder();

    //表示此组件可以提供,SysUIComponent.Builder对象
    SysUIComponent.Builder getSysUIComponent();

    //注:上面两个方法提供的返回类型可以看到,他们依然是一个Component

    //表示可以提供ThreadFactory对象
    ThreadFactory createThreadFactory();
}

在上面的例子中,我们提到了模块module这个概念,在介绍这个概念之前,先思考一个问题:如果GlobalRootComponent中有很多很多依赖怎么办呢?如果每个都画在图上,就会显得杂乱无章,因此dagger2提供一种功能,将这些不同的依赖用Module进行逻辑上面的划分。然后只需要在给Component画图时,进行如下指定即可:

@Component(modules={xxx.class})

我们选取SysUISubComponentModule进行查看,源码如下:

//@Module:告诉Dagger2,这个module内的所有依赖,逻辑划分为:SysUISubcomponentModule
//subcomponents = {SysUIComponent.class}:告诉Dagger2,这个模块含有SysUIComponent子组件
//关于子组件的概念,我们下文介绍
@Module(subcomponents = {SysUIComponent.class})
public abstract class SysUISubcomponentModule {
}

在上面的例子中,我们提到了subcomponent,在介绍这个概念之前,我们再来想个问题:如果一个Component提供一个对象给其他使用者,被提供的对象,应该是每次都创建,还是只创建一次呢?这就涉及到对象的生命周期,为了能够更好的管理生命周期,我们建议,同属于一个生命周期的对象,放在一个子组件中。因此,上面的SysUIComponent子组件中所有对象,将属于同一个生命周期。当然subcomponent也不仅仅可以隔离生命周期,还可以隔离模块使代码更加清晰

那么我们看看这个subcomponent是怎么被告知Dagger2的。

//@SysUISingleton:告诉Dagger2所有SysUISingleton注解的对象生命周期相同
//@Subcomponent:告诉Dagger2,这是一个子组件
//modules={xxx}:告诉dagger2,这个子组件有这么些module的需要依赖
@SysUISingleton
@Subcomponent(modules = {
        DefaultComponentBinder.class,
        DependencyProvider.class,
        SystemUIBinder.class,
        SystemUIModule.class,
        SystemUIDefaultModule.class})
public interface SysUIComponent {

    //告诉Dagger2生命周期
    @SysUISingleton
    //告诉Dagger2这个子组件的Builder接口定义
    @Subcomponent.Builder
    interface Builder {
        //省略若干相同部分

        //@BindsInstance:告诉Dagger2,将t绑定到这个Builder对象中
        //在Dagger2根据我们画的图,会根据这个Builder接口,生成一个SysUIComponentBuilder对象
        //在这个对象中,会有一个成员,类型为Optional<TaskSurfaceHelper>名字为setTaskSurfaceHelper.
        //然后这个setTaskSurfaceHelper()接口函数的实现,就会将参数传入的t保存在setTaskSurfaceHelper成员中。
        //这个过程就叫做:绑定实例,也即@BindsInstance的语义
        @BindsInstance
        Builder setTaskSurfaceHelper(Optional<TaskSurfaceHelper> t);

        //任何一个Builder接口,都必须有一个build()函数,且返回类型为需要构建的对象类型,此处即为SysUIComponent
        SysUIComponent build();
    }

    //定义了一个默认方法,这个方法什么也没有做
    default void init() {
        // Do nothing
    }

    //告诉Dagger2它的生命周期
    @SysUISingleton
    //subcomponent和component一样,如果想要对外提供依赖,就可以定义任何一个函数,函数的返回类型就是
    //被提供对象的类型。
    BootCompleteCacheImpl provideBootCacheImpl();


    //省略若干相同部分

    //当返回类型为空,而传入类型不为空的时候,表示需要向传入类型对象(SystemUIAppComponentFactory)
    //中被@inject标记的成员赋值,叫做注入
    //理论上,函数名为任意值,但是此种函数,几乎只会完成注入的功能,因此此函数最后都叫做inject
    void inject(SystemUIAppComponentFactory factory);

    //省略若干相同部分
}

在上面的inject函数中,我们可以看看SystemUIAppComponentFactory是怎么样的,源码如下:

public class SystemUIAppComponentFactory extends AppComponentFactory {

    //@Inject:告诉Dagger2,这个成员,需要Dagger2的注入。
    //可是Dagger2又是如何知道,该怎么创建ContextComponentHelper的呢?
    //这就是我们给Dagger2画图的作用,我们已经提前画好图给Dagger2,告诉它应该
    //怎么创建这个ContextComponentHelper
    @Inject
    public ContextComponentHelper mComponentHelper;

}

接下来,看看我们是怎么给ContextComponentHelper画图的,源码如下:

public interface ContextComponentHelper {
    //省略若干无用部分
}

从上面的源码可以知道,它没有任何注解,即它没有被画如Dagger2的图中,被画入Dagger2图的另有它类,ContextComponentHelper的实现类为:ContextComponentResolver,源码如下:

//@SysUISingleton:告诉Dagger2它的生命周期
@SysUISingleton
public class ContextComponentResolver implements ContextComponentHelper {
    private final Map<Class<?>, Provider<Activity>> mActivityCreators;
    private final Map<Class<?>, Provider<Service>> mServiceCreators;
    private final Map<Class<?>, Provider<SystemUI>> mSystemUICreators;
    private final Map<Class<?>, Provider<RecentsImplementation>> mRecentsCreators;
    private final Map<Class<?>, Provider<BroadcastReceiver>> mBroadcastReceiverCreators;

    //@Inject:此处就是告诉Dagger图,注入Dagger2的各种辅助功能,帮助创建这个对象
    //在创建对象的时候,需要它的各种参数,而这些参数又应该怎么被Dagger2提供呢?
    //只要我们把需要的参数,画好图给Dagger2即可,过程就和这个ContextComponentResolver一样啦,
    //在构造器上面标注一下@Inject就可以了
    @Inject
    ContextComponentResolver(Map<Class<?>, Provider<Activity>> activityCreators,
            Map<Class<?>, Provider<Service>> serviceCreators,
            Map<Class<?>, Provider<SystemUI>> systemUICreators,
            Map<Class<?>, Provider<RecentsImplementation>> recentsCreators,
            Map<Class<?>, Provider<BroadcastReceiver>> broadcastReceiverCreators) {
        mActivityCreators = activityCreators;
        mServiceCreators = serviceCreators;
        mSystemUICreators = systemUICreators;
        mRecentsCreators = recentsCreators;
        mBroadcastReceiverCreators = broadcastReceiverCreators;
    }

    //省略若干无用部分
}

看到此处,我们已经大体知道了Dagger2该怎么使用(该怎么提供一个图给它)。但是仔细思考依然会有一个问题——要是某些类,不是由我们创建,那么我们就没法在构造器上面加上@Inject了,那么怎么才能让Dagger2知道:当它需要这个对象的时候,应该怎么创建呢?此时Dagger2提供了另外一个注解@Provides .

我们以GlobalModule为例进行说明,源码如下:

//@Module:告诉Dagger2,定义一个逻辑模块,这个模块包含FrameworkServicesModule,GlobalConcurrencyModule
@Module(includes = {
        FrameworkServicesModule.class,
        GlobalConcurrencyModule.class})
public class GlobalModule {

    //@Provides:告诉Dagger2,如果需要DisplayMetrics对象,就调用provideDisplayMetrics()函数即可
    //至于这个函数需要的参数Context,该怎么创建,Dagger2已经能够从我们给它的图中自动找到了
    @Provides
    public DisplayMetrics provideDisplayMetrics(Context context) {
        DisplayMetrics displayMetrics = new DisplayMetrics();
        context.getDisplay().getMetrics(displayMetrics);
        return displayMetrics;
    }

}

至此,我们已经大体介绍完了Dagger2中如何画图(即怎么使用注解),如,怎么使用@Component,@SubComponent,@Inject,@Provides,@Module等等,Dagger2中还有其他注解没有引入,但是这已经足够本文接下来的阅读了。关于其他注解的内容,可直接参考Dagger2的文档,本文只专注于SystemUI的分析

可是上面只是画图,并没有提到怎么使用的,接下来,我们将结合SystemUI的启动过程,看看怎么使用上面画出来的Dagger2的图。

SystemUI的启动过程

任何一个Apk的启动,都是从它的四大组件开始启动,而在四大组件,开始启动之前,会去查看是否有自定义的Application,如果有,则会先创建Application。

从Android 9开始,增加了一个AppComponentFactory用来在创建四大组件之前,进行相应的操作。它同Application一样,被配置在了AndroidManifest.xml中,如下:

<application
        android:name=".SystemUIApplication"
        .
        .
        .
        android:appComponentFactory=".SystemUIAppComponentFactory">
        <!--省略若干不相干话题-->
</application>

从这个配置中我们可以看到如下的启动过程:

SystemUIAppComponentFactory->SystemUIApplication->某个欲启动的组件(Android四大组件)。

在进一步分析这个流程之前,先来看看谁启动了SystemUI

system_server的出发点

可是有读者会问:SystemUI到底是由谁启动的呢?你看其他的app都是通过点击图标启动,SystemUI是由谁启动的?

正确答案:SystemUI由Android系统中,一个叫做sytstem_server的进程启动,system_server在开机的时候启动,然后由system_server启动各种关键服务,其中就包括启动SystemUI.这在后面分析system_server时,会详细讲解.

此处只给出system_server启动SystemUI的简略说明:

  1. 系统启动system_server进程之后,会执行
new SystemServer().run();
  1. 在run()方法中,会去启动各种服务,这些服务包括:
  • 启动引导服务
  • 核心服务
  • 其他服务
  1. 在启动其他服务的时候,会去启动SystemUI(通过Intent)。而要获得启动SystemUI的具体组件,就通过
    Android的PackageManager得到,

  2. 而PacakgeManager则通过读取配置config_systemUIServiceComponent得到具体的组件名。Android系统中这个配置

<string name="config_systemUIServiceComponent" translatable="false"
            >com.android.systemui/com.android.systemui.SystemUIService</string>

可见这正是,我们在SystemUI中定义的组件。

那么我们就可以总结一下SystemUI的启动过程了

  1. Android系统启动完成,启动system_server
  2. system_server,根据配置,通过Intent来启动SystemUI的组件
  3. SystemUI在启动组件之前,会先创建SystemUIAppComponentFactory对象,然后调用其相应方法
  4. 接着,SystemUI会创建SystemUIApplication,然后调用其相应方法
  5. 最后,SystemUI会创建SystemUIService,并调用相应方法,在创建SystemUIService之前,则又会调用在第3步中创建的SystemUIAppComponentFactory对象的相应方法

为何要使用SystemUIAppComponentFactory

SystemUIAppComponentFactory源码如下:

public class SystemUIAppComponentFactory extends AppComponentFactory {

    private static final String TAG = "AppComponentFactory";
    @Inject
    public ContextComponentHelper mComponentHelper;

    public SystemUIAppComponentFactory() {
        super();
    }

    @NonNull
    @Override
    //在创建Application之前,这个函数被调用
    public Application instantiateApplicationCompat(
            @NonNull ClassLoader cl, @NonNull String className)
            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        //调用父类方法,创建Application,此处会创建AndroidManifest.xml中配置的类
        //也即SystemUIApplication
        Application app = super.instantiateApplicationCompat(cl, className);
        //倘若创建的组件是ContextInitializer,则注册一个回调
        //请一定注意:虽然此处创建了Application,但是它还不能当做Context来使用
        if (app instanceof ContextInitializer) {
            ((ContextInitializer) app).setContextAvailableCallback(
                    context -> {
                        //1.在回调中,首先创建SystemUIFactory对象
                        SystemUIFactory.createFromConfig(context);
                        //2.通过这个SystemUIFactory得到SysUIComponent
                        //3.注入SystemUIAppComponentFactory中的成员,见上一小节
                        SystemUIFactory.getInstance().getSysUIComponent().inject(
                                SystemUIAppComponentFactory.this);
                    }
            );
        }

        return app;
    }

    @NonNull
    @Override
    //ContentProvider被创建之前,该函数被回调
    public ContentProvider instantiateProviderCompat(
            @NonNull ClassLoader cl, @NonNull String className)
            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        //省略若干
        //此处没有列出内容,原因是:它的逻辑和上一个函数一样
    }

    @NonNull
    @Override
    public Activity instantiateActivityCompat(@NonNull ClassLoader cl, @NonNull String className,
            @Nullable Intent intent)
            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        //省略若干
        //此处没有列出内容,原因是:它的逻辑和上一个函数一样
    }

    @NonNull
    @Override
    public Service instantiateServiceCompat(
            @NonNull ClassLoader cl, @NonNull String className, Intent intent)
            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        //判断是否为空,如果是,则再次注入
        //第一次注入,在instantiateApplicationCompat()函数设置的回调中,
        //这个回调由SystemUIApplication的onCreate()触发
        if (mComponentHelper == null) {
            // This shouldn't happen, but does when a device is freshly formatted.
            // Bug filed against framework to take a look: http://b/141008541
            SystemUIFactory.getInstance().getSysUIComponent().inject(
                    SystemUIAppComponentFactory.this);
        }
        //注意:这里的Service的创建
        //1. 先查询mComponentHelper中是否有对应的Service
        //2. 如果有则直接用,如果没有则调用父类方法创建
        //对于SystemUIService而言,它的构造函数有@Inject注解,因此当调用mComponentHelper.resolveService时,能够正确返回SystemUIService
        //请思考:为什么这个不要系统自己创建?
        //答案:因为SystemUIService,需要有其他依赖对象,若是由系统创建,那么必然会有
        //像SystemUIService.setXXX()之类的函数,会增加代码和逻辑。如果由Dagger2来创建则不会有
        //这些烦恼
        Service service = mComponentHelper.resolveService(className);
        if (service != null) {
            return service;
        }
        return super.instantiateServiceCompat(cl, className, intent);
        
    }

    @NonNull
    @Override
    public BroadcastReceiver instantiateReceiverCompat(@NonNull ClassLoader cl,
            @NonNull String className, @Nullable Intent intent)
            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        //省略若干
        //此处没有列出内容,原因是:它的逻辑和上一个函数一样
    }

}

在这个类中,最最主要的功能就三点:

  1. 相应组件的创建,如SystemUIApplication,SystemUIService等
  2. mComponentHelper的初始化,也即通过注入实现
  3. 而在Service组件创建之前,可能会先查询mComponentHelper中是否已经有组件,若有则直接使用

在查看SystemUIApplication 和SystemUIService的创建之前,我们还是要再次思考一个问题:
为什么要用SystemUIAppComponentFactory这个类?这个类真的合理吗?有更好的替代方案吗?

我想答案就在SystemUIService的创建上。正是SystemUIAppComponentFactory这个类的使用,才让我们更好的注入依赖

在进入SystemUIService之前,最先创建的是SystemUIApplication。我们来看看它所做的工作。

SystemUIApplication

源码如下:

public class SystemUIApplication extends Application implements
        SystemUIAppComponentFactory.ContextInitializer {

    public SystemUIApplication() {
        super();
        Log.v(TAG, "SystemUIApplication constructed.");
        // SysUI may be building without protolog preprocessing in some cases
        ProtoLog.REQUIRE_PROTOLOGTOOL = false;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.v(TAG, "SystemUIApplication created.");
        //用于跟踪启动和关闭的时序数据
        TimingsTraceLog log = new TimingsTraceLog("SystemUIBootTiming",
                Trace.TRACE_TAG_APP);
        log.traceBegin("DependencyInjection");
        //这就是初始化各种Dagger2依赖的地方,这个回调在SystemUIAppComponentFactory中被设置
        mContextAvailableCallback.onContextAvailable(this);
        //有了Dagger2,就是直接使用对应的组件
        mRootComponent = SystemUIFactory.getInstance().getRootComponent();
        mSysUIComponent = SystemUIFactory.getInstance().getSysUIComponent();
        mComponentHelper = mSysUIComponent.getContextComponentHelper();
        mBootCompleteCache = mSysUIComponent.provideBootCacheImpl();
        log.traceEnd();

        //设置主题
        setTheme(R.style.Theme_SystemUI);

        //判断是否为主进程
        if (Process.myUserHandle().equals(UserHandle.SYSTEM)) {
            //监听系统的启动广播
            IntentFilter bootCompletedFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
            bootCompletedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);

            //设置线程渲染优先级
            int sfPriority = SurfaceControl.getGPUContextPriority();
            Log.i(TAG, "Found SurfaceFlinger's GPU Priority: " + sfPriority);
            if (sfPriority == ThreadedRendererCompat.EGL_CONTEXT_PRIORITY_REALTIME_NV) {
                Log.i(TAG, "Setting SysUI's GPU Context priority to: "
                        + ThreadedRendererCompat.EGL_CONTEXT_PRIORITY_HIGH_IMG);
                ThreadedRendererCompat.setContextPriority(
                        ThreadedRendererCompat.EGL_CONTEXT_PRIORITY_HIGH_IMG);
            }

            //注册广播接收器
            registerReceiver(new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    //mBootCompleteCache表示的是:是否SytemUI的各种服务启动完成
                    //这些服务的启动,可能早于系统启动完成广播,也可能晚于系统启动完成广播

                    //1. 如果SystemUI的各种服务已经启动完成则直接返回
                    if (mBootCompleteCache.isBootComplete()) return;

                    if (DEBUG) Log.v(TAG, "BOOT_COMPLETED received");
                    //2. 如果没有启动完成,则挨个启动
                    unregisterReceiver(this);
                    mBootCompleteCache.setBootComplete();
                    if (mServicesStarted) {
                        final int N = mServices.length;
                        for (int i = 0; i < N; i++) {
                            mServices[i].onBootCompleted();
                        }
                    }
                }
            }, bootCompletedFilter);

            //监听是否Local改变
            //如果Local改变,则通知中的显示就需要改变,如中英文切换等
            IntentFilter localeChangedFilter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED);
            registerReceiver(new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    if (Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) {
                        if (!mBootCompleteCache.isBootComplete()) return;
                        // Update names of SystemUi notification channels
                        NotificationChannels.createAll(context);
                    }
                }
            }, localeChangedFilter);
        } else {
            //如果是子进程则会进入此部分逻辑

            //如果是主用户下的子进程,则什么也不做,直接返回
            String processName = ActivityThread.currentProcessName();
            ApplicationInfo info = getApplicationInfo();
            if (processName != null && processName.startsWith(info.processName + ":")) {
                return;
            }
            //如果不是主用户,则需要去启动必要的SystemUI组件
            startSecondaryUserServicesIfNeeded();
        }
    }

    //省略若干,简单代码

}

SystemUIApplication的代码相对来讲比较简单,都已经标记在注释里面了。接下来看看SystemUIService

SystemUIService

源码如下:

public class SystemUIService extends Service {

    //省略若干,简单代码

    //@Inject:嘿嘿,这就是给Dagger画的图,好让Dagger2知道怎么创建SystemUIService
    @Inject
    public SystemUIService(
            @Main Handler mainHandler,
            DumpHandler dumpHandler,
            BroadcastDispatcher broadcastDispatcher,
            LogBufferFreezer logBufferFreezer,
            BatteryStateNotifier batteryStateNotifier) {
        //省略赋值代码
    }

    @Override
    public void onCreate() {
        super.onCreate();

        //对没错,startServicesIfNeeded作为整个SystemUI关键服务的启动源头,就在这里了。
        //在进入分析之前,先思考:为什么要放在这里执行呢?就不能直接放在SystemUIApplication中吗?
        ((SystemUIApplication) getApplication()).startServicesIfNeeded();

        //LogBufferFreezer接收bugreport开始的广播,然后停止对应的LogBuffer的记录
        mLogBufferFreezer.attach(mBroadcastDispatcher);

        //是否监听电池的状态,并且会提示在通知栏上
        if (getResources().getBoolean(R.bool.config_showNotificationForUnknownBatteryState)) {
            mBatteryStateNotifier.startListening();
        }

        //调试代码,用debug.crash_sysui触发RescueParty
        if (Build.IS_DEBUGGABLE && SystemProperties.getBoolean("debug.crash_sysui", false)) {
            throw new RuntimeException();
        }

        //Binder调试相关
        //如果太多binder调用就触发onLimitReached回调
        if (Build.IS_DEBUGGABLE) {
            //设置Binder代理计数开
            BinderInternal.nSetBinderProxyCountEnabled(true);
            //配置Binder代理触发BinderProxyLimitListener回调的最高和最低阈值,最低表示:只有降到最低以下,才能再次触发
            BinderInternal.nSetBinderProxyCountWatermarks(1000,900);
            //设置BinderProxyLimitListener监听
            BinderInternal.setBinderProxyCountCallback(
                    new BinderInternal.BinderProxyLimitListener() {
                        @Override
                        public void onLimitReached(int uid) {
                            Slog.w(SystemUIApplication.TAG,
                                    "uid " + uid + " sent too many Binder proxies to uid "
                                    + Process.myUid());
                        }
                    }, mMainHandler);
        }

        //启动DumpService,如果系统运行bugreport,SystemUIAuxiliaryDumpService会将SystemUI中的一些关键数据dump出来
        startServiceAsUser(
                new Intent(getApplicationContext(), SystemUIAuxiliaryDumpService.class),
                UserHandle.SYSTEM);
    }

    //省略若干,简单代码
}

接下来,我们看看startServicesIfNeeded()函数的具体内容

startServicesIfNeeded()函数

该函数位于SystemUIApplication类内,源码如下:

public void startServicesIfNeeded() {
    //1. 获取需要start的服务列表
    //2. 然后调用startServicesIfNeed()继续启动
    //注意:看到这里,其实大家应该大胆假设,getSystemUIServiceComponents函数
    //是不是通过Dagger2的依赖得到的。如果不是为什么?
    String[] names = SystemUIFactory.getInstance().getSystemUIServiceComponents(getResources());
    startServicesIfNeeded(/* metricsPrefix= */ "StartServices", names);
}

private void startServicesIfNeeded(String metricsPrefix, String[] services) {
    //省略判断

    mServices = new SystemUI[services.length];

    //启动完成缓存对象的修改,简单,略
    
    //首先获取DumpManager
    final DumpManager dumpManager = mSysUIComponent.createDumpManager();

    //trace跟踪点,略

    //挨个启动服务
    // 1. 首先查看mComponentHelper是否有缓存,如果有则直接使用
    // 2. 如果没有则反射创建
    // 3. 创建完成调用start()
    // 4. 判断是否系统启动完成,如果完成则调用onBootCompleted()
    // 5. 将启动的服务,加入DumpManager中,以便bugreport触发其dump
    final int N = services.length;
    for (int i = 0; i < N; i++) {
        String clsName = services[i];
        if (DEBUG) Log.d(TAG, "loading: " + clsName);
        log.traceBegin(metricsPrefix + clsName);
        long ti = System.currentTimeMillis();
        try {
            SystemUI obj = mComponentHelper.resolveSystemUI(clsName);
            if (obj == null) {
                Constructor constructor = Class.forName(clsName).getConstructor(Context.class);
                obj = (SystemUI) constructor.newInstance(this);
            }
            mServices[i] = obj;
        } catch (ClassNotFoundException
                | NoSuchMethodException
                | IllegalAccessException
                | InstantiationException
                | InvocationTargetException ex) {
            throw new RuntimeException(ex);
        }

        if (DEBUG) Log.d(TAG, "running: " + mServices[i]);
        mServices[i].start();
        log.traceEnd();

        // Warn if initialization of component takes too long
        ti = System.currentTimeMillis() - ti;
        if (ti > 1000) {
            Log.w(TAG, "Initialization of " + clsName + " took " + ti + " ms");
        }
        if (mBootCompleteCache.isBootComplete()) {
            mServices[i].onBootCompleted();
        }

        dumpManager.registerDumpable(mServices[i].getClass().getName(), mServices[i]);
    }
    mSysUIComponent.getInitController().executePostInitTasks();
    log.traceEnd();

    mServicesStarted = true;
}

从上面可以看到,SystemUIService主要功能,就是去调用各个服务的start()和onBootCompleted()功能。完成启动。

现在请思考,为什么这部分内容,要放在SystemUIApplication中,就不能直接放在SystemUIService中吗?

在回答这个问题之前,我们先来看看getSystemUIServiceComponents()函数如何得到需要启动的服务的

getSystemUIServiceComponents函数获取欲启动的服务

源码如下:

public String[] getSystemUIServiceComponents(Resources resources) {
    return resources.getStringArray(R.array.config_systemUIServiceComponents);
}

上面的函数通过,配置的字符串数组获得,可能大家就会好奇了——为什么不同Dagger2呢?这么方便的东西怎么还配置个字符串数组呀。

为什么这么配置,我想主要有如下的几个原因:

  1. AOSP代码的编译,可以使用override功能,将这部分资源文件,放在自定义的目录下达到修改的目的
  2. 历史原因,以前就是这么配置的,哈哈

现在我们来思考,启动服务的功能逻辑,为什么就不能放在SystemUIservice中而要放在SystemUIApplication中。
如果要放在SystemUIApplication中,为什么不通过SystemUIApplication来启动,而要SystemUIService来启动。

  1. 除了SystemUIService启动服务以外,在多用户的情况下,也需要启动一些服务,而此时,SystemUI应用

    先调用SystemUIApplication,而不会调用SystemUIService。因为SystemUIService的触发是由system_server启动的。

    加上,监听系统的启动逻辑,需要统一处理,将启动逻辑,放入SystemUIApplication,变得理所当然。

注意:显然这就导致一个bug,倘若SystemUI中途报错,停止运行,当其再次运行的时候,由SystemUIService启动的各个
服务,还能够正确的初始化吗?显然不能,这在我写这边文章过程中,经常出现

  1. 既然启动逻辑已经放入了SystemUIApplication中,那么由SystemUIApplication来启动这部分服务不可以吗?

    为什么要单独一个SystemUIService作为入口进行启动呢?要回答这个问题,就需要知道,Android的应用启动,是通过启动某个待运行的组件。即system_server若要运行SystemUI,必然要启动某个组件,而不能只启动Application。

    故此,我们需要一个用来启动的组件,这个组件就是SystemUI. 由它负责各个具体服务的启动。加上对开机广播的监听,多用户下的协作,就将这部分内容,放在了SystemUIApplication中完成。

    又因为SystemUIService需要依赖注入,所以创建了SystemUIAppComponentFactory来实现对应的依赖注入。

至此,我们已经完全弄清了,SystemUIService,SystemUIApplication,SystemUIAppComponentFactory的启动流程
以及为什么这么分配功能。现总结如下。

  1. system_server启动之后,开始启动各种服务
  2. 在启动其他服务的时候,会先通过PackageManager,获得要启动systemui的组件名字,然后根据名字启动systemui组件
  3. 在上面一步获得的名字就是SystemUIService。
  4. 启动SystemUIService,则会先创建SystemUIApplication,在创建之前会先调用SystemUIAppComponentFactory添加相应的依赖注入
  5. SystemUIApplication创建之后,则会监听系统的启动广播。
  6. 接着创建SystemUIService,再创建之前,还会先调用SystemUIAppComponentFactory相应的方法,添加依赖注入
  7. 创建SystemUIService之后,通过SystemUIApplication启动各种服务

至此,整个SystemUI启动完成。

给SystemUI添加一个自定义的服务

有了前面的分析,我们现在需要,进行测试一下:写一个自定义服务的模块,在这个模块中,我们仅仅打印出其启动过程即可。

  1. 新建一个类,叫做PrintLogService,这个类继承SystemUI即可。如下
public class PrintLogService extends SystemUI{

    private String TAG = "PrintLogService";

    //使用@Inject标记,让Dagger2自动管理依赖
    @Inject
    public PrintLogService(Context context) {
        super(context);
    }

    //简单打印log
    @Override
    public void start() {
        Slog.d(TAG, "Start PrintLogService");
    }
    //简单打印log
    @Override
    protected void onBootCompleted() {
        Slog.d(TAG,"PrintLogService boot completed");
    }

}
  1. 将这个类的名字放入配置数组中即config_systemUIServiceComponents如下:
<!-- 最后一行加入我们自定义的服务 -->
    <string-array name="config_systemUIServiceComponents" translatable="false">
        <item>com.android.systemui.util.NotificationChannels</item>
        <item>com.android.systemui.keyguard.KeyguardViewMediator</item>
        <item>com.android.systemui.recents.Recents</item>
        <item>com.android.systemui.volume.VolumeUI</item>
        <item>com.android.systemui.statusbar.phone.StatusBar</item>
        <item>com.android.systemui.usb.StorageNotification</item>
        <item>com.android.systemui.power.PowerUI</item>
        <item>com.android.systemui.media.RingtonePlayer</item>
        <item>com.android.systemui.keyboard.KeyboardUI</item>
        <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item>
        <item>@string/config_systemUIVendorServiceComponent</item>
        <item>com.android.systemui.util.leak.GarbageMonitor$Service</item>
        <item>com.android.systemui.LatencyTester</item>
        <item>com.android.systemui.globalactions.GlobalActionsComponent</item>
        <item>com.android.systemui.ScreenDecorations</item>
        <item>com.android.systemui.biometrics.AuthController</item>
        <item>com.android.systemui.SliceBroadcastRelayHandler</item>
        <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item>
        <item>com.android.systemui.theme.ThemeOverlayController</item>
        <item>com.android.systemui.accessibility.WindowMagnification</item>
        <item>com.android.systemui.accessibility.SystemActions</item>
        <item>com.android.systemui.toast.ToastUI</item>
        <item>com.android.systemui.wmshell.WMShell</item>
        <item>com.android.systemui.PrintLogService</item>
    </string-array>

  1. 使用如下的命令,进行编译,并push到手机中
mmm frameworks/base/packages/SystemUI
adb root
adb remount
adb shell rm -rf system_ext/priv-app/SystemUI
adb push out/**/system_ext/priv-app/SystemUI /system_ext/priv-app/

然后使用kill杀死现有SystemUI进程,即可

  1. 从log中我们可以看到如下的输出

在这里插入图片描述

这表示我们自定义的服务启动成功!!!

本文完!!

在文中,我们仅仅对于mContextAvailableCallback.onContextAvailable(this);一笔带过
这里面是关于Dagger2中各种Component的初始化,下一篇文章,将会从这个函数出发,一探SystemUI中各种Component的初始化,理解SystemUI中各个组件应该怎样被使用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/909943.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

工业类LMQ61460AASRJRR,汽车类LMQ61460AFSQRJRRQ1、LMQ61460AASQRJRRQ1 6A、降压转换器简化原理图

一、LMQ61460AASRJRR器件概述&#xff1a; LMQ61460 是一款具有集成旁路电容器的高性能直流/直流同步降压转换器。该器件具有集成式高侧和低侧MOSFET&#xff0c;能够在 3.0V 至 36V 的宽输入电压范围内提供高达 6A 的输出电流&#xff1b;可耐受 42V 电压&#xff0c;简化了输…

智影 R100:首款三维Mesh建模的SLAM激光扫描仪

近年来&#xff0c;激光SLAM系统凭借其更加快速且准确获取更丰富信息的优势&#xff0c;迅速风靡测绘领域&#xff0c;让原本耗时耗力的外业测量变得更加高效。手持激光扫描仪作为基于激光SLAM技术衍生的众多产品之一&#xff0c;相较于架站式激光扫描仪更加轻巧便利&#xff0…

AUTOSAR规范与ECU软件开发(实践篇)5.2 ETAS ISOLAR-A工具入门

目录 1、 ISOLAR-A安装方法 2 、ISOLAR-A界面说明 1、 ISOLAR-A安装方法 ISOLAR-A工具安装方法较为便捷, 按照安装提示默认操作即可。双击打开ETAS ISOLAR-A的安装包文件夹, 双击运行Autostart.exe, 会出现如图5.2所示的安装界面, 然后点击Main, 会弹出如图5.3所示界面,…

Stable Diffusion web UI 部署详细教程

前言 本文使用 AutoDL 平台进行 Stable Diffusion web UI 云端部署 AutoDL 官网&#xff1a;AutoDL算力云 | 弹性、好用、省钱。租GPU就上AutoDL Stable Diffusion web UI 官网&#xff1a;AUTOMATIC1111/stable-diffusion-webui: Stable Diffusion web UI (github.com) 步…

string(模拟实现与深拷贝)

目录 深拷贝与浅拷贝 浅拷贝&#xff1a; 深拷贝 写时拷贝(了解) 模拟实现 准备 完整代码 深拷贝与浅拷贝 浅拷贝&#xff1a; 也称位拷贝&#xff0c;编译器只是将对象中的值拷贝过来。如果对象中管理资源&#xff0c;最后就会导致多个对象共享同一份资源&#xff0c;当一…

AMSET计算极化声子频率

前面已有文章介绍过amset这个软件&#xff0c;包括形变势VASPAMSET计算形变势、迁移率散射AMSET计算不同散射机制下载流子迁移率的计算和绘制功能中能带结构VASPAMSET plot 绘制能带图的使用。 同时&#xff0c;通过amset可以处理密度泛函微扰计算&#xff08;DFPT&#xff09;…

软件产品鉴定测试需要哪些材料?选择第三方软件检测机构的必要性

在当今数字化时代&#xff0c;软件产品无论是在企业还是个人生活中都扮演着重要的角色。然而&#xff0c;随着软件产品的不断增多和市场竞争的加剧&#xff0c;软件产品进行鉴定测试显得尤为重要。 一、软件鉴定测试需要提供哪些材料? 1、软件产品的版本号和更新说明 2、软…

2023国赛数学建模思路 - 案例:感知机原理剖析及实现

文章目录 1 感知机的直观理解2 感知机的数学角度3 代码实现 4 建模资料 # 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 感知机的直观理解 感知机应该属于机器学习算法中最简单的一种算法&#xff0c;其…

docker容器的基础操作

一、安装docker服务&#xff0c;配置镜像加速器 1.安装必要的一些系统工具 2.添加软件源信息 3.更新源并安装 Docker-ce 4.安装校验 [rootnode ~]# docker version 5.docker镜像加速 可以在阿里云的容器镜像服务中查看配置镜像加速器 二、下载系统镜像&#xff08;Ubuntu、 …

AUTOSAR规范与ECU软件开发(实践篇)5.1 ETAS ISOLAR-A工具简介

前言 如前所述, 开发者可以先在系统级设计工具ISOLAR-A中设计软件组件框架, 包括端口接口、 端口等, 即创建各软件组件arxml描述性文件; 再将这些软件组件描述性文件导入到行为建模工具, 如Matlab/Simulink中完成内部行为建模。 亦可以先在行为建模工具中完成逻辑建模, 再…

【ES6】—数组的扩展

一、类数组/ 伪数组 1. 类/伪数组: 并不是真正意义的数组&#xff0c;有长度的属性&#xff0c;但无法使用Array原型上的方法 let divs document.getElementsByTagName(div) console.log(divs) // HTMLCollection []let divs2 document.getElementsByClassName("xxx&q…

C++ malloc/free/new/delete详解(内存管理)

C malloc/free/new/delete详解&#xff08;内存管理&#xff09; malloc/free典型用法内存分配实现过程brk和mmap申请小于128k的内存申请大于128k的内存释放内存brk和mmap的区别 new/delete典型用法 内存分配实现过程new/delete和malloc/free的区别malloc对于给每个进程分配的内…

【matlab利用shp文件制作mask白化文件】

matlab白化文件 mask文件的作用matlab制作mask文件mask结果 mask文件的作用 地理信息绘图中的 “mask” 通常指的是遮罩或掩膜&#xff0c;用于在地图或图像上隐藏、高亮或标记特定区域。 数据可视化&#xff1a; 地理信息绘图 mask 可以用于突出显示特定地理区域&#xff0c;使…

第四讲:Bean的生命周期

Bean的生命周期 一、生命周期演示二、添加Bean后置处理器 简单的描述并测试Bean的生命周期&#xff08;细节会在后续文章中单独讲解&#xff09;&#xff0c;并加入一些BeanPostPorcessor处理测试。 一、生命周期演示 准备一个普通的SpringBoot项目 import lombok.extern.slf…

CentOS7安装部署Doris

文章目录 CentOS7安装部署Doris一、前言1.简介2.环境 二、正文1.Doris基础1&#xff09;架构图2&#xff09;通讯端口 2.部署服务器3.安装基础环境1&#xff09;安装JDK 112&#xff09;安装GCC3&#xff09;设置文件句柄数4&#xff09;关闭交换分区&#xff08;swap&#xff…

python 打印一个条形堆积图

背景 今天介绍一个不使用 matplot&#xff0c;通过 DebugInfo模块打印条形堆积图 的方法。 引入模块 pip install DebugInfo打印销售转化数据 下面的代码构建了两个销售团队&#xff0c;团队A 和团队B&#xff1b;两个团队的销售数据构成了公司总的销售成果。以条形堆积图的…

不同屏幕的触控技术

不同显示屏的触控技术原理有所不同。触摸屏的基本原理是&#xff0c;用手指或其他物体触摸安装在显示器前端的触摸屏时&#xff0c;所触摸的位置(以坐标形式)由触摸屏控制器检测&#xff0c;并通过接口(如RS-232串行口)送到CPU&#xff0c;从而确定输入的信息。 目前市场上常…

机器学习之数据清洗

一、介绍 数据清洗是机器学习中的一个重要步骤&#xff0c;它涉及对原始数据进行预处理和修复&#xff0c;以使数据适用于机器学习算法的训练和分析。数据清洗的目标是处理数据中的噪声、缺失值、异常值和不一致性等问题&#xff0c;以提高数据的质量和准确性。 二、方法 处理…

电动汽车智能充电桩及运营管理云解决方案-安科瑞黄安南

摘要&#xff1a;电动汽车采用了电力作为发动能源&#xff0c;但是同样存在很大缺陷,即续航能力方面存在较大不足。因此如何利用现代技术进行电动汽车的智 能充电便十分重要。在电动汽车智能充 电的研究过程中需要用到的技术有有电力电子 变流技术、REIP无线射频技术、智能监控…

Java代码审计13之URLDNS链

文章目录 1、简介urldns链2、hashmap与url类的分析2.1、Hashmap类readObject方法的跟进2.2、URL类hashcode方法的跟进2.3、InetAddress类的getByName方法 3、整个链路的分析3.1、整理上述的思路3.2、一些疑问的测试3.3、hashmap的put方法分析3.4、反射3.5、整个代码 4、补充说明…