Android 10.0 动态壁纸 LiveWallpaper
前言
在 Android 中,壁纸分为动态与静态两种,但其实两者得本质都是一样。都以一个 Service 得形式在后台运行,在一个类型为 TYPE_WALLPAPER 的窗口上绘制内容。也可以这么去理解:静态壁纸是一种特殊的动态壁纸,它仅在窗口上渲染了一张图片,而不会对用户的操作做出反应。动态壁纸不能只应用于锁屏。
壁纸实现时涉及的几个主要的类:
- WallpaperService 及其内部类 Engine: 壁纸在 WallpaperService 这个服务中运⾏,当需要实现⾃⼰的壁纸时,继承和实现这个类,是⾸先需要做的。Engine是WallpaperService中的⼀个内部类,实现了壁纸服务窗⼝的创建以及 Surface 的维护,同时 Engine 内部类还提供了 onVisibilityChanged(),onCommand() 等回调⽅法,⽤于可见状态变化和⽤户触摸事件等。Engine 类因此也是壁纸实现的核⼼类,实现和重写其接⼝在开发中也相当重要。
- WallpaperManagerService 和 WallpaperManager: WallpaperManagerService ⽤于管理壁纸的运⾏与切换,并通过 WallpaperManager 对外界提供操作壁纸的接⼝。
- WindowMangerService: 该类⽤于计算壁纸窗⼝的 Z 序,可见性以及为壁纸窗⼝应⽤动画。
壁纸服务的两种启动场景:
1、重启壁纸服务启动流程:
SystemService 进程启动时,会启动各种系统服务。在该类的 startOtherServices() ⽅法中会⾸先拉起 WallpaperManagerService,通过该类,WallpaperService 后⾯才得以启动。
frameworks/base/services/core/java/com/android/server/SystemService.java
if (context.getResources().getBoolean(R.bool.config_enableWallpaperService)) {t.traceBegin("StartWallpaperManagerService");mSystemServiceManager.startService(WALLPAPER_SERVICE_CLASS);t.traceEnd();} else {Slog.i(TAG, "Wallpaper service disabled by config");}
WallpaperManagerService 启动之后回调用到 systemReady() ⽅法。
frameworks/base/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
public static class Lifecycle extends SystemService {private IWallpaperManagerService mService;public Lifecycle(Context context) {super(context);}@Overridepublic void onStart() {// 省略部分代码......}@Overridepublic void onBootPhase(int phase) {if (mService != null) {mService.onBootPhase(phase);}}@Overridepublic void onUnlockUser(int userHandle) {// 省略部分代码......}}@Overridepublic void onBootPhase(int phase) {if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {systemReady();} else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {switchUser(UserHandle.USER_SYSTEM, null);}}void systemReady() {if (DEBUG) Slog.v(TAG, "systemReady");initialize();// 省略部分代码......try {ActivityManager.getService().registerUserSwitchObserver(new UserSwitchObserver() {@Overridepublic void onUserSwitching(int newUserId, IRemoteCallback reply) {switchUser(newUserId, reply);}}, TAG);} catch (RemoteException e) {e.rethrowAsRuntimeException();}}
会通过 initialize() 方法调用 loadSettingsLocked() ⽅法加载⽤户设置过的壁纸信息,然后监听⽤户是否切换⽤户 switchUser(),当切换⽤户时,switchWallpaper() 会调⽤ bindWallpaperComponentLocked() ⽅法拉起对应的壁纸服务。
2、⼿动切换时壁纸服务的启动流程
⼿动切换壁纸(这里说的是动态壁纸)时需要通过WallpaperManager.getInstance(activity).setWallpaperComponent() ⽅法完成,我们在这个接⼝中传⼊壁纸服务对应的 ComponentName,getIWallpaperManager 返回的是 WallpaperManagerService 的 Bp(binder proxy binder代理) 端,在 WallpaperManagerService 端,我们可以查看到 setWallpaperComponent 的具体实现:
注意:WallpaperManager.getIWallpaperManager() 并没有作为 SDK 的一部分提供给开发者。因此第三方应用无法进行动态壁纸的设置。
frameworks/base/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
private void setWallpaperComponent(ComponentName name, int userId) {userId = ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId,false /* all */, true /* full */, "changing live wallpaper", null /* pkg */);/* ⾸先调⽤该⽅法的时候回去校验权限,该权限定义在frameworks/base/core/res/AndroidManifest.xml,<permission android:name="android.permission.SET_WALLPAPER_COMPONENT"android:protectionLevel="signature|privileged" />查看protectionLevel,只有是特权应⽤或者系统签名的应⽤才能获取到这个系统权限,所以普通的应⽤是没有办法进⾏壁纸设置的*/// 设置动态壁纸需要调用者拥有一个签名级的系统权限checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);int which = FLAG_SYSTEM;boolean shouldNotifyColors = false;WallpaperData wallpaper;synchronized (mLock) {Slog.v(TAG, "setWallpaperComponent name=" + name);/*此处会先通过当前的⽤户ID获取到与该⽤户相关的壁纸信息,WallpaperManagerService⽀持多⽤户机制,⽤户的信息在mWallpaperMap中存储,每⼀个⽤户对应⼀个WallpaperData,WallpaperData存储壁纸相关信息,并在随后更新其内容使之保存新壁纸的信息。*/wallpaper = mWallpaperMap.get(userId);if (wallpaper == null) {throw new IllegalStateException("Wallpaper not yet initialized for user " + userId);}// 省略部分代码......// New live wallpaper is also a lock wallpaper if nothing is setif (mLockWallpaperMap.get(userId) == null) {which |= FLAG_LOCK;}try {wallpaper.imageWallpaperPending = false;boolean same = changingToSame(name, wallpaper);// 在这⾥真正会去拉起对应的 WallPaperServiceif (bindWallpaperComponentLocked(name, false, true, wallpaper, null)) {// 省略部分代码......}} finally {Binder.restoreCallingIdentity(ident);}}// 省略部分代码......}
上述两种场景都是通过 bindWallpaperComponentLocked() 方法来拉起相关服务的。下面看看 bindWallpaperComponentLocked() 方法:
frameworks/base/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
private boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force,boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) {// 省略部分代码......try {// 当componentName为null时表示使用默认壁纸。// 这里会将componentName参数改为默认壁纸的componentNameif (componentName == null) {// mDefaultWallpaperComponent是从com.android.internal.R.string.default_wallpaper_component// 中获取默认壁纸的componentName。这个值的设置位于res/values/config.xml中,// 开发者可以通过修改这个值设置默认壁纸componentName = mDefaultWallpaperComponent;// 倘若在上述的资源文件中没有指定一个默认壁纸,即default_wallpaper_component的// 值被设置为@null),则使用ImageWallpaper代替默认壁纸。ImageWallpaper就是前文所述的静态壁纸,// 路径在:R.string.image_wallpaper_componentif (componentName == null) {componentName = mImageWallpaper;}}// 接下来WallpaperMangerService会尝试从PackageManager中尝试获取ComponentName所// 指定的Service的描述信息,获取此信息的目的在于确认该Service是一个符合要求的壁纸服务int serviceUserId = wallpaper.userId;ServiceInfo si = mIPackageManager.getServiceInfo(componentName,PackageManager.GET_META_DATA | PackageManager.GET_PERMISSIONS, serviceUserId);if (si == null) {return false;}// 第一个检查,要求这个Service必须声明其访问权限为 BIND_WALLPAPER。// 这个签名级的系统权限这是为了防止壁纸服务被第三方应用程序启动而产生混乱if (!android.Manifest.permission.BIND_WALLPAPER.equals(si.permission)) {String msg = "Selected service does not have "+ android.Manifest.permission.BIND_WALLPAPER+ ": " + componentName;if (fromUser) {throw new SecurityException(msg);}Slog.w(TAG, msg);return false;}WallpaperInfo wi = null;// 第二个检查,要求这个Service必须可以用来处理android.service.wallpaper.WallpaperService这个Action// 其检查方式是从PackageManager中查询所有可以处理 android.service.wallpaper.WallpaperService的服务,// 然后检查即将启动的服务是否在PackageManager的查询结果之中Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);if (componentName != null && !componentName.equals(mImageWallpaper)) {// Make sure the selected service is actually a wallpaper service.// 获取所有可以处理android.service.wallpaper.WallpaperService的服务信息List<ResolveInfo> ris =mIPackageManager.queryIntentServices(intent,intent.resolveTypeIfNeeded(mContext.getContentResolver()),PackageManager.GET_META_DATA, serviceUserId).getList();// 第二个检查,这个检查来校验服务是否声明了android.service.wallpaper.WallpaperService这个action。// 如果这个服务没有声明这个action的话那么,ris中就不会含有这个component信息。for (int i=0; i<ris.size(); i++) {ServiceInfo rsi = ris.get(i).serviceInfo;if (rsi.name.equals(si.name) &&rsi.packageName.equals(si.packageName)) {try {// 第三个检查,获取名为android.service.wallpaper中的meta-data信息,// 该meta-data信息中提供了缩略图,开发者,简单的描述等。会将这些信息转换成 WallpaperInfo// 如果即将启动的服务位于查询结果之中,便可以确定这是一个壁纸服务。wi = new WallpaperInfo(mContext, ris.get(i));} catch (XmlPullParserException e) {if (fromUser) {throw new IllegalArgumentException(e);}Slog.w(TAG, e);return false;} catch (IOException e) {if (fromUser) {throw new IllegalArgumentException(e);}Slog.w(TAG, e);return false;}break;}}if (wi == null) {// wi为null表示即将启动的服务没有位于查询结果之中,或者没有提供必须的meta-data。// 此时返回false表示绑定失败String msg = "Selected service is not a wallpaper: "+ componentName;if (fromUser) {throw new SecurityException(msg);}Slog.w(TAG, msg);return false;}}// 当壁纸服务⽀持在ambient模式下进⾏绘制的时候,需要检查是否有 AMBIENT_WALLPAPER 权限。if (wi != null && wi.supportsAmbientMode()) {final int hasPrivilege = mIPackageManager.checkPermission(android.Manifest.permission.AMBIENT_WALLPAPER, wi.getPackageName(),serviceUserId);// 省略部分代码......}// Bind the service!if (DEBUG) Slog.v(TAG, "Binding to:" + componentName);final int componentUid = mIPackageManager.getPackageUid(componentName.getPackageName(),MATCH_DIRECT_BOOT_AUTO, wallpaper.userId);// 1、这里创建一个WallpaperConnection,它不仅实现 ServiceConnetion 接口用于监听 WallpaperService 之间的连接状态,// 同时还实现了 IWallpaperService.Stub,也就说它支持跨进程通信。// 在服务绑定成功后的 WallpaperConnection.ServiceConnetion()方法调用中,WallpaperConnection 的实例回发送给 WallpaperService,// 使其作为 WallpaperService 向 WallpaperManagerService 通信的桥梁。WallpaperConnection newConn = new WallpaperConnection(wi, wallpaper, componentUid);// 为启动壁纸服务准备Intentintent.setComponent(componentName);intent.putExtra(Intent.EXTRA_CLIENT_LABEL,com.android.internal.R.string.wallpaper_binding_label);intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivityAsUser(mContext, 0,Intent.createChooser(new Intent(Intent.ACTION_SET_WALLPAPER),mContext.getText(com.android.internal.R.string.chooser_wallpaper)),PendingIntent.FLAG_IMMUTABLE, null, new UserHandle(serviceUserId)));// 2、这⾥启动指定的壁纸服务,服务启动后,壁纸还没有办法进⾏显⽰,// 还需要WallpaperConnection.onServiceConnected中进⾏相应的处理if (!mContext.bindServiceAsUser(intent, newConn,Context.BIND_AUTO_CREATE | Context.BIND_SHOWING_UI| Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE| Context.BIND_INCLUDE_CAPABILITIES,new UserHandle(serviceUserId))) {String msg = "Unable to bind service: "+ componentName;if (fromUser) {throw new IllegalArgumentException(msg);}Slog.w(TAG, msg);return false;}// 3、新的的壁纸服务启动成功后,便通过detachWallpaperLocked()销毁旧有的壁纸服务if (wallpaper.userId == mCurrentUserId && mLastWallpaper != null&& !wallpaper.equals(mFallbackWallpaper)) {detachWallpaperLocked(mLastWallpaper);}// 4、将新的壁纸服务的运行信息保存到WallpaperData中wallpaper.wallpaperComponent = componentName;wallpaper.connection = newConn;newConn.mReply = reply;if (wallpaper.userId == mCurrentUserId && !wallpaper.equals(mFallbackWallpaper)) {mLastWallpaper = wallpaper;}updateFallbackConnection();} catch (RemoteException e) {String msg = "Remote exception for " + componentName + "\n" + e;if (fromUser) {throw new IllegalArgumentException(msg);} Slog.w(TAG, msg);return false;}return true;}
上述代码主要做了两件事:
1、检查拉起服务的条件
- 检查这个 Service 必须声明其访问权限为 BIND_WALLPAPER。为了防止壁纸服务被第三方应用程序启动而产生混乱。
- 检查服务是否声明了 android.service.wallpaper.WallpaperService 这个 action。如果这个服务没有声明这个 action 的话那么, ris 中就不会含有这个 component 信息。
- 检查这个 Service 在 meta-data 中有没有提供壁纸描述信息,该 meta-data 信息中提供了缩略图,开发者,简单的描述等。会将这些信息转换成 WallpaperInfo。
2、拉起壁纸服务
- 创建了 WallpaperConnection 对象,由于实现了 ServiceConnection 接⼝,所以WallpaperConnection 可以⽤来监听和壁纸服务的连接状态,另外由于继承了IWallpoaperConnection.Stub 接⼝,所以 WallpaperConnection 具有了跨进程通信的能⼒。
- 启动壁纸服务:这⾥仅仅是拉起服务,和拉起普通服务的⽅式基本⼀致,拉起⽅式上则使⽤了 bindServiceAsUser,查看官⽅注解,该接口增加了校验该⽤户是否能拉起该服务,其余的⾏为和 bindService 相同。
- 保存当前 WallpaperConnection 实例,ConponentName,到 WallpaperData 中。
bindWallpaperComponentLocked() 函数将壁纸服务拉了起来,但是仅仅将壁纸服务拉起来是没有办法显⽰图像的,因为启动的服务并没有窗口令牌,这样就没办法添加窗⼝。剩下的这部分显⽰的⼯作在 WallpaperConnection#onServiceConnected() ⽅法中进⾏,在该回调中同样也能拿到壁纸服务端提供的 Binder 对象。
WallpaperService 在被 bind(绑定 )的时候返回了一个 IWallpaperServiceWrapper 对象,从代码中可以看到,该对象中保存了 WallpaperService 实例:
WallpaperConnection#onServiceConnected()
frameworks/base/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {synchronized (mLock) {if (mWallpaper.connection == this) {// 客户端 拿 WallpaperService 的 BindermService = IWallpaperService.Stub.asInterface(service);// 绑定壁纸服务。attachServiceLocked()会调用connectLocked()方法.attachServiceLocked(this, mWallpaper);if (!mWallpaper.equals(mFallbackWallpaper)) {// 保存当前壁纸的运行状态到文件系统中,以便在系统重启或发生用户切换时可以恢复saveSettingsLocked(mWallpaper.userId);}FgThread.getHandler().removeCallbacks(mResetRunnable);mContext.getMainThreadHandler().removeCallbacks(mTryToRebindRunnable);if (mPerformance != null) {mPerformance.notifyWallpaperChanged(name.getPackageName());}}}}
在 attachServiceLocked() 方法中会调用 connectLocked() 方法,connectLocked() 接口中调用了 IWallpaperServiceWrapper#attach() 方法传递了壁纸服务所需要的信息。
WallpaperConnection#connectLocked()
frameworks/base/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
void connectLocked(WallpaperConnection connection, WallpaperData wallpaper) {if (connection.mService == null) {Slog.w(TAG, "WallpaperService is not connected yet");return;}if (DEBUG) Slog.v(TAG, "Adding window token: " + mToken);// 向 WMS 申请注册一个WALLPAPER类型的窗口令牌,// 且之后被传递给 WallpaperService 用于作为后者添加窗口的通行证。mWindowManagerInternal.addWindowToken(mToken, TYPE_WALLPAPER, mDisplayId);final DisplayData wpdData = getDisplayDataOrCreate(mDisplayId);try {// 调用 IWallpaperService.attach()方法将壁纸服务创建窗口所需的信息传递过去。connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false,wpdData.mWidth, wpdData.mHeight,wpdData.mPadding, mDisplayId);} catch (RemoteException e) {// 未能在显示屏上添加壁纸Slog.w(TAG, "Failed attaching wallpaper on display", e);if (wallpaper != null && !wallpaper.wallpaperUpdating&& connection.getConnectedEngineSize() == 0) {bindWallpaperComponentLocked(null /* componentName */, false /* force */,false /* fromUser */, wallpaper, null /* reply */);}}}
attach() 方法回传了很多信息,其中 connection 为 WallpaperConnection 的实例。WallpaperConnection 之所以具有跨进程通信的能力是因为继承了IWallpaperConnection.Stub 类。
该 Stub 对象中有一个重要的方法 attachEngine() 方法,因为 Engine 实现才是动态壁纸的核心,WallpaperService 会将创建好的 Engine 引用通过 attachEngine() 回传给 WallpaperManagerService 进行管理。
IWallpaperServiceWrapper 继承了 IWallpaperService.Stub,并实现了该接口的两个方法 attach() 和 detach()。
IWallpaperServiceWrapper
frameworks/base/core/java/android/service/wallpaper/WallpaperService.java
class IWallpaperServiceWrapper extends IWallpaperService.Stub {private final WallpaperService mTarget;private IWallpaperEngineWrapper mEngineWrapper;public IWallpaperServiceWrapper(WallpaperService context) {mTarget = context;}@Overridepublic void attach(IWallpaperConnection conn, IBinder windowToken,int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding,int displayId) {mEngineWrapper = new IWallpaperEngineWrapper(mTarget, conn, windowToken,windowType, isPreview, reqWidth, reqHeight, padding, displayId);}@Overridepublic void detach() {mEngineWrapper.detach();}}
在 attach() 方法中创建了一个 IWallpaperEngineWrapper 对象 mEngineWrapper 。
IWallpaperEngineWrapper
frameworks/base/core/java/android/service/wallpaper/WallpaperService.java
class IWallpaperEngineWrapper extends IWallpaperEngine.Stubimplements HandlerCaller.Callback {private final HandlerCaller mCaller;final IWallpaperConnection mConnection;final IBinder mWindowToken;final int mWindowType;final boolean mIsPreview;boolean mShownReported;int mReqWidth;int mReqHeight;final Rect mDisplayPadding = new Rect();final int mDisplayId;final DisplayManager mDisplayManager;final Display mDisplay;private final AtomicBoolean mDetached = new AtomicBoolean();Engine mEngine;IWallpaperEngineWrapper(WallpaperService context,IWallpaperConnection conn, IBinder windowToken,int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding,int displayId) {mCaller = new HandlerCaller(context, context.getMainLooper(), this, true);// 省略部分代码......// 该消息用于壁纸服务引擎的创建Message msg = mCaller.obtainMessage(DO_ATTACH);mCaller.sendMessage(msg);}// 省略部分代码......@Overridepublic void executeMessage(Message message) {if (mDetached.get()) {if (mActiveEngines.contains(mEngine)) {doDetachEngine();}return;}switch (message.what) {case DO_ATTACH: {try {// 将IWallpaperEngineWapper对象传递给WallpaperConnection进行保存,// 通过这个引用,WallpaperManagerService也可以通过它与engine进行通信mConnection.attachEngine(this, mDisplayId);} catch (RemoteException e) {Log.w(TAG, "Wallpaper host disappeared", e);return;}// 创建一个引擎,该方法为抽象方法,需要子类根据自身实现具体的引擎Engine engine = onCreateEngine();mEngine = engine;mActiveEngines.add(engine);// 该方法中会完成窗口的创建、surface创建等工作。engine.attach(this);return;}// 省略部分代码......}}}
由于 mConnection.attachEngine() 方法将 IWallpaperEngineWrapper 传递给了WallpaperManagerService,因此 WallpaperManagerService 可以转发相关的请求和设置到 Engine 对象中,实现 WallpaperManagerService 到壁纸的通信。onCreateEngine() 方法执行后,引擎创建完成,之后通过 engine.attach()方法进行引擎相关的初始化。
frameworks/base/core/java/android/service/wallpaper/WallpaperService.java
public class Engine {// 省略部分代码......void attach(IWallpaperEngineWrapper wrapper) {if (DEBUG) Log.v(TAG, "attach: " + this + " wrapper=" + wrapper);if (mDestroyed) {return;}mIWallpaperEngine = wrapper;mCaller = wrapper.mCaller;mConnection = wrapper.mConnection;mWindowToken = wrapper.mWindowToken;mSurfaceHolder.setSizeFromLayout();mInitializing = true;// 这个session用于和WMS进行通信mSession = WindowManagerGlobal.getWindowSession();// mWindow是一个IWindow对象,用于接收从WMS发送过来的消息mWindow.setSession(mSession);mLayout.packageName = getPackageName();mIWallpaperEngine.mDisplayManager.registerDisplayListener(mDisplayListener,mCaller.getHandler());mDisplay = mIWallpaperEngine.mDisplay;mDisplayContext = createDisplayContext(mDisplay);mDisplayState = mDisplay.getState();if (DEBUG) Log.v(TAG, "onCreate(): " + this);// 子类可以重写该接口,在该接口中可以修改mSurfaceHolder相关的属性,这个时候// 窗口尚未创建。设置的相关属性将在updateSurface中创建窗口时使用onCreate(mSurfaceHolder);mInitializing = false;mReportedVisible = false;// updateSurface会进行窗口以及Surface的创建。updateSurface(false, false, false);}// 省略部分代码......}
attach() 方法执行的完成,标志着壁纸启动的完成,之后可以调用壁纸的 surface 显示图像。
在WallpaperManagerService和WallpaperService交互的过程中,主要有下面三个跨进程通信的Binder对象:
- WallpaperConnection:实现在WallpaperManagerService中,并通过IWallpaperService.attach回调传递给了IWallpaperEngineWrapper,通过WallpaperConnection.attachEngine()方法,WallpaperService将IWallpaperEngineWrapper回传给了WallpaperManagerService,实现了双向的通信。
- IWallpaperService:实现在WallpaperService中,该对象提供了attach方法,用于从WallpaperManagerService获取引擎创建时需要的WindowToken等信息。
- IWallpaperEngineWrapper:实现在壁纸服务进程中,同时引用交给了WallpaperManagerService,该对象封装了Engine类,WallpaperManagerService对引擎相关的控制需要通过该对象提供的接口实现。
相关文章:

Android 10.0 动态壁纸 LiveWallpaper
前言 在 Android 中,壁纸分为动态与静态两种,但其实两者得本质都是一样。都以一个 Service 得形式在后台运行,在一个类型为 TYPE_WALLPAPER 的窗口上绘制内容。也可以这么去理解:静态壁纸是一种特殊的动态壁纸,它仅在…...

Linux内核与驱动面试经典“小”问题集锦(4)
接前一篇文章:Linux内核与驱动面试经典“小”问题集锦(3) 问题5 问:Linux内核中内存分配都有哪些方式?它们之间的使用场景都是什么? 备注:这个问题是笔者近期参加蔚来面试时遇到的一个问题。这…...
使用python实现:判断任意坐标点在STL几何模型的内部或外部
简介 在STL几何模型处理的过程中,经常需要判断一个点是否在模型的内部。网上给出的资料主要是使用C vtk的,而python vtk的很少。本文给出了一段精简版的python代码,实现判断任意坐标点在STL几何模型的内部或外部。 代码 首先定义三个函数 …...

leetcode(滑动窗口)483.找到字符中所有字母异位词(C++详细解释)DAY4
文章目录 1.题目示例提示 2.解答思路3.实现代码结果 4.总结 1.题目 给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。 异位词 指由相同字母重排列形成的字符串(包括相同的字符串&a…...

Leaf——美团点评分布式ID生成系统
0.普通算法生成id的缺点 1.Leaf-segment数据库方案 第一种Leaf-segment方案,在使用数据库的方案上,做了如下改变: - 原方案每次获取ID都得读写一次数据库,造成数据库压力大。改为利用proxy server批量获取,每次获取一…...

ProcessSlot构建流程分析
ProcessorSlot ProcessorSlot构建流程 // com.alibaba.csp.sentinel.CtSph#lookProcessChain private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)throws BlockException {// 省略创建 Context 的代码// 黑盒…...

工业笔记本丨行业三防笔记本丨亿道加固笔记本定制丨极端温度优势
工业笔记本是专为在恶劣环境条件下工作而设计的高度耐用的计算机设备。与传统消费者级笔记本电脑相比,工业笔记本在极端温度下展现出了许多优势。本文将探讨工业笔记本在极端温度环境中的表现,并介绍其优势。 耐高温性能: 工业笔记本具有更高的耐高温性…...

游戏服务器多少钱一台?腾讯云32元,阿里云26元
游戏服务器租用多少钱一年?1个月游戏服务器费用多少?阿里云游戏服务器26元1个月、腾讯云游戏服务器32元,游戏服务器配置从4核16G、4核32G、8核32G、16核64G等配置可选,可以选择轻量应用服务器和云服务器,阿腾云atengyu…...

实战分享:SpringBoot在创新创业项目管理中的应用
✍✍计算机编程指导师 ⭐⭐个人介绍:自己非常喜欢研究技术问题!专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目:有源码或者技术上的问题欢迎在评论区一起讨论交流! ⚡⚡ Java实战 |…...
vue3:28— Vue 2 对 Vue 3 的所有非兼容性改变。(vue3学习笔记终)
非兼容性改变 | Vue 3 迁移指南 过渡类名v-enter 修改为 v-enter-from、过渡类名 v-leave 修改为 v-leave-from 。keyCode 作为 v-on 修饰符的支持。v-model 指令在组件上的使用已经被重新设计,替换掉了v-bind.sync.v-if 和 v-for 在同一个元素身上使用时的优先级发…...

【学习笔记】TypeScript学习笔记1 --TypeScript中的类型
文章目录 TS总的变量类型References TS总的变量类型 备注: 如果一个变量设置为了any 类型之后相当于变量关闭了TS的类型检测 let d: any; d 10; d hello;//unknown表示的是未知类型,实际是上一个安全的any,unknown类型的变量不能直接赋值给其他变量le…...
矩阵的正定(positive definite)性质的作用
1. 定义 注意,本文中正定和半正定矩阵不要求是对称或Hermite的。 2. 性质 3. 作用 (1)Axb直接法求解 cholesky实对称正定矩阵求解复共轭对称正定矩阵求解LDL实对称非正定矩阵求解复共轭对称非正定矩阵求解复对称矩阵求解LU实非对称矩阵求解…...

用python编写爬虫,爬取房产信息
题目 报告要求 工程报告链接放在这里 https://download.csdn.net/download/Samature/88816284使用 1.安装jupyter notebook 2.用jupyter notebook打开工程里的ipynb文件,再run all就行 注意事项 可能遇到的bug 暂无,有的话私信我...

Swift Combine 从入门到精通一
1. Combine 简介 用 Apple 官方的话来说,Combine 是: a declarative Swift API for processing values over time. Combine 是 Apple 用来实现函数响应式编程的库, 类似于 RxSwift。 RxSwift 是 ReactiveX 对 Swift 语言的实现。 Combine 使用了许多可以…...

探索前端开发框架:React、Angular 和 Vue 的对决(一)
🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 🍚 蓝桥云课签约作者、上架课程《Vue.js 和 E…...

企业飞书应用机器人,使用python发送图文信息到群
企业飞书应用的自动化,需要创建企业应用,应用开通机器人能力,并获取机器人所需的app_id与app_secret(这一部分大家可以在飞书的控制台获取:https://open.feishu.cn/api-explorer/) 文章目录 步骤1ÿ…...

设计模式1-访问者模式
访问者模式是一种行为设计模式,它允许你定义在对象结构中的元素上进行操作的新操作,而无需修改这些元素的类。这种模式的主要思想是将算法与元素的结构分离开,使得可以在不修改元素结构的情况下定义新的操作。 所谓算法与元素结构分离&#x…...

Android meminfo 查看方法及解析
目录 Android 上查看memory 信息的方法 内存限制的信息 手动释放缓存 例 adb shell dumpsys meminfo pid 解析 adb shell dumpsys meminfo 汇总信息说明 Total RAM Free RAM ION Used RAM Lost RAM ZRAM /proc/meminfo 参考文档 Android 上查看memory 信息的方法 …...

微信小程序解决华为手机保存图片到相册失败
1.新增隐私设置 2.优化代码 新增uni.authorize判断 _saveCode() {let that this;console.log(点击了保存图片)console.log(this.result)uni.authorize({scope: scope.writePhotosAlbum,success(e) {console.log(e)if (this.result ! "") {uni.saveImageToPhotosAlb…...

板块零 IDEA编译器基础:第三节 下载和在IDEA中集成 Tomcat服务器 来自【汤米尼克的JAVAEE全套教程专栏】
板块零 IDEA编译器基础:第三节 下载和在IDEA中集成 Tomcat服务器 一、为什么选择Tomcat(1)常见的JAVA WEB服务器(2)选择Tomcat的理由 二、Tomcat 8.5下载解压三、Tomcat 结构目录四、在IDEA中集成Tomcat 假设我们已经…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...

K8S认证|CKS题库+答案| 11. AppArmor
目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作: 1)、切换集群 2)、切换节点 3)、切换到 apparmor 的目录 4)、执行 apparmor 策略模块 5)、修改 pod 文件 6)、…...
MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例
一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...

Python:操作 Excel 折叠
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...

visual studio 2022更改主题为深色
visual studio 2022更改主题为深色 点击visual studio 上方的 工具-> 选项 在选项窗口中,选择 环境 -> 常规 ,将其中的颜色主题改成深色 点击确定,更改完成...
AtCoder 第409场初级竞赛 A~E题解
A Conflict 【题目链接】 原题链接:A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串,只有在同时为 o 时输出 Yes 并结束程序,否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...

【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)
服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...

【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...
React Native在HarmonyOS 5.0阅读类应用开发中的实践
一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强,React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 (1)使用React Native…...
在Ubuntu24上采用Wine打开SourceInsight
1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...