0%

一、简介

Dagger2 是主流的依赖注入框架,凭借优秀的性能表现在Android上使用较为广泛,在AOSP源码中也集成了该部分源码,在WORKSPACE/external/dagger2 主要在SystemUI中引用

Dagger 的名字取自有向无环图 DAG (directed acyclic graph),因为程序里的依赖关系拼接起来就是一个或者多个有向无环图。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Heater { //加热器
...
}
class Pump { //泵
private Heater heater;
...
}

class CoffeeMaker {
private Heater heater;
private Pump pump;
//相比在对象内部自己创建,而是交给外部注入
public CofeeMaker(Heater heater, Pump pump) {
this.heater = heater;
this.pump = pump;
}
void brew() {
}
}

对于这个简单的例子来说我们不管是在CoffeeMaker中实例化Heater和Pump 还是手写依赖注入都是非常简单的但是如果一个类有这么多的依赖,并且每个依赖又存在若干个不同的依赖这么组成的一个依赖图,这对我们后期项目的开发以及维护都是一场灾难

1
2
3
4
5
6
7
8
9
10
11
public NotificationPanelViewController(NotificationPanelView view,
@Main Handler handler,
@ShadeDisplayAware LayoutInflater layoutInflater,
FeatureFlags featureFlags,
......此处省略约80
NaturalScrollingSettingObserver naturalScrollingSettingObserver,
MSDLPlayer msdlPlayer,
BrightnessMirrorShowingInteractor brightnessMirrorShowingInteractor) {

....
}

而dagger2的做法是 帮助开发者自动管理依赖并且以一种代码可追踪性的,性能优越的、编译期间可检验正确性的方式

dagger2是通过编译期间生成代码的方式实现依赖注入 如果存在异常会更早点的被检验出,相比其他框架如Spring在运行期间(Spring启动时)通过扫描注解反射的方式创建对象 要有更佳的性能、如果生成了代码如果出错,可以更加简单的追踪到错误位置,并且编译后生成的java文件可以随时查看使用起来更加清晰

二、dagger使用

1、申明依赖

使用@Inject注解位于需要提供依赖的构造方法或者字段上,被@Inject标记的构造方法Dagger将知道该类的构造,当该类被当成依赖被其他类需要时dagger将会执行他的构造方法构造实例 而作为setter或者字段注入的时候则不能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Heater {
...
}
class Pump {


public Heater heater;

@Inject
public Pump(Heater heater) {
this.pump = pump;
}
}

class CoffeeMaker {
private Heater heater;
private Pump pump;

@Inject
public CofeeMaker(Heater heater, Pump pump) {
this.heater = heater;
this.pump = pump;
}
...
}

注意:@Inject不是万能的

  • 接口不能被构造
  • 三方库不能被标记@Inject
  • 可配置对象必须配置好

2、满足依赖关系

根据第一个步骤已经确定了依赖关系,并且由于CoffeMaker和Pump类是用构造方法注入的,也就是说Dagger知道了其构造方法可以构造CoffeMaker和Pump,可是Heater 呢 他们是怎么提供呢?

1
2
3
4
5
6
7
8
9
@Module
abstract class HeaterModule {

@Provider
public static Heater provideHeater() {
return new Heater();
}

}

这里我们假设Heater 已经是叶子节点(没有下一层的依赖),那我们需要为叶子节点注册获取方法告诉dagger 该对象该如何获取,这里我们使用@Provider注解表示该方法将提供一个Heater对象实例,dagger是通过方法的返回值确定依赖类型的

然而实际开发中往往CoffeMaker依赖的不是具体Heater实例,而是Heater接口或抽象类,或者存在多个Heater对象

1
2
3
4
5
6
7
8
9
interface Heater {
...
}
class ElectricHeater implements Heater {
...
}
class GasHeater implements Heater {
...
}

针对这种情况我们如何处理呢?可以看到一下代码,两个方法返回值都是Heater,如果不加以修饰dagger将不知道该获取哪个作为正确依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Module
abstract class HeaterModule {

@Provider @Named("electric")
public static Heater provideElectricHeater() {
return new ElectricHeater();
}

@Provider @Name("gas")
public static Heater provideGasHeater() {
return new GasHeater();
}

}

这里我们假设ElectricHeater GasHeater 是叶子节点,他们共同实现一个接口,为了让dagger区分这两个对象,使用了@Name注解去区分,只需要在获取该依赖的地方同样加上该注解即可获取对应的对象内容后续@Qualifier会提到

public CofeeMaker(@Named("gas") Heater heater, @Name("electric") Pump pump) {

this.heater = heater;

this.pump = pump;

}

然而实际开发过程中情况可能是这样的Pump可能也不止一个,也只能看作一个抽象接口并且Pump他的实例是通过构造方法@Inject标记的,所以他是非叶子节点不能用@Provider去提供接口实例,但是由于我们CoffeeMaker中引用的是Pump接口

1
2
3
4
5
6
7
8
9
10
11
interface Pump {

}
class Thermosiphon implements Pump { // 假设这是非叶子节点
...
}
class ElectricPump implements Pump { // 假设这是非叶子节点
...
@Inject
public ElectricPump (Heater heater) {...}
}

对于以上这种情况我们需要将接口与实例进行绑定 因此可以使用@Binds帮我们实现

1
2
3
4
5
6
7
8
9
10
@Module
abstract class HeaterModule {

@Binds @Named("electric")
abstract Pump bindElectricPump(ElectricPump pump);

@Binds @Name("Thermosiphon")
abstract Pump bindThermosiphon(Thermosiphon pump);

}

我们不论是Binds还是Provider都是写在@Module里面的,作为依赖提供给dagger

这样一来 叶子节点通过@Provider提供 而非叶子节点通过@Inject构造方法提供 另外接口可以由@Binds进行绑定,那么层层依赖关系就全部缕清楚咯

3、构建对象图

@Inject@Provides 标记的类,会自动拼成一张“依赖关系图”,里面的对象根据需要互相连接起来。那应用程序要怎么用这张图呢?通常就是在入口地方,比如 main 方法或者 Android 的 Application定义一个明确的接口,去拿到里面的对象。

在 Dagger 2 里,你要先定义一个接口,这个接口里写一些没参数、直接返回你想要对象的方法。然后你给这个接口加上 @Component 注解,并把需要用到的模块(modules)传进去。Dagger 2 就会帮你生成一个实现这个接口的类,自动把所有依赖都装配好。

这样说起来就像:你只要列个清单(接口),告诉 Dagger “我要这些东西”,它就会帮你把背后复杂的依赖链都串起来,然后直接交给你。

1
2
3
4
5
6
7
8
9
@Component(modules = HeaterModule.class)
interface CoffeeShop {

@Component.Builder
interface builder{
CoffeShop build();
}
CoffeeMaker maker();
}

编译过后dagger将帮你自动生成一个类:DaggerCoffeeShop.java作为Component实例 并且根据已经申明好了的依赖和满足好的依赖关系自动帮你实现了maker()方法

我们可以在需要使用CoffeeMaker的地方调用

CoffeeShop coffeeShop = DaggerCoffeeShop.Builder.build();

1
CoffeMaker maker = coffeeShop.maker();

即可获取到CoffeMaker的实例,通常我们使用dagger框架时,会在程序的起点main函数或者Anndroid中 Application里面获取到顶层的Component实例,通过这个实例获取我们需要的实例如:SystemUI启动后通过Dagger实例化他的组件服务以及他们的依赖(以及依赖的依赖),实例化好后开启服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
编译产物
@DaggerGenerated
@Generated(
value = "dagger.internal.codegen.ComponentProcessor",
comments = "https://dagger.dev"
)
@SuppressWarnings({
"unchecked",
"rawtypes",
"KotlinInternal",
"KotlinInternalInJava",
"cast",
"deprecation",
"nullness:initialization.field.uninitialized"
})
public final class DaggerCoffeeShop {
private DaggerCoffeeShop() {
}

public static CoffeeShop.Builder builder() {
return new Builder();
}

public static CoffeeShop create() {
return new Builder().build();
}

private static final class Builder implements CoffeeShop.Builder {
@Override
public CoffeeShop build() {
return new CoffeeShopImpl();
}
}

private static final class CoffeeShopImpl implements CoffeeShop {
private final CoffeeShopImpl coffeeShopImpl = this;

private CoffeeShopImpl() {


}

private ElectricPump electricPump() {
return new ElectricPump(HeaterModule_ProvideElectricHeaterFactory.provideElectricHeater());
}

@Override
public CoffeeMaker maker() {
return new CoffeeMaker(HeaterModule_ProvideElectricHeaterFactory.provideElectricHeater(), electricPump());
}
}
}

