Android 10.0 锁屏壁纸 LockscreenWallpaper

前言

一、设置壁纸

通过系统设置进行锁屏壁纸和桌面壁纸的设置。
Setting 部分的代码:
packages/apps/WallpaperPicker2/src/com/android/wallpaper/module/DefaultWallpaperPersister.java

    private int setStreamToWallpaperManagerCompat(InputStream inputStream, boolean allowBackup,int whichWallpaper) {try {// whichWallpaper  // 壁纸类型return mWallpaperManagerCompat.setStream(inputStream, null, allowBackup,whichWallpaper);} catch (IOException e) {return 0;}}...
//    int whichWallpaper;    // 壁纸类型
//    if (mDestination == DEST_HOME_SCREEN) {    // 桌面壁纸
//        whichWallpaper = WallpaperManagerCompat.FLAG_SYSTEM;
//    } else if (mDestination == DEST_LOCK_SCREEN) {  // 锁屏壁纸
//        whichWallpaper = WallpaperManagerCompat.FLAG_LOCK;
//    } else { // DEST_BOTH    // 桌面壁纸 和 锁屏壁纸//       whichWallpaper = WallpaperManagerCompat.FLAG_SYSTEM
//                | WallpaperManagerCompat.FLAG_LOCK;
//    }...

mWallpaperManagerCompat 其实就是 WallpaperManagerCompatV16 的对象。
packages/apps/WallpaperPicker2/src/com/android/wallpaper/compat/WallpaperManagerCompatV16.java

    @Overridepublic int setStream(InputStream data, Rect visibleCropHint, boolean allowBackup,int whichWallpaper) throws IOException {mWallpaperManager.setStream(data);// Return a value greater than zero to indicate success.return 1;}

由此可知,壁纸的设置是通过 WallpaperManager 类来进行的。

二、锁屏壁纸的显示

锁屏壁纸显示流程图:

上面应用程序设置完成了,下面就该进行壁纸显示了。
WallpaperManager#setStream()
frameworks/base/core/java/android/app/WallpaperManager.java

    public int setStream(InputStream bitmapData, Rect visibleCropHint,boolean allowBackup, @SetWallpaperFlags int which)throws IOException {// 省略部分代码......try {//sGlobals.mService即 WallpaperManagerService。// WallpaperManager 在 SystemServiceRegistry 实例化,// 过程中传入 WallpaperManagerService 的 binder 对象。   ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null,mContext.getOpPackageName(), visibleCropHint, allowBackup,result, which, completion, mContext.getUserId());if (fd != null) {FileOutputStream fos = null;try {// 将壁纸copy一份并存储到对应目录,// 默认是/data/system/users/0/wallpaper(或wallpaper_lock),// 其中0是主用户的userId,支持多用户fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);copyStreamToWallpaperFile(bitmapData, fos);fos.close();completion.waitForCompletion();} finally {IoUtils.closeQuietly(fos);}}} catch (RemoteException e) {throw e.rethrowFromSystemServer();}return result.getInt(EXTRA_NEW_WALLPAPER_ID, 0);}

这里注意两个方法:sGlobals.mService.setWallpaper()和fos.close()。

先看第一个 WallpaperManagerService#setWallpaper() 方法:
frameworks/base/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java

    public ParcelFileDescriptor setWallpaper(String name, String callingPackage,Rect cropHint, boolean allowBackup, Bundle extras, int which,IWallpaperManagerCallback completion, int userId) {userId = ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId,false /* all */, true /* full */, "changing wallpaper", null /* pkg */);// 检查有没有设置壁纸的权限checkPermission(android.Manifest.permission.SET_WALLPAPER);//调用setStream方法的时候参数which必须是正确的if ((which & (FLAG_LOCK|FLAG_SYSTEM)) == 0) {final String msg = "Must specify a valid wallpaper category to set";Slog.e(TAG, msg);throw new IllegalArgumentException(msg);}// 省略部分代码......synchronized (mLock) {if (DEBUG) Slog.v(TAG, "setWallpaper which=0x" + Integer.toHexString(which));WallpaperData wallpaper;//如果当前没有锁屏壁纸的话,并且是设置桌面壁纸即which == FLAG_SYSTEM,那么同时设置为锁屏壁纸if (which == FLAG_SYSTEM && mLockWallpaperMap.get(userId) == null) {migrateSystemToLockWallpaperLocked(userId);}wallpaper = getWallpaperSafeLocked(userId, which);final long ident = Binder.clearCallingIdentity();try {// updateWallpaperBitmapLocked() 将创建一个文件描述符ParcelFileDescriptor pfd = updateWallpaperBitmapLocked(name, wallpaper, extras);if (pfd != null) {wallpaper.imageWallpaperPending = true;wallpaper.whichPending = which;wallpaper.setComplete = completion;wallpaper.cropHint.set(cropHint);wallpaper.allowBackup = allowBackup;}return pfd;} finally {Binder.restoreCallingIdentity(ident);}}}

这里再跟进一步,看下 updateWallpaperBitmapLocked() 方法:
frameworks/base/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java

    ParcelFileDescriptor updateWallpaperBitmapLocked(String name, WallpaperData wallpaper,Bundle extras) {if (name == null) name = "";try {// 通过getWallpaperDir() 获取文件路径;这个方法值得注意:后面会讲到。File dir = getWallpaperDir(wallpaper.userId);if (!dir.exists()) {dir.mkdir();FileUtils.setPermissions(dir.getPath(),FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,-1, -1);}// 创建一个文件描述符,并返回。ParcelFileDescriptor fd = ParcelFileDescriptor.open(wallpaper.wallpaperFile,MODE_CREATE|MODE_READ_WRITE|MODE_TRUNCATE);// 省略部分代码......return fd;} catch (FileNotFoundException e) {Slog.w(TAG, "Error setting wallpaper", e);}return null;}

这里再看 fos.close() ,这个方法本身没什么可以看的,就是 FileOutputStream 文件字节输出流结束。但是这里涉及到了 WallpaperManagerService 的一个内部类 WallpaperObserver,通过名字我们就能知道它是一个观察者。
WallpaperObserver 初始化:在 WallpaperManagerService 初始化时,会调用 systemReady() 通过getWallpaperSafeLocked()方法初始化 WallpaperData,而这个 WallpaperData 中有个变量 wallpaperObserver ,也在开机时服务初始化, systemReady() 中调用 switchUser() 执行了 wallpaperObserver.startWatching()。
WallpaperObserver 这个内部类的作用:观察壁纸的变化并通知所有 IWallpaperServiceCallbacks 壁纸已经改变。 CREATE 在没有设置壁纸时触发,并且是第一次创建。每次更改壁纸时都会触发 CLOSE_WRITE,这也是关注 fos.close() 的原因。
所以文件的变化触发 WallpaperObserver 的 onEvent() :
frameworks/base/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java

        @Overridepublic void onEvent(int event, String path) {if (path == null) {return;}final boolean moved = (event == MOVED_TO);final boolean written = (event == CLOSE_WRITE || moved);// 获取发生了 CLOSE_WRITE 事件的文件路径final File changedFile = new File(mWallpaperDir, path);// System and system+lock changes happen on the system wallpaper input file;// lock-only changes happen on the dedicated lock wallpaper input file// 用于判断事件是不是这个事件发生的。final boolean sysWallpaperChanged = (mWallpaperFile.equals(changedFile));final boolean lockWallpaperChanged = (mWallpaperLockFile.equals(changedFile));int notifyColorsWhich = 0;WallpaperData wallpaper = dataForEvent(sysWallpaperChanged, lockWallpaperChanged);// 如果是锁屏壁纸更新if (moved && lockWallpaperChanged) {SELinux.restorecon(changedFile);notifyLockWallpaperChanged();notifyWallpaperColorsChanged(wallpaper, FLAG_LOCK);return;}synchronized (mLock) {if (sysWallpaperChanged || lockWallpaperChanged) {notifyCallbacksLocked(wallpaper);if (wallpaper.wallpaperComponent == null|| event != CLOSE_WRITE // includes the MOVED_TO case|| wallpaper.imageWallpaperPending) {if (written) {SELinux.restorecon(changedFile);if (moved) {loadSettingsLocked(wallpaper.userId, true);}generateCrop(wallpaper);wallpaper.imageWallpaperPending = false;if (sysWallpaperChanged) {// 桌面壁纸变化,那么bind ImageWallpaper,ImageWallpaper是负责显示静态桌面壁纸的bindWallpaperComponentLocked(mImageWallpaper, true,false, wallpaper, null);notifyColorsWhich |= FLAG_SYSTEM;}if (lockWallpaperChanged|| (wallpaper.whichPending & FLAG_LOCK) != 0) {if (DEBUG) {Slog.i(TAG, "Lock-relevant wallpaper changed");}if (!lockWallpaperChanged) {//如果参数which是system+lock,也就是同时设置锁屏和桌面壁纸,那么remove锁屏壁纸,因为已经是同一张壁纸了mLockWallpaperMap.remove(wallpaper.userId);}// and in any case, tell keyguard about itnotifyLockWallpaperChanged();notifyColorsWhich |= FLAG_LOCK;}saveSettingsLocked(wallpaper.userId);// Publish completion *after* we've persisted the changesif (wallpaper.setComplete != null) {try {wallpaper.setComplete.onWallpaperChanged();} catch (RemoteException e) {// if this fails we don't really care; the setting app may just// have crashed and that sort of thing is a fact of life.}}}}}}// Outside of the lock since it will synchronize itselfif (notifyColorsWhich != 0) {notifyWallpaperColorsChanged(wallpaper, notifyColorsWhich);}}

先看锁屏壁纸更新这一部分 notifyLockWallpaperChanged()
frameworks/base/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java

    private void notifyLockWallpaperChanged() {final IWallpaperManagerCallback cb = mKeyguardListener;if (cb != null) {try {cb.onWallpaperChanged();} catch (RemoteException e) {// Oh well it went away; no big deal}}}@Overridepublic boolean setLockWallpaperCallback(IWallpaperManagerCallback cb) {checkPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW);synchronized (mLock) {mKeyguardListener = cb;}return true;}

notifyLockWallpaperChanged 中执行 cb.onWallpaperChanged();这里的 cb = mKeyguardListener,而 mKeyguardListener 在 setLockWallpaperCallback() 方法中得到。 跟进我们发现 cb 其实就是 LockscreenWallpaper 引用,在 LockscreenWallpaper 的构造方法里赋值调用:
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java

    @Injectpublic LockscreenWallpaper(WallpaperManager wallpaperManager,@Nullable IWallpaperManager iWallpaperManager,KeyguardUpdateMonitor keyguardUpdateMonitor,DumpManager dumpManager,NotificationMediaManager mediaManager,@Main Handler mainHandler) {// 省略部分代码......if (iWallpaperManager != null) {// Service is disabled on some devices like Automotivetry {// iWallpaperManager 是 WallpaperManagerService 的 binder对象,// 通过 dagger 在 SystemServicesModule 实例化。iWallpaperManager.setLockWallpaperCallback(this);} catch (RemoteException e) {Log.e(TAG, "System dead?" + e);}}}

所以当锁屏壁纸更新时,就会回调到 LockscreenWallpaper#onWallpaperChanged()
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java

    @Overridepublic void onWallpaperChanged() {// Called on Binder thread.postUpdateWallpaper();}private void postUpdateWallpaper() {if (mH == null) {Log.wtfStack(TAG, "Trying to use LockscreenWallpaper before initialization.");return;}mH.removeCallbacks(this);mH.post(this);}

而 LockscreenWallpaper 类实现了 Runnable 接口的,所以看下它的 run() 方法; LockscreenWallpaper#run()
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java

    @Overridepublic void run() {// Called in response to onWallpaperChanged on the main thread.if (mLoader != null) {mLoader.cancel(false /* interrupt */);}final int currentUser = mCurrentUserId;final UserHandle selectedUser = mSelectedUser;mLoader = new AsyncTask<Void, Void, LoaderResult>() {@Overrideprotected LoaderResult doInBackground(Void... params) {return loadBitmap(currentUser, selectedUser);}@Overrideprotected void onPostExecute(LoaderResult result) {super.onPostExecute(result);if (isCancelled()) {return;}if (result.success) {mCached = true;mCache = result.bitmap;mUpdateMonitor.setHasLockscreenWallpaper(result.bitmap != null);// 通知StatusBar更新壁纸mMediaManager.updateMediaMetaData(true /* metaDataChanged */, true /* allowEnterAnimation */);}mLoader = null;}}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);}

异步获取壁纸,并通知StatusBar去更新壁纸。
NotificationMediaManager#updateMediaMetaData()
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java

    public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {Trace.beginSection("StatusBar#updateMediaMetaData");// 省略部分代码......Bitmap artworkBitmap = null;if (mediaMetadata != null && !mKeyguardBypassController.getBypassEnabled()) {artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART);if (artworkBitmap == null) {artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);}}//在后台线程上处理图稿并将生成的位图发送到finishUpdateMediaMetaData。if (metaDataChanged) {for (AsyncTask<?, ?, ?> task : mProcessArtworkTasks) {task.cancel(true);}mProcessArtworkTasks.clear();}if (artworkBitmap != null && !Utils.useQsMediaPlayer(mContext)) {mProcessArtworkTasks.add(new ProcessArtworkTask(this, metaDataChanged,allowEnterAnimation).execute(artworkBitmap));} else {finishUpdateMediaMetaData(metaDataChanged, allowEnterAnimation, null);}Trace.endSection();}

对锁屏壁纸所在 view 做 setImageBitmap。
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java

    private void finishUpdateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation,@Nullable Bitmap bmp) {Drawable artworkDrawable = null;if (bmp != null) {artworkDrawable = new BitmapDrawable(mBackdropBack.getResources(), bmp);}// 省略部分代码......if ((hasArtwork || DEBUG_MEDIA_FAKE_ARTWORK)&& (mStatusBarStateController.getState() != StatusBarState.SHADE || allowWhenShade)&&  mBiometricUnlockController != null && mBiometricUnlockController.getMode()!= BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING&& !hideBecauseOccluded) {// 省略部分代码......if (metaDataChanged) {if (mBackdropBack.getDrawable() != null) {Drawable drawable =mBackdropBack.getDrawable().getConstantState().newDrawable(mBackdropFront.getResources()).mutate();// 设置壁纸 setImageDrawable()mBackdropFront.setImageDrawable(drawable);mBackdropFront.setAlpha(1f);mBackdropFront.setVisibility(View.VISIBLE);} else {mBackdropFront.setVisibility(View.INVISIBLE);}if (DEBUG_MEDIA_FAKE_ARTWORK) {final int c = 0xFF000000 | (int)(Math.random() * 0xFFFFFF);Log.v(TAG, String.format("DEBUG_MEDIA: setting new color: 0x%08x", c));mBackdropBack.setBackgroundColor(0xFFFFFFFF);mBackdropBack.setImageDrawable(new ColorDrawable(c));} else {mBackdropBack.setImageDrawable(artworkDrawable);}if (mBackdropFront.getVisibility() == View.VISIBLE) {if (DEBUG_MEDIA) {Log.v(TAG, "DEBUG_MEDIA: Crossfading album artwork from "+ mBackdropFront.getDrawable()+ " to "+ mBackdropBack.getDrawable());}mBackdropFront.animate().setDuration(250).alpha(0f).withEndAction(mHideBackdropFront);}}} else {// 省略部分代码......}}

通过 mBackdropFront.setImageDrawable(drawable) 方法将图片设置进去,完成锁屏壁纸的更新。
mBackdropFront 在 NotificationMediaManager的 setup() 方法被赋值,而 setup() 方法在 StatusBar 的 makeStatusBarView() 中被调用初始化。
StatusBar#makeStatusBarView()
frameworks/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java

    protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {// 省略部分代码......mMediaManager.setup(backdrop, backdrop.findViewById(R.id.backdrop_front),backdrop.findViewById(R.id.backdrop_back), mScrimController, mLockscreenWallpaper);// 省略部分代码......}

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

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

相关文章

[数学]高斯消元

介绍 用处&#xff1a;求解线性方程组 加减消元法和代入消元法 这里引用了高斯消元解线性方程组----C实现_c用高斯消元法解线性方程组-CSDN博客 改成了自己常用的形式&#xff1a; int gauss() {int c, r; // column, rowfor (c 1, r 1; c < n; c ){int maxx r; //…

《动手学深度学习(PyTorch版)》笔记8.5

注&#xff1a;书中对代码的讲解并不详细&#xff0c;本文对很多细节做了详细注释。另外&#xff0c;书上的源代码是在Jupyter Notebook上运行的&#xff0c;较为分散&#xff0c;本文将代码集中起来&#xff0c;并加以完善&#xff0c;全部用vscode在python 3.9.18下测试通过&…

Java学习-常用API(二)

Math类及其常用API&#xff1a; 演示&#xff1a; StringBuilder的认识及其常用方法&#xff1a; StringBuilder支持链式编程 StringBuilder sbnew StringBuilder&#xff08;&#xff09;&#xff1b;sb.append&#xff08;12&#xff09;.append.&#xff08;“itHeima”&am…

KingSCADA实现按钮点击效果

哈喽&#xff0c;你好啊&#xff0c;我是雷工&#xff01; 在做SCADA项目的时候&#xff0c;按钮是不可缺少的功能&#xff0c;但软件自带的按钮太丑&#xff0c;已经无法满足现如今客户对界面美观度的要求。 这时候就需要UI小姐姐设计美观大气的SCADA界面&#xff0c;但UI设计…

C++入门学习(二十七)跳转语句—break语句

1、与switch语句联合使用 C入门学习&#xff08;二十三&#xff09;选择结构-switch语句-CSDN博客 #include <iostream> #include <string> using namespace std;int main() { int number;cout<<"请为《斗萝大路》打星(1~5※)&#xff1a;" &…

【51单片机】串口通信实验(包括波特率如何计算)

目录 串口通信实验通信的基本概念串行通信与并行通信异步通信与同步通信单工、 半双工与全双工通信通信速率 51单片机串口介绍串口介绍串口通信简介串口相关寄存器串口工作方式方式0方式1方式 2 和方式 3 串口的使用方法&#xff08;计算波特率&#xff09; 硬件设计软件设计1、…

Junit常用注解

注解是方法的“标签” 说明每个方法的“职责” Q:总共有那些注解? 参见官方的API文档 0.常用主机及其特点 BeforeClass 只会执行一次必须用static修饰常用来初始化测试需要的变量 Before 会执行多次&#xff08;只要写一次&#xff09;在每个Test执行执行之前执行可以和…

VTK 三维场景的基本要素(相机) vtkCamera

观众的眼睛好比三维渲染场景中的相机&#xff0c;在VTK中用vtkCamera类来表示。vtkCamera负责把三维场景投影到二维平面&#xff0c;如屏幕&#xff0c;相机投影示意图如下图所示。 1.与相机投影相关的要素主要有如下几个&#xff1a; 1&#xff09;相机位置: 相机所处的位置…

文件的操作(上)

上一期代码题中我们补充一下&#xff0c;代码1中我们创建了一个指针变量来接收我们开辟的空间的首地址&#xff0c;出了函数只是变量被销毁&#xff0c;但是我们在堆区申请的空间却不会自己销毁&#xff0c;这样容易造成内存泄漏&#xff0c;只有等整个程序结束&#xff0c;才会…

[2024]常用的pip指令

[2024]常用的pip指令 HI&#xff0c;这里是肆十二&#xff0c;好久不见&#xff0c;大家&#xff01; 新年好&#xff01; pip是Python的包管理工具&#xff0c;它可以用来安装、升级、卸载Python包。以下是一些常用的pip指令&#xff1a; 安装包&#xff1a; bash复制代码…

C#,泰波拿契数(Tribonacci Number)的算法与源代码

1 泰波拿契数&#xff08;Tribonacci Number&#xff09; 泰波拿契数&#xff08;Tribonacci Number&#xff09;是斐波那契的拓展。 泰波拿契数 (Tribonacci Number) 即把费波拿契数 (Fibonacci Number) 的概念推广至三个数。 2 计算结果 3 源程序 using System; namespace…

通过平扫CT实现胰腺癌早筛(平扫CT+AI)

Large-scale pancreatic cancer detection via non-contrast CT and deep learning - PubMed (nih.gov) 实验团队&#xff1a;海军军医大学第一附属医院&#xff08;上海长海医院&#xff09;&#xff0c;放射诊断科曹凯主治医生为共同第一作者&#xff0c;邵成伟、陆建平等教…

Linux笔记之Docker进行镜像备份与迁移

Linux笔记之Docker进行镜像备份与迁移 ——2024-02-11 code review! 文章目录 Linux笔记之Docker进行镜像备份与迁移1. 导出容器文件系统为 tar 归档文件2. 将 tar 归档文件导入为新的 Docker 镜像3. 运行新的 Docker 镜像并创建容器 1. 导出容器文件系统为 tar 归档文件 要导…

Windows快捷键大全(包含语音输入、剪切板历史快捷键)

最近发现了微软官网上给出的快捷键大全&#xff0c;并且使用了其中几个新的键盘快捷键&#xff08;语音输入、剪切板历史&#xff09;&#xff0c;确实方便快捷&#xff0c;所以写个博客记录分享一下。 注&#xff1a;windows快捷键大全微软官方已经给出&#xff0c;此处不再赘…

每日一练:LeeCode-654、最大二叉树【二叉树+DFS+分治】

本文是力扣LeeCode-654、最大二叉树【二叉树DFS分治】 学习与理解过程&#xff0c;本文仅做学习之用&#xff0c;对本题感兴趣的小伙伴可以出门左拐LeeCode。 给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建: 创建一个根节点&#xff0c;其…

OpenCV-36 多边形逼近与凸包

目录 一、多边形的逼近 二、凸包 一、多边形的逼近 findContours后的轮廓信息countours可能过于复杂不平滑&#xff0c;可以用approxPolyDP函数对该多边形曲线做适当近似&#xff0c;这就是轮廓的多边形逼近。 apporxPolyDP就是以多边形去逼近轮廓&#xff0c;采用的是Doug…

git合入的parents和child

最近在管理代码&#xff0c;有2的权限&#xff0c;看到一些以前1看不到的东西。 有时候会遇到多个人基于同一节点提交代码&#xff0c;那就要选择先合入和后合入&#xff0c;如果这多人修改到同一个文件同一个地方&#xff0c;就可能产生冲突&#xff0c;一般要避免这种情况出…

Kotlin和Java 单例模式

Java 和Kotlin的单例模式其实很像&#xff0c;只是Kotlin一部分单例可以用对象类和委托lazy来实现 Java /*** 懒汉式&#xff0c;线程不安全*/ class Singleton {private static Singleton instance;private Singleton() {}public static Singleton getInstance() {if (insta…

Unity学习笔记(零基础到就业)|Chapter04:C#篇补充到Unity篇过渡

Unity学习笔记&#xff08;零基础到就业&#xff09;&#xff5c;Chapter02:C#篇补充到Unity篇过渡 前言C#总结补充1.值类型和引用类型有什么区别&#xff0c;他们在值的传递上分别有怎样的特性2.string是引用类型&#xff0c;但是他对外表现出值类型的特性&#xff0c;为什么&…

联想thinkpad-E450双系统升级记

早期笔记本联想thinkpad-E450双系统 大约16年花4000多大洋&#xff0c;买了一台thinkpad-L450屏幕是16寸本&#xff0c;有AMD独立显卡&#xff0c;i5cpu&#xff0c;4G内存。 . 后来加了一个同型号4G内存组成双通道&#xff0c; . 加了一个三星固态500G&#xff0c; . 换了一个…