Android SystemUI组件(10)禁用/重启锁屏流程分析

embedded/2024/10/10 22:03:30/

该系列文章总纲链接:专题分纲目录 Android SystemUI组件


本章关键点总结 & 说明:

说明:本章节持续迭代之前章节的思维导图,主要关注左侧上方锁屏分析部分 应用入口处理流程解读 即可。

在 Android 系统中,禁用锁屏Keyguard)通常需要 DISABLE_KEYGUARD 权限。但是,这个权限属于签名权限,只能由系统应用或者具有系统签名的应用使用,比如Setting、Launcher等。

对于普通应用来说,通常不允许禁用锁屏,因为这会降低设备的安全性。然而,有些情况下,比如在设置应用或者需要解锁功能的其他预装应用中,可能需要这个权限。在 AndroidManifest.xml 文件中声明权限的示例:

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

但是,即使声明了这个权限,普通应用也无法获得这个权限,系统会忽略它们的请求。对于系统应用或者具有系统权限的应用,使用 KeyguardManager 禁用锁屏和重新启用锁屏的代码如下:

//获取系统服务
KeyguardManager keyguardManager = (KeyguardManager)getSystemService(Context.KEYGUARD_SERVICE);//创建一个KeyguardLock对象,这个对象提供了控制锁屏Keyguard)行为的方法。
//参数"UNIQUE_LOCK_INSTANCE"是一个标识符,用于区分不同的锁屏控制实例
KeyguardManager.KeyguardLock keyguardLock = keyguardManager.newKeyguardLock("UNIQUE_LOCK_INSTANCE");//常用操作:禁用锁屏
keyguardLock.disableKeyguard();//常用操作:重新启用锁屏
keyguardLock.reenableKeyguard();

接下来从KeyguardManager的2个关键API disableKeyguard和reenableKeyguard来继续分析。

1 disableKeyguard和reenableKeyguard方法入手分析

disableKeyguard和reenableKeyguard的代码实现如下:

public class KeyguardManager {private IWindowManager mWM;private ITrustManager mTrustManager;//...public void disableKeyguard() {try {mWM.disableKeyguard(mToken, mTag);} catch (RemoteException ex) {}}public void reenableKeyguard() {try {mWM.reenableKeyguard(mToken);} catch (RemoteException ex) {}}//...
}

IWindowManager是一个Binder接口,它定义了一系列管理窗口的方法。在Android系统中,服务通常是通过Binder IPC(进程间通信)机制进行通信的。IWindowManager接口的实现类是WindowManagerService,它运行在系统的服务端,并管理所有窗口的状态和行为。

KeyguardManagerdisableKeyguard()reenableKeyguard()方法被调用时,它们会通过mWM(即IWindowManager的实例)向WindowManagerService发送请求,以禁用或重新启用锁屏。具体代码实现如下:

public class WindowManagerService extends IWindowManager.Stubimplements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {static final String TAG = "WindowManager";//...@Overridepublic void disableKeyguard(IBinder token, String tag) {//权限检查if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DISABLE_KEYGUARD)!= PackageManager.PERMISSION_GRANTED) {throw new SecurityException("Requires DISABLE_KEYGUARD permission");}if (token == null) {throw new IllegalArgumentException("token == null");}mKeyguardDisableHandler.sendMessage(mKeyguardDisableHandler.obtainMessage(KeyguardDisableHandler.KEYGUARD_DISABLE, new Pair<IBinder, String>(token, tag)));}@Overridepublic void reenableKeyguard(IBinder token) {//权限检查if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DISABLE_KEYGUARD)!= PackageManager.PERMISSION_GRANTED) {throw new SecurityException("Requires DISABLE_KEYGUARD permission");}if (token == null) {throw new IllegalArgumentException("token == null");}mKeyguardDisableHandler.sendMessage(mKeyguardDisableHandler.obtainMessage(KeyguardDisableHandler.KEYGUARD_REENABLE, token));}//...
}