4、完善作用域

注解@Provider的方法每次被执行都会产生不同的对象,有时候我们需要在不同类中依赖同一个对象,如

1
2
CoffeShop`依赖 `Heater
Pump` 依赖 `Heater

我们通常希望两者依赖的Heater是同一个对象,可以使用@Singleton注解解决此问题,加上该注解可以保持Component级别的单例(只要Component不变获取到的实例及是不变的)

1
2
3
4
5
@Component(modules = DripCoffeeModule.class)
@Singleton
interface CoffeeShop {
CoffeeMaker maker();
}

将@Singleton定义在Component接口上表示该Component的作用域是@Singleton,看上去非常的令人迷惑听上去也是,但是不着急我们慢慢看

1
2
3
4
5
6
7
8
@Module
abstract class HeaterModule {
@Provider
@ActivityScope // 在此处+ @Singleton
public static Heater provideElectricHeater() {
return new ElectricHeater();
}
}

表示Heater的作用域在@SingletonCoffeShop是相同作用域,于是Heater在实例化过一次后将会缓存在这个作用域,如果该作用域还有其他的类依赖该Heater,直接命中缓存,通过这样的方式在相同作用域里面实现单例,

通过阅读Singleton定义可以的出@Singleton只是一个特殊的@Scope,我们也可以自定义一些Scope代表一些含义

1
2
3
4
@Scope
@Documented
@Retention(RUNTIME)
public @interface ActivityScope {}

比如定义一个ActivityScope 将其描述于一个子Component(@Subcomponent)中,该Component我们交由Activity管理在onCreate时获取,onDestroy时销毁,那在此期间可以保持单例。一旦Activity销毁重建Component也将重新生成,将在下一个周期保持不变但与上一个周期不同,实现在Activity级别的单例,同理交给Application管理可以实现app级别单例

5、子组件(子图)

子组件是继承并扩展父组件对象图的组件。你可以使用子组件把应用的对象图拆分成更小的子图:

  • 一方面可以让应用的不同部分彼此隔离;
  • 另一方面也可以在同一个组件体系里使用多个不同的作用域(scope)。

在子组件中绑定的对象,可以依赖:

  • 父组件或任何祖先组件里绑定的对象,
  • 以及子组件自己模块中绑定的对象。

但是反过来不行:

  • 父组件里的对象 不能依赖 子组件里的对象;
  • 一个子组件里的对象 也不能依赖 另一个“兄弟”子组件里的对象。

换句话说:

父组件的对象图,其实只是子组件对象图的一个子集。

声明子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//ReferenceSysUIComponent.java
@SysUISingleton
@Subcomponent(modules = {
DefaultComponentBinder.class,
...
ReferenceSystemUIModule.class})
public interface ReferenceSysUIComponent extends SysUIComponent {

@SysUISingleton
@Subcomponent.Builder
interface Builder extends SysUIComponent.Builder {
ReferenceSysUIComponent build();
}

void inject(CustomizationProvider customizationProvider);
}

//ReferenceGlobalRootComponent.java
@Singleton
@Component(modules = {GlobalModule.class})
public interface ReferenceGlobalRootComponent extends GlobalRootComponent {

/**
* Builder for a ReferenceGlobalRootComponent.
*/
@Component.Builder
interface Builder extends GlobalRootComponent.Builder {
ReferenceGlobalRootComponent build();
}

/**
* Builder for a {@link ReferenceSysUIComponent}, which makes it a subcomponent of this class.
*/
ReferenceSysUIComponent.Builder getSysUIComponent(); //通过获取副组件构造器的方式确定父子关系
}

子组件理由

为了使用作用域(scope)

举个例子:在使用Scope后依赖都是单例的,但是有时候我希望在Activity销毁重建后能得到一个新的对象, 比如登陆表单的时候,每次输入错误表单界面会有错误状态、提示信息等等,我希望重新进入页面时候是一个全新的表单而不是原来那个带有错误状态的,就可以将该部分依赖移动至Subomponent,定义一个新的Scope

用子组件做封装

使用子组件的另一个原因,是为了让应用的不同部分彼此隔离。

例如,服务器里的两个服务(或应用中的两个界面)可能共享一些绑定(如认证、授权),但各自又有独立绑定。这时可以为每个服务或界面创建单独的子组件,把共享的绑定放到父组件里

6、多重绑定

Dagger 允许你将多个对象绑定到一个集合中,即使这些对象绑定在不同的模块中,这称为 multibindings(多重绑定)。Dagger 会组装这个集合,使应用程序代码可以注入它,而不需要直接依赖各个单独的绑定。

你可以使用 multibindings 来实现插件架构,例如,多个模块可以贡献各自的插件接口实现,然后中央类可以使用所有插件。或者,你可以让多个模块贡献单独的服务提供者到一个 map 中,以名字作为 key。

SystemUI中的组件服务就是这个架构

Map绑定

Dagger 允许你向可注入的 map 贡献条目,只要 map 的 key 在编译时已知。

向多重绑定 map 贡献一个条目,在模块方法上返回值并加上 @IntoMap 注解,同时使用一个自定义注解指定 map key。Key 必须唯一,如果 map 内有重复 key,会报编译错误。带限定符的 map 条目需要在每个 @IntoMap 方法上加上限定符。

然后你可以注入 map 本身 (Map<K, V>) 或带 value provider 的 map (Map<K, Provider>)。后一种方式有用,例如不希望立即实例化所有值(懒加载),或者想处理 Provider 异常,或者每次查询都得到新实例。

