Android 10.0 动态壁纸 LiveWallpaper

2024-02-05 11:44

本文主要是介绍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的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/680809

相关文章

第10章 中断和动态时钟显示

第10章 中断和动态时钟显示 从本章开始,按照书籍的划分,第10章开始就进入保护模式(Protected Mode)部分了,感觉从这里开始难度突然就增加了。 书中介绍了为什么有中断(Interrupt)的设计,中断的几种方式:外部硬件中断、内部中断和软中断。通过中断做了一个会走的时钟和屏幕上输入字符的程序。 我自己理解中断的一些作用: 为了更好的利用处理器的性能。协同快速和慢速设备一起工作

动态规划---打家劫舍

题目: 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。 思路: 动态规划五部曲: 1.确定dp数组及含义 dp数组是一维数组,dp[i]代表

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

代码随想录冲冲冲 Day39 动态规划Part7

198. 打家劫舍 dp数组的意义是在第i位的时候偷的最大钱数是多少 如果nums的size为0 总价值当然就是0 如果nums的size为1 总价值是nums[0] 遍历顺序就是从小到大遍历 之后是递推公式 对于dp[i]的最大价值来说有两种可能 1.偷第i个 那么最大价值就是dp[i-2]+nums[i] 2.不偷第i个 那么价值就是dp[i-1] 之后取这两个的最大值就是d

android-opencv-jni

//------------------start opencv--------------------@Override public void onResume(){ super.onResume(); //通过OpenCV引擎服务加载并初始化OpenCV类库,所谓OpenCV引擎服务即是 //OpenCV_2.4.3.2_Manager_2.4_*.apk程序包,存

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动

Android 10.0 mtk平板camera2横屏预览旋转90度横屏拍照图片旋转90度功能实现

1.前言 在10.0的系统rom定制化开发中,在进行一些平板等默认横屏的设备开发的过程中,需要在进入camera2的 时候,默认预览图像也是需要横屏显示的,在上一篇已经实现了横屏预览功能,然后发现横屏预览后,拍照保存的图片 依然是竖屏的,所以说同样需要将图片也保存为横屏图标了,所以就需要看下mtk的camera2的相关横屏保存图片功能, 如何实现实现横屏保存图片功能 如图所示: 2.mtk

android应用中res目录说明

Android应用的res目录是一个特殊的项目,该项目里存放了Android应用所用的全部资源,包括图片、字符串、颜色、尺寸、样式等,类似于web开发中的public目录,js、css、image、style。。。。 Android按照约定,将不同的资源放在不同的文件夹中,这样可以方便的让AAPT(即Android Asset Packaging Tool , 在SDK的build-tools目

Android fill_parent、match_parent、wrap_content三者的作用及区别

这三个属性都是用来适应视图的水平或者垂直大小,以视图的内容或尺寸为基础的布局,比精确的指定视图的范围更加方便。 1、fill_parent 设置一个视图的布局为fill_parent将强制性的使视图扩展至它父元素的大小 2、match_parent 和fill_parent一样,从字面上的意思match_parent更贴切一些,于是从2.2开始,两个属性都可以使用,但2.3版本以后的建议使