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 假设我们已经…...
wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...
MPNet:旋转机械轻量化故障诊断模型详解python代码复现
目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...
深入剖析AI大模型:大模型时代的 Prompt 工程全解析
今天聊的内容,我认为是AI开发里面非常重要的内容。它在AI开发里无处不在,当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗",或者让翻译模型 "将这段合同翻译成商务日语" 时,输入的这句话就是 Prompt。…...
Python爬虫实战:研究feedparser库相关技术
1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...
【网络安全产品大调研系列】2. 体验漏洞扫描
前言 2023 年漏洞扫描服务市场规模预计为 3.06(十亿美元)。漏洞扫描服务市场行业预计将从 2024 年的 3.48(十亿美元)增长到 2032 年的 9.54(十亿美元)。预测期内漏洞扫描服务市场 CAGR(增长率&…...
【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...
家政维修平台实战20:权限设计
目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系,主要是分成几个表,用户表我们是记录用户的基础信息,包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题,不同的角色…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...
MinIO Docker 部署:仅开放一个端口
MinIO Docker 部署:仅开放一个端口 在实际的服务器部署中,出于安全和管理的考虑,我们可能只能开放一个端口。MinIO 是一个高性能的对象存储服务,支持 Docker 部署,但默认情况下它需要两个端口:一个是 API 端口(用于存储和访问数据),另一个是控制台端口(用于管理界面…...
Spring Security 认证流程——补充
一、认证流程概述 Spring Security 的认证流程基于 过滤器链(Filter Chain),核心组件包括 UsernamePasswordAuthenticationFilter、AuthenticationManager、UserDetailsService 等。整个流程可分为以下步骤: 用户提交登录请求拦…...
