Android 12 源码分析 —— 应用层 三(SystemUIFactory及其Dependency解析)
Android 12 源码分析 —— 应用层 三(SystemUIFactory及其Dependency解析)
在上一篇文章中,介绍了SystemUI的启动流程,并且简单提及了Dagger2用来管理各个SystemUI中要用的依赖。而这部分代码就在:mContextAvailableCallback.onContextAvailable(this);流程中。而这部分流程我们只是一笔带过,并没有仔细分析。
接下来将会从这个调用开始,介绍SystemUI中Component,subcomponent的初始化,并理解应该怎么使用这些Component。
本文会先以一个例子,简单引入Dagger2中对各个注解的生成模板,然后在介绍SystemUI中重要component和subcomponent的创建过程,最后过渡到一个关键的类Dependency
注意:此处之所以另起一个例子来说明Dagger2的生成模板,完全是因为Dagger2对SystemUI的生成代码太多,导致不太好列成文档
理解Dagger2的模板
为了能够更好理解Dagger2的生成代码的模板,我们按照如下的例子进行编写,然后查看生成的类.
将下面的代码放入一个DemoComponent.java的文件中
//最顶层的Component,其有两个模块,分别为:
// DemoSubComponent1.DemoModule1
// DemoSubComponent2.DemoModule2
//最顶层Component,提供两个接口,用于得到SubComponent的Factory和Builder类
@Component(modules = {DemoSubComponent1.DemoModule1.class,DemoSubComponent2.DemoModule2.class})
public interface DemoComponent {DemoSubComponent1.Factory getSubComponent1Factory();DemoSubComponent2.Builder getSubComponent2Builder();
}//SubComponent1,提供如下两个功能
// 1. 获得DemoAClass对象
// 2. 向DemoInject对象中,注入需要的对象
@SysUISingleton
@Subcomponent
interface DemoSubComponent1{@Subcomponent.Factoryinterface Factory {DemoSubComponent1 create();}DemoAClass getAClass();void inject(DemoInject inject);//SubComponent1隶属于的模块//指定了DemoInterface 和DemoBinds之间的关系,当需要DemoInterface的时候,// Dagger2会创建对应的DemoBinds对象@Module(subcomponents = DemoSubComponent1.class)abstract class DemoModule1{@Bindsabstract DemoInterface getInterface(DemoBinds bindings);}
}//SubComponent2,提供一个功能
// 1. 获取DemoBClass对象
@Subcomponent
interface DemoSubComponent2{//为了和SubComponent1作区分,这里使用了Builder类来创建SubComponent2@Subcomponent.Builderinterface Builder {DemoSubComponent2 build();}DemoBClass getBClass();//SubComponent2隶属的模块,该模块还提供了一个@Provides@Module(subcomponents = DemoSubComponent2.class)class DemoModule2{//当需要DemoProvider对象的时候,就会调用此方法@ProvidesDemoProvider getProvider(){return new DemoProvider();}}
}//用于演示@Binds
interface DemoInterface{void test();
}//用于演示@Binds
class DemoBinds implements DemoInterface{@InjectDemoBinds(){}@Overridepublic void test() {}
}//用于演示@Provides
class DemoProvider{}//用于演示,inject函数
class DemoInject{@InjectDemoAClass a;
}//用于演示,生命周期
@SysUISingleton
class DemoAClass{@Injectpublic DemoAClass(){}
}//用于演示返回的对象
class DemoBClass{@Injectpublic DemoBClass(){}
}
有了上面所画的图,接下来查看,Dagger2根据图生成的文件,生成的文件有:
- DaggerDemoComponent.java
- DemoAClass_Factory.java
- DemoBClass_Factory.java
- DemoBinds_Factory.java
- DemoInject_MembersInjector.java
- DemoSubComponent2_DemoModule2_GetProviderFactory.java
- DemoSubComponent2_DemoModule2_Proxy.java
接下来我们挨个查看生成的7个文件,分别是什么内容,DaggerDemoComponent.java中会使用剩下的6个类。因此,我们先看剩下的6个类,最后再查看DaggerDemoFactory.java
DemoAClass_Factory,DemoBClass_Factory,DemoBinds_Factory
DemoAClass_Factory的源码如下:
//对于任何类来讲,只要其构造函数带有@Inject,则Dagger2都会创建一个对应的工厂类,
//叫做:XXX_Factory,它实现了Factory<T>接口
//欲使用这个工厂类,则调用create()方法,得到这个工厂类的实例,工厂类的实例永远只有一个
//欲使用这个工厂类产生的对象,则调用newInstance()方法,得到工厂类的产生对象,
//工厂类产生的对象可以有多个
public final class DemoAClass_Factory implements Factory<DemoAClass> {@Overridepublic DemoAClass get() {return newInstance();}public static DemoAClass_Factory create() {return InstanceHolder.INSTANCE;}public static DemoAClass newInstance() {return new DemoAClass();}//用于缓存工厂类实例private static final class InstanceHolder {private static final DemoAClass_Factory INSTANCE = new DemoAClass_Factory();}
}
DemoBClass_Factory,DemoBinds_Factory同DemoAClass_Factory一模一样,不再啰嗦。
DemoInject_MembersInjector
DemoInject_MembersInjector用于辅助成员的注入,因为在DemoInject类中如下的代码
class DemoInject{@InjectDemoAClass a;
}
整个辅助类源码如下:
//每一个辅助注入的类,都是MembersInjector<T>的具体实现
//在实现中,对要注入的成员,使用Provider<T>表示,抽象为:T的提供者。它会在辅助类创建的时候被
//初始化好
// 对被注入的类,使用instance表示
//同工厂类一样,辅助注入类的实例化,也是通过create()方法进行
//同工厂类不一样的是,每次create,都会创建不同的辅助注入对象
//注入方式,则直接调用injectMembers()函数进行
public final class DemoInject_MembersInjector implements MembersInjector<DemoInject> {private final Provider<DemoAClass> aProvider;public DemoInject_MembersInjector(Provider<DemoAClass> aProvider) {this.aProvider = aProvider;}public static MembersInjector<DemoInject> create(Provider<DemoAClass> aProvider) {return new DemoInject_MembersInjector(aProvider);}@Overridepublic void injectMembers(DemoInject instance) {injectA(instance, aProvider.get());}@InjectedFieldSignature("com.android.systemui.dagger.DemoInject.a")public static void injectA(Object instance, Object a) {((DemoInject) instance).a = (DemoAClass) a;}
}
上面的辅助注入类也很简单,接着往下看
DemoSubComponent2_DemoModule2_GetProviderFactory
DemoSubComponent2_DemoModule2_GetProviderFactory类表示的是对@Provides的实现工厂类,源码如下:
//对于任何一个被@Provides标记的函数,Dagger2都会创建一个提供者工厂类,它实现了Factory<T>
//同DemoAClass工厂类一样,它也会有一个create()方法,用于实例化提供者工厂类
//不同之处在于,每次调用都会创建一个新的提供者工厂类//只有需要提供者工厂类,生产提供者时,才会调用get()方法,用于返回生产出来的对象。public final class DemoSubComponent2_DemoModule2_GetProviderFactory implements Factory<DemoProvider> {private final DemoSubComponent2.DemoModule2 module;public DemoSubComponent2_DemoModule2_GetProviderFactory(DemoSubComponent2.DemoModule2 module) {this.module = module;}@Overridepublic DemoProvider get() {return getProvider(module);}public static DemoSubComponent2_DemoModule2_GetProviderFactory create(DemoSubComponent2.DemoModule2 module) {return new DemoSubComponent2_DemoModule2_GetProviderFactory(module);}public static DemoProvider getProvider(DemoSubComponent2.DemoModule2 instance) {return Preconditions.checkNotNullFromProvides(instance.getProvider());}
}
上面的代码,依然很简单,不再啰嗦
DemoSubComponent2_DemoModule2_Proxy
DemoSubComponent2_DemoModule2_Proxy是对抽象模块的表示,源码如下:
public final class DemoSubComponent2_DemoModule2_Proxy {private DemoSubComponent2_DemoModule2_Proxy() {}public static DemoSubComponent2.DemoModule2 newInstance() {return new DemoSubComponent2.DemoModule2();}
}
在DemoModule2中,没有任何需要实现的地方,且@Binds已经语义足够明显,所以上面生成的类,没有任何过多的地方。较简单
接下来,就是看看DaggerDemoComponent如何使用上面创建的类来处理他们之间的依赖关系
DaggerDemoComponent
DaggerDemoComponent是整个依赖注入的入口,其源码如下:
//实现我们的DemoComponent接口,其命名为DaggerAAA_BBB。其中下划线后面的名字,为内部类的名字
public final class DaggerDemoComponent implements DemoComponent {private DaggerDemoComponent() {}//builder()方法和create()方法,都是为了创建DaggerDemoComponent的实例public static Builder builder() {return new Builder();}public static DemoComponent create() {//委托给Builder类创建return new Builder().build();}//返回subcomponent1的Factory类,用于创建subcomponent1@Overridepublic DemoSubComponent1.Factory getSubComponent1Factory() {//见后文return new DemoSubComponent1Factory();}//返回subcomponent2的Bulder类,用于创建subcomponent2@Overridepublic DemoSubComponent2.Builder getSubComponent2Builder() {return new DemoSubComponent2Builder();}//用于创建DaggerDemoComponent,在这个类中,只是简单的new了一个DaggerDemoComponent即可public static final class Builder {private Builder() {}@Deprecatedpublic Builder demoModule2(DemoSubComponent2.DemoModule2 demoModule2) {Preconditions.checkNotNull(demoModule2);return this;}public DemoComponent build() {return new DaggerDemoComponent();}}//实现DemoSubComponent1的Factory接口,该接口提供create()方法来创建对应的实例private final class DemoSubComponent1Factory implements DemoSubComponent1.Factory {@Overridepublic DemoSubComponent1 create() {//创建DemoSubComponent1的实例对象,并返回return new DemoSubComponent1Impl();}}//subcomponet1的具体实现private final class DemoSubComponent1Impl implements DemoSubComponent1 {private Provider<DemoAClass> demoAClassProvider;private DemoSubComponent1Impl() {initialize();}//辅助构造函数,对DemoAClass的提供者,进行初始化//因为我们将DemoAClass的生命周期和DemoSubComponent1的生命周期,都标记为//@SysUISingleton//所以他们同生同死,为了达到这个效果,需要在subcomponent1中持有对DemoAClass的提供者,//而不是每次重新创建(对比下面的getBClass()函数)//这样,在需要DemoAClass的地方(如getAClass()函数),就直接让提供者来提供,而提供者保证了//每次都提供同一个对象//DemoAClass的提供者,我们首先想到的就是,前面介绍过的DemoAClass_Factory工厂类,//调用其newInstance()//就会创建一个DemoAClass对象。但是它并不能保证每次提供同一个对象,因此再次对//DemoAClass_Factory进行封装//这个封装就是DoubleCheck对象,它保证了每次提供同一个对象,同时也保证了线程安全//DoubleCheck最终也会使用DemoAClass_Factory类来实例化DemoAClass对象@SuppressWarnings("unchecked")private void initialize() {this.demoAClassProvider = DoubleCheck.provider(DemoAClass_Factory.create());}//调用DemoAClass的提供者,让其提供同一个对象@Overridepublic DemoAClass getAClass() {return demoAClassProvider.get();}//向DemoInject中注入需要的成员@Overridepublic void inject(DemoInject inject) {injectDemoInject(inject);}//调用对应的辅助注入类,注入需要的成员private DemoInject injectDemoInject(DemoInject instance) {DemoInject_MembersInjector.injectA(instance, demoAClassProvider.get());return instance;}}//subcomponnet2的Builder接口实现private final class DemoSubComponent2Builder implements DemoSubComponent2.Builder {@Overridepublic DemoSubComponent2 build() {//创建subcomponent2对象,并直接返回return new DemoSubComponent2Impl();}}//subcomponent2的具体实现类private final class DemoSubComponent2Impl implements DemoSubComponent2 {//因为DemoSubComponent2和DemoBClass并不是同一生命周期,所以,每次都创建一个新的//DemoBClass对象返回//因此,也就不需要相应的提供者类,也就不需要相应的对提供者类进行初始化 private DemoSubComponent2Impl() {}@Overridepublic DemoBClass getBClass() {return new DemoBClass();}}
}
在Component中完成了如下的工作:
- 实现了component,subcomponent,以及他们对应的Builder, Factory类,subcomponent是component的内部类
- 实现对应的接口方法,这些方法,会委托给其他的xxx_Factory,xxxx_MembersInjector辅助类来完成
- 被委托的xxx_Factory工厂类,xxx_MembersInjector辅助类,都会被正确的初始化
至此,我们对Dagger2生成的模板有了一个整体把握,并对其中的实现细节,做了一些注释说明。
接下来我们继续看看SystemUI中的重要组件的创建过程
SystemUI 创建Dagger2依赖的起点
第一篇文章,我们一笔带过了SystemUIApplication.onCreate()中的mContextAvailableCallback.onContextAvailable(this).
这就是Dagger2创建依赖的起点,mContextAvailableCallback的赋值,在SystemUIAppComponentFactory.java中。
如下:
@NonNull
@Override
//此函数在创建SystemUIApplication之前调用
public Application instantiateApplicationCompat(@NonNull ClassLoader cl, @NonNull String className)throws InstantiationException, IllegalAccessException, ClassNotFoundException {//调用父类方法,创建Application,此处的app实例为SystemUIApplicationApplication app = super.instantiateApplicationCompat(cl, className);if (app instanceof ContextInitializer) {//给SystemUIApplication的mContextAvailableCallback赋值((ContextInitializer) app).setContextAvailableCallback(context -> {//SystemUIApplication的onCreate处开始调用//1. 首先创建SystemUIFactory(SystemUIFactory会负责创建各种依赖)//2. 然后马上注入SystemUIAppComponentFactory所需的成员(即// ContextComponentHelper)SystemUIFactory.createFromConfig(context);SystemUIFactory.getInstance().getSysUIComponent().inject(SystemUIAppComponentFactory.this);});}return app;
}
从上面我们看见了SystemUIFactory调用静态方法createFromConfig进行创建。从名字也能猜测一二:从配置中创建SystemUIFactory
唉,这个就值得思考了,为何要从配置中创建?难道可以配置成不同的SytemUIFactory吗?每个SystemUIFactory有什么区别吗?
我们带着问题往下看
SystemUIFactory
SystemUIFactory从名字上,似乎是SystemUI的工厂类。在上一篇文章Android 12 源码分析 —— 应用层 二(SystemUI大体组织和启动过程):http://t.csdn.cn/AuzsL的自定义组件小节中,我们自定以的组件继承了SystemUI
难道SystemUIFactory是它的工厂类?
我们来看看源码,为了减少干扰,我们从上一小节中的createFromConfig方法开始查看,如下
public static void createFromConfig(Context context) {createFromConfig(context, false);
}@VisibleForTesting
//最终调用的地方
public static void createFromConfig(Context context, boolean fromTest) {//1. 如果SystemUIFactory,即mFactory,已经存在,则什么也不做if (mFactory != null) {return;}//2. 倘若1不满足,则读取配置文件中的类名,然后使用反射创建这个对象//config_systemUIFactoryComponent的值即为com.android.systemui.SystemUIFactory//那么请思考,是不是还有另外的类名,事实上是有的,还可以为://com.android.systemui.tv.TvSystemUIFactory.我们后面来比较他们之间的区别final String clsName = context.getString(R.string.config_systemUIFactoryComponent);if (clsName == null || clsName.length() == 0) {throw new RuntimeException("No SystemUIFactory component configured");}//3. 创建完成对象之后,则调用init方法try {Class<?> cls = null;cls = context.getClassLoader().loadClass(clsName);mFactory = (SystemUIFactory) cls.newInstance();mFactory.init(context, fromTest);} catch (Throwable t) {Log.w(TAG, "Error creating SystemUIFactory component: " + clsName, t);throw new RuntimeException(t);}
}
接下来,继续进入其初始化函数中,如下:
@VisibleForTestingpublic void init(Context context, boolean fromTest)throws ExecutionException, InterruptedException {//是否要运行初始化的一个判断,全部满足下面的条件则初始化//1. 非测试模块运行//2. 是主用户//3. 是主进程mInitializeComponents = !fromTest&& android.os.Process.myUserHandle().isSystem()&& ActivityThread.currentProcessName().equals(ActivityThread.currentPackageName());//又见GlobalRootComponent。在上一篇文章中已经提及过//这里就是去构建整个应用最最顶层的那个ComponentmRootComponent = buildGlobalRootComponent(context);//从GlobalRootComponent中,获取需要创建依赖的Builder。然后创建依赖//1. 先创建WmComponent (这是一个与SysUIComponent站在同一维度的Component,它负责提供WindowManager相关的东西,此处可不用太过在意,后面会详解)mWMComponent = mRootComponent.getWMComponentBuilder().build();if (mInitializeComponents) {//然后初始化mWMComponent.init();}//2. 再创建SysUIComponentSysUIComponent.Builder builder = mRootComponent.getSysUIComponent();if (mInitializeComponents) {//将需要的各种食材,放入Builder,最后build()一下就是最终对象//使用Builder,可以不用考虑这些函数的调用顺序,他们会在Builder类中被处理好builder = prepareSysUIComponentBuilder(builder, mWMComponent).setPip(mWMComponent.getPip()).setLegacySplitScreen(mWMComponent.getLegacySplitScreen()).setSplitScreen(mWMComponent.getSplitScreen()).setOneHanded(mWMComponent.getOneHanded()).setBubbles(mWMComponent.getBubbles()).setHideDisplayCutout(mWMComponent.getHideDisplayCutout()).setShellCommandHandler(mWMComponent.getShellCommandHandler()).setAppPairs(mWMComponent.getAppPairs()).setTaskViewFactory(mWMComponent.getTaskViewFactory()).setTransitions(mWMComponent.getTransitions()).setStartingSurface(mWMComponent.getStartingSurface()).setTaskSurfaceHelper(mWMComponent.getTaskSurfaceHelper());} else {builder = prepareSysUIComponentBuilder(builder, mWMComponent).setPip(Optional.ofNullable(null)).setLegacySplitScreen(Optional.ofNullable(null)).setSplitScreen(Optional.ofNullable(null)).setOneHanded(Optional.ofNullable(null)).setBubbles(Optional.ofNullable(null)).setHideDisplayCutout(Optional.ofNullable(null)).setShellCommandHandler(Optional.ofNullable(null)).setAppPairs(Optional.ofNullable(null)).setTaskViewFactory(Optional.ofNullable(null)).setTransitions(Transitions.createEmptyForTesting()).setStartingSurface(Optional.ofNullable(null)).setTaskSurfaceHelper(Optional.ofNullable(null));}//创建,然后初始化mSysUIComponent = builder.build();if (mInitializeComponents) {mSysUIComponent.init();}//3. 通过SysUIComponent获得Dependency对象,然后调用start()函数进行初始化,// (似乎叫做init()函数更加合理一点)Dependency dependency = mSysUIComponent.createDependency();dependency.start();}
在SystemUIFactory.init()函数中,主要做了四件事:
- 创建并初始化GlobalRootComponent
- 创建并初始化WMComponent
- 创建并初始化SysUIComponent
- 创建并初始化Dependency
这4个对象,即为SystemUI整个应用需要用到的组件,通过这4个组件,就可以得到Dagger2中的各种各样的依赖。
在挨个介绍这4个对象之前。我们先处理一下前面的疑问,分别有:
- SystemUIFactory为何要从配置中创建?
- SystemUIFactory的命名,是不是感觉有些奇怪了?
回答问题1:因为SystemUIFactory不仅仅可以直接实例化SysetmUIFactory,有时还需要实例化SystemUIFactory的子类(比如TvSystemUIFactory),之所以需要有一个TvSystemUIFactory,是因为它需要一个不同版本的GlobalRootComponent叫做TvGloabRootComponent.而这个TvGlobalRootComponent继承于GlobalRootComponent
回答问题2:显然SystemUIFactory提供的实例创建更应该叫GloablRootComponentFactory。而不应该叫SystemUIFactory.我想这也是开发人员,不够严谨的地方。
当然,也可能还有我未理解到的意思
此时,SystemUIFactory其实已经没有什么内容可以介绍了,剩下的内容无外乎getter,内容简单,此处略过
接下来就是几个Component的创建过程了
GlobalRootComponent
GlobalRootComponent的创建,需要从它的图说起。在Android 12 源码分析 —— 应用层 二(SystemUI大体组织和启动过程):http://t.csdn.cn/AuzsL一文中,我们给GlobalRootComponent定义了一个Builder接口。
如下:
@Singleton
@Component(modules = {GlobalModule.class,SysUISubcomponentModule.class,WMModule.class})
public interface GlobalRootComponent {@Component.Builderinterface Builder {@BindsInstanceBuilder context(Context context);GlobalRootComponent build();}//省略无关紧要部分
}
那么Dagger2对这个Builder是怎么生成的呢?
Dagger2生成的GlobalRootComponent.Builder
在编译的过程中,上面的Builder接口,将会被Dagger2实现,并生成一个名叫Builder的类。如下:
该类位于:
out/soong/.intermediates/frameworks/base/packages/SystemUI/SystemUI-core/android_common/kapt/gen/sources/com/android/systemui/dagger/DaggerGlobalRootComponent.java
private static final class Builder implements GlobalRootComponent.Builder {private Context context;@Overridepublic Builder context(Context context) {//1. 先判断传入的参数是否为空//2. 将传入的参数,赋值给内成员(这也是在上一篇文章中提及的BindsInstance的语义)this.context = Preconditions.checkNotNull(context);return this;}@Overridepublic GlobalRootComponent build() {//1. 先判断context是否为空//2. new一个DaggerGlobalRootComponennt对象,该对象就是Dagger2中实现// GlobalRootComponent接口的对象Preconditions.checkBuilderRequirement(context, Context.class);return new DaggerGlobalRootComponent(new GlobalModule(), context);}}
接下来我们看看Dagger2对GlobalRootComponent的实现细节。
Dagger2生成的GlobalRootComponent
生成的文件位于:
out/soong/.intermediates/frameworks/base/packages/SystemUI/SystemUI-core/android_common/kapt/gen/sources/com/android/systemui/dagger/DaggerGlobalRootComponent.java
内容特别多,为了简化分析,我会缩进部分代码,并对其中的实现细节进行简化,因为他们的实现和前面介绍的模板几乎没有差别,现在只需对其结构进行了解。
public final class DaggerGlobalRootComponent implements GlobalRootComponent {//定义一个成员变量,类型为Provider<T> 它可以通过//Provider<T>.get() 得到对应的具体的对象//正是这个get()函数,可以让成员真正被需要的时候才初始化//这样可以减少内存的使用//可将其理解为:Context的提供者(同前面的模板一起理解,几乎可以肯定,它的实现为// DoubleCheck类)private Provider<Context> contextProvider;//省略余下的类似的成员定义//构造器函数,Dagger2根据我们画的图,会正确的处理构造函数所需的依赖private DaggerGlobalRootComponent(GlobalModule globalModuleParam, Context contextParam) {this.context = contextParam;initialize(globalModuleParam, contextParam);}//辅助构造函数的方法,用于初始化成员变量@SuppressWarnings("unchecked")private void initialize(final GlobalModule globalModuleParam, final Context contextParam) {//在方法体中,Dagger2根据我们画的图,自动去找寻需要的对象,并初始化本对象成员//这也正是Dagger2的最大作用,它可以简化我们的代码编写。自动帮我们完成各种各样的依赖//方法体中的赋值相对简单。不做过多介绍//这里面其实就是:使用DoubleCheck对成员进行初始化(结合前面的模板进行思考)} //同构造函数一样,mainResources你也不需要知道为什么会出现在这个地方(这属于Dagger2的内部// 细节)。Dagger2会自动处理好对于这个函数的调用private Resources mainResources() {//AAA_BBBFactory通常表示,AAA类的BBB方法的工厂类,它实现了Factory<T>接口return FrameworkServicesModule_ProvideResourcesFactory.provideResources(context);}//同MainResources()函数一样,Dagger2自动处理好其调用private Executor mainExecutor() {return GlobalConcurrencyModule_ProvideMainExecutorFactory.provideMainExecutor(context);}//这个是实现的GlobalRootComponent的接口.在这个接口中,直接new了一个对象并返回//倘若每次调用这个方法,则会创建不同的对象。这是因为他们的生命周期不一样//GlobalRootComponent的生命周期为@SingleTton//而WMComponent.Builder的生命周期为@WMSingleton@Overridepublic WMComponent.Builder getWMComponentBuilder() {return new WMComponentBuilder();}//同WMComponent.Builder一样,直接new了一个对象返回,因为其生命周期为//@SysUISingleton@Overridepublic SysUIComponent.Builder getSysUIComponent() {return new SysUIComponentBuilder();}//对GlobalRootComponent的接口方法的实现。在实现中,使用了ThreadFactoryImpl_Factory类来//实例化对象//这里可能就会有以问题了,为何不是直接new一个对象,反而用了ThreadFactoryImpl_Factory来实例化//因为在GlobalConcurrencyModule里面有如下的绑定关系//@Binds//public abstract ThreadFactory bindExecutorFactory(ThreadFactoryImpl impl)//在需要ThreadFactory的时候,就会创建ThreadFactoryImpl实例。而ThreadFactoryImpl实例,//则由//其对应的工厂类(ThreadFactoryImpl_Factory)来创建@Overridepublic ThreadFactory createThreadFactory() {return ThreadFactoryImpl_Factory.newInstance();}//返回一个为空的Optionalprivate static <T> Provider<Optional<T>> absentJdkOptionalProvider() {@SuppressWarnings("unchecked") // safe covariant castProvider<Optional<T>> provider = (Provider<Optional<T>>) ABSENT_JDK_OPTIONAL_PROVIDER;return provider;}//使用Provider包装一个非空的Optional,所有操作最后都会调用到其内部的delegateprivate static final class PresentJdkOptionalInstanceProvider<T> implements Provider<Optional<T>> {}//暂时未知private static final class PresentJdkOptionalLazyProvider<T> implements Provider<Optional<Lazy<T>>> {}//实现Builder接口//各个接口,只是简单的setterprivate static final class Builder implements GlobalRootComponent.Builder {}//实现接口private final class WMComponentBuilder implements WMComponent.Builder {}//实现子组件,只要是其子组件,就在内部实现一个非静态的类,因为subcomponent含有//component的所有依赖关系,因此需要实现为一个内部类,这样它就会持有一个对外部类的引用//同理,WMComponentImpl的subcomponent也会在其内部出现private final class WMComponentImpl implements WMComponent {}//实现Builderprivate final class SysUIComponentBuilder implements SysUIComponent.Builder {}//实现子组件private final class SysUIComponentImpl implements SysUIComponent {}
}
在对GlobalRootImpl接口的实现中,分别完成了如下的工作:
- 根据Dagger2中的图实现对应的构造函数
- 根据Dagger2中的图实现对应的接口方法
- 对subcomponent,将其实现为内部类
- 对各个Builder接口,进行实现
从上我们也可以看到,其和我们的Demo模板的生产,差异不大,只是体量变大了很多
接下来看看WMComponent的实现
WMComponent
WMComponent的定义如下
@WMSingleton
@Subcomponent(modules = {WMShellModule.class})
public interface WMComponent {//@Subcomponent.Builder定义了Builder接口//在上面,已经看到了WMComponent.Builder的接口实现了@Subcomponent.Builderinterface Builder {WMComponent build();}//默认方法,用来初始化整个Shell.此处可以暂时不用管Shell对应的逻辑语义//实际上,你可以将其理解为对WindowManager的部分功能的封装并提供接口让用户与WM可以交互//犹如Bash shell(Bourne-Again Shell)在OS中的作用default void init() {getShellInit().init();}//用来获取ShellInit的实现,它只会在上面的init函数中被调用//ShellInit的抽象:表示对Shell进行初始化的一个入口点。@WMSingletonShellInit getShellInit();//返回一个Optional<ShellCommandHandler>对象@WMSingletonOptional<ShellCommandHandler> getShellCommandHandler();//省略类似部分
}
在上面的图中,我们“绘制”了WMComponent的Builder接口,同时还绘制了可以获取ShellInit的接口。
在查看Dagger2生成的代码之前,我们看看,我们是怎么绘制图,让Dagger2来生成ShellInit的。
再次注意:ShellInit的抽象表示的是WMShell的一个入口对象。
从上面的代码可以知道,WMComponent的模块有WMShellModule。直接搜寻其源码,查看是否有ShellInit的返回,发现并没有。但是WMShellModule的还包含一个WMShellBaseModule在其中可以搜寻到ShellInit的返回。如下
WMShellBaseModule
WMShellBaseModuel,告诉了Dagger2如何创建ShellInit对象,如下
@Module
public abstract class WMShellBaseModule{//省略部分代码//通过@Provides告诉Dagger2,当需要ShellInit的时候,将会调用本函数//即返回ShellInitImpl的asShellInit()方法//而ShellInitImpl的对象,则通过下一个方法进行提供@WMSingleton@Providesstatic ShellInit provideShellInit(ShellInitImpl impl) {return impl.asShellInit();}//告诉Dagger2如果需要ShellInitImpl对象的时候,则由本方法提供//在本方法中,通过new一个ShellInitImpl对象来创建对应的ShellInitImpl@WMSingleton@Providesstatic ShellInitImpl provideShellInitImpl(DisplayImeController displayImeController,DragAndDropController dragAndDropController,ShellTaskOrganizer shellTaskOrganizer,Optional<BubbleController> bubblesOptional,Optional<LegacySplitScreenController> legacySplitScreenOptional,Optional<SplitScreenController> splitScreenOptional,Optional<AppPairsController> appPairsOptional,Optional<PipTouchHandler> pipTouchHandlerOptional,FullscreenTaskListener fullscreenTaskListener,Transitions transitions,StartingWindowController startingWindow,@ShellMainThread ShellExecutor mainExecutor) {return new ShellInitImpl(displayImeController,dragAndDropController,shellTaskOrganizer,bubblesOptional,legacySplitScreenOptional,splitScreenOptional,appPairsOptional,pipTouchHandlerOptional,fullscreenTaskListener,transitions,startingWindow,mainExecutor);}
}
从上面的代码可以知道,WMShell的初始化入口点,是ShellInitImpl的asShellInit()方法返回的对象.至于该对象内部是如何进行初始化的,会在合适的时候介绍到,现在我们专注于WMComponent的各种初始化上
同理对WMComponent中的其他接口方法,实现细节类似。
接下来看看Dagger2对WMComponenet相关代码的实现。首先是其Builder的实现
Dagger2生成的WMComponent.Builder
在前文中的GlobalRootComponent的实现中,我们看到了WMComponent.Builder的实现,现将其内容展现如下:
public final class DaggerGlobalRootComponent implements GlobalRootComponent{//省略部分代码private final class WMComponentBuilder implements WMComponent.Builder{@Overridepublic WMComponent build(){//直接new一个WMComponentImpl,这个对象是对WMComponent接口的实现return new WMComponentImpl();}}//省略部分代码
}
接下来看看,Dagger2对WMComponent接口的实现。
Dagger2生成的WMComponent
WMComponent作为GlobalRootComponent的subcomponent,则WMComponent被放置在了DaggerGlobalRootComponent的内部,如下:
public final class DaggerGlobalRootComponent implements GlobalRootComponent{//省略部分代码private final class WMComponentImpl implements WMComponent {//同component一样,将成员定义成Provider的封装//只要在需要的时候才通过其Provider<T>.get()方法获取其内部的实现private Provider<Handler> provideShellMainHandlerProvider;//省略相似代码//生成的构造函数,通过调用initialize()方法进行初始化private WMComponentImpl() {initialize();}//对内部的成员进行初始化@SuppressWarnings("unchecked")private void initialize() {//Dagger2自动找到需要的依赖,然后对本对象的成员,进行赋值初始化操作}//实现其对应的接口@Overridepublic ShellInit getShellInit() {return provideShellInitProvider.get();}//省略其他相似代码}//省略部分代码
}
从上面生成的代码中,我们看一看到WMComponent这个subcomponent 几乎和GlobalRootComponent的结构一致。
而这些生成内的东西,现在不需要关系,我们只需要知道其结构和大体抽象代码,在真正的业务实现细节中,会在有需要的地方进行提及。
再次提醒:WMComponent为Window Manager提供给SystemUI的交互接口。现在只需知道这一层抽象意义即可
接下来继续看看SysUIComponent.
SysUIComponent
先看看源码中,是如何定义SysUIComponent的,即我们如何绘制SysUIComponent的,如下:
@SysUISingleton
@Subcomponent(modules = {DefaultComponentBinder.class,DependencyProvider.class,SystemUIBinder.class,SystemUIModule.class,SystemUIDefaultModule.class})
public interface SysUIComponent {//又来Builder模式//并传递WMComponent中相应的组件给SysUIComponent//正是基于此,WMComponent组件的初始化必须在SysUIComponent组件之前完成//且可以通过提供Optional<?>.empty()来替换其中的组件,这也是SystemUIFactory中的init()//函数中的部分代码@SysUISingleton@Subcomponent.Builderinterface Builder {//绑定实例,内容如前文,对应的语义见上一篇博文<http://t.csdn.cn/4LyLq>@BindsInstanceBuilder setPip(Optional<Pip> p);//省略一些相似代码//build函数,返回SysUIComponent接口的实现//其实,仔细看过前面的WMComponent就会知道,返回的实例多半叫做SysUIComponentImplSysUIComponent build();}//默认的初始化操作,为了保持跟WMComponent一致的操作步调,这里依然有一个init()接口//但是它的函数体为空default void init() {// Do nothing}//返回一个BootCompleteCacheImpl对象//这个对象,我们在前一篇博文<http://t.csdn.cn/4LyLq>中介绍过,用于缓存是否所有的//SystemUI服务都已经启动完成//其实,这里读者可以思考一下,是否BootCompleteCacheImpl对象还会再需要?如果不需要了,//它能被GC回收吗?//如果不能回收,是不是代表BootCompleteCacheImpl的生命周期不应该这样设置?@SysUISingletonBootCompleteCacheImpl provideBootCacheImpl();//返回一个Dependency对象//Dependency对象,提供了SystemUI各个组件的快捷访问,后文我们将会其进行详细介绍@SysUISingletonDependency createDependency();//省略相同的代码//inject接口,表示对PeopleProvider对象中,注入被@Inject标记的成员void inject(PeopleProvider peopleProvider);
}
其实有了上面的注释,以及前面的模板,读者应该已经知道了Dagger2对上面的一些生成代码了。
接下来我们会先看对应的Builder接口的生成,然后再看SysUIComponent接口的生成
Dagger2生成的SysUIComponent.Builder
SysUIComponent.Builder接口依然在DaggerGlobalRootComponent类中,如下:
private final class SysUIComponentBuilder implements SysUIComponent.Builder {//成员的定义,省略//定义必要的成员,用于在build阶段生成最终的SysUIComponentImpl//各种setter接口函数的实现,用于保存传入的参数,很简单,省略//接口的build函数实现,先检查,然后new一个SysUIComponentImpl对象@Overridepublic SysUIComponent build() {Preconditions.checkBuilderRequirement(setPip, Optional.class);Preconditions.checkBuilderRequirement(setLegacySplitScreen, Optional.class);Preconditions.checkBuilderRequirement(setSplitScreen, Optional.class);Preconditions.checkBuilderRequirement(setAppPairs, Optional.class);Preconditions.checkBuilderRequirement(setOneHanded, Optional.class);Preconditions.checkBuilderRequirement(setBubbles, Optional.class);Preconditions.checkBuilderRequirement(setTaskViewFactory, Optional.class);Preconditions.checkBuilderRequirement(setHideDisplayCutout, Optional.class);Preconditions.checkBuilderRequirement(setShellCommandHandler, Optional.class);Preconditions.checkBuilderRequirement(setTransitions, ShellTransitions.class);Preconditions.checkBuilderRequirement(setStartingSurface, Optional.class);Preconditions.checkBuilderRequirement(setTaskSurfaceHelper, Optional.class);return new SysUIComponentImpl(new DependencyProvider(), new NightDisplayListenerModule(), new UserModule(), setPip, setLegacySplitScreen, setSplitScreen, setAppPairs, setOneHanded, setBubbles, setTaskViewFactory, setHideDisplayCutout, setShellCommandHandler, setTransitions, setStartingSurface, setTaskSurfaceHelper);}}
Builder函数的实现很是简单
- 保存必要的中间对象,如各种Optional<T>
- new一个SysUIComponent接口的实现
接下来看看SysUIComponent的实现,其实到这里,大家已经非常熟悉了,它的结构等同于GlbalRootComponent,也等同于WMComponent
Dagger2生成的SysUIComponent
作为GlobalRootComponent的subcomponent,所以SysUIComponent的生成依然在DaggerGlobalRootComponent中,如下
public final class DaggerGlobalRootComponent implements GlobalRootComponent {//省略不相干的部分private final class SysUIComponentImpl implements SysUIComponent {//省略成员定义,其结构和内容,相似于GlobalRootComponent和WMComponent,故不再啰嗦//对应的构造函数private SysUIComponentImpl(DependencyProvider dependencyProviderParam,NightDisplayListenerModule nightDisplayListenerModuleParam, UserModule userModuleParam,Optional<Pip> setPipParam, Optional<LegacySplitScreen> setLegacySplitScreenParam,Optional<SplitScreen> setSplitScreenParam, Optional<AppPairs> setAppPairs,Optional<OneHanded> setOneHandedParam, Optional<Bubbles> setBubblesParam,Optional<TaskViewFactory> setTaskViewFactoryParam,Optional<HideDisplayCutout> setHideDisplayCutoutParam,Optional<ShellCommandHandler> setShellCommandHandlerParam,ShellTransitions setTransitionsParam, Optional<StartingSurface> setStartingSurfaceParam,Optional<TaskSurfaceHelper> setTaskSurfaceHelper) {//因为成员函数过多,因此分成了7个初始化辅助函数进行初始化}//initialize,initialize1,initialize2,initialize3,initialize4,initialize5,//initialize6//这几个函数都是用于初始化成员的,其中初始化成员的方法和过程,跟GlobalRootComponent//和WMComponent类似@SuppressWarnings("unchecked")private void initialize()//实现的接口,省略,较简单//对PeopleProvider中被@Inject标注的成员,进行赋值@Overridepublic void inject(PeopleProvider peopleProvider) {injectPeopleProvider(peopleProvider);}//使用辅助类 Type_MembersInjector。进行成员的赋值//PeopleProvider_MembersInjector.injectMPeopleSpaceWidgetManager()的实现就为://instance.mPeopleSpaceWidgetManager = mPeopleSpaceWidget;private PeopleProvider injectPeopleProvider(PeopleProvider instance) {PeopleProvider_MembersInjector.injectMPeopleSpaceWidgetManager(instance, peopleSpaceWidgetManagerProvider.get());return instance;}//SysUIComponent的subcomponent的builder的实现private final class ExpandableNotificationRowComponentBuilder implements ExpandableNotificationRowComponent.Builder {}//SysUIComponent的subcomponent的实现private final class ExpandableNotificationRowComponentImpl implements ExpandableNotificationRowComponent {}//SysUIComponent的subcomponent的Factory的实现private final class KeyguardBouncerComponentFactory implements KeyguardBouncerComponent.Factory {}//SysUIComponent的subcomponent的实现private final class KeyguardBouncerComponentImpl implements KeyguardBouncerComponent {}//省略相似部分}
}
从上面的生成结果中,我们可以看到,SysUIComponent的生成,跟GlobalRootComponent很是类似。主要完成:
- 其内部subcomponent的Bulider的实现
- 其内部subcomponent的Factory的实现
- 其内部subcomponent的实现
- 其内部接口方法的实现
有了上面的介绍,现在基本上已经非常明确,各个组件的初始化过程和内部实现细节了。
接下来还剩下最后一个Dependency的实现细节
Dependency
Dependency对应的抽象是什么?这里直接给出一个结论:为了方便在各个模块中能够直接引用Dagger2中的依赖。举个例子,如果我要通过SysUIComponent访问某个依赖,则可能出现如下的调用情况:
d=sysuicomponent.getxxx().getxxx().getxxx().getxxx();
d.xxx();
上面的代码中,出现了多层的getxxx()函数,因此,为了方便,将一些常见的依赖,通过Dependency来调用即可。现在提供的方法为:Dependency.get(依赖.class)从而获得对应的依赖
这也是这个类的名字的由来。
有了上面的观点就非常容易看懂Dependency.java文件了
如下:
@SysUISingleton
public class Dependency {//一些简单的成员定义,省略//由Dagger2进行创建@Injectpublic Dependency() {}//初始化各个依赖protected void start() {//mProviders是一个以Object为对象,以LazyDependencyCreator为值的Map//LazyDependencyCreator封装创建依赖的方法,用于在需要的时候创建mProviders.put(TIME_TICK_HANDLER, mTimeTickHandler::get);//省略相似部分//将Dependency对象保存在Static字段中,便于访问Dependency.setInstance(this);}//省略一些简单的函数//当访问某个具体的依赖时,最终的访问点private synchronized <T> T getDependencyInner(Object key) {@SuppressWarnings("unchecked")//1. 首先检查依赖的缓存中,是否有对应的依赖,其中缓存使用了ArrayMap进行存储(注意其和// HashMap之间的差别)//2. 如果没有,调用createDependency创建T obj = (T) mDependencies.get(key);if (obj == null) {obj = createDependency(key);//3. 如果创建完成,就将其加入缓存中,方便未来调用。注意此处有同步控制,防止并发// 带来的问题mDependencies.put(key, obj);//4. 如果某些依赖,需要使用dump功能,则将其注册到DumpManager。如同// SystemUIService中的初始化最后部分一样if (autoRegisterModulesForDump() && obj instanceof Dumpable) {mDumpManager.registerDumpable(obj.getClass().getName(), (Dumpable) obj);}}return obj;}//创建依赖的具体实现@VisibleForTestingpublic <T> T createDependency(Object cls) {//1. 检查参数,必须是DependencyKey类型或者Class类型Preconditions.checkArgument(cls instanceof DependencyKey<?> || cls instanceof Class<?>);//2. 通过key,得到LazyDependencyCreator对象。这个对象通常封装着一个用于创建具// 体对象的方法。如Lazy<T>的get()方法@SuppressWarnings("unchecked")LazyDependencyCreator<T> provider = mProviders.get(cls);if (provider == null) {throw new IllegalArgumentException("Unsupported dependency " + cls+ ". " + mProviders.size() + " providers known.");}//3. 调用封装的方法(如Lazy<T>.get()),创建具体的对象return provider.createDependency();}//定义延迟创建的一种封装形式private interface LazyDependencyCreator<T> {T createDependency();}//对依赖的key的封装public static final class DependencyKey<V> {private final String mDisplayName;public DependencyKey(String displayName) {mDisplayName = displayName;}@Overridepublic String toString() {return mDisplayName;}}//省略掉一些简单的代码
}
从上面可以知道,Dependency就做了两件事情:
- 缓存需要用的依赖的Lazy<T>封装
- 缓存需要用的依赖的具体实例。
- 然后在需要的时候通过get()函数,即可得到相应的依赖
至此,我们介绍了SystemUIFactory的init()方法中,重要组件的创建和初始化细节。也完成了整个SystemUI的启动
现在将整个启动过程,总结如下:
- system_server启动之后,开始启动各种服务
- 在启动其他服务的时候,会先通过PackageManager,获得要启动systemui的组件名字,然后根据名字启动systemui组件
- 在上面一步获得的名字就是SystemUIService。
- 启动SystemUIService,则会先创建SystemUIApplication,在创建之前会先调用SystemUIAppComponentFactory添加相应的依赖注入
- SystemUIApplication创建之后,通过调用SystemUIFactory.init() 方法初始化Dagger2中的依赖。(本文的内容)
- 同时在SystemUIApplication中会监听系统的启动广播。
- 接着创建SystemUIService,再创建之前,还会先调用SystemUIAppComponentFactory相应的方法,添加依赖注入
- 创建SystemUIService之后,通过SystemUIApplication启动各种服务
接下来,我们将进入SystemUI的UI部分。看看状态栏是如何被加入整个屏幕的。然后一步步拆分,查看下拉状态栏中的各个图标的实现
因为最近要上课,尽量保持一周一更
相关文章:
Android 12 源码分析 —— 应用层 三(SystemUIFactory及其Dependency解析)
Android 12 源码分析 —— 应用层 三(SystemUIFactory及其Dependency解析) 在上一篇文章中,介绍了SystemUI的启动流程,并且简单提及了Dagger2用来管理各个SystemUI中要用的依赖。而这部分代码就在:mContextAvailableC…...
考前冲刺上岸浙工商MBA的备考经验分享
2023年对于许多人来说都是不平凡的一年,历经三年的抗争,我们终于成功结束了疫情。而我也很幸运的被浙工商MBA项目录取,即将开始全新的学习生活。身为一名已在职工作6年的人,能够重回校园真是一种特别令人激动的体验。今天…...
XmlDocument.SelectNodes 不起作用
今天采用Xpath读取Xml节点,怎么都读不出。 问题分析: 错误代码如下: XmlDocument xmlD new XmlDocument();xmlD.PreserveWhitespace true;xmlD.LoadXml(xStr);xmlD.SelectNodes("job-scheduling-data/schedule/job");经排查 do…...
部署单点elasticsearch
部署elasticsearch 创建网络 因为我们还需要部署kibana容器,因此需要让es和kibana容器互联。这里先创建一个网络 docker network create es-net 拉取镜像 我们采用elasticsearch的7.12.1版本的镜像 docker pull elasticsearch:7.12.1 运行 运行docker命令&a…...
ElementUI浅尝辄止16:Tag 标签
用于标记和选择。 1.如何使用? 由type属性来选择tag的类型,也可以通过color属性来自定义背景色。<el-tag>标签一</el-tag> <el-tag type"success">标签二</el-tag> <el-tag type"info">标签三</e…...
Java虚拟机(JVM)框架
见:GitHub - eHackyd/Java_JVM: Java虚拟机(JVM)框架的学习笔记...
配置Publisher 的编译规则
步骤 1:创建ROS Package 使用以下命令创建一个新的ROS软件包: catkin_create_pkg my_publisher_package roscpp std_msgs步骤 2:编辑 CMakeLists.txt 文件 打开您的ROS软件包的 CMakeLists.txt 文件,通常位于软件包的根目录。您…...
【SpringBoot】接口实现:SpringBoot实现博客系统的文章列表页接口代码
以下是一个简单的Spring Boot博客系统的文章列表页接口代码示例: java RestController RequestMapping("/articles") public class ArticleController {Autowiredprivate ArticleService articleService;GetMapping("/")public List<Artic…...
如何使用SQL系列 之 如何在SQL中插入数据
简介 结构化查询语言,通常被称为SQL,在允许您将数据插入表中方面提供了极大的灵活性。例如,你可以使用VALUES关键字指定单独的行数据,使用SELECT查询从现有表中复制整组数据,以及以使SQL自动插入数据的方式定义列。 …...
【LeetCode题目详解】1281题 整数的各位积和之差 面试题 01.01. 判定字符是否唯一 python题解(作业一二)
本文章以python为例! 一、力扣第1281题:整数的各位积和之差 问题描述: 1281. 整数的各位积和之差 给你一个整数 n,请你帮忙计算并返回该整数「各位数字之积」与「各位数字之和」的差。 示例 1: 输入:n 234 输出…...
1.12 进程注入ShellCode套接字
在笔者前几篇文章中我们一直在探讨如何利用Metasploit这个渗透工具生成ShellCode以及如何将ShellCode注入到特定进程内,本章我们将自己实现一个正向ShellCodeShell,当进程被注入后,则我们可以通过利用NC等工具连接到被注入进程内,…...
MySQL 日志系统
重要日志模块 日志文件bin logredo log**关于循环写入和擦除的checkpoint 规则**redo log 怎么刷入磁盘的 binlog 和 redo log 有什么区别?undo log 日志文件 错误日志(error log): 错误日志文件对 MySQL 的启动、运行、关闭过程进…...
LeetCode刷题---Two Sum(一)
文章目录 🍀题目🍀解法一🍀解法二🍀哈希表 🍀题目 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。 你可以假设每…...
算法通关村第十七关——插入区间
LeetCode435,给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。 示例1: 输入:interva1s[[1,3],[6,9]],newInterva1[2,5] 输出:[[1,5],[6,9]] 解释:新区间[2,5]与[1,3]重…...
Jenkins java8安装版本安装
一、首先准备Jenkins、Jdk8、Tomcat9安装包 根据Jenkins官网介绍,Jenkins支持Java8的版本如下: 我们选择2.164版本进行安装,根据版本号支持输入下载地址:https://archives.jenkins.io/war/2.164/jenkins.war,进行下载…...
线上问诊:数仓开发(二)
系列文章目录 线上问诊:业务数据采集 线上问诊:数仓数据同步 线上问诊:数仓开发(一) 线上问诊:数仓开发(二) 文章目录 系列文章目录前言一、DWS1.最近1日汇总表1.交易域医院患者性别年龄段粒度问诊最近1日汇总表2.交易域医院患者…...
Ansible自动化运维工具(三)
目录 Ansible 的脚本 --- playbook 剧本 编辑2.vars模块实战实例 3.指定远程主机sudo切换用户 4.when模块实战实例 5.with_items迭代模块实战实例 6.Templates 模块实战实例 (1)先准备一个以 .j2 为后缀的 template 模板文件,设置引用…...
ChatGPT在创新和创业中的应用如何?
ChatGPT是一种基于大规模预训练的语言模型,它在创新和创业中有着广泛的应用。作为一种具备自然语言处理能力的模型,ChatGPT可以与用户进行对话,并提供相关的信息、建议和创意。以下是ChatGPT在创新和创业中的一些应用: 创意生成和…...
Log4j2 配置日志记录发送到 kafka 中
前言 log4j2 在 2.11.0 之后的版本,已经内置了 KafkaAppender 支持可以将打印的日志直接发送到 kafka 中,在这之前如果想要集中收集应用的日志,就需要自定义一个 Layout 来实现,相对来说还是比较麻烦的。 官网文档:L…...
Linux用户与组管理(03)(八)
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 目录 前言 一、组管理 1、概述 2、用户信息查看 总结 前言 今天是学习用户与组管理的最后一节课,这节课主要是组管理的内容,希望能一起学习ÿ…...
Java自定义异常
Java标准库定义的常用异常包括: 当我们在代码中需要抛出异常时,尽量使用JDK已定义的异常类型。例如,参数检查不合法,应该抛出IllegalArgumentException: static void process1(int age) {if (age < 0) {throw new…...
vscode远程调试php
使用vscode远程调试php的方法 1.安装remote ssh插件 2.连接服务器 可以点击左下角的绿色按钮,或者ctrlshiftp打开命令框输入remote ssh应该也有。 3.在服务器端vscode安装php debug插件 4.安装xdebug xdebug是用来调试php的软件,原本和vscode没什么关…...
C语言:截断+整型提升练习
详情关于整型提升与截断见文章:《C语言:整型提升》 一、代码一 int main() { char a -1; signed char b -1; unsigned char c -1; printf("%d %d %d", a, b, c); return 0; } 求输出结果 解析如下代码: int mai…...
Kubernetes技术--k8s核心技术kubectl命令行工具
(1).概述 kubectl是Kubernetes集群的命令行工具,通过 kubectl 能够对集群本身进行管理,并能够在集群上进行容器化应用的安装部署。 (2).语法 Kubectl [command] [type] [name] [flags] 语法参数说明: command: 指定要对资源执行的操作,例如 create、get、describe 和 delet…...
Element浅尝辄止9:Popover 弹出框组件
Popover 的属性与 Tooltip 很类似,它们都是基于Vue-popper开发的,因此有重复属性 1.如何使用? /*trigger属性用于设置何时触发 Popover,支持四种触发方式: hover,click,focus 和 manual。 对于…...
程序开发:构建功能强大的应用的艺术
程序开发是在今天的数字化时代中扮演重要角色的一项技术。通过编写代码,开发人员能创造出无数不同的应用,从简单的计算器到复杂的社交平台。电子商务应用、在线教育平台、医疗记录系统等,都重视程序开发的重要性,通过这其中的交互…...
(七)k8s实战-高级调度
一、CronJob 定时任务 1、cron 表达式 # ┌───────────── 分钟 (0 - 59) # │ ┌───────────── 小时 (0 - 23) # │ │ ┌───────────── 月的某天 (1 - 31) # │ │ │ ┌───────────── 月份 (1 - 12) # │ │ │ │ ┌…...
HTTP/1.1协议中的八种请求
2023年8月29日,周二晚上 目录 概述八种请求GET请求POST请求PUT请求PATCH请求DELETE请求HEAD请求OPTIONS请求TRACE请求 概述八种请求 HTTP/1.1协议中定义了8种常用的请求方法,分别是:1. GET 用途:请求指定的页面信息,并返回实体主体。例子:获取一个网页、图片等静态…...
面试系列 - JVM内存模型和调优详解
目录 一、JVM内存模型 1. 程序计数器(Program Counter Register): 2.Java虚拟机栈(Java Virtual Machine Stacks): 3. 本地方法栈(Native Method Stack): 5. 方法区…...
JavaScript -【第一周】
文章来源于网上收集和自己原创,若侵害到您的权利,请您及时联系并删除~~~ JavaScript 介绍 变量、常量、数据类型、运算符等基础概念 能够实现数据类型的转换,结合四则运算体会如何编程。 体会现实世界中的事物与计算机的关系理解什么是数据并…...
wordpress浏览器主题下载/最佳磁力吧ciliba
介绍 这里所说的接口不仅是LCD显示器,而且还包含了相关的LCD电视相关接口。也可能没说说全,以后可添加。 VGA VGA的全称为Video Graphic Array,即显示绘图阵列。在PC行业发展的初期,VGA以其支持在640X480的较高分辨率下同时显示…...
当当网站建设优点/全媒体运营师报考官网在哪里
引文: 个人名言:“同一条河里淹死两次的人,是傻子,淹死三次及三次以上的人是超人”。经历过上次悲催的面试,决定沉下心来,好好的补充一下基础知识点。本文是这一系列第一篇:进程间通讯之mmap。 …...
一键查询注册过的网站/网络推广都有哪些方式
一、用 python 进行微信好友分析 1、使用到的库 (1)wxpy:初始化微信机器人 (2)openpyxl:保存微信好友数据为Excel表格 (3) pyecharts:生成可视化的地图 (4&am…...
wordpress简约高端企业通用产品/搜索引擎有哪几个网站
$(function () { $(#windowsMSG).window({ onBeforeClose: function () { //当面板关闭之前触发的事件 if (confirm(窗口正在关闭,请确认您当前的操作已保存。\n 是否继续关闭窗口?)) { …...
共享经济网站建设策划书/头条今日头条新闻头条
在调研的基础,通过对用户需求的分析、归纳和总结,拟定本课题的主要研究内容暂定如下: (1)实现对信息数据的浏览、查询、编辑和管理等基本数据库操作,采用模块化设计方法,根据用户的需求及程序的…...
关于织金县网站建设的论文/万州网站建设
日志信息 故障现象描述 与硬盘关系 scsi1: ERROR on channel 0, id 7, lun 0, CDB: Read (10) 00 73 fc 62 bf 00 00 80 00 Info fld0x73fc6326, Current sdi: sense key Medium Error Additional sense: Unrecovered read error SMART规范定义“Medium Error”错误是一种不…...