针对这KeyguardDisableHandler的sendMessage的消息处理,代码实现如下:

public class KeyguardDisableHandler extends Handler {private static final String TAG = "KeyguardDisableHandler";//...// Message.what constantsstatic final int KEYGUARD_DISABLE = 1;static final int KEYGUARD_REENABLE = 2;static final int KEYGUARD_POLICY_CHANGED = 3;//...@Overridepublic void handleMessage(Message msg) {if (mKeyguardTokenWatcher == null) {mKeyguardTokenWatcher = new KeyguardTokenWatcher(this);}switch (msg.what) {case KEYGUARD_DISABLE:final Pair<IBinder, String> pair = (Pair<IBinder, String>)msg.obj;mKeyguardTokenWatcher.acquire(pair.first, pair.second);break;case KEYGUARD_REENABLE:mKeyguardTokenWatcher.release((IBinder)msg.obj);break;case KEYGUARD_POLICY_CHANGED:mAllowDisableKeyguard = ALLOW_DISABLE_UNKNOWN;if (mKeyguardTokenWatcher.isAcquired()) {// If we are currently disabled we need to know if the keyguard// should be re-enabled, so determine the allow state immediately.mKeyguardTokenWatcher.updateAllowState();if (mAllowDisableKeyguard != ALLOW_DISABLE_YES) {mPolicy.enableKeyguard(true);}} else {// lazily evaluate this next time we're asked to disable keyguardmPolicy.enableKeyguard(true);}break;}}//...
}

接下来着重分析mKeyguardTokenWatcher.acquire和mKeyguardTokenWatcher.release的实现,代码具体实现如下:

class KeyguardTokenWatcher extends TokenWatcher {public KeyguardTokenWatcher(final Handler handler) {super(handler, TAG);}//更新是否允许禁用锁屏的状态。这是通过查询DevicePolicyManager服务来实现的,//它管理设备策略,包括密码质量要求等。public void updateAllowState() {DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);if (dpm != null) {try {//检查当前用户是否有密码质量要求,如果返回PASSWORD_QUALITY_UNSPECIFIED,//则表示没有密码要求,允许禁用锁屏(ALLOW_DISABLE_YES);//否则,不允许禁用锁屏(ALLOW_DISABLE_NO)。mAllowDisableKeyguard = dpm.getPasswordQuality(null,ActivityManagerNative.getDefault().getCurrentUser().id)== DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED ?ALLOW_DISABLE_YES : ALLOW_DISABLE_NO;} catch (RemoteException re) {// Nothing much we can do}}}//获取到一个令牌时(请求禁用锁屏)@Overridepublic void acquired() {if (mAllowDisableKeyguard == ALLOW_DISABLE_UNKNOWN) {updateAllowState();}if (mAllowDisableKeyguard == ALLOW_DISABLE_YES) {mPolicy.enableKeyguard(false);} else {Log.v(TAG, "Not disabling keyguard since device policy is enforced");}}//释放一个令牌时(请求要启用锁屏)@Overridepublic void released() {mPolicy.enableKeyguard(true);}
}

这里的关键方法为:mPolicy.enableKeyguard,代码具体实现如下:

public class PhoneWindowManager implements WindowManagerPolicy {static final String TAG = "WindowManager";static final boolean DEBUG = false;//.../** {@inheritDoc} */@Overridepublic void enableKeyguard(boolean enabled) {if (mKeyguardDelegate != null) {mKeyguardDelegate.setKeyguardEnabled(enabled);}}//...
}

这里继续分析mKeyguardDelegate.setKeyguardEnabled,代码实现如下:

public class KeyguardServiceDelegate {public static final String KEYGUARD_PACKAGE = "com.android.systemui";public static final String KEYGUARD_CLASS = "com.android.systemui.keyguard.KeyguardService";private static final String TAG = "KeyguardServiceDelegate";private static final boolean DEBUG = true;protected KeyguardServiceWrapper mKeyguardService;//...public void setKeyguardEnabled(boolean enabled) {if (mKeyguardService != null) {mKeyguardService.setKeyguardEnabled(enabled);}mKeyguardState.enabled = enabled;}//...
}

