“系统的UI”——SystemUI

news2024/11/17 17:38:35

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 service
      StatusBarIconList 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其实指的是PhoneStatusBar
        mStatusBarWindow.setOnTouchListener(new View.OnTouchListener() {//设置触摸事件
            @Override
            public 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。

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

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

相关文章

基于云计算的区域LIS系统系统源码

在医疗机构内部&#xff0c;院内实验室主要负责本院临床科室的检验&#xff0c;院内LIS系统必须满足实验室日常的标本处理入库、仪器联机、检验结果处理、报告打印、报告发布、检验信息统计、检验信息报告发布、标本流程、外部医疗机构检验报告调阅等工作。 在医疗机构间&#…

【JUC系列-04】精通Synchronized底层的实现原理

JUC系列整体栏目 内容链接地址【一】深入理解JMM内存模型的底层实现原理https://zhenghuisheng.blog.csdn.net/article/details/132400429【二】深入理解CAS底层原理和基本使用https://blog.csdn.net/zhenghuishengq/article/details/132478786【三】熟练掌握Atomic原子系列基本…

嘉泰实业举行“互联网金融知识社区”“安全理财风险讲座”等活动

每一次暖心的沟通都是一次公益,真诚不会因为它的渺小而被忽略;每一声问候都是一次公益,善意不会因为它的普通而被埋没。熟悉嘉泰实业的人都知道,这家企业不但擅长在金融理财领域里面呼风唤雨,同时也非常擅长在公益事业当中践行,属于企业的责任心,为更多有困难的群体带来大爱的传…

【数据结构】搜索树MapSet

目录 1.搜索树 1.1概念 1.2查找 1.3插入 1.4删除 2.Map 2.1map说明 2.2TreeMap和HashMap 2.3常用方法 3.Set 3.1set说明 3.2TreeSet和HashSet 3.3常用方法 1.搜索树 1.1概念 二叉搜索树又称二叉排序树&#xff0c;它或者是一棵空树&#xff0c;或者具有以下性质&…

Linux CentOS7命令及命令行

Linux CentOS7中命令及命令行是非常重要的概念。对大多数初学者来说是既熟悉又了解甚少。本文初步讨论这方面的内容&#xff0c;与同行者交流。 一、命令 命令又称为指令&#xff0c;&#xff08;英语命令 command&#xff0c;可用简写cmd表示&#xff09;&#xff0c;在终端…

爬虫逆向实战(30)-某查查股东关联公司(HmacSHA512)

一、数据接口分析 主页地址&#xff1a;某查查 1、抓包 通过抓包可以发现数据接口是api/people/getRelatCompany 2、判断是否有加密参数 请求参数是否加密&#xff1f; 无 请求头是否加密&#xff1f; 通过查看“标头”可以发现&#xff0c;请求头中有一个key和value都是…

怎么把视频转换成mp4格式

怎么把视频转换成mp4格式&#xff1f;如今&#xff0c;随着科技的不断发展&#xff0c;我们在工作中接触到的多媒体视频格式也越来越多。其中&#xff0c;MP4作为一种广泛兼容的视频格式&#xff0c;在许多软件中都能轻松播放&#xff0c;并且成为了剪辑与裁剪视频时大家常用的…

视频画质修复神器,视频修复专家告诉你怎样提高画质

如果您的视频画质模糊、失真或者过于昏暗&#xff0c;那么本文将给大家分享一个非常实用的视频修复技巧。利用一些工具增强视频细节和清晰度的高级技术&#xff0c;向您呈现最详细的视频修复教程。 修复视频画质的话也可以使用去噪滤镜和锐化滤镜。 调整视频分辨率:将视频的分…

【图文并茂】c++介绍之队列

1.1队列的定义 队列&#xff08;queue&#xff09;简称队&#xff0c;它也是一种操作受限的线性表&#xff0c;其限制为仅允许在表的一端进行插入操作&#xff0c;而在表的另一端进行删除操作 一些基础概念&#xff1a; 队尾&#xff08;rear&#xff09; &#xff1a;进行插…

C++-map和set

本期我们来学习map和set 目录 关联式容器 键值对 pair 树形结构的关联式容器 set multiset map multimap 关联式容器 我们已经接触过 STL 中的部分容器&#xff0c;比如&#xff1a; vector 、 list 、 deque 、forward_list(C11)等&#xff0c;这些容器统称为序列式…

为何电商界都重视社交媒体客户服务软件

如今&#xff0c;几乎每个企业都有自己的像Facebook、WhatsApp和telegram等社交媒体渠道&#xff0c;客户可以利用这些渠道找到产品相关内容&#xff0c;发现有关产品或服务的其他信息&#xff0c;并接收有用的优惠和特别优惠。然而&#xff0c;真正成功的电子商务公司仅仅拥有…

element-plus 表格-自定义样式实现

效果如下 代码如下 <template><h2>表格自定义样式</h2><div style"background-color: cadetblue; height: 600px;"><div class"regulaContainer"><el-table ref"tableRef" :data"tableData" border …

Spring 自定义注解 面向切面编程

Spring 自定义注解 JDK元注解规范 Documented -注解是否将包含在JavaDoc中 Retention -什么时候使用该注解(生命周期)RetentionPolicy.SOURCE: 在变异阶段丢弃&#xff0c;这些注解在编译结束之后就不再有任何意义&#xff0c;所以不会写入到字节码中RetentionPolicy.CLASS:…

2023年数学建模国赛A 定日镜场的优化设计思路分析

构建以新能源为主体的新型电力系统&#xff0c;是我国实现“碳达峰”“碳中和”目标的一项重要措施。塔式太阳能光热发电是一种低碳环保的新型清洁能源技术[1]。定日镜是塔式太阳能光热发电站&#xff08;以下简称塔式电站&#xff09;收集太阳能的基本组件&#xff0c;其底座由…

【算法训练-字符串 三】最长公共子串、最长公共子序列

废话不多说&#xff0c;喊一句号子鼓励自己&#xff1a;程序员永不失业&#xff0c;程序员走向架构&#xff01;本篇Blog的主题是【】&#xff0c;使用【】这个基本的数据结构来实现&#xff0c;这个高频题的站点是&#xff1a;CodeTop&#xff0c;筛选条件为&#xff1a;目标公…

Promise异步请求/async-await

问题&#xff1a;调接口时&#xff0c;非以往的函数异步请求去调接口。而是用到了Promise中.then方法 Promise Promise是一种用于处理异步操作的对象。它代表了一个尚未完成但预计会未来完成的操作&#xff0c;并提供了一种结构化的方式来处理操作的结果。它起到代理作用&…

合宙Air724UG LuatOS-Air LVGL API控件-键盘 (Keyboard)

键盘 (Keyboard) LVGL 可以添加触摸键盘&#xff0c;但是很明显&#xff0c;使用触摸键盘的话必须要使用触摸的输入方式&#xff0c;否则无法驱动键盘。 示例代码 function keyCb(obj, e)-- 默认处理事件lvgl.keyboard_def_event_cb(keyBoard, e)if(e lvgl.EVENT_CANCEL)the…

后流量时代的跨境风口:Facebook广告

Facebook拥有超过25亿各个年龄段和人群的每月活跃用户&#xff0c;可以帮助您接触世界各地的相关消费者。无论您是需要吸引新的潜在客户还是吸引回头客访问您的在线商店&#xff0c;Facebook广告都可以为电子商务提供丰厚的投资回报&#xff1b;无论您是在沃尔玛、eBay、亚马逊…

Spring-MVC的crud增删改查--详细讲解

目录 一.前言 二.crud---配置文件 2.1 pom.xml文件 2.2 web.xml文件 2.3 spring-context.xml 2.4 spring-mvc.xml 2.5 spring-MyBatis.xml 2.6 jdbc.properties数据库配置文件 2.7 generatorConfig.xml 2.8 日志文件log4j 三.后台 3.1 pageBean.java 3.2 pageTag 3.…

进军公有云这一年,OceanBase做了什么

*本文转载自微信公众号“机器之心&#xff0c;ID&#xff1a;almosthuman2014” 如今&#xff0c;数据库市场正在迈入新的竞争阶段——一场云上的角逐。 2022 年&#xff0c;中国公有云数据库市场规模首次过半[1]&#xff0c;预计未来占比将进一步扩大。许多中国的数据库厂商也…