Android 12 源码分析 —— 应用层 二(SystemUI大体组织和启动过程)
Android 12 源码分析 —— 应用层 二(SystemUI大体组织和启动过程)
在前一篇文章中,我们介绍了SystemUI怎么使用IDE进行编辑和调试。这是分析SystemUI的最基础,希望读者能尽量掌握。
本篇文章,将会介绍SystemUI的大概组织架构,以及它的启动过程。本篇文章读完,将会知道:
- SystemUI为什么选择使用Dagger2
- SystemUI怎么新建一个模块
- SystemUI的启动流程
在进行阅读之前,请跟着我思考如下的问题:
- SystemUI要完成哪些功能?
- 各个功能之间需要沟通吗?
- 倘若各个功能之间需要进行沟通,怎样组织他们之间的引用关系
- 各个功能需要与系统服务沟通吗?
- 倘若各个功能需要与系统服务沟通,怎么高效的组织他们之间的引用
- 各个功能之间有依赖关系吗?
- 各个功能之间启动有顺序要求吗?
针对上述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篇
- LongScreenShotActivity:长截图使用的视图,用户决定长截图时,调起这个Activity。
- ScreenRecordDialog:录屏时弹出的选项框视图。
- TunerActivity:这是给研发人员用的微调界面。可以使用下面命令打开界面入口
adb shell pm enable com.android.systemui/com.android.systemui.tuner.TunerActivity
然后在设置->系统->System UI Tuner进入界面
- DemoMode:SystemUI的Demo模式,也是给研发人员用的,他是TunerActivity的功能补充,可在开发者选项中打开
- ForceReSizableInfoActivity:弹出应用无法在分屏模式,或者辅助屏幕下运行
- UsbPermissionActivity:确定USB权限弹框
- UsbResolverActivity:为USB设备选择一个应用弹框
- UsbConfirmActivity:弹出一个视图,用来确定是否要使用某个app,是UsbResolverActivity的后续视图
- SensorUseStartedActivity:当传感器在privacy mode下,欲使用传感器时的弹框
- TvUnblockSensorActivity:同SensorUseStartedActivity,只不过这个是运用在电视机上的视图
- UsbAccessoryUriActivity:弹出一个框,让你去下载这个USB设备对应的应用
- UsbContaminantActivity:弹出一个框,表示USB已经停用,停用的原因可能是usb端口处有赃物等。
- UsbDebuggingActivity:弹出是否允许USB调试
- UsbDebuggingActivityAlias:这个是UsbDebuggingActivity的别名
- WifiDebuggingActivity:弹出是否允许网络进行无线调试
- WifiDebuggingActivityAlias:是WifiDebuggingActivity的别名
- WifiDebuggingSecondaryUserActivity:弹出目前登录的用户无法开启无线调试功能,需要切换为主用户
- NetworkOverLimitActivity:弹出数据流量已经达到上限
- MediaProjectionPermissionActivity:多媒体投屏权限确认
- TvNotificationPanelActivity:TV专用,弹出一个消息框
- SlicePermissionActivity:Slice权限弹框
- DessertCase:彩蛋之一
- MLandActivity:彩蛋小游戏
- PeopleSpaceActivity:提示Pepole Space UI的位置,android 11新增功能
- LaunchConversationActivity:当会话被点击的时候,展开视图,Android 11 新增功能
- WorkLockActivity:解锁work profile的界面
- CreateUserActivity:创建用户视图
- Somnambulator:屏保
- BrightnessDialog:亮度弹框
- ForegroundServicesDialog:展示前台services的一个弹框
- ChooserActivity:弹出一个框,让用户选择打开哪一个应用,来处理当前的Intent
- ControlsProviderSelectorActivity 弹出“选择要添加控制器的应用”
- ControlsEditingActivity:编辑控制器,拖拽进行编辑
- ControlsFavoritingActivity:控制器,偏好设置
- ControlsActivity:列出设备控制器
- WalletActivity:电子钱包
- ControlsRequestDialog:control请求添加设备控制器弹框
注意:这里的Controls,是外部设备的控制器,如全屋智能中的控制器。
上面只是一个非常简单的概览,而一些常见组件逻辑和UI细节,将会在后续文章中出现。
看到这里,可能会有读者提问:上面的Activity似乎没有状态栏和锁屏呀,他们的视图,难道不在这些Activity里面吗?
欲探讨这个问题,还需要先看剩下的组件。
Services篇
- SystemUIService:哇哦,多么让人提神的名字,这个Service包含了SystemUI内部的大部分功能它也是我们SystemUI源码分析的重中之重。
- SystemUISecondaryUserService:多用户情况下,该service保证多用户的SystemUI功能正常
- SystemUIAuxiliaryDumpService:开发使用,dump出各个必要部件的信息并查看
- TakeScreenshotService:截屏相关的service
- RecordingService:录屏相关的service
- ImageWallpaper:壁纸相关的service
- PeopleBackupFollowUpJob:People service ui相关的服务
- DessertCaseDream:小彩蛋
- KeyguardService:锁屏相关的服务
- AuxiliaryPersistenceWrapper$DeletionJobService:外部设备控制器相关的服务
- DozeService:跟Doze相关的服务
ContentProvider篇
- FileProvider:提供文件
- KeyguardSliceProvider:提供锁屏Slice
- ClockOptionsProvider:为选择器程序提供时钟预览
- PeopleProvider:返回给定快捷方式的 People Tile 预览
BroadcastReceiver篇
- ScreenshotServiceErrorReceiver:截屏失败广播接收器
- SysuiRestartReceiver:重启SystemUI广播接收器
- ActionProxyReceiver:拦截share和edit intent,便于提前处理一些事情的广播接收器
- DeleteScreenshotReceiver:删除截屏广播接收器
- SmartActionsReceiver:用户点击通知中的smart action之后,用于接收对应的广播,并执行smart action
- ControlsRequestReciver:接收增加控制器请求的广播接收器
- TunerService$ClearReciver:用于调用TunerService的clear的广播接收器
- KeyboardShortcutsReceiver:展示或者隐藏键盘快捷键的广播接收器
- MediaOutputDialogReceiver:接收媒体输出Intent的广播接收器
- PeopleSpaceWidgetPinnedReceiver:当一个联系人Tile widget被添加之后,这个接收器被调用
- 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.Builderinterface Builder {@BindsInstanceBuilder 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.Builderinterface Builder {//省略若干相同部分//@BindsInstance:告诉Dagger2,将t绑定到这个Builder对象中//在Dagger2根据我们画的图,会根据这个Builder接口,生成一个SysUIComponentBuilder对象//在这个对象中,会有一个成员,类型为Optional<TaskSurfaceHelper>名字为setTaskSurfaceHelper.//然后这个setTaskSurfaceHelper()接口函数的实现,就会将参数传入的t保存在setTaskSurfaceHelper成员中。//这个过程就叫做:绑定实例,也即@BindsInstance的语义@BindsInstanceBuilder setTaskSurfaceHelper(Optional<TaskSurfaceHelper> t);//任何一个Builder接口,都必须有一个build()函数,且返回类型为需要构建的对象类型,此处即为SysUIComponentSysUIComponent build();}//定义了一个默认方法,这个方法什么也没有做default void init() {// Do nothing}//告诉Dagger2它的生命周期@SysUISingleton//subcomponent和component一样,如果想要对外提供依赖,就可以定义任何一个函数,函数的返回类型就是//被提供对象的类型。BootCompleteCacheImpl provideBootCacheImpl();//省略若干相同部分//当返回类型为空,而传入类型不为空的时候,表示需要向传入类型对象(SystemUIAppComponentFactory)//中被@inject标记的成员赋值,叫做注入//理论上,函数名为任意值,但是此种函数,几乎只会完成注入的功能,因此此函数最后都叫做injectvoid inject(SystemUIAppComponentFactory factory);//省略若干相同部分
}
在上面的inject函数中,我们可以看看SystemUIAppComponentFactory是怎么样的,源码如下:
public class SystemUIAppComponentFactory extends AppComponentFactory {//@Inject:告诉Dagger2,这个成员,需要Dagger2的注入。//可是Dagger2又是如何知道,该怎么创建ContextComponentHelper的呢?//这就是我们给Dagger2画图的作用,我们已经提前画好图给Dagger2,告诉它应该//怎么创建这个ContextComponentHelper@Injectpublic 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就可以了@InjectContextComponentResolver(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已经能够从我们给它的图中自动找到了@Providespublic 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中,如下:
<applicationandroid: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的简略说明:
- 系统启动system_server进程之后,会执行
new SystemServer().run();
- 在run()方法中,会去启动各种服务,这些服务包括:
- 启动引导服务
- 核心服务
- 其他服务
-
在启动其他服务的时候,会去启动SystemUI(通过Intent)。而要获得启动SystemUI的具体组件,就通过
Android的PackageManager得到, -
而PacakgeManager则通过读取配置config_systemUIServiceComponent得到具体的组件名。Android系统中这个配置
为
<string name="config_systemUIServiceComponent" translatable="false">com.android.systemui/com.android.systemui.SystemUIService</string>
可见这正是,我们在SystemUI中定义的组件。
那么我们就可以总结一下SystemUI的启动过程了
- Android系统启动完成,启动system_server
- system_server,根据配置,通过Intent来启动SystemUI的组件
- SystemUI在启动组件之前,会先创建SystemUIAppComponentFactory对象,然后调用其相应方法
- 接着,SystemUI会创建SystemUIApplication,然后调用其相应方法
- 最后,SystemUI会创建SystemUIService,并调用相应方法,在创建SystemUIService之前,则又会调用在第3步中创建的SystemUIAppComponentFactory对象的相应方法
为何要使用SystemUIAppComponentFactory
SystemUIAppComponentFactory源码如下:
public class SystemUIAppComponentFactory extends AppComponentFactory {private static final String TAG = "AppComponentFactory";@Injectpublic ContextComponentHelper mComponentHelper;public SystemUIAppComponentFactory() {super();}@NonNull@Override//在创建Application之前,这个函数被调用public Application instantiateApplicationCompat(@NonNull ClassLoader cl, @NonNull String className)throws InstantiationException, IllegalAccessException, ClassNotFoundException {//调用父类方法,创建Application,此处会创建AndroidManifest.xml中配置的类//也即SystemUIApplicationApplication 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@Overridepublic Activity instantiateActivityCompat(@NonNull ClassLoader cl, @NonNull String className,@Nullable Intent intent)throws InstantiationException, IllegalAccessException, ClassNotFoundException {//省略若干//此处没有列出内容,原因是:它的逻辑和上一个函数一样}@NonNull@Overridepublic 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/141008541SystemUIFactory.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@Overridepublic BroadcastReceiver instantiateReceiverCompat(@NonNull ClassLoader cl,@NonNull String className, @Nullable Intent intent)throws InstantiationException, IllegalAccessException, ClassNotFoundException {//省略若干//此处没有列出内容,原因是:它的逻辑和上一个函数一样}}
在这个类中,最最主要的功能就三点:
- 相应组件的创建,如SystemUIApplication,SystemUIService等
- mComponentHelper的初始化,也即通过注入实现
- 而在Service组件创建之前,可能会先查询mComponentHelper中是否已经有组件,若有则直接使用
在查看SystemUIApplication 和SystemUIService的创建之前,我们还是要再次思考一个问题:
为什么要用SystemUIAppComponentFactory这个类?这个类真的合理吗?有更好的替代方案吗?
我想答案就在SystemUIService的创建上。正是SystemUIAppComponentFactory这个类的使用,才让我们更好的注入依赖
在进入SystemUIService之前,最先创建的是SystemUIApplication。我们来看看它所做的工作。
SystemUIApplication
源码如下:
public class SystemUIApplication extends Application implementsSystemUIAppComponentFactory.ContextInitializer {public SystemUIApplication() {super();Log.v(TAG, "SystemUIApplication constructed.");// SysUI may be building without protolog preprocessing in some casesProtoLog.REQUIRE_PROTOLOGTOOL = false;}@Overridepublic 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() {@Overridepublic 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() {@Overridepublic void onReceive(Context context, Intent intent) {if (Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) {if (!mBootCompleteCache.isBootComplete()) return;// Update names of SystemUi notification channelsNotificationChannels.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@Injectpublic SystemUIService(@Main Handler mainHandler,DumpHandler dumpHandler,BroadcastDispatcher broadcastDispatcher,LogBufferFreezer logBufferFreezer,BatteryStateNotifier batteryStateNotifier) {//省略赋值代码}@Overridepublic 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触发RescuePartyif (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() {@Overridepublic 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];//启动完成缓存对象的修改,简单,略//首先获取DumpManagerfinal DumpManager dumpManager = mSysUIComponent.createDumpManager();//trace跟踪点,略//挨个启动服务// 1. 首先查看mComponentHelper是否有缓存,如果有则直接使用// 2. 如果没有则反射创建// 3. 创建完成调用start()// 4. 判断是否系统启动完成,如果完成则调用onBootCompleted()// 5. 将启动的服务,加入DumpManager中,以便bugreport触发其dumpfinal 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 longti = 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呢?这么方便的东西怎么还配置个字符串数组呀。
为什么这么配置,我想主要有如下的几个原因:
- AOSP代码的编译,可以使用override功能,将这部分资源文件,放在自定义的目录下达到修改的目的
- 历史原因,以前就是这么配置的,哈哈
现在我们来思考,启动服务的功能逻辑,为什么就不能放在SystemUIservice中而要放在SystemUIApplication中。
如果要放在SystemUIApplication中,为什么不通过SystemUIApplication来启动,而要SystemUIService来启动。
-
除了SystemUIService启动服务以外,在多用户的情况下,也需要启动一些服务,而此时,SystemUI应用
先调用SystemUIApplication,而不会调用SystemUIService。因为SystemUIService的触发是由system_server启动的。
加上,监听系统的启动逻辑,需要统一处理,将启动逻辑,放入SystemUIApplication,变得理所当然。
注意:显然这就导致一个bug,倘若SystemUI中途报错,停止运行,当其再次运行的时候,由SystemUIService启动的各个
服务,还能够正确的初始化吗?显然不能,这在我写这边文章过程中,经常出现
-
既然启动逻辑已经放入了SystemUIApplication中,那么由SystemUIApplication来启动这部分服务不可以吗?
为什么要单独一个SystemUIService作为入口进行启动呢?要回答这个问题,就需要知道,Android的应用启动,是通过启动某个待运行的组件。即system_server若要运行SystemUI,必然要启动某个组件,而不能只启动Application。
故此,我们需要一个用来启动的组件,这个组件就是SystemUI. 由它负责各个具体服务的启动。加上对开机广播的监听,多用户下的协作,就将这部分内容,放在了SystemUIApplication中完成。
又因为SystemUIService需要依赖注入,所以创建了SystemUIAppComponentFactory来实现对应的依赖注入。
至此,我们已经完全弄清了,SystemUIService,SystemUIApplication,SystemUIAppComponentFactory的启动流程
以及为什么这么分配功能。现总结如下。
- system_server启动之后,开始启动各种服务
- 在启动其他服务的时候,会先通过PackageManager,获得要启动systemui的组件名字,然后根据名字启动systemui组件
- 在上面一步获得的名字就是SystemUIService。
- 启动SystemUIService,则会先创建SystemUIApplication,在创建之前会先调用SystemUIAppComponentFactory添加相应的依赖注入
- SystemUIApplication创建之后,则会监听系统的启动广播。
- 接着创建SystemUIService,再创建之前,还会先调用SystemUIAppComponentFactory相应的方法,添加依赖注入
- 创建SystemUIService之后,通过SystemUIApplication启动各种服务
至此,整个SystemUI启动完成。
给SystemUI添加一个自定义的服务
有了前面的分析,我们现在需要,进行测试一下:写一个自定义服务的模块,在这个模块中,我们仅仅打印出其启动过程即可。
- 新建一个类,叫做PrintLogService,这个类继承SystemUI即可。如下
public class PrintLogService extends SystemUI{private String TAG = "PrintLogService";//使用@Inject标记,让Dagger2自动管理依赖@Injectpublic PrintLogService(Context context) {super(context);}//简单打印log@Overridepublic void start() {Slog.d(TAG, "Start PrintLogService");}//简单打印log@Overrideprotected void onBootCompleted() {Slog.d(TAG,"PrintLogService boot completed");}}
- 将这个类的名字放入配置数组中即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>
- 使用如下的命令,进行编译,并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进程,即可
- 从log中我们可以看到如下的输出
这表示我们自定义的服务启动成功!!!
本文完!!
在文中,我们仅仅对于mContextAvailableCallback.onContextAvailable(this);一笔带过
这里面是关于Dagger2中各种Component的初始化,下一篇文章,将会从这个函数出发,一探SystemUI中各种Component的初始化,理解SystemUI中各个组件应该怎样被使用。
相关文章:
Android 12 源码分析 —— 应用层 二(SystemUI大体组织和启动过程)
Android 12 源码分析 —— 应用层 二(SystemUI大体组织和启动过程) 在前一篇文章中,我们介绍了SystemUI怎么使用IDE进行编辑和调试。这是分析SystemUI的最基础,希望读者能尽量掌握。 本篇文章,将会介绍SystemUI的大概…...
【C#】通用类型转换
【C#】通用类型转换 自动类型转换(隐式类型转换)强制类型转换(显式类型转换)通过函数进行转换(通过方法进行类型转换)使用 as 操作符转换通用类型转换方法实现 数据类型转换就是将数据(变量、数…...
传统DNS、负载均衡服务发现框架与专业服务发现框架(Eurek、nacos)分析
1、DNS 服务器 DNS 服务器可以在一定程度上用作服务发现的机制,以下是其冲动服务发现的一些利弊 优势 广泛性: DNS是互联网的标准协议之一,已经广泛地被支持和使用。因此,使用DNS作为服务发现的机制可以借助现有的网络基础设施…...
js中数组常用操作函数
js数组经常会用到,当涉及到 JavaScript 数组的函数,有许多常用的函数可用于对数组进行操作和转换。以下是一些常见的数组函数的讲解 splice() splice() 函数用于修改数组,可以删除、插入或替换数组中的元素。 var fruits [apple, banana,…...
Windows、Mac、Linux端口占用解决
Windows、Mac、Linux端口占用解决 简介 在使用计算机网络时,经常会遇到端口被占用的问题。当一个应用程序尝试使用已经被其他程序占用的端口时,会导致端口冲突,使应用程序无法正常运行。本文将介绍在Windows、Mac和Linux操作系统上解决端口…...
企业文件透明加密软件——「天锐绿盾」数据防泄密管理软件系统
PC访问地址: 首页 一、文档透明加密软件 文档透明加密功能:在不影响单位内部员工对电脑任何正常操作的前提下,文档在复制、新建、修改时被系统强制自动加密。文档只能在单位内部电脑上正常使用,在外部电脑上使用是乱码或无法打…...
Postman接口自动化测试实例
一.实例背景 在实际业务中,经常会出现让用户输入用户密码进行验证的场景。而为了安全,一般都会先请求后台服务器获取一个随机数做为盐值,然后将盐值和用户输入的密码通过前端的加密算法生成加密后串传给后台服务器,后台服务器接到…...
软件团队降本增效-构建人员评价体系
在软件团队中,最大成本往往来自于人力。这是因为软件开发是一项高度技术密集和智力密集的工作,需要研发人员具备较高的专业知识和技能。研发人员的工作状态和主动性对产出和质量具有极大的影响。如果研发人员缺乏积极性和投入度,可能会导致项…...
Python实现SSA智能麻雀搜索算法优化随机森林分类模型(RandomForestClassifier算法)项目实战
说明:这是一个机器学习实战项目(附带数据代码文档视频讲解),如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 麻雀搜索算法(Sparrow Search Algorithm, SSA)是一种新型的群智能优化算法,在2020年提出&a…...
web JS高德地图标点、点聚合、自定义图标、自定义窗体信息、换肤等功能实现和高复用性组件封装教程
文章目录 前言一、点聚合是什么?二、开发前准备三、API示例1.引入高德地图2.创建地图实例3.添加标点4.删除标点5.删除所有标点(覆盖物)6.聚合点7.自定义聚合点样式8.清除聚合9.打开窗体信息 四、实战开发需求要求效果图如下:封装思…...
AlpacaFarm: A Simulation Framework for Methods that Learn from Human Feedback
本文是LLM系列文章,针对《》的翻译。 AlpacaFarm:从人类反馈中学习方法的模拟框架 摘要1 引言2 背景与问题描述3 构造AlpacaFarm4 验证AlpacaFarm模拟器5 AlpacaFarm的基准参考方法6 相关工作7 不足和未来方向 摘要 像ChatGPT这样的大型语言模型由于能够很好地遵循…...
【Linux】Linux工具篇(yum、vim、gcc/g++、gdb、Makefile、git)
🚀 作者简介:一名在后端领域学习,并渴望能够学有所成的追梦人。 🚁 个人主页:不 良 🔥 系列专栏:🛹Linux 🛸C 📕 学习格言:博观而约取ÿ…...
自己实现 SpringMVC 底层机制 系列之-实现任务阶段 5- 完成 Spring 容器对象的自动装配 -@Autowried
😀前言 自己实现 SpringMVC 底层机制 系列之-实现任务阶段 5- 完成 Spring 容器对象的自动装配 -Autowried 🏠个人主页:尘觉主页 🧑个人简介:大家好,我是尘觉,希望我的文章可以帮助到大家&…...
linux的http服务
Web通信基本概念 基于B/S(Browser/Server)架构的网页服务 服务端提供网页 浏览器下载并显示网页 Hyper Text Markup Lanuage,超文本标记语言 Hyper Text Transfer Protocol,超文本传输协议 虚拟机A:构建基本的Web服务 [root…...
Restful架构简单了解
Restful Rest全称representational status transfer 表述性状态转移。 原则 资源与URI URI既可以看成是资源的地址,也可以看成是资源的名称。如果某些信息没有使用URI来表示,那它就不能算是一个资源, 只能算是资源的一些信息而已。URI的设计…...
conda常用命令
使用conda可以在电脑上创建很多套相互隔离的Python环境,命令如下: 创建环境 创建一个名为deeplearning的环境,python版本为3.7 conda create --name deeplearning python3.7查看版本 conda --version切换环境 切换到deeplearning环境 c…...
Linux:shell脚本:基础使用(6)《正则表达式-awk工具》
简介 awk是行处理器: 相比较屏幕处理的优点,在处理庞大文件时不会出现内存溢出或是处理缓慢的问题,通常用来格式化文本信息 awk处理过程: 依次对每一行进行处理,然后输出 1)awk命令会逐行读取文件的内容进行处理 2)a…...
国际阿里云腾讯云:阿里云服务器怎么打包
近年来,跟着云计算的发展,越来越多的人开始运用云服务器来保管自己的运用和网站。其间,阿里云服务器是国内最大的云计算服务供给商之一,能够供给高效安稳的服务器服务。可是,阿里云服务器的打包办法相较于其他云服务器…...
FPGA中锁存器(latch)、触发器(flip-flop)以及寄存器(register)详解
文章目录 1 定义1.1 锁存器(latch)1.2 触发器(flip-flop)1.3 寄存器(register) 2 比较2.1 锁存器(Latch)危害即产生原因2.2 寄存器和锁存器的区别2.3 锁存器和触发器的区别 3 结构3.…...
【正点原子STM32连载】第十八章 通用定时器PWM输出实验 摘自【正点原子】APM32F407最小系统板使用指南
1)实验平台:正点原子stm32f103战舰开发板V4 2)平台购买地址:https://detail.tmall.com/item.htm?id609294757420 3)全套实验源码手册视频下载地址: http://www.openedv.com/thread-340252-1-1.html# 第十…...
分类预测 | MATLAB实现BWO-TCN-Attention数据分类预测
分类预测 | MATLAB实现BWO-TCN-Attention数据分类预测 目录 分类预测 | MATLAB实现BWO-TCN-Attention数据分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.BWO-TCN-Attention数据分类预测程序; 2.无Attention适用于MATLAB 2022b版及以上版本…...
6.链路追踪-Zipkin
链路追踪(Distributed Tracing)是一种用于监视分布式应用程序的技术,通过收集和展示分布式系统中不同组件之间的调用和交互情况,帮助开发人员和运维团队理解系统中的请求流程、性能瓶颈和异常情况。 1.Zipkin Zipkin 是一个开源的…...
基于ACF,AMDF算法的语音编码matlab仿真
目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 .......................................................................... plotFlag …...
python 基础篇 day 1 初识变量和数据类型
文章目录 变量变量作用——用于存储和表示数据。变量命名规则命名法大驼峰小驼峰下划体n j i a x 通常作为临时变量使用 建议 变量种类全局变量(Global Variables)局部变量(Local Variables)静态变量(Static Variables…...
Window下部署使用Stable Diffusion AI开源项目绘图
Window下部署使用Stable Diffusion AI开源项目绘图 前言前提条件相关介绍Stable Diffusion AI绘图下载项目环境要求环境下载运行项目打开网址,即可体验文字生成图像(txt2img)庐山瀑布 参考 本文里面的风景图,均由Stable Diffusion…...
【MySQL】好好学习一下InnoDB中的页
文章目录 一. 前言二. 从宏观层面看页三. 页的基本内容3.1 页的数据结构3.2 用户空间内的数据行结构3.3 页目录 四. 问题集4.1 索引 和 数据页 有什么区别4.2 页的大小是什么决定的4.3 页的大小对哪些情况有影响4.4 一般情况下说的链表有哪几个4.5 如果页的空间满了怎么办4.6 如…...
git开发常用命令
版本回退 soft:git reset --soft HEAD^ 将版本库回退一个版本,且这次提交的所有文件都移动到暂存区 mixed(默认):git reset HEAD^ 将版本库回退一个版本,且这次提交的所有文件都移动到工作区,会…...
WEB APIs day5
一、window对象 BOM属于window对象 1.BOM(浏览器对象模型) bom里面包含着dom,只不过bom我们平时用得比较少,我们经常使用的是dom操作,因为我们页面中的这些标签都是在dom中取的,所以我们操作dom多一点。 window对象…...
html动态爱心代码【一】(附源码)
前言 七夕马上就要到了,为了帮助大家高效表白,下面再给大家带来了实用的HTML浪漫表白代码(附源码)背景音乐,可用于520,情人节,生日,表白等场景,可直接使用。 效果演示 文案修改 var loverNam…...
【仿写tomcat】六、解析xml文件配置端口、线程池核心参数
线程池改造 上一篇文章中我们用了Excutors创建了线程,这里我们将它改造成包含所有线程池核心参数的形式。 package com.tomcatServer.http;import java.util.concurrent.*;/*** 线程池跑龙套** author ez4sterben* date 2023/08/05*/ public class ThreadPool {pr…...
个人网站设计制作步骤/百度搜索下载app
上一期我们研究了StringBuffer的构造方法---1.0那么现在来研究一下StringBuffer有哪些功能吧。 目录 StringBuffer的添加功能 StringBuffer的插入功能 StringBuffer的删除功能 StringBuffer的替换功能 StringBuffer的反转功能 StringBuffer的截取功能 StringBuffer的添加功…...
河南网站建设设计价格/软件外包公司排名
转载自https://www.cnblogs.com/shimily/articles/10702011.html 微信小程序开发文档中针对picker做了详细的解释,但根据笔者的使用,发现了一个好玩的地方。 先针对picker 普通选择器:mode selector 这个模式说下属性吧 上图是微信小程序…...
wordpress时光轴页面/长沙seo优化推荐
英语单词是学好英语的前提条件。英语单词记不住是我们英语学习的一大难题。如何解决一个月单词记不住的难题呢?机农觉得英语词根词缀记忆法是最为高效的单词记忆法,它能让我们在短时间内记下单词,而且不容易遗忘。今天机农通过几个单词实例和…...
南宁百度推广seo/seo优化工具
Report的生成 report_power表示产生power report,update_power表示进行power analysis。 report_power命令可以生成四种形式的report, 1) Power group-based,default的方式。 2) Cell-based,加入-cell_power的option …...
企业关键词推广/seo工资服务
原文地址为: Python进阶08 异常处理作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明。谢谢! 本文特别感谢kylinfish的纠正,相关讨论见留言区。 异常处理 在项目开发中ÿ…...
潍坊 公司 网站/靠网络营销火起来的企业
对于快速标注时,捕捉交叉点等,设置 对象捕捉: 一,对象捕捉工具栏: 右击鼠标于工具栏上打开捕捉 1,利用捕捉功能,在绘图中可迅速的捕捉到各种特征点 2,有端点&…...