注意:这里的mKeyguardState.enabled状态的目的是为了维护一个本地副本,以便快速检查锁屏Keyguard)是否当前被启用或禁用,而不需要每次都调用KeyguardService来获取这个状态。接下来继续分析mKeyguardService.setKeyguardEnabled,代码实现如下:

public class KeyguardServiceWrapper implements IKeyguardService {private KeyguardStateMonitor mKeyguardStateMonitor;private IKeyguardService mService;private String TAG = "KeyguardServiceWrapper";//...@Override // Binder interfacepublic void setKeyguardEnabled(boolean enabled) {try {mService.setKeyguardEnabled(enabled);} catch (RemoteException e) {Slog.w(TAG , "Remote Exception", e);}}//...
}

继续分析mService.setKeyguardEnabled,代码实现如下:

public class KeyguardService extends Service {static final String TAG = "KeyguardService";static final String PERMISSION = android.Manifest.permission.CONTROL_KEYGUARD;//...private final IKeyguardService.Stub mBinder = new IKeyguardService.Stub() {//...@Override // Binder interfacepublic void setKeyguardEnabled(boolean enabled) {checkPermission();//权限检查mKeyguardViewMediator.setKeyguardEnabled(enabled);}//...}//...
}

KeyguardViewMediator的setKeyguardEnabled,终于到了真正实现功能的部分了。

2 功能实现 KeyguardViewMediator.setKeyguardEnabled方法

接下来继续分析代码,主要关注逻辑功能的实现,代码实现如下:

public class KeyguardViewMediator extends SystemUI {private static final int KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT = 30000;private static final long KEYGUARD_DONE_PENDING_TIMEOUT_MS = 3000;private static final boolean DEBUG = KeyguardConstants.DEBUG;//...public void setKeyguardEnabled(boolean enabled) {synchronized (this) {// 设置锁屏是否被外部启用mExternallyEnabled = enabled;// 如果请求禁用锁屏(enabled为false)且锁屏当前正在显示if (!enabled && mShowing) {// 如果当前有正在进行的解锁操作,则忽略禁用请求if (mExitSecureCallback != null) {return;}// 标记需要在锁屏重新启用时重新显示mNeedToReshowWhenReenabled = true;// 更新输入限制状态updateInputRestrictedLocked();// 隐藏锁屏hideLocked();} else if (enabled && mNeedToReshowWhenReenabled) {// 如果请求启用锁屏且之前有标记需要重新显示mNeedToReshowWhenReenabled = false;// 更新输入限制状态updateInputRestrictedLocked();// 如果当前有解锁操作的回调,则处理解锁结果if (mExitSecureCallback != null) {try {mExitSecureCallback.onKeyguardExitResult(false);} catch (RemoteException e) {Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e);}mExitSecureCallback = null;// 重置状态resetStateLocked();} else {// 显示锁屏showLocked(null);// 标记正在等待锁屏变为可见状态mWaitingUntilKeyguardVisible = true;// 延迟发送消息,以便在超时后继续执行mHandler.sendEmptyMessageDelayed(KEYGUARD_DONE_DRAWING, KEYGUARD_DONE_DRAWING_TIMEOUT_MS);// 等待锁屏变为可见状态while (mWaitingUntilKeyguardVisible) {try {wait();} catch (InterruptedException e) {Thread.currentThread().interrupt();}}}}}}//...
}