Interface Provider {

​ T get();

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Module(
includes = [
...
WallpaperModule::class,
]
)
abstract class SystemUICoreStartableModule {
/** Inject into BiometricNotificationService */
@Binds
@IntoMap
@ClassKey(BiometricNotificationService::class)
abstract fun bindBiometricNotificationService(
service: BiometricNotificationService //实例由构造Inject提供
): CoreStartable

/** Inject into ClipboardListener. */
@Binds
@IntoMap
@ClassKey(ClipboardListener::class)
abstract fun bindClipboardListener(sysui: ClipboardListener): CoreStartable

/** Inject into GlobalActionsComponent. */
@Binds
@IntoMap
@ClassKey(GlobalActionsComponent::class)
abstract fun bindGlobalActionsComponent(sysui: GlobalActionsComponent): CoreStartable
}

获取的同样在Component中

1
2
3
4
5
6
7
8
9
10
11
12
13
@SysUISingleton
@Subcomponent(modules = {
...
SystemUICoreStartableModule.class,
...)
public interface SysUIComponent {

......

Map<Class<?>, Provider<CoreStartable>> getStartables();

......
}

编译后真实情况:

1
2
3
4
5
6
7
8
9
10
//编译后
public Map<Class<?>, javax.inject.Provider<CoreStartable>> getStartables() {
return MapBuilder.newMapBuilder(109)
.put(NotificationLogger.class, this.provideCoreStartableProvider)
.put(ShortcutHelperActivityStarter.class, this.starterProvider)
.put(ShortcutHelperRepository.class, this.repoProvider)
.........
.put(BroadcastDispatcherStartable.class, this.broadcastDispatcherStartableProvider)
.put(Recents.class, this.volumeUIProvider).build();
}

后续可以通过该Map在开机后一次性启动组件服务

1
2
3
4
@Binds
@IntoMap
@ClassKey(ForegroundServicesDialog.class)
public abstract Activity bindForegroundServicesDialog(ForegroundServicesDialog activity);

還可以將Activity Service Broadcast ContentProvider 四大組件綁定到Map中,由于四大组件实例化过程在系统端而非应用端所以一般来说无法在他们其中通过构造方法实现依赖注入,可以通过这种方式配合AppComponentFactory实现在四大组件的构造器中注入依赖

7、其他

外部绑定实例

绑定实例到Component中,可以在后续依赖注入时直接使用

比如在其他地方想注入主线程Handler方便执行主线程操作,但是Handler的获取要通过Context获得,而Context由App启动时Application才初始化好,所以dagger只能在构建对象图时获取

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface GlobalRootComponent {

/**
* Builder for a GlobalRootComponent.
*/
interface Builder {
@BindsInstance
Builder context(Context context); //绑定Context到依赖库
@BindsInstance
Builder instrumentationTest(@InstrumentationTest boolean test);
GlobalRootComponent build();
}
}

同个类型多个不同实例

定义于注解上区分相同类型的不同依赖。比如需要注入两个Executor 分别代表着主线程以及后台线程可以这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//Main.java
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Main {
}
//Background.java
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Background {
}


//GlobalConcurrencyModule.java
@Provides
@Singleton
@Main
public static Executor provideMainExecutor(Context context) {
return context.getMainExecutor();
}
//SysUIConcurrencyModule.kt

@Provides
@SysUISingleton
@Background
fun provideBackgroundExecutor(@Background looper: Looper): Executor = ExecutorImpl(looper)



//DreamOverlayStatusBarItemsProvider.java
@Inject
public DreamOverlayStatusBarItemsProvider(@Main Executor executor) {
mExecutor = executor;
}
//ObservableServiceConnection.java
@Inject
public ObservableServiceConnection(Context context, Intent serviceIntent,
UserTracker userTracker,
@Background Executor bgExecutor,
ServiceTransformer<T> transformer) {
...
mBgExecutor = bgExecutor;
...
}

@Named

自身由Qualifier定义,作用与Qualifier一致

1
2
3
4
5
6
7
8
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {

/** The name. */
String value() default "";
}

辅助注入

用于构造对象时,部分参数由 DI 框架提供,而其他参数必须在创建时由用户传入(半自动管理)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@AssistedInject //声明这是一个辅助注入类型
public DelayedWakeLock(@Background Lazy<Handler> bgHandler,
@Main Lazy<Handler> mainHandler,
Context context, WakeLockLogger logger,
@Assisted String tag) { // 声明该参数由用户传入
mInner = WakeLock.createPartial(context, logger, tag);
mHandler = Flags.delayedWakelockReleaseOnBackgroundThread() ? bgHandler.get()
: mainHandler.get();
}
...
@AssistedFactory //声明助理工厂,编译中DelayedWakeLock的构造方法根据自动生成动态依赖String tag的方法
public interface Factory {
/** creates the instance of DelayedWakeLock class. */
DelayedWakeLock create(String tag);
}

相当于DelayedWakeLock注入到DelayedWakeLock.Factory中,其他地方需要使用DelayedWakeLock,通过注入该工厂获取即可

@Inject DelayedWakeLock.Factory delayedWakeLockFactory; // 工厂通过Inject获取依赖

1
DelayedWakeLock wakelock = delayedWakeLockFactory.create("Scrims"); 

三、查看SystemUI中的依赖注入流程

SystemUI启动流程

SystemUIService 是在SystemUI app 中的一个 Service ,要启动Service 先会实例化SystemUIApplication

Application的代理实例化

由于Application的实例化过程是在系统端,为了获取Application实例化的控制权使用AppComponentFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//SystemUIAppComponentFactoryBase.kt  --> AppComponentFactory.java

override fun instantiateApplicationCompat(cl: ClassLoader, className: String): Application {
val app = super.instantiateApplicationCompat(cl, className)
if (app !is ContextInitializer) {
throw RuntimeException("App must implement ContextInitializer")
} else {
app.setContextAvailableCallback { context -> //设置一个在onCreate方法中执行的回回调函数
createSystemUIInitializerInternal(context)
}
}
return app
}

private fun createSystemUIInitializerInternal(context: Context): SystemUIInitializer {
return systemUIInitializer ?: run {
val initializer = createSystemUIInitializer(context.applicationContext)
try {
initializer.init(false) // 重点在这
} catch (exception: ExecutionException) {
throw RuntimeException("Failed to initialize SysUI", exception)
} catch (exception: InterruptedException) {
throw RuntimeException("Failed to initialize SysUI", exception)
}
initializer.sysUIComponent.inject(
this@SystemUIAppComponentFactoryBase
)

systemUIInitializer = initializer
return initializer
}
}

AppComponentFactory 是实例化 Application 、 Activity 、Service 、ContentProvicder 、BroadcastReceiver的工厂,系统在实例化四大组件以及Application时是交由他去实例化的、我们可以自行实现该类并且注册至Manifest中,在系统实例化以上类的时候会替换到自定义的工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
//SystemUIInitializer.java

public void init(boolean fromTest) throws ExecutionException, InterruptedException {
mRootComponent = getGlobalRootComponentBuilder()
.context(mContext)
.instrumentationTest(fromTest)
.build();

mInitializationChecker = mRootComponent.getInitializationChecker();
boolean initializeComponents = mInitializationChecker.initializeComponents();

// Stand up WMComponent
setupWmComponent(mContext); // 初始化 mWMComponent

// And finally, retrieve whatever SysUI needs from WMShell and build SysUI.
SysUIComponent.Builder builder = mRootComponent.getSysUIComponent();
// 将WMShell 模块核心依赖添加到 ApplicationSingeton scope中
if (initializeComponents) {
// Only initialize when not starting from tests since this currently initializes some
// components that shouldn't be run in the test environment
builder = prepareSysUIComponentBuilder(builder, mWMComponent)
.setShell(mWMComponent.getShell())
.setPip(mWMComponent.getPip())
.setSplitScreen(mWMComponent.getSplitScreen())
.setOneHanded(mWMComponent.getOneHanded())
.setBubbles(mWMComponent.getBubbles())
.setTaskViewFactory(mWMComponent.getTaskViewFactory())
.setShellTransitions(mWMComponent.getShellTransitions())
.setKeyguardTransitions(mWMComponent.getKeyguardTransitions())
.setStartingSurface(mWMComponent.getStartingSurface())
.setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper())
.setRecentTasks(mWMComponent.getRecentTasks())
.setBackAnimation(mWMComponent.getBackAnimation())
.setDesktopMode(mWMComponent.getDesktopMode());

// Only initialize when not starting from tests since this currently initializes some
// components that shouldn't be run in the test environment
mWMComponent.init();
} else {
......
}
mSysUIComponent = builder.build(); // 实例化Component,(生成依赖关系图)

// Every other part of our codebase currently relies on Dependency, so we
// really need to ensure the Dependency gets initialized early on.
Dependency dependency = mSysUIComponent.createDependency();
dependency.start(); // 初始化全局仓库
}

Application初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//SystemUIApplication.java

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

mInitializer = mContextAvailableCallback.onContextAvailable(this); // 初始化Component
//获取子组件Component scope: @SysUISingleton
mSysUIComponent = mInitializer.getSysUIComponent();
mBootCompleteCache = mSysUIComponent.provideBootCacheImpl();
log.traceEnd();
//获取根组件Component scope: @Singleton
GlobalRootComponent rootComponent = mInitializer.getRootComponent();

// Enable Looper trace points.
// This allows us to see Handler callbacks on traces.
......
}

启动SystemUIService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//SystemUIService.java
@Override
public void onCreate() {
super.onCreate();

// Start all of SystemUI
((SystemUIApplication) getApplication()).startSystemUserServicesIfNeeded();

......
}

//SystemUIApplication

public void startSystemUserServicesIfNeeded() {
if (!shouldStartSystemUserServices()) {
Log.wtf(TAG, "Tried starting SystemUser services on non-SystemUser");
return; // Per-user startables are handled in #startSystemUserServicesIfNeeded.
}
final String vendorComponent = mInitializer.getVendorComponent(getResources());

// Sort the startables so that we get a deterministic ordering.
// TODO: make #start idempotent and require users of CoreStartable to call it.
Map<Class<?>, Provider<CoreStartable>> sortedStartables = new TreeMap<>(
Comparator.comparing(Class::getName));
sortedStartables.putAll(mSysUIComponent.getStartables()); // 获取所有Corestartabe
sortedStartables.putAll(mSysUIComponent.getPerUserStartables());
startServicesIfNeeded(
sortedStartables, "StartServices", vendorComponent); //启动服务的核心方法
}

