当前位置: 首页 > news >正文

“系统的UI”——SystemUI

SystemUI的实现

以StatusBar为例,来分析下Android系统具体是如何实现它们的。
相关代码分为两部分,即:

  • Service部分

代码路径:frameworks/base/services/java/com/android/server。

  • 应用部分

代码路径:frameworks/base/packages/SystemUI。

下面来看看SystemUI的“目录”:

…
<applicationandroid:persistent="true"android:allowClearUserData="false"          
android:allowBackup="false"android:hardwareAccelerated="true"android:label="@string/app_ label"     android:icon="@*android:drawable/platlogo"android:supportsRtl="true" ><service android:name="SystemUIService"android:exported="true"/>/*SystemUIService是我们分析的重点,状态栏等系统UI实现都是在这里完成的*/<service android:name=".screenshot.TakeScreenshotService"android:process=":screenshot" android:exported="false" />/*由此可知,SystemUI提供了截屏操作。有兴趣的读者可以自己研究下是如何实现的*/<receiver android:name=".BootReceiver"androidprv:primaryUserOnly="true" >/*开机自启动,不过这里启动的是LoadAverageService,而不是SystemUIService*/<intent-filter><action android:name="android.intent.action.BOOT_COMPLETED" /></intent-filter>
</receiver>
…

通过AndroidManfiest我们知道SystemUIService是整个系统UI的“载体”,所以接下来将根据这一线索来把整个代码流程“串”起来。和其他很多系统服务一样,SystemUIService也是在SystemServer中启动的。具体而言,SystemServer会在适当的时机通知ActivityManagerService“系统已经就绪(systemReady),可以进一步运行第三方模块了”——这其中就包括将由startServiceAsUser启动的SystemUIService

SystemUIService继承了标准的Service组件,因而必须重载onCreate接口:

/*frameworks/base/packages/systemui/src/com/android/systemui/SystemUIService.java*/public void onCreate() {… IWindowManager wm = WindowManagerGlobal.getWindowManagerService();//获取WMS服务try {SERVICES[0] = wm.hasSystemNavBar()? R.string.config_systemBarComponent: R.string.config_statusBarComponent;//是StatusBar还是SystemBar?} catch (RemoteException e) {Slog.w(TAG, "Failing checking whether status bar can hide", e);}final int N = SERVICES.length;mServices = new SystemUI[N];for (int i=0; i<N; i++) {Class cl = chooseClass(SERVICES[i]);Slog.d(TAG, "loading: " + cl);try {mServices[i] = (SystemUI)cl.newInstance();} …mServices[i].mContext = this;Slog.d(TAG, "running: " + mServices[i]);mServices[i].start();//mServices中的每个元素都继承自SystemUI}}

SERVICES是一个object数组,它的初始值如下所示:

final Object[] SERVICES = new Object[] {0, // system bar or status bar, filled in below.com.android.systemui.power.PowerUI.class,com.android.systemui.media.RingtonePlayer.class,com.android.systemui.settings.SettingsUI.class,};

其中,SERVICES[0]在初始化时没有赋值。它将根据hasSystemNavBar的执行结果来决定是用systemBar还是statusBar。上面这段代码首先取出SERVICES数组中的class名,然后分别实例化它们,最后调用start接口统一启动。因此,每一个系统ui元素(包括statusBar,PowerUI等)都必须继承自SystemUI这个抽象类,并重载其中的start方法。

函数hasSystemNavBar做了哪些判断来对statusBar和systemBar进行取舍呢?WindowManager的真正实现体是WindowManagerService。

  /*frameworks/base/services/java/com/android/server/wm/WindowManagerService.java*/public boolean hasSystemNavBar() {return mPolicy.hasSystemNavBar();}

**Policy是Android中定义UI行为的一个“规范”。**比如有没有Navigation Bar,WindowLayer如何排布等。以PhoneWindowManager为例,它判断当前系统是否需要导航条。

我们假设设备的分辨率是800*480,屏幕密度为ldpi:

/*frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java*/int shortSizeDp = shortSize*DisplayMetrics.DENSITY_DEFAULT/ density;/*在这个场景中,shortSize=480,DENSITY_DEFAULT=160,density =120, 所以最终shortSizeDp = 640*/if (shortSizeDp < 600) {//在这个场景中不成立mHasSystemNavBar = false;mNavigationBarCanMove = true;} else if (shortSizeDp < 720) {/*本场景属于这一分支*/mHasSystemNavBar = false;mNavigationBarCanMove = false;} if (!mHasSystemNavBar) {//进一步判断是否有Navigation Bar…} else {mHasNavigationBar = false;}

所以在这个场景中,经过上面的判决后mHasSystemNavBar为false。换句话说,对于分辨率800*480且密度为ldpi的屏幕,它的SERVICES[0]对应的class类名是R.string.config_statusBar Component即“com.android.systemui.statusbar.phone.PhoneStatusBar”。下面以PhoneStatusBar为例来看看它的创建过程及具体样式:

/*frameworks/base/packages/systemui/src/com/android/systemui/statusbar/phone/  PhoneStatusBar.java*/public void start() {mDisplay = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();/*mDisplay记录了当前默认显示屏的大小,密度等等信息*/…super.start();// 关键语句,下面我们会重点介绍addNavigationBar();/*不是所有Phone都需要Navigation Bar。比如设备本身已经配备了物理按键,这种情况下如果一直在屏幕上显示导航条反而是一种累赘*/…}

PhoneStatusBar的“父类”是BaseStatusBar,很多框架性的操作都是在这里面完成的(但UI界面的具体描述还是会通过回调PhoneStatusBar中的方法来确定):

 /*frameworks/base/packages/systemui/src/com/android/systemui/statusbar/BaseStatusBar.java*/public void start() {…mBarService = IStatusBarService.Stub.asInterface(ServiceManager.getService(Context.STATUS_BAR_SERVICE));// Connect in to the status bar manager serviceStatusBarIconList iconList = new StatusBarIconList();//状态栏图标列表ArrayList<IBinder> notificationKeys = new ArrayList<IBinder>();ArrayList<StatusBarNotification> notifications = new ArrayList<StatusBarNoti fication>();mCommandQueue = new CommandQueue(this, iconList);int[] switches = new int[7];ArrayList<IBinder> binders = new ArrayList<IBinder>();try {mBarService.registerStatusBar(mCommandQueue,iconList,notificationKeys,notifications,switches, binders); /*经过一系列对象的创建与初始化后,开始向StatusBarService进行注册。这里涉及跨进程操作,因而传递的参数都是继承自Parcelable的*/} catch (RemoteException ex) {// If the system process isn't there we're doomed anyway.}createAndAddWindows(); /*这是真正将Status Bar显示出来的地方*/…}

好不容易快到“水落石出”的时候了,但是上面这段代码却又杀出一个“程咬金”——StatusBarService。

既然SystemUI这个应用程序中已经有StatusBar了,为什么又需要StatusBarService?

先来看看StatusBarService是在哪里启动的。

   /*frameworks/base/services/java/com/android/server/SystemServer.java*/try {Slog.i(TAG, "Status Bar");statusBar = new StatusBarManagerService(context, wm); /*确实在这里。而且具体的实现类叫做StatusBarManagerService*/ServiceManager.addService(Context.STATUS_BAR_SERVICE, statusBar);} catch (Throwable e) {reportWtf("starting StatusBarManagerService", e);}

现在可以进一步分析StatusBarManagerService的实现了。针对上面BaseStatusBar中调用的注册操作:

public void registerStatusBar(IStatusBar bar, StatusBarIconList iconList,List<IBinder>notificationKeys,List<StatusBarNotification> notifications,int switches[], List<IBinder> binders) {enforceStatusBarService();mBar = bar;synchronized (mIcons) {iconList.copyFrom(mIcons); /*复制Icon列表,注意方向是从StatusBarManager->  BaseStatusBar*/}synchronized (mNotifications) {for (Map.Entry<IBinder,StatusBarNotification> e: mNotifications.entrySet()) {notificationKeys.add(e.getKey());notifications.add(e.getValue());/*和Icon列表类似,方向也是从StatusBarManager到BaseStatusBar*/}}…}

由上面这段代码可以看出,registerStatusBar有两个作用:
其一,为新启动的SystemUI应用中的StatusBar赋予当前系统的真实值(比如有多少需要显示的图标)。其二,通过成员变量mBar记录下IStatusBar对象——它在SystemUI中对应的是CommandQueue。

我们再回到BaseStatusBar。向StatusBarManagerService注册完成后,它会执行如下语句。

createAndAddWindows();

BaseStatusBar中的这个方法是抽象的,因而其子类PhoneStatusBar必须要重载它:

  /*frameworks/base/packages/systemui/src/com/android/systemui/statusbar/phone/Ph-  oneStatusBar.java*/public void createAndAddWindows() {addStatusBarWindow();}private void addStatusBarWindow() {final int height = getStatusBarHeight();/*首先获取StatusBar的高度。默认的高度值是通过com.android.internal.R.dimen.status_bar_height来指定的,因而开发人员如果需要更改StatusBar高度的话,可以考虑修改这个值*/ final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, /*宽度是MATCH_PARENT*/height, //高度值是可定制的WindowManager.LayoutParams.TYPE_STATUS_BAR, /*指定窗口类型*/WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,/*设置flag, 下面还会加上硬件加速属性*/PixelFormat.TRANSLUCENT/*半透明的*/);lp.flags |=windowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;lp.gravity = getStatusBarGravity();/*设置Gravity属性,默认值为Gravity.TOP  |Gravity.FILL_HORIZONTAL,所以StatusBar是在屏幕上方*/lp.setTitle("StatusBar"); //标题lp.packageName = mContext.getPackageName();makeStatusBarView(); //下面会详细介绍mWindowManager.addView(mStatusBarWindow, lp); /*将一切就绪的mStatusBarWindow加入WindowManager中。请参见本书显示系统章节的讲解*/}

从makeStatusBarView这个函数名可以推断出,StatusBarView会被创建并且初始化。先来了解下两个重要的变量。

  • mStatusBarWindow
    这是一个StatusBarWindowView类对象,同时我们通过addView传给WindowManager的也是这个变量——说明它很可能包含了StatusBarView。

  • mStatusBarView
    这就是makeStatusBarView需要操作的对象。

/*frameworks/base/packages/systemui/src/com/android/systemui/statusbar/phone/Ph-one  
StatusBar.java*/protected PhoneStatusBarView makeStatusBarView() {…mStatusBarWindow = (StatusBarWindowView) View.inflate(context, R.layout.super_   status_bar, null);mStatusBarWindow.mService = this; //mService其实指的是PhoneStatusBarmStatusBarWindow.setOnTouchListener(new View.OnTouchListener() {//设置触摸事件@Overridepublic boolean onTouch(View v, MotionEvent event) {if (event.getAction() == MotionEvent.ACTION_DOWN) {//支持下拉手势if (mExpandedVisible) {animateCollapsePanels ();//通知栏的“下拉展开”需要动画效果,不然会很突兀}}return mStatusBarWindow.onTouchEvent(event);}});mStatusBarView =(PhoneStatusBarView)mStatusBarWindow.findViewById(R.id.status_ bar);mStatusBarView.setBar(this); /*状态栏出场了*/…mNotificationPanel = (NotificationPanelView) mStatusBarWindow.findViewById(R.id. notification_panel);mNotificationPanel.setStatusBar(this); /*通知栏也很关键,只不过它只有在下拉后才会出现*//*从下面开始将利用mStatusBarView为PhoneStatusBar中的众多内部变量赋值*/…try {boolean showNav = mWindowManagerService.hasNavigationBar();/*决定是否需要导航条*/if (showNav) {mNavigationBarView = (NavigationBarView) View.inflate(context, R.layout. navigation_ bar, null);/*Navigation Bar对应的layout。有兴趣的读者可以自己看一下*/…}} catch (RemoteException ex) {/*Android中的不少代码在捕捉异常时,很常见的一种处理就是“听天由命”…*/}/*接下来通过findViewById从mStatusBarView中获取StatusIcons、NotificationIcons、ClearButton等一系列按键。我们将会在StatusBar布局文件中做统一分析。这里暂时略过*/…       /*最后动态注册需要接收的广播,比如系统设置改变,屏幕关闭等*/IntentFilter filter = new IntentFilter();filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);…context.registerReceiver(mBroadcastReceiver, filter);…return mStatusBarView;//注意最终返回值是mStatusBarWindow的子View}

变量mStatuBarWindow来源于super_status_bar布局。它本质上还是一个FrameLayout,包含的元素也很简单,就是status_barstatus_bar_expanded两个布局,前者对应的是状态栏mStatusBarView,后者其实就是通知栏mNotificationPanel。为status_bar中的众多元素(按键、背景等)进行初始化。

最终的返回值是mStatusBarView。然后利用WindowManager的addView接口将mStatus- BarWindow(注意:不是mStatusBarView)添加进窗口系统中。接下来的主动权就转交给WindowManager。

这样我们就把StatusBar,Notification和NavigationBar的调用流程“串”起来了。

在这里插入图片描述

Android壁纸资源——WallpaperService

除了前面讲解的状态栏、通知栏外,壁纸也属于SystemUI管理的一个重点。在Android系统中,用户可以从设备内部或者外存储器(比如SD卡中)中选取图片资源作为壁纸。另外,系统还支持动态壁纸的显示。

壁纸管理系统主要包括以下几个方面。

  • WallpaperManagerService(WPMS)
    它是壁纸机制的“大总管”,静态、动态壁纸都是在这里统一调度的。

  • WallpaperService(WPS)
    WPS继承了标准的Service组件,因而它一定会实现onCreate、onDestroy、onBind等一系列方法。此外它还包含了一个重要的嵌套类engine,我们在后面会做详细讲解。WPS是静态、动态壁纸的基类,代表了作为“壁纸”所应该具有的一切属性。

  • ImageWallpaper(IWP)
    从名称可以看出,它是静态壁纸的实现类,而且一定是继承自上面的WPS。

在这里插入图片描述

WallPaperManagerService

WPMS既然是基于AIDL实现的,我们来看看它的接口描述:

/*frameworks/base/core/java/android/app/IWallpaperManager.aidl*/
interface IWallpaperManager {ParcelFileDescriptor setWallpaper(String name); /*设置壁纸*/void setWallpaperComponent(in ComponentName name); /*设置动态壁纸*/ParcelFileDescriptor getWallpaper(IWallpaperManagerCallback cb,out Bundle outParams);WallpaperInfo getWallpaperInfo();…
}

从上面的接口定义可以看出,WPMS的工作并不复杂——它提供了全局的壁纸注册、取消和查询功能,并在接收到事件时进行合理分配。

和其他系统服务一样,WPMS是在SystemServer.java中启动并注册进ServiceManager中的,如下所示:

 /*frameworks/base/services/java/com/android/server/SystemServer.java*/try {Slog.i(TAG, "Wallpaper Service");if (!headless) {wallpaper = new WallpaperManagerService(context);ServiceManager.addService(Context.WALLPAPER_SERVICE, wallpaper);}} catch (Throwable e) {reportWtf("starting Wallpaper Service", e);}

接下来的一个问题是:既然系统同时支持静态壁纸和动态壁纸,而且每种类型中还包含了N个实例(比如原生态系统就自带多个动态壁纸供用户选择),那么系统在显示时是如何选择的呢?我们很自然地会想到,在WPMS启动时它应该会去读取某个“配置文件”,这个文件记录了用户最近一次的选择:

 public WallpaperManagerService(Context context) {…loadSettingsLocked(UserHandle.USER_OWNER);//加载配置}

当WPMS构造时,它调用了loadSettingsLocked

 private void loadSettingsLocked(int userId) {//这里传进来的userId=0…try {stream = new FileInputStream(file);XmlPullParser parser = Xml.newPullParser();parser.setInput(stream, null);int type;do {type = parser.next();if (type == XmlPullParser.START_TAG) {String tag = parser.getName();if ("wp".equals(tag)) {…wallpaper.name = parser.getAttributeValue(null, "name");String comp = parser.getAttributeValue(null, "component");…}}} while (type != XmlPullParser.END_DOCUMENT);success = true;} …        }

上面这段代码会按照写入时的格式将wallpaper的配置信息读出来,并保存在WallpaperData结构中——专门用于描述壁纸信息的通用数据结构。不过这时壁纸还没有真正显示出来,而是要等到系统进入Ready状态(此时系统会回调SystemReady接口)后才会通知具体的壁纸程序进行绘制:

 public void systemReady() {WallpaperData wallpaper = mWallpaperMap.get(UserHandle.USER_OWNER);switchWallpaper(wallpaper, null);…}

接着进入Wallpaper的具体处理中:

void switchWallpaper(WallpaperData wallpaper, IRemoteCallback reply) {synchronized (mLock) {…try {ComponentName cname = wallpaper.wallpaperComponent != null ?wallpaper.wallpaperComponent : wallpaper.nextWallpaperComponent;if (bindWallpaperComponentLocked(cname, true, false, wallpaper,reply)) {return;}} …}

系统开机后,wallpaper.wallpaperComponent为空(除非上一次用户选择了其他方式);而wallpaper.nextWallpaperComponent则在loadSettingsLocked中被设置为wallpaper.imageWallpaper Component,即我们前面提到的ImageWallpaper这个Service。所以当调用bindWallpaperComponentLocked时,传入的cname就代表了ImageWallpaper。从bindWallpaperComponentLocked的函数名称可以看出,它将会以bindService的方式来启动目标壁纸Service(所以后期如果确认已经不再使用这个Service,还要主动执行unbind,然后这个壁纸服务就会自动销毁)。

WPMS启动后就可以接收客户端的请求了,因为它属于实名的BinderServer,意味着所有人都可以自由地使用它所提供的服务。比如我们既可以在系统自带的Launcher应用程序中选择壁纸,也完全可以自己编写一个更改壁纸的应用程序。

下面我们以设置壁纸这一场景为例来分析WPMS的内部实现:

 /*frameworks/base/services/java/com/android/server/WallpaperManagerService.java*/public ParcelFileDescriptor setWallpaper(String name) {checkPermission(android.Manifest.permission.SET_WALLPAPER);synchronized (mLock) {int userId = UserHandle.getCallingUserId();WallpaperData wallpaper = mWallpaperMap.get(userId);…final long ident = Binder.clearCallingIdentity();try {ParcelFileDescriptor pfd = updateWallpaperBitmapLocked(name, wallpaper);…return pfd;} finally {Binder.restoreCallingIdentity(ident);}}}

首先系统会做下权限检查,所以提供壁纸设置功能的应用程序一定要在AndroidManifest.xml中显式写上如下权限声明:

<uses-permission android:name="android.permission.SET_WALLPAPER" />

变量wallpaper是从mWallpaperMap取出来的,代表UserId为0时的壁纸——如果不为空就进入以下函数:

ParcelFileDescriptor updateWallpaperBitmapLocked(String name, WallpaperData wallpaper) {if (name == null) name = "";try {File dir = getWallpaperDir(wallpaper.userId);//wallpaper的路径if (!dir.exists()) {//指定的路径不存在,需要创建dir.mkdir();FileUtils.setPermissions(dir.getPath(),FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH, -1, -1);}File file = new File(dir, WALLPAPER);ParcelFileDescriptor=ParcelFileDescriptor.open(file,MODE_CREATE|MODE_READ_WRITE);if (!SELinux.restorecon(file)) {return null;}wallpaper.name = name;return fd;} catch (FileNotFoundException e) {Slog.w(TAG, "Error setting wallpaper", e);}return null;}    

上面getWallpaperDir将得到一个WALLPAPER_BASE_DIR+“/”+userId的路径,其中WALLPAPER_BASE_DIR默认值是"/data/system/users"。

ImageWallpaper

前面讲过,当WPMS开机启动时,默认情况下会选择ImageWallpaper这个壁纸实现,并且以bindService的方式来启动它。在bindService中,WPMS同时传入名为newConn的Binder对象(WallpaperConnection)来使ImageWallpaper(其他WallpaperService也是一样的)可以访问到WPMS。而ImageWallpaper则响应onBind返回一个IWallpaperServiceWrapper的Binder对象,如图所示。

在这里插入图片描述我们来看看当绑定成功后WPMS中的操作:

 public void onServiceConnected(ComponentName name, IBinder service) {synchronized (mLock) {if (mWallpaper.connection == this) {…attachServiceLocked(this, mWallpaper);…saveSettingsLocked(mWallpaper);}}}

WPMS除了要保存当前所选的壁纸外,还要调用attachServiceLocked(间接调用Iwallpaper ServiceWrapper.attach)来执行实际的工作。

WPS这边的attach函数将生成一个IWallpaperEngineWrapper对象并给它发送一个DO_ATTACH,这个消息最终由IWallpaperEngineWrapper. executeMessage来处理:

 public void executeMessage(Message message) {switch (message.what) {case DO_ATTACH: {try {mConnection.attachEngine(this);} catch (RemoteException e) {Log.w(TAG, "Wallpaper host disappeared", e);return;}Engine engine = onCreateEngine();mEngine = engine;mActiveEngines.add(engine);engine.attach(this);return;}

上述代码段通过onCreateEngine生成了一个壁纸引擎——这也是各壁纸应用间最核心的差异。所以系统要求每一个WallpaperService实例必须要重载onCreateEngine来实现自己的engine。在ImageWallpaper中,它将产生一个DrawableEngine——这个engine随后会被加入mActiveEngines的全局list中,然后调用它提供的attach接口。如下所示:

/*frameworks/base/core/java/android/service/wallpaper/WallpaperService.java*/public class Engine {…void attach(IWallpaperEngineWrapper wrapper) {…mSession = WindowManagerGlobal.getWindowSession();mWindow.setSession(mSession);…IntentFilter filter = new IntentFilter();filter.addAction(Intent.ACTION_SCREEN_ON);filter.addAction(Intent.ACTION_SCREEN_OFF);registerReceiver(mReceiver, filter);             …updateSurface(false, false, false);//更新Surface
}…

Engine内部首先需要进行各重要变量的初始化,然后注册监听屏幕的开/关事件,最后调用updateSurface。

相关文章:

“系统的UI”——SystemUI

SystemUI的实现 以StatusBar为例&#xff0c;来分析下Android系统具体是如何实现它们的。 相关代码分为两部分&#xff0c;即&#xff1a; Service部分 代码路径&#xff1a;frameworks/base/services/java/com/android/server。 应用部分 代码路径&#xff1a;frameworks…...

类和对象:构造函数,析构函数与拷贝构造函数

1.类的6个默认成员函数 如果一个类中什么成员都没有&#xff0c;简称为空类。 空类中真的什么都没有吗&#xff1f;并不是&#xff0c;任何类在什么都不写时&#xff0c;编译器会自动生成以下6个默认成员函数。 默认成员函数&#xff1a;用户没有显式实现&#xff0c;编译器…...

谈谈Java的特点和优点以及选择Java的原因

&#xfeff;​ 如果面试官问你&#xff1a;请你说说Java的特点和优点&#xff0c;为什么要选择Java&#xff1f;你该怎么回答&#xff1f; 得分点 Java的特点 Java与C的区别 Java的优点标准回答 Java是一门非常纯粹的面向对象的编程语言&#xff0c;它吸收了C语言的各种优…...

消息队列(MQ)面试

目录 讲一讲MQ 面试官: 在你之前的项目中&#xff0c;你是否使用过消息队列&#xff08;MQ&#xff09;&#xff1f;能详细介绍一下你在项目中如何使用MQ吗&#xff1f; 在用户和用户之间的多对多聊天通信中如何使用&#xff0c;请具体来讲一下。 那你可以讲一下消息的确认…...

无涯教程-JavaScript - COUPNUM函数

描述 COUPNUM函数返回结算日和到期日之间应付的息票数量,四舍五入到最接近的整数。 语法 COUPNUM (settlement, maturity, frequency, [basis])争论 Argument描述Required/OptionalSettlement 证券的结算日期。 证券结算日期是指在发行日期之后将证券交易给买方的日期。 Re…...

上海控安携汽车网络安全新研产品出席AUTOSEMO“恒以致远,共创共赢”主题研讨会

8月31日&#xff0c;AUTOSEMO“恒以致远&#xff0c;共创共赢”主题研讨会在天津成功召开。本次大会由中国汽车工业协会软件分会中国汽车基础软件生态标委会&#xff08;简称&#xff1a;AUTOSEMO&#xff09;与天津市西青区人民政府联合主办。现场汇聚了100余位来自产学研政企…...

小程序引入高德/百度地图坐标系详解

小程序引入高德/百度地图坐标系详解 官网最近更新时间&#xff1a;最后更新时间: 2021年08月17日 高德官网之在原生小程序中使用的常见问题 链接 目前在小程序中使用 高德地图只支持以下功能 &#xff1a;地址描述、POI和实时天气数据 小结&#xff1a;从高德api中获取数…...

英诺森 “供应链智能数据平台”荣获“科技进步奖”

近日&#xff0c;2023年中国物流与采购联合会科学技术奖正式公布&#xff0c;该奖项经国家科技部批准&#xff0c;在国家科学技术奖励工作办公室登记备案&#xff0c;是我国物流行业最具影响力的奖项之一。 英诺森联合客户申报的科技项目“英诺森供应链智能数据平台”&#xf…...

kafka 3.5 主题分区的Follower创建Fetcher线程从Leader拉取数据源码

Kakfa集群有主题&#xff0c;每一个主题下又有很多分区&#xff0c;为了保证防止丢失数据&#xff0c;在分区下分Leader副本和Follower副本&#xff0c;而kafka的某个分区的Leader和Follower数据如何同步呢&#xff1f;下面就是讲解的这个 首先要知道&#xff0c;Follower的数据…...

Golang web 项目中实现自定义 recovery 中间件

为什么需要实现自定义 recovery 中间件&#xff1f; 在 Golang 的 Web 项目中&#xff0c;自定义 recovery 中间件是一种常见的做法&#xff0c;用于捕获并处理应用程序的运行时错误&#xff0c;以避免整个应用程序崩溃并返回对应格式的响应数据。 很多三方 web 框架&#xf…...

Direct3D绘制旋转立方体例程

初始化文件见Direct3D的初始化_direct3dcreate9_寂寂寂寂寂蝶丶的博客-CSDN博客 D3DPractice.cpp #include <windows.h> #include "d3dUtility.h" #include <d3dx9math.h>IDirect3DDevice9* Device NULL; IDirect3DVertexBuffer9* VB NULL; IDirect3…...

ElementUI浅尝辄止31:Tabs 标签页

选项卡组件&#xff1a;分隔内容上有关联但属于不同类别的数据集合。 常见于网站内容信息分类或app内容信息tab分类 1.如何使用&#xff1f; Tabs 组件提供了选项卡功能&#xff0c;默认选中第一个标签页&#xff0c;你也可以通过 value 属性来指定当前选中的标签页。 <temp…...

将 ChatGPT 用于数据科学项目的指南

推荐&#xff1a;使用 NSDT场景编辑器 快速搭建3D应用场景 我们都知道 ChatGPT 的受欢迎程度以及人们如何使用它来提高生产力。但是&#xff0c;如果您是新手&#xff0c;则值得注册ChatGPT免费演示并尝试它所能做的一切。您还应该参加我们的 ChatGPT 简介课程&#xff0c;学习…...

06-JVM对象内存回收机制深度剖析

上一篇&#xff1a;05-JVM内存分配机制深度剖析 堆中几乎放着所有的对象实例&#xff0c;对堆垃圾回收前的第一步就是要判断哪些对象已经死亡&#xff08;即不能再被任何途径使用的对象&#xff09;。 1.引用计数法 给对象中添加一个引用计数器&#xff0c;每当有一个地方引…...

[VSCode] 替换掉/去掉空行

VSCode中使用快捷键CtrlH&#xff0c;出现替换功能&#xff0c;在上面的“查找”框中输入正则表达式&#xff1a; ^\s*(?\r?$)\n然后选择右侧的“使用正则表达式”&#xff1b;“替换”框内为空&#xff0c;点击右侧的“全部替换”&#xff0c;即可去除所有空行。 参考 [VS…...

时序分解 | MATLAB实现ICEEMDAN+SE改进的自适应经验模态分解+样本熵重构分量

时序分解 | MATLAB实现ICEEMDANSE改进的自适应经验模态分解样本熵重构分量 目录 时序分解 | MATLAB实现ICEEMDANSE改进的自适应经验模态分解样本熵重构分量效果一览基本介绍程序设计参考资料 效果一览 基本介绍 ICEEMDANSE改进的自适应经验模态分解样本熵重构分量 包括频谱图 避…...

python内网环境安装第三方包【内网搭建开发环境】

文章目录 一、问题二、解决方法三、代码实现一、问题 内网安装第三方包的应用场景,一般是一些需要在没网的环境下进行开发的情况。这些环境一般仅支持本地局域网访问,所以只能在不下载任何第三方包的情况下艰难开发。 二、解决方法 将当前应用依赖的第三方包提前下载到本地…...

7.13 在SpringBoot中 正确使用Validation实现参数效验

文章目录 前言引入Maven依赖一、POST/PUT RequestBody参数校验1.1 Valid或Validated注解配合constraints注解1.2 测试运行 二、GET/DELETE RequestParam参数校验2.1 Validated注解配合constraints注解2.2 测试运行 三、GET 无注解参数校验3.1 Valid或Validated注解配合constrai…...

Matlab图像处理之Lee滤波器

目录 一、前言:二、LEE滤波器2.1 LEE滤波器原理2.2 LEE滤波器实现步骤三、MATLAB代码示例一、前言: LEE滤波器是一种常用于合成孔径雷达(SAR)图像去噪的滤波器。它能增强图像的局部对比度。今天我们将通过MATLAB来实现这种滤波器。 二、LEE滤波器 2.1 LEE滤波器原理 LEE滤…...

C++系列-const修饰的常函数

const修饰的常函数 常函数常对象 常函数 成员函数后加const&#xff0c;称为常函数。常函数内部不可以修改成员变量。常函数内可以改变加了mutable修饰的成员变量。 code:#include <iostream>using namespace std;class Horse{public:int age 3;mutable string color …...

使用VSCode开发Django指南

使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架&#xff0c;专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用&#xff0c;其中包含三个使用通用基本模板的页面。在此…...

<6>-MySQL表的增删查改

目录 一&#xff0c;create&#xff08;创建表&#xff09; 二&#xff0c;retrieve&#xff08;查询表&#xff09; 1&#xff0c;select列 2&#xff0c;where条件 三&#xff0c;update&#xff08;更新表&#xff09; 四&#xff0c;delete&#xff08;删除表&#xf…...

在Ubuntu中设置开机自动运行(sudo)指令的指南

在Ubuntu系统中&#xff0c;有时需要在系统启动时自动执行某些命令&#xff0c;特别是需要 sudo权限的指令。为了实现这一功能&#xff0c;可以使用多种方法&#xff0c;包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法&#xff0c;并提供…...

前端开发面试题总结-JavaScript篇(一)

文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包&#xff08;Closure&#xff09;&#xff1f;闭包有什么应用场景和潜在问题&#xff1f;2.解释 JavaScript 的作用域链&#xff08;Scope Chain&#xff09; 二、原型与继承3.原型链是什么&#xff1f;如何实现继承&a…...

使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台

🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...

大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计

随着大语言模型&#xff08;LLM&#xff09;参数规模的增长&#xff0c;推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长&#xff0c;而KV缓存的内存消耗可能高达数十GB&#xff08;例如Llama2-7B处理100K token时需50GB内存&a…...

渗透实战PortSwigger Labs指南:自定义标签XSS和SVG XSS利用

阻止除自定义标签之外的所有标签 先输入一些标签测试&#xff0c;说是全部标签都被禁了 除了自定义的 自定义<my-tag onmouseoveralert(xss)> <my-tag idx onfocusalert(document.cookie) tabindex1> onfocus 当元素获得焦点时&#xff08;如通过点击或键盘导航&…...

Git 命令全流程总结

以下是从初始化到版本控制、查看记录、撤回操作的 Git 命令全流程总结&#xff0c;按操作场景分类整理&#xff1a; 一、初始化与基础操作 操作命令初始化仓库git init添加所有文件到暂存区git add .提交到本地仓库git commit -m "提交描述"首次提交需配置身份git c…...

【工具教程】多个条形码识别用条码内容对图片重命名,批量PDF条形码识别后用条码内容批量改名,使用教程及注意事项

一、条形码识别改名使用教程 打开软件并选择处理模式&#xff1a;打开软件后&#xff0c;根据要处理的文件类型&#xff0c;选择 “图片识别模式” 或 “PDF 识别模式”。如果是处理包含条形码的 PDF 文件&#xff0c;就选择 “PDF 识别模式”&#xff1b;若是处理图片文件&…...

JUC并发编程(二)Monitor/自旋/轻量级/锁膨胀/wait/notify/锁消除

目录 一 基础 1 概念 2 卖票问题 3 转账问题 二 锁机制与优化策略 0 Monitor 1 轻量级锁 2 锁膨胀 3 自旋 4 偏向锁 5 锁消除 6 wait /notify 7 sleep与wait的对比 8 join原理 一 基础 1 概念 临界区 一段代码块内如果存在对共享资源的多线程读写操作&#xf…...