这段代码的逻辑确保了锁屏的显示和隐藏能够根据请求和当前状态正确地进行,同时处理了解锁操作的回调和状态同步。整体逻辑可拆解为4个部分:

  1. 锁屏状态更新:首先,方法同步并更新锁屏的外部启用状态。

  2. 处理禁用请求:如果请求是禁用锁屏,并且锁屏当前正在显示,会检查是否有正在进行的解锁操作。如果没有,将标记需要在锁屏重新启用时重新显示,并隐藏锁屏

  3. 处理启用请求:如果请求是启用锁屏,并且之前有标记需要重新显示,会清除该标记,并根据是否有解锁操作的回调来处理。如果有回调,会处理解锁结果并重置状态。如果没有回调,会显示锁屏,并等待锁屏变为可见状态。

  4. 等待锁屏可见:在启用锁屏后,通过发送延迟消息和等待机制确保锁屏已经绘制完成,避免在锁屏未完全显示时用户操作导致的问题。

接下来分析两个重要的锁屏功能API:显示锁屏showLocked 和 隐藏锁屏hideLocked。这部分代码向下的分析参考了文章Android SystemUI组件(07)锁屏KeyguardViewMediator分析 的后半部分。代码实现如下:

public class KeyguardViewMediator extends SystemUI {private static final int KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT = 30000;private static final long KEYGUARD_DONE_PENDING_TIMEOUT_MS = 3000;private static final boolean DEBUG = KeyguardConstants.DEBUG;//...handler 发送消息 SHOW/HIDEprivate void showLocked(Bundle options) {if (DEBUG) Log.d(TAG, "showLocked");// ensure we stay awake until we are finished displaying the keyguardmShowKeyguardWakeLock.acquire();Message msg = mHandler.obtainMessage(SHOW, options);mHandler.sendMessage(msg);}private void hideLocked() {if (DEBUG) Log.d(TAG, "hideLocked");Message msg = mHandler.obtainMessage(HIDE);mHandler.sendMessage(msg);}//...handler处理private Handler mHandler = new Handler(Looper.myLooper(), null, true /*async*/) {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case SHOW:handleShow((Bundle) msg.obj);break;case HIDE:handleHide();break;//...}}};//handler关键处理方法handleShowprivate void handleShow(Bundle options) {synchronized (KeyguardViewMediator.this) {// 如果系统还没有准备好,忽略显示锁屏的请求if (!mSystemReady) {return;}// 标记锁屏为显示状态setShowingLocked(true);// 调用状态栏锁屏视图管理器显示锁屏mStatusBarKeyguardViewManager.show(options);// 标记锁屏不是在隐藏状态mHiding = false;// 重置锁屏完成挂起的状态resetKeyguardDonePendingLocked();// 标记没有运行隐藏动画mHideAnimationRun = false;// 更新活动锁屏状态updateActivityLockScreenState();// 调整状态栏adjustStatusBarLocked();// 用户活动事件userActivity();// 在最后执行,以免延迟锁屏显示playSounds(true);// 释放锁屏显示时持有的WakeLockmShowKeyguardWakeLock.release();}// 显示锁屏管理器mKeyguardDisplayManager.show();}private final Runnable mKeyguardGoingAwayRunnable = new Runnable() {@Overridepublic void run() {try {// 通知窗口管理器锁屏正在消失mWM.keyguardGoingAway(mStatusBarKeyguardViewManager.shouldDisableWindowAnimationsForUnlock(),mStatusBarKeyguardViewManager.isGoingToNotificationShade());} catch (RemoteException e) {Log.e(TAG, "Error while calling WindowManager", e);}}};//handler关键处理方法handleHideprivate void handleHide() {synchronized (KeyguardViewMediator.this) {mHiding = true; // 标记锁屏正在隐藏// 如果锁屏当前正在显示并且没有被遮挡if (mShowing && !mOccluded) {// 如果还没有运行隐藏动画if (!mHideAnimationRun) {// 开始执行预隐藏动画,并在动画结束后执行mKeyguardGoingAwayRunnablemStatusBarKeyguardViewManager.startPreHideAnimation(mKeyguardGoingAwayRunnable);} else {// 如果已经在运行隐藏动画,则直接执行mKeyguardGoingAwayRunnablemKeyguardGoingAwayRunnable.run();}} else {// 如果锁屏没有显示,或者被遮挡,不依赖于WindowManager来启动退出动画// 直接处理开始退出动画handleStartKeyguardExitAnimation(SystemClock.uptimeMillis() + mHideAnimation.getStartOffset(),mHideAnimation.getDuration());}}}//...
}