小而美的Corestartable组件服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public interface CoreStartable extends Dumpable {
String STARTABLE_DEPENDENCIES = "startable_dependencies";

/** Main entry point for implementations. Called shortly after SysUI startup. */
void start();

@Override
default void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
}

/** Called to determine if the dumpable should be registered as critical or normal priority */
default boolean isDumpCritical() {
return true;
}

/** Called immediately after the system broadcasts
* {@link android.content.Intent#ACTION_LOCKED_BOOT_COMPLETED} or during SysUI startup if the
* property {@code sys.boot_completed} is already set to 1. The latter typically occurs when
* starting a new SysUI instance, such as when starting SysUI for a secondary user.
* {@link #onBootCompleted()} will never be called before {@link #start()}. */
default void onBootCompleted() {
}
}

SystemUI的每个组件服务都是一个小巧的CoreStartable接口,启动服务就是执行其中的start()方法即可并非Android四大组件中的Service,或许将来你编写的一个SystemUI的功能也可以作为服务的形式添加到SystemUI中

我们知道服务就是一个个CoreStartable的实例,自然我们需要实例化他们,他们的依赖由dagger提供,举例分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@SysUISingleton
public class KeyguardUpdateMonitor implements TrustManager.TrustListener, CoreStartable {

@Inject
protected KeyguardUpdateMonitor(
Context context,
UserTracker userTracker,
@Main Looper mainLooper,
BroadcastDispatcher broadcastDispatcher, // 以这个类为例
DumpManager dumpManager,
@Background Executor backgroundExecutor,
@Main Executor mainExecutor,
StatusBarStateController statusBarStateController,
LockPatternUtils lockPatternUtils,
AuthController authController,
......
}
}
1
2
3
4
5
6
7
8
9
10
11
@SysUISingleton
open class BroadcastDispatcher @Inject constructor(
private val context: Context,
@Main private val mainExecutor: Executor,
@BroadcastRunning private val broadcastLooper: Looper,
@BroadcastRunning private val broadcastExecutor: Executor,
private val dumpManager: DumpManager,
private val logger: BroadcastDispatcherLogger,
private val userTracker: UserTracker,
private val removalPendingStore: PendingRemovalStore
) : Dumpable {

在启动Corestartable前将会先实例化,在实例化过程中由dagger

打个比方,如果你是一个电脑生产工厂那么dagger就是你的工厂助理

1、如果你需要生产一台电脑,那么你将大致需要(依赖) CPU 内存 硬盘 主板 显卡 等等,你将这些依赖需求告知dagger

2、dagger如果有现成的这些配件那么将会直接利用起来(命中缓存) 如果没有还需要分别生产

3、我们简单的以显卡为例,显卡生产又需要(依赖) 核心 显存 PCB版 等等 dagger将重复第2步找得到依赖项的依赖直到为最初加工品或者命中缓存, 这样dagger助理就帮我们完成了复杂的依赖传递

结果就是dagger将为你生产CPU 内存 硬盘 主板 显卡 等等 并交给你完成一台电脑的生产

Linux 工作流总结

​ 由于已经工作一年了,由于进行Android系统的客制化任务,其编译环境需要再Ubuntu环境下进行,故期间不停的在使用指令操作,渐渐的对Linux工作流有了新的认识与看法.回想起第一次秋招面试时有位面试官问我linux指令相关问题,:如果我要看log最后面几行我该怎么看.当时由于实战经验不足,又没有对该方面的知识死记硬背过,最后拧拧巴巴的最后不知道说了一通什么狗屎.

标准输入流与标准输出流

​ 要是在大二期间我看到这几个字,我一定是一脸问号,回想以前学Java时也遇到过这几个关键字,为什么称之为关键字,因为当时并没有理解什么时输出流与输入流,更不明白何为标准,学完Java后有了新的认识,一种抽象概念,讲的是将数据或者其他东西输入到系统(内存)中,这一过程抽象为输入流,反之从内存输出到其他设备如磁盘\显示器等则为输出流.在这一过程中我以为这只是Java这一编程语言的抽象概念而已.直到我开始阅读Linux的书籍<,发现有> 2> 这样的标准输出流存在,我开始怀疑我以前的体系(何来体系?)于是我开始接触Linux系统编程,通过系统API去感受所谓的输入流与输出流(虽然感觉并没有什么卵用)

于是没有人教我便自己领悟了怎么去保存diff文件,看书很神奇的一点在于它并未教你如何处理该情况,它只是告诉了你所谓的原理,你就能在工程中实战出来.由于在工作中经常要处理代码冲突的情况,并且是在Shell框中,一切都不是那么的自动化,所有的流程需要手动操作,所以难免的觉得麻烦,最开始由于不熟悉git操作,我采取的操作是直接将修改回退,并拉取到最新.此时如果修改不保存的话需要重新修改,故我才去的方式是将diff保存起来,方式也特别简单,由于执行 git diff时,会将diff信息输出到屏幕,也就是默认输出到屏幕.而最简单的方式便是将diff信息输出到文件即可,故 git diff > patch.diff .这样信息就不会输出到屏幕,而是被重定向输出到了patch.diff文件中.当将代码拉新后 git apply patch.diff 即可将刚保存的修改应用至代码中,简单粗暴.由于标准输出被重定向到了文件中故屏幕不再显示,此时也无法观察到diff信息,甚至不知道是否diff为空,如果不检查一下diff文件的话,将来apply时可能是竹篮打水一场空,白忙活一场,故可以使用升级方案: git diff | tee patch.diff,该方法的好处在于,不仅可以将diff保存到文件还让它在显示器上输出信息. 利用管道符号| 将输出重定向到 tee工具 ,该工具的作用在于不仅可以将标准输出流显示在屏幕还可以将其保存为文件.

​ 过滤信息,当我想要在一堆文件中过滤到想要的文件名时, 可以使用 tree -fi| grep xxx, 当然还可以用find(不熟悉)去找.

环境变量

​ 以前在学校学编程语言的时候,经常要做的第一件事情就是下载安装配置好该语言的环境变量,以前只知道根据教程一步一步走,并不知道为什么要这么操作,或者并不知道这样做会有什么用.

以Java为例,当我们开始学习该语言时,最最基本的我们需要去编译该语言,需要运行程序,所以我们需要JDK支撑,也就是说jdk里面的程序可以帮我们完成编译与运行操作,具体来说就是javac与java程序,配置环境变量的目的就是为了让计算机能够找到这两个程序.让我们在任意路径下都能够直接使用该程序.就是这么简单,那为什么配置好环境变量后就可以随处运行了呢? 原因也很简单,因为配置环境变量的位置会在第一次进入会话时执行一遍,既然执行过配置的环境变量,那变量自然也被赋予上了初始值.当我们使用bash去执行程序时,如果当前目录不存在该程序bash会帮我们去环境变量里配置好的路径里去寻找是否有我们需要的程序.这样自然就可以找到并执行了,环境变量分为系统变量与用户变量,三言两语难以讲明白,在加载时优先系统变量后加载用户变量,故用户变量配置的信息可以覆盖系统变量,优先级更高,在bash寻找程序时时先寻找用户变量配置,找到了便不会再去系统变量中寻找,故又是用户变量优先级更高.

​ 环境变量配置在用户配置文件或者系统配置文件,在每一次会话开始时被执行,这样的操作在后续是无感的,用起来非常方便,于是通过这个方法还可以做一些其他事情比如:别名 Linux命令行操作,有时候需要执行步骤复杂,对于的指令也非常复杂,于是我们可以将一些复杂的指令起一个别名 例如 alias ll =’ls -laF’, 起完别名后我们即可使用 ll 带她后面的操作,可是,alias的生效时间仅仅只有在当前会话中,如果退出登录后再登陆就再也没有用了,再想使用又要重新设置,于是为了解决这一办法,可以将该别名的设置写入用户配置文件中,这样每次退出会话时该别名虽然失效了,但进来时又会被重新设定,这样的无感知设置对于用户来说不就是永久生效吗? 别名是个好工具,在做调试Android部分源码时候由于代码比较多,嵌套文件夹很深,用cd一个个去进入很麻烦感觉像是在windows去点击寻找文件夹,设置了别名后像是在桌面加了一个快捷方式,直接点击即可进入.

Linux与C

​ c语言编译与java编译不一样的点在于,c编译[包括编译链接等一系列操作]最终生成就是二进制可执行文件,可以直接在系统上运行的,不需要其他环境,不像Java依赖于Java虚拟机才可运行.另外,在linux上编写程序遇到不知道使用的方法,可以通过man直接查阅使用方法.可惜我英文水平优先,阅读起来有点吃力.

编写脚本

​ 在开发过程中会遇到大量的重复性工作,比如拉取代码时有时候由于代码钟存在未commit内容问题,有时由于网络波动导致代码拉取失败,前者可以通过进入到对应仓库进行checkout 再手动拉取,后者可以通过不断重试解决,而这些操作要么繁琐,要么需要不断监督,很浪费个人精力,于是可以通过编写脚本优化工作流程,shell的编写方式简单、直接调用c语言库就可以解决大部分问题。

构建你自己的 GPT4 聊天窗口

已经存在chatGPT,Gemini等等很好的AI聊天对话框、为啥还需要自己构建一个对话框 ?

  • 1、无法访问他们服务本身
  • 2、无法访问更高级的模型 : GPT4turbo

如果你刚好被以上问题困扰、那你很适合阅读该博文

准备条件

  • openAI 账号
  • 域名(可选: 反向代理方案)
  • github 账号
  • 信用卡(虚拟)

首先解释一下: 若需要使用 GPT4 服务 本身并不是免费的、所以需要花钱,需要用信用卡支付,如果你只是用GPT3.5的话、更没有必要构建自己的聊天窗口了,直接用官方的即可,花的钱也足够让你买机场好几个月

快速开始

1、进入openAI官网、 获取token(凭证) 保存下来,想要调用三方服务、少不了付费

2、购买域名、 利用域名做反向代理是能够在国内访问openai 服务的关键、如果不想每次访问前都要多一步操作、请不要省这点钱 便宜的域名 大概不到3$ (21RMB)/1 year,但不要买国内的,国内的使用起来会非常的麻烦(备案),我购买的是NameSilo上面的

3、将域名挂在到cloudflare上,操作比较简单、大致意思是将域名填到cloudflare上、然后将原本的购买服务商那里的删除 。。。

4、github fork 项目、 该项目源码已经公开了、咱们要做的就是按照要求、一键部署

  • 1、进入源码界面
  • 2、在README中直接点击Deploy
  • 3、利用Vercel一键部署

他会让你先登录、用github关联、会让你fork该项目、然后会让你写三个参数:

OPENAI_API_KEY : 填入你openAI 的token即可

CODE : 设置访问你自己网站的访问码/密码

GOOLE_API_KEY : Google API token (可选)

接下来等待即可、傻瓜式部署就完成了、会生成一个随机命名的域名、就可以直接访问了

前面我们说到、自己购买的域名是可以自由访问的关键、因为给的域名随时有可能被墙的风险、所以自己设置一个域名是很有必要的、其次自己的域名可读性也更强,以后只需要记住域名就好了。

5、配置域名、与反向代理page

在cloudflare中新建一个page

…此处省略(不想写了)

lambda表达式

对于面向对象对象语言中 lambda表达式其实是不重要的

如果你有了解过JavaScript、你一定对函数式编程比较熟悉(Js太恶心啦!!! 用的时候看不了规则根本不知道怎么写)

多说无益、先看效果:首先我们先假设有这么一个场景要实现一个数组排序 给定一个数组、将数组按指定要求(正序/倒序)排序

Java中我们可以直接使用Arrays.sort()调用JDK中 的方法进行排序(但该方法只提供了正序排序的实现)

1
2
3
4
5
6
7
8
9
public class Sort {

public static void main(String[] args) {
int[] nums = new int[]{1,3,5,25,7,23,7,34,8,12,5,3,71,236,12};
Arrays.sort(nums);
System.out.println(Arrays.toString(nums));
// outputL: [1, 3, 3, 5, 5, 7, 7, 8, 12, 12, 23, 25, 34, 71, 236]
}
}

当然sort()方法也可以实现倒序排序、只不过有点不一样

1
2
3
4
5
6
7
8
9
10
public class Sort {


public static void main(String[] args) {
Integer[] nums = new Integer[]{1,3,5,25,7,23,7,34,8,12,5,3,71,236,12}; // 是包装类 Integer 不是int
Arrays.sort(nums, Collections.reverseOrder());
System.out.println(Arrays.toString(nums));
// output: [236, 71, 34, 25, 23, 12, 12, 8, 7, 7, 5, 5, 3, 3, 1]
}
}

ok , 通过这两个简单方法就可以实现我们的功能、但这并不是本问介绍的重点、重点是我们要通过这个案例来学习了解到lambda表达式、并且间接的感受Java函数式编程思想

接下来我们就来实现一个简易版本的Arrays.sort(T[]nums,Comparable)
首先确定想要的效果、T[]nums 确定一个任何对象类型的数组、传入一个比较器接口类、该接口仅仅做一件事情就是帮我比较两个数大小,叫做比较器,当我们的排序数据换成其他对象时候、原始的+-已经不能满足我们的需求了、比如比较String 大小 在String中存在CompareTo方法比较两个字符串大小,因此为满足其他复杂数据类型也能够排序、引入比较器来自定义比较两个数据大小尤为重要
先定义比较器、为了不予Jdk本身接口(Comparator)冲突我取名叫ShenComparator.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@FunctionalInterface // 做个约束 改接口只能定义一个属于自己的抽象方法
public interface ShenComparator<T> {

/**
* 比较两个数的大小
* @param o1 第一个数
* @param o2 第二个数
* @return > 0 ? 前面的数字大 : 后面的数字大
* NOTICE:: 这里不仅仅表示值 比如integer double 等数值 、
* 可以表示一切对象按照指定规则比较大小
*
*/

int compare(T o1,T o2);

// 该方法属于Object(出现在这里属于多余)
boolean equals(Object obj);

}

这里的比较器唯一的规则就是 比较大小结果是大于0还是小于0,举个例子 假设我们需要比较两个Integer类型数字、可以直接将前面数字减去后面的数字相减

1
2
3
4
5
6
// 实现类
// 如果 o1 > o2 结果自然大于零, o1 < o2 结果自然小于零, 如果将结果反序则得到的排序结果也是相反逻辑
int compare(Integer o1,Integer o2) {
return o1 - o2;
// return o2 - o1;
}

ok、有了比较器也还先别着急、 先用最简易版本的数字排序来熟悉一下排序的过程、这里为了简单我直接用快速排序算法来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

public class Utils {

/**
* 排序
* @param nums 待排序的数组
* 而是一个具体的排序规则 ::
* 字符串有字符串的规则
* 数字类型有数字类型比较规则
*/
public static void sort(int[] nums) {
quickSort(nums,0,nums.length - 1);
}


private static void quickSort(int[] nums,int left,int right) {
if (left >= right)
return;
int pivot = partition(nums, left, right);
quickSort(nums, left, pivot - 1);
quickSort(nums, pivot + 1, right);
}

private static void swap(int[] nums,int x, int y) {
int mid = nums[x];
nums[x] = nums[y];
nums[y] = mid;
}

/* 哨兵划分 */
private static int partition(int[] nums, int left, int right) {
// 以 nums[left] 作为基准数
int i = left, j = right;
while (i < j) {
while (i < j && nums[j] >= nums[left])
j--;
while (i < j && nums[i] <= nums[left])
i++;
swap(nums, i, j);
}
swap(nums, i, left);
return i;
}
}

现在已经实现了基本的排序功能、但是只针对于int[]数组、而且不支持随意切换正序还是倒序,现在测试一下

1
2
3
4
5
6
7
8
9
public class Main {

private static final int[] nums = {43,1,56,7,23,12,46,23,8,9,4,23,56,30,23,5};
public static void main(String[] args) {
Utils.sort(nums);
System.out.println(Arrays.toString(nums));
// output: [1, 4, 5, 7, 8, 9, 12, 23, 23, 23, 23, 30, 43, 46, 56, 56]
}
}

可见基本上排序功能是已经实现了、但是还缺少本文想要描述的重点、假设现在不是一堆数字要排序、而是一堆’学生’对象想通过成绩、年龄或通过名称排序,那该怎么办,接下来就需要引入我们自定义的比较器接口 以及给我们的int[] 都替换成 T[] 来实现, 另外我们算法中对排序数比较大小的-+号都可以用comparator.compare()方法来代替

加上比较器和泛型后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class Utils {

/**
* 排序
* @param nums 待排序的数组
* @param comparator 排序规则比较器 传入不是具体的某一个数据
* 而是一个具体的排序规则 ::
* 字符串有字符串的规则
* 数字类型有数字类型比较规则
*/
public static <T> void sort(T[] nums, ShenComparator<T> comparator) {
quickSort(nums,0,nums.length - 1,comparator);
}


private static <T> void quickSort(T[] nums,int left,int right,ShenComparator<T> comparator) {
if (left >= right)
return;
int pivot = partition(nums, left, right,comparator);
quickSort(nums, left, pivot - 1,comparator);
quickSort(nums, pivot + 1, right,comparator);
}

private static <T> void swap(T[] nums,int x, int y) {
T mid = nums[x];
nums[x] = nums[y];
nums[y] = mid;
}

/* 哨兵划分 */
private static <T> int partition(T[] nums, int left, int right,ShenComparator<T> comparator) {
// 以 nums[left] 作为基准数
int i = left, j = right;
while (i < j) {
// 替换 nums[j] >= nums[left]
while (i < j && comparator.compare(nums[j],nums[left]) >= 0)
j--;
// 替换nums[i] <= nums[left]
while (i < j && comparator.compare(nums[i],nums[left]) <= 0)
i++;
swap(nums, i, j);
}
swap(nums, i, left);
return i;
}
}

直接测试!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Main {


public static void main(String[] args) {

Student[] students = new Student[]{
new Student(18,"lip",99F),
new Student(19,"dabbe",45.5F),
new Student(20,"miky",50.5F)};

Utils.sort(students, new ShenComparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
// 按照成绩从大到小排序
return (int)(o2.getScore() - o1.getScore());
}
});
System.out.println(Arrays.toString(students));
//output:[Student(age=18, name=lip, score=99.0), Student(age=20, name=miky, score=50.5), Student(age=19, name=dabbe, score=45.5)]
}
}