接下来总结下2个关键流程内容:

handleShow方法,它负责处理显示锁屏的逻辑。具体如下:

  1. 设置锁屏状态:如果系统已准备好,标记锁屏为显示状态,并调用mStatusBarKeyguardViewManager.show(options)来显示锁屏
  2. 重置状态:重置锁屏完成挂起的状态,标记没有运行隐藏动画,更新活动锁屏状态,调整状态栏,记录用户活动事件。
  3. 播放声音:在锁屏显示后播放声音,这通常包括锁屏出现的声音。
  4. 释放WakeLock:释放在显示锁屏时持有的mShowKeyguardWakeLock,以允许设备在锁屏显示后进入休眠状态。
  5. 显示锁屏管理器:调用mKeyguardDisplayManager.show()来显示锁屏管理器。

handleShow方法的目的是确保在需要时显示锁屏界面,更新系统状态以反映锁屏的显示,并通过播放声音或振动向用户提供反馈,同时保持设备的安全性和良好的用户体验。

handleHide方法,它负责处理隐藏锁屏的逻辑。具体如下:

  1. 设置隐藏标志:将mHiding标志设置为true,表示锁屏正在被隐藏。
  2. 检查锁屏状态:检查锁屏是否正在显示并且没有被遮挡。
  3. 处理没有显示的锁屏:如果锁屏没有显示或者被遮挡,不依赖于WindowManager来启动退出动画,而是直接调用handleStartKeyguardExitAnimation方法来处理开始退出动画。

handleHide方法的目的是确保在锁屏隐藏时能够正确地执行动画和相关的清理工作。它根据锁屏的当前状态来决定是否启动动画,以及是否直接处理退出动画。这种方法确保了锁屏隐藏过程的平滑和一致性,同时避免了不必要的依赖和潜在的冲突。

接下来我们专注分析show的逻辑实现,这里调用了mStatusBarKeyguardViewManager的show方法,代码具体实现如下:

public class StatusBarKeyguardViewManager {//step1 显示锁屏public void show(Bundle options) {mShowing = true; // 标记锁屏为显示状态mStatusBarWindowManager.setKeyguardShowing(true); // 通知状态栏窗口管理器锁屏正在显示reset(); // 调用reset方法来重置锁屏状态}//step2 重置锁屏状态public void reset() {if (mShowing) {if (mOccluded) {mPhoneStatusBar.hideKeyguard(); // 如果锁屏被遮挡,隐藏锁屏mBouncer.hide(false /* destroyView */); // 隐藏解锁界面(Bouncer)} else {showBouncerOrKeyguard(); // 显示解锁界面或锁屏}updateStates(); // 更新锁屏状态}}//step3 显示解锁界面或锁屏private void showBouncerOrKeyguard() {if (mBouncer.needsFullscreenBouncer()) {mPhoneStatusBar.hideKeyguard(); // 需要全屏解锁界面时,隐藏锁屏mBouncer.show(true); // 显示解锁界面(Bouncer)} else {mPhoneStatusBar.showKeyguard(); // 不需要全屏解锁界面时,显示锁屏mBouncer.hide(false); // 隐藏解锁界面(Bouncer)mBouncer.prepare(); // 准备解锁界面(Bouncer)}}
}

接下来主要解读mBouncer.show和hide的实现及相关流程。主要以show方法(加载视图)为主。接下来继续分析KeyguardBouncer的show方法和hide方法流程,代码具体实现如下:

public class KeyguardBouncer {private Context mContext;//...private final Runnable mShowRunnable = new Runnable() {@Overridepublic void run() {// 设置锁屏视图的可见性为可见mRoot.setVisibility(View.VISIBLE);// 恢复锁屏视图的活动状态mKeyguardView.onResume();// 开始锁屏视图的显示动画mKeyguardView.startAppearAnimation();// 清除锁屏即将显示的标志mShowingSoon = false;}};//...//加载及锁屏界面关键流程//step1 显示锁屏界面public void show() {// 确保锁屏视图已经创建,锁屏View的加载ensureView();// 如果锁屏视图已经是可见的或者即将显示,则不需要再次显示if (mRoot.getVisibility() == View.VISIBLE || mShowingSoon) {// 更新当前的安全方法,这在锁屏已经显示但当前安全方法发生变化时是必要的mKeyguardView.show();return;}// 尝试dismiss锁屏。如果没有设置安全模式,这将dismiss整个锁屏。// 如果需要认证,则显示解锁界面(Bouncer)。if (!mKeyguardView.dismiss()) {// 设置标志,表示锁屏即将显示mShowingSoon = true;// 在多个帧上分散工作mChoreographer.postCallbackDelayed(Choreographer.CALLBACK_ANIMATION, mShowRunnable,null, 48);}}//step2 确保锁屏视图已经创建private void ensureView() {//检查mRoot(锁屏界面的根视图)是否已经存在。if (mRoot == null) {//加载锁屏界面inflateView();}}//step3 加载锁屏界面private void inflateView() {// 如果之前已经添加过锁屏视图,先将其移除removeView();// 通过LayoutInflater从keyguard_bouncer布局文件中加载锁屏界面布局mRoot = (ViewGroup) LayoutInflater.from(mContext).inflate(R.layout.keyguard_bouncer, null);// 从加载的布局中获取KeyguardViewBase实例mKeyguardView = (KeyguardViewBase) mRoot.findViewById(R.id.keyguard_host_view);// 为锁屏视图设置锁图案工具,用于处理锁屏图案相关逻辑mKeyguardView.setLockPatternUtils(mLockPatternUtils);// 为锁屏视图设置ViewMediatorCallback,用于处理锁屏界面的回调事件mKeyguardView.setViewMediatorCallback(mCallback);// 将锁屏视图添加到容器视图中,确保它在容器的最后面mContainer.addView(mRoot, mContainer.getChildCount());// 初始时将锁屏视图的可见性设置为不可见mRoot.setVisibility(View.INVISIBLE);// 设置系统UI可见性,禁用HOME按钮,这样用户在锁屏界面上不会看到HOME按钮mRoot.setSystemUiVisibility(View.STATUS_BAR_DISABLE_HOME);}//...//隐藏锁屏界面关键流程//step1 隐藏锁屏界面public void hide(boolean destroyView) {// 取消任何即将执行的显示锁屏的操作cancelShowRunnable();// 如果锁屏视图不为空,则进行清理if (mKeyguardView != null) {// 移除锁屏视图上的解散动作,即用户不再能通过这个视图解散锁屏mKeyguardView.setOnDismissAction(null);// 清理锁屏视图,这可能包括重置状态、停止动画等mKeyguardView.cleanUp();}// 如果传入的参数destroyView为true,则完全移除锁屏视图if (destroyView) {removeView();} else if (mRoot != null) {// 如果不销毁视图,只是将其设置为不可见mRoot.setVisibility(View.INVISIBLE);}}//step2 锁屏界面不显示,取消线程private void cancelShowRunnable() {// 从Choreographer中移除之前安排的动画帧更新回调mChoreographer.removeCallbacks(Choreographer.CALLBACK_ANIMATION, mShowRunnable, null);// 将mShowingSoon标志设置为false,表示锁屏界面不再即将显示mShowingSoon = false;}
}

其中,StatusBarKeyguardViewManager类中的show方法负责实际显示锁屏界面。它首先将锁屏的显示状态设置为true,然后调用reset方法来重置锁屏状态。reset方法会根据锁屏是否被遮挡来决定是显示解锁界面(Bouncer)还是锁屏界面。