ok 这里可以看到、我们调用Utils.sort方法时new 了一个匿名类部类 并重写里面的 compare方法来实现传递比较器,这就是自定义排序规则的根本原因。

但是到现在我们还根本没有跟lambda表达式沾边、是的、java8 以后匿名类部类可以改写简化成lambda表达式、但是有前提:

  • 该接口中(ShenComparator)只能有一个抽象方法
  • 也许还有。。。不记得了

用lambda表达式改写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Main {


public static void main(String[] args) {

Student[] students = new Student[]{
new Student(18,"lip",99F),
new Student(19,"dabbe",45.5F),
new Student(20,"miky",50.5F)};

Utils.sort(students, (o1, o2) -> (int)(o2.getScore() - o1.getScore())); // 只需要一行代码及可完成
System.out.println(Arrays.toString(students));
}
}
1
Utils.sort(students,(a,b) -> b.getName().compareTo(a.getName())); // 根据名字倒序排序
1
Utils.sort(students,(a,b) -> a.getAge() - b.getAge()); // 根据年龄正序排序

备注

为了更容易理解、贴出类项目结构(额、好像没必要,算了贴都贴了反正就两个重要类ShenComparator 与 Utils)

建议查看java函数式编程的源码内容本文只是仿照他写的一个简易实现

我是怎么使用Lambda表达式的?

首先Lambda表达式主要用于集合的操作中

其中java Stream流配合Lambda表达式对集合操作就非常便捷

比如:假设有一个包含员工信息的列表,我们希望按照部门对员工进行分组,并计算每个部门的平均工资。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Main {


@Data // 为了节省空间就不写setter getter构造方法了 直接用lombok 自动生成了
@AllArgsConstructor
static class Employee {
private String name;
private String department;
private double salary;
}

public static void main(String[] args) {

List<Employee> list = Arrays.asList(new Employee("Alice", "HR", 50000),
new Employee("Bob", "Engineering", 60000),
new Employee("Charlie", "HR", 55000),
new Employee("David", "Engineering", 70000),
new Employee("Eva", "HR", 48000),
new Employee("Frank", "Sales", 75000));


Map<String, Double> averageSalaryByDepartment = list.stream()
.collect(Collectors.groupingBy(Employee::getDepartment, // 按部门分组
Collectors.averagingDouble(Employee::getSalary))); // 求平均值

averageSalaryByDepartment.forEach((department, salaryAVG) -> System.out.println("Department: " + department + ", Average Salary: " + salaryAVG));
//output:Department: Engineering, Average Salary: 65000.0
//Department: Sales, Average Salary: 75000.0
//Department: HR, Average Salary: 51000.0
}

}

另外使用lambda表达式除了要会查文档外更建议用Idea查看具体需要类型,如 我这里使用filter方法实现过滤功能、但看不明白需要传递参数是什么、可以通过idea查看、需要一个Predicate (断言)接口 、里面一个test方法 返回值为boolean类型 需要一个参数、知道这些信息就可以很轻松写出lambda表达式,不需要new 个匿名类部类再改写。

代理模式

What is that

代理模式是一种结构型设计模式,它允许通过创建一个代理对象来控制原始对象的访问。代理对象充当一个中介,通过在客户端和目标对象之间建立通信桥梁,帮助管理对目标对象的访问。代理类在接收到客户端的请求时,可以在执行具体操作之前或之后,对原始对象进行额外的处理,和如果我需要执行某段程序前想要输出日志,我不需要每次都将日志输出代码都写入代码块里,造成代码冗余。

How to use it

1
2
3
4
5
6
7
8
9
10
11
package shen.dao;

import shen.entity.User;

public interface IUserDao {
/*
* 查找单个用户
* */

User findUserById(Integer id);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package shen.dao.impl;

import shen.dao.IUserDao;
import shen.entity.User;

public class UserDaoImpl implements IUserDao {

@Override
public User findUserById(Integer id) {

User user = new User(0,"you catch me","Hi");
return user;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package shen.entity;

public class User {
private Integer id;
private String username;
private String password;

public User() {}
public User(Integer id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package shen.handler;
import shen.dao.IUserDao;
import shen.dao.impl.UserDaoImpl;
import shen.entity.User;

public class ProxyHandler implements IUserDao {


public void before() {
System.out.println("turn on transaction");
}
@Override
public User findUserById(Integer id) {
this.before();
UserDaoImpl userDao = new UserDaoImpl();
User user = userDao.findUserById(id);
System.out.println(user);
this.after();
return user;
}
public void after() {
System.out.println("turn off transaction");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package shen.test;

import shen.entity.User;
import shen.handler.ProxyHandler;

public class test {
public static void main(String[] args) {
IUserDao proxyHandler = new ProxyHandler();
proxyHandler.findUserById(0);
}
}
```output
turn on transaction
User{id=0, username='You catch me', password='Hi'}
turn off transaction

通过这样静态代理实现接口的方式用proxyHandler实现获取User的同时还完成了after(),与before()两个方法

Why

代理模式存在的原因是为了达到一些额外的目的,例如:提供一些额外的安全措施、控制对资源的访问、实现懒加载等。通过代理模式,我们可以灵活地管理和控制对目标对象的访问。

JDK动态代理

What is that

如果每个类都自己实现编写静态代理,不仅麻烦而且会导致程序非常复杂,这时可以用到JDK的动态代理API,JDK动态代理是一种特殊类型的代理模式,它允许在运行时动态生成代理类和代理对象。JDK动态代理需要使用Java的反射机制,利用代理类和InvocationHandler接口来实现代理功能。

How to use it

在以上代码基础上对handler 包和test包进行更改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package shen.handler;

import shen.dao.IUserDao;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class DaoTransactionHandler implements InvocationHandler {
// if other class want use proxy you can choose Object class
private Object obj;

public DaoTransactionHandler(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object res = null;
// if you want to enhance just only one method
if ("findUserById".equals(method.getName())) {
this.before();
res = method.invoke(obj,args);
this.after();
}else {
res = method.invoke(args);
}
return res;
}

private void before() {
System.out.println("turn on enhance transaction");
}
private void after(){
System.out.println("turn off enhance transaction");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package shen.test;

import shen.dao.IUserDao;
import shen.dao.impl.UserDaoImpl;
import shen.handler.DaoTransactionHandler;

import java.lang.reflect.Proxy;

public class test {
public static void main(String[] args) {
// target Class
UserDaoImpl userDao = new UserDaoImpl();
// enhance Class
// invocation class
DaoTransactionHandler daoTransactionHandler = new DaoTransactionHandler(userDao);
// act proxy object
IUserDao userDaoProxy =(IUserDao) Proxy.newProxyInstance(UserDaoImpl.class.getClassLoader(),
UserDaoImpl.class.getInterfaces(), daoTransactionHandler);
userDaoProxy.findUserById(0);

}
}
```output
turn on enhance transaction
User{id=0, username='you catch me', password='Hi'}
turn off enhance transaction

Why

JDK动态代理的好处在于,它不需要事先编写代理类的源代码,而是在运行时根据需要动态生成代理类和代理对象。这样可以大大简化开发过程,特别是当需要为多个类生成代理时,能够提高代码的灵活性和可维护性。

JDK动态代理的原理是利用了Java的反射机制,通过在运行时创建一个新的类,实现目标类所实现的接口,并在方法调用时使用InvocationHandler的实现类来处理额外逻辑。当代理对象的方法被调用时,JDK动态代理会通过反射将调用转发给InvocationHandler实例中的invoke()方法,从而实现代理功能。

可以集合JDK自动生成的$Proxy的源码分析:

修改test中代码可以生成$Proxy文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package shen.test;

import shen.dao.IUserDao;
import shen.dao.impl.UserDaoImpl;
import shen.handler.DaoTransactionHandler;
import sun.misc.ProxyGenerator;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Proxy;

public class test {
public static void main(String[] args) {
// target Class
UserDaoImpl userDao = new UserDaoImpl();
// enhance Class
// invocation class
DaoTransactionHandler daoTransactionHandler = new DaoTransactionHandler(userDao);
// act proxy object
IUserDao userDaoProxy =(IUserDao) Proxy.newProxyInstance(UserDaoImpl.class.getClassLoader(),
UserDaoImpl.class.getInterfaces(), daoTransactionHandler);
userDaoProxy.findUserById(0);
// path such as "C:\\IdeaProject\\proxyDemo\\src"
saveProxyClass("__your project path __\\src");

}
//writing $proxy to contain in localhost
private static void saveProxyClass(String path) {
byte[] $Proxy1s = ProxyGenerator.generateProxyClass("$Proxy1",
UserDaoImpl.class.getInterfaces());
FileOutputStream out = null;
try {
out = new FileOutputStream(new File(path + "$Proxy1.class"));
out.write($Proxy1s);
}catch (Exception e) {
e.printStackTrace();
}finally {
if (out != null) {
try {
out.flush();
out.close();
} catch (IOException e) {
throw new RuntimeException(e);
}

}
}
}
}

分析源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public final class $Proxy1 extends Proxy implements IUserDao {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;

public $Proxy1(InvocationHandler var1) throws {
super(var1);
}

public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}

public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

public final User findUserById(Integer var1) throws {
try {
return (User)super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}

public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("shen.dao.IUserDao").getMethod("findUserById", Class.forName("java.lang.Integer"));
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}

JDK动态代理的原理是利用了Java的反射机制,通过在运行时创建一个新的类,实现目标类所实现的接口,并在方法调用时使用InvocationHandler的实现类来处理额外逻辑。当代理对象的方法被调用时,JDK动态代理会通过反射将调用转发给InvocationHandler实例中的invoke()方法,从而实现代理功能。

uml图

以上就是我的理解!

香橙派打造软路由

硬件介绍

Orange Pi R1 Plus LTS(软路由)此产品定位即为了打造路由器

官方参数如下:

官方自带有openwrt、ubuntu、debian固件等,but相信大家伙眼界没有受限于此

博主也建议选择社区版本openwrt固件这样优点很明显,能够拥有更多你想要的功能,废话不多说

固件引用

官方固件

推荐固件(如果你与博主用的硬件一样请选择最后一个镜像下载)

安装固件

需要条件:

1、microSD卡(读卡器)

2、烧录工具

3、固件镜像文件

操作步骤:

1、下载镜像文件、烧录工具

2、插入读卡器(microSD卡)

3、烧录系统(烧录好后千万不要格式化)

4、插上Pi 直接可以启动

启动路由器

操作步骤

1、连接电源,插上SD卡,连接lan网口至PC

2、PC检查网络连接找到IPv4 DNS服务器ip地址

3、浏览器访问该网址:默认为192.168.1.1

出现网站信息即成功!!!

4、登录openwrt、账号:root、原版没有密码、lean大佬版本密码:password

祝你好运!!!

附录

编译固件

想要自己diy插件功能等,可以自行编译固件,有详细教程,不需要编程知识,要求具备一定互联网搜索解决问题能力。

源码及编译教程地址

鸣谢

感谢固件提供地址:

1
https://openwrt.mpdn.fun:8443/?dir=lede/rockchip

感谢openwrt可编译源码

1
https://github.com/coolsnowwolf/lede

模拟电商日志mapRuce计算

准备环境

  • ubuntu22.04虚拟机/物理机
  • jdk安装以及环境配置
  • maven安装以及环境配置
  • hadoop安装以及环境配置

日志信息模拟生成

利用javaIO模拟生成100w条购买记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
&20320230401170400&Larry&虚拟产品&68.0&3.0&204.0&北京
&66420230401170400&吴九&电脑&56.0&13.0&728.0&北京
&90620230401170400&郑十&牙刷&32.0&9.0&288.0&北京
&60020230401170400&郑十&毛巾&78.0&1.0&78.0&长沙
&23020230401170400&吴九&虚拟产品&61.0&17.0&1037.0&长沙
&70520230401170400&张三&鞋子&70.0&15.0&1050.0&上海
&44620230401170400&王五&牙刷&11.0&7.0&77.0&上海
&18320230401170400&赵六&篮球&48.0&3.0&144.0&上海
&61620230401170400&周八&牙刷&30.0&19.0&570.0&上海
......
&80320230401170400&李四&水杯&19.0&9.0&171.0&北京
&48620230401170400&周八&牙刷&97.0&9.0&873.0&张家界
&43120230401170400&张三&篮球&36.0&6.0&216.0&佛山
&93720230401170400&李四&毛巾&56.0&19.0&1064.0&广州

构建maven项目

项目结构:将hadoop/etc/hadoop目录下的core-site.xml,hdfs-site.xml,log4g.properties文件分别复制到resources目录下

修改pom.xml 配置文件(博主用的Hadoop2.10.1,jdk1.8 maven3.9)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.example</groupId>
<artifactId>mapReduceDemo</artifactId>
<version>1.0-SNAPSHOT</version>

<repositories>
<repository>
<id>apache</id>
<url>http://maven.apache.org</url>
</repository>
</repositories>


<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>2.10.1</version>
</dependency>

<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>2.10.1</version>
</dependency>

<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-core</artifactId>
<version>2.10.1</version>
</dependency>

<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-jobclient</artifactId>
<version>2.10.1</version>
</dependency>

<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>

</project>

计算每个城市对应的购买总金额,accountByCity.java代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
//import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

import java.io.IOException;
import java.util.StringTokenizer;

/**
* <城市,小计金额>
*
* <城市,{小计金额,小计金额...}>
*
* <城市,总计金额>
*/
public class accountByCity {
public static class myMapper extends Mapper<Object,Text, Text, LongWritable> {
private final static LongWritable account = new LongWritable();
private Text city = new Text();
private long startTime;
@Override
protected void setup(Context context)throws IOException,InterruptedException{
super.setup(context);
startTime = System.currentTimeMillis();

}
@Override
public void map(Object key,Text value, Context context)
throws IOException,InterruptedException{
// 此处的value是文档中的第一行文本数据
String msg = value.toString();
String[] list = msg.split(";&";);
city.set(list[7]);
double myDouble = Double.parseDouble(list[6]);
long myLong = (long)myDouble;
account.set(myLong);
context.write(city,account);
}

protected void cleanup(Context context) throws IOException,InterruptedException {
super.cleanup(context);
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
System.out.println(";MapperTime : ";+ duration+ ";ms";);//获取map计算总时间
}


}

public static class myReduce extends Reducer<Text,LongWritable,Text,LongWritable> {
private LongWritable result = new LongWritable();
private long startTime;
@Override
protected void setup(Context context) throws IOException,InterruptedException{
super.setup(context);
startTime = System.currentTimeMillis();
}
// 从这可以看出reduce处理的输入数据是<key,value-list>类型的键值对
public void reduce(Text key, Iterable<LongWritable> values, Context context)
throws IOException,InterruptedException{
int sum = 0;
// reduce 函数就是对列表value中的数值进行相加
for(LongWritable val : values) {
sum += val.get();
}
result.set(sum);
// 将结果写入context
context.write(key,result);
}
@Override
protected void cleanup(Context context) throws IOException,InterruptedException{
super.cleanup(context);
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
System.out.println(";ReducerTime :";+duration+"; ms";);//获取reduce计算总时间
}
}
/**
* 1、accountByCity的main函数
* 2、main函数主要创建一个job对象,然后对accountBycity任务所需要的map函数,reduce函数,输入文件路径,输出文件路径的信息
* 进行配置
*/
public static void main(String[] args) throws Exception{
Configuration conf = new Configuration();
Job job = Job.getInstance(conf,";;accountByCity";);//获取一个任务实例
job.setJarByClass(accountByCity.class);//设置工作类
job.setMapperClass(myMapper.class);//设置Mapper类
job.setReducerClass(myReduce.class);//设置Reduce类
job.setOutputKeyClass(Text.class);//设置输出键值对中的key的类型
job.setOutputValueClass(LongWritable.class);//设置输出键值对中value的类型
FileInputFormat.addInputPath(job,new Path(args[0]));//设置输入文件的路径
FileOutputFormat.setOutputPath(job,new Path(args[1]));
FileSystem fs = FileSystem.get(conf);//获取HDFS文件系统
fs.delete(new Path(args[1]),true);//删除输出路径下可能已经存在的文件

运行项目

首先启动Hadoop服务

start-all.sh

上传日志文件到Hadoop的hdfs文件关系系统

关于Hadoop shell指令上传日志文件

假设这是我的日志文件路径/hadoop/test

hadoop fs -mkdir /data创建一个data目录

hadoop fs -mkdir /output创建一个output目录

hadoop fs -put /hadoop/test /data上传文件到data目录下

idea构建项目

执行项目

—success

map 100% reduce 100% 说明运行成功

1
2
3
4
5
6
7
8
......
23በ቞ 22:57:00 INFO mapred.LocalJobRunner: Finishing task: attempt_local524911839_0001_r_000000_0
23በ቞ 22:57:00 INFO mapred.LocalJobRunner: reduce task executor complete.
23በ቞ 22:57:01 INFO mapreduce.Job: map 100% reduce 100%
23በ቞ 22:57:01 INFO mapreduce.Job: Job job_local524911839_0001 completed successfully
23በ቞ 22:57:01 INFO mapreduce.Job: Counters: 35
......

查看生成数据

音频转文字

书接上回,AI图片生成,问答生成,接下来博主继续介绍 openAI的音频转换API,实现音频提取并翻译(->English)文字

1、 准备环境

    1、python基本环境
    2、pip install openai(下载第三方库,改库来自openAI官方)
    3、拥有openAI账号(chatGPT账号,没有可以注册一个)
    4、具有代理上网能力(全局),或者挂在境外服务器上运行

2、实践

首先进入openAI_API的官网获取API_Key—>openAI

进入View API keys

点击创建Create new secret key

并复制所创建的API_Key备用

博主依旧是润色官网代码实现功能:

    1、提取文字(不翻译)
1
2
3
4
5
6
7
8
# Note: you need to be using OpenAI Python v0.27.0 for the code below to work
import openai

openai.api_key = "your API_Key"
audio_file= open("./demo2.mp3", "rb") #路径修改为你的音频文件路径
# 音频转化为文字
response = openai.Audio.transcribe("whisper-1", audio_file)
print(response['text'])
    2、提取文字并翻译为英文
1
2
3
4
5
6
7
8
# Note: you need to be using OpenAI Python v0.27.0 for the code below to work
import openai

openai.api_key = "your API_Key"
audio_file= open("./demo2.mp3", "rb") #路径修改为你的音频文件路径
# 音频转化为文字并翻译为英文
response = openai.Audio.translate("whisper-1",audio_file)
print(response['text'])

3、运行截图

4、总结

相比于图形生成其效果还可以,也许适用于做短视频的字幕生成。

大家对AI感兴趣的可以阅读官方文档—>openAI_Doc

值得注意的是使用API并不是免费的,每分钟的音频计费$0.006,但每个账户都有$18的额度

调用openAI-Image models的API

调用openAI-Image models的API

博主这里承接上文调用chatGPT的API实现AI对话,接着继续用API实现AI画图

依旧用Python代码实现

1、 准备环境

    1、python基本环境
    2、pip install openai(下载第三方库,改库来自openAI官方)、pip install requests
    3、拥有openAI账号(chatGPT账号,没有可以注册一个)
    4、具有代理上网能力(全局),或者挂在境外服务器上运行

2、实践

首先进入openAI_API的官网获取API_Key—>openAI

进入View API keys

点击创建Create new secret key

并复制所创建的API_Key备用

博主这里从官网直接复制代码,并添加了存储本地功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import openai
import os
import requests
def image(prompt):
openai.api_key = '你自己的API_Key'
response = openai.Image.create(
prompt=prompt,
n=1,
size="1024x1024"
)
image_url = response['data'][0]['url']
# 存储图片
fileDown(image_url)


def fileDown(url):
if not os.path.exists('./image'):
os.mkdir('./image')
headers={
"User-Agent":"Mozilla/5.0(Windows NT 10.0;Win64;x64) AppleWebKit / 537.36(KHTML, likeGecko) Chrome / 88.0.4324.150 Safari / 537.36"
}
res = requests.get(url=url,headers=headers).content
src_path = './image/' +url.split('/')[-1]+ '.jpg'
with open(src_path,'wb') as fp:
fp.write(res)

if __name__ == "__main__":
prompt = input('请输入描述信息:')
print('请稍后...')
image(prompt=prompt)
print('完成,请移步当前目录下image文件...')

3、运行结果截图

效果如大家所见,就是难以形容,大家可以自己尝试一下!!!

大家对AI感兴趣的可以阅读官方文档—>openAI_Doc

值得注意的是使用API并不是免费的,每张1024*1024的图片花费 $0.020,但每个账户都有$18的额度

调用chatGPT的API(GPT-3.5-turbo)

博主在这里用python编写代码的方式实现调用GPT-3.5-turbo的API实现终端访问chatGPT

1、 准备环境

    1、python基本环境
    2、pip install openai(下载第三方库,改库来自openAI官方)
    3、拥有openAI账号(chatGPT账号,没有可以注册一个)
    4、具有代理上网能力(全局),或者挂在境外服务器上运行

2、实践

首先进入openAI_API的官网获取API_Key—>openAI

进入View API keys

点击创建Create new secret key

并复制所创建的API_Key备用

博主这里从官网直接复制并添加润色了以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Note: you need to be using OpenAI Python v0.27.0 for the code below to work
import openai
openai.api_key = '你的API'
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
# {"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Who are you?"},#这里content表示输入问题
# {"role": "assistant", "content": "The Los Angeles Dodgers won the World Series in 2020."},
# {"role": "user", "content": "Where was it played?"}
]
)
print(response['choices'][0]['message']['content'])

3、结果显示

打印类似如下内容说明成功!

1
As an AI language model developed by OpenAI, I am not a person or a human being. I am a computer program designed to understand and generate natural language responses to interact with humans.

大家对AI感兴趣的可以阅读官方文档—>openAI_Doc

值得注意的是使用API并不是免费的,但每个账户都有US$ 18的额度

参考视频—> TechDIYLife