KeyguardBouncer类中的show方法用于显示解锁界面(Bouncer)。如果需要全屏解锁界面,它会隐藏锁屏并显示解锁界面。否则,它会显示锁屏并隐藏解锁界面,并准备解锁界面以供用户输入。

KeyguardBouncer类中的hide方法用于隐藏解锁界面。它会取消任何即将执行的显示操作,并根据传入的参数决定是销毁视图还是仅仅将其设置为不可见。

这些方法共同工作,确保了锁屏界面能够在适当的时机显示或隐藏,同时提供了用户反馈和设备安全性。


http://www.ppmy.cn/embedded/125241.html

相关文章

【MySQL 06】表的增删查改

目录 1.insert 增添数据 1.1单行数据 全列插入 1.2多行数据 指定列插入 1.3插入否则更新 1.4.插入否则替换 2.select查找 2.1 全列查找 2.2指定列查找 2.3查询字段为表达式 2.4为查询结果指定别名 2.5 结果去重 2.6 where条件查询 2.7结果排序 2.8.筛选分页结果…

vue-element-admin后台集成方案

文章目录 vue-element-admin后台集成方案介绍使用安装目录介绍 vue-element-admin后台集成方案 介绍 官方网站 https://panjiachen.github.io/vue-element-admin-site/zh/guide/#%E5%8A%9F%E8%83%BD使用 安装 这里有三个模板&#xff0c;我们一般选择基础模板进行开发就好…

RFID学习

24.10.5学习目录 一.简介1.组成2.RFID协议3.RFID卡 一.简介 RFID被称为无线射频识别&#xff0c;其是一种通信技术&#xff0c;通过无线电讯号耦合识别特定目标并读写相关数据&#xff1b; RFID主要位于典型物联网架构中的感知层&#xff0c;其因为具有非接触式特性&#xff…

Win32 API 控制台鼠标操作、坐标获取与相关函数介绍

Win32 API 控制台鼠标操作、坐标获取与相关函数介绍 一、前置介绍读取控制台输入缓冲区数据 ReadConsoleInput 函数控制台输入缓冲区中的输入事件 INPUT_RECORD 结构鼠标输入事件 MOUSE_EVENT_RECORD 结构更改输入模式 SetConsoleMode 函数 二、鼠标坐标获取(以下代码环境为 VS…

使用正则表达式删除文本的奇数行或者偶数行

用智谱清言和kimi搜出来的结果都没法在notepad生效&#xff0c;后面在overflow上找到的答案比较靠谱。 查找&#xff1a;^[^\n]*\n([^\n]*) 替换&#xff1a;\1 删除偶数行 查找&#xff1a;^([^\n]*)\n[^\n]* 替换&#xff1a;\1 代码解释 ^&#xff1a;这个符号代表字符…

一文彻底学会单例模式

一、为什么使用单例模式&#xff1f; 单例模式是一种创建型设计模式&#xff0c;目的是确保一个类在应用程序中只有一个实例&#xff0c;并提供全局访问点。以下是使用单例模式的几个好处 资源节省&#xff1a;对于需要频繁创建和销毁的对象&#xff0c;比如数据库连接或日志…

鲁大师2024年手机Q3季报:顶级旗舰激烈角逐,骁龙、天玑新芯片发布前最后一战

距离高通、联发科掏出自家骁龙8 Gen4和天玑9400的时间愈发接近&#xff0c;众多手机厂商正在为自家新旗舰做最后冲刺。因此&#xff0c;2024年Q3季度的手机市场&#xff0c;也迎来了属于搭载骁龙8 Gen3和天玑9300系列机型的最后一回合战斗。 回顾整个Q3&#xff0c;安卓手机阵营…

flash-attention代码逻辑

setup.py&#xff1a;python项目中&#xff0c;setup.py用于管理项目的构建、打包和分发过程。这个文件通常包含项目的元数据以及如何构建和安装模块的指令 三个相关命令 构建扩展模块&#xff1a;python setup.py build_ext清理构建文件&#xff1a;python setup.py clean安装…