Android 12.0 通知--PendingIntent源码分析

结论: PendingIntent 是延迟触发的一种 Intent , 通过上图的过程看,PendingIntent 的执行,是一种跨进程通信.首先创建PendingIntent对象时,就把该对象定义到 ActivityManagerService, 到执行 PengdingIntent 动作时, 也是在 ActivityManagerService 找到 目标PengdingIntent, 从而执行相应操作.

1. PendingIntent 在 Android 通知中的使用场景

使用场景: Android 通知的 setContentIntent() 需要传入 PendingIntent , 即当点击通知时,执行 intent 的动作.如下例子:


Intent intent = new Intent(this, MainActivity1.class);//1.获取能启动 Activity 的PendingIntent 对象 PendingIntent pendingIntent =  PendingIntent.getActivity(MainActivity.this, 0, intent, PendingIntent.FLAG_MUTABLE); NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "channel_id").setSmallIcon(R.drawable.notification_icon) .setContentTitle("Notification Title")                .setContentText("Notification content text...")               .setContentIntent(pendingIntent) //2.设置PendingIntent.setPriority(NotificationCompat.PRIORITY_DEFAULT);NotificationManager notificationManager = (NotificationManager.class);NotificationChannel channel = new NotificationChannel("11", "channel-name", NotificationManager.IMPORTANCE_DEFAULT);notificationManager.createNotificationChannel(channel);notificationManager.notify(11, builder.build());

2. PendingIntent 的使用

(1) PendingIntent 的获取

核心源码路径 : frameworks/base/core/java/android/app/PendingIntent.java1. 跳转到 Activity ,指定一个 Activitypublic static PendingIntent getActivity(Context context, int requestCode,Intent intent, @Flags int flags)2. 跳转到 Activity , 指定多各 Activity public static PendingIntent getActivities(Context context, int requestCode,@NonNull Intent[] intents, @Flags int flags)3. 打开 广播 组件
public static PendingIntent getBroadcast(Context context, int requestCode,@NonNull Intent intent, @Flags int flags) 4. 打开 服务 组件public static PendingIntent getService(Context context, int requestCode,@NonNull Intent intent, @Flags int flags) 

 (2) 参数说明:

  • requestCode : 发送方设定的请求码.
  • intent : 要启动的意图.(注意: 需要使用显示 Intent , 即明确将要启动的组件名).
  • flags :   主要是方便后续管理所有的PendingIntent对象,可选的标签如下:

    1) FLAG_ONE_SHOT :  PendingIntent 只能使用一次.该标签后面还能说明.

    2) FLAG_NO_CREATE : 不创建 PendingIntent, 如果PendingIntent存在,则返回null.

    3) FLAG_CANCEL_CURRENT : 如果所描述 PendingIntent 已存在,则在生成新的 PendingIntent 之前,取消当前已存在的PendingIntent.

    4) FLAG_UPDATE_CURRENT : 如果所描述 PendingIntent 已存在,则保留该对象,并用新的数据更新该对象.

    5) FLAG_IMMUTABLE : PendingIntent 不可变.

    6) FLAG_MUTABLE : PendingIntent 可变.

3. PendingIntent 的源码分析

以上图通知的代码为例子,说明创建 PendingIntent 对象的过程.

PendingIntent pendingIntent =  PendingIntent.getActivity(MainActivity.this, 0, intent, PendingIntent.FLAG_MUTABLE); 
(1) 调用 PendingIntent 中的 getActivity() , 源码如下 :
核心源码路径 : frameworks/base/core/java/android/app/PendingIntent.javapublic static PendingIntent getActivity(Context context, int requestCode,Intent intent, @Flags int flags) {return getActivity(context, requestCode, intent, flags, null);}public static PendingIntent getActivity(Context context, int requestCode,@NonNull Intent intent, @Flags int flags, @Nullable Bundle options) {// Some tests only mock Context.getUserId(), so fallback to the id Context.getUser() is nullfinal UserHandle user = context.getUser();return getActivityAsUser(context, requestCode, intent, flags, options,user != null ? user : UserHandle.of(context.getUserId()));}public static PendingIntent getActivityAsUser(Context context, int requestCode,@NonNull Intent intent, int flags, Bundle options, UserHandle user) {String packageName = context.getPackageName(); //获取包名 String resolvedType = intent.resolveTypeIfNeeded(context.getContentResolver());checkFlags(flags, packageName); try {intent.migrateExtraStreamToClipData(context);intent.prepareToLeaveProcess(context);IIntentSender target =ActivityManager.getService().getIntentSenderWithFeature(INTENT_SENDER_ACTIVITY, packageName,context.getAttributionTag(), null, null, requestCode, new Intent[] { intent },resolvedType != null ? new String[] { resolvedType } : null,flags, options, user.getIdentifier()); //获取 IntentSenderreturn target != null ? new PendingIntent(target) : null; //创建 PendingIntent 对象,} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}

       上面最终会调用到 getActivityAsUser( ) , 其中 创建 PendingIntent 对象的时候,需要传入 IIntentSender target 的对象, 而该对象是从 ActivityManagerService.getIntentSenderWithFeature( ) 获取,其中的第一个参数,这里传递的是 INTENT_SENDER_ACTIVITY , 代码获取启动 activity 的PendingIntent , 其他的类型如下:

  • ActivityManager. INTENT_SENDER_ACTIVITY
  • ActivityManager. INTENT_SENDER_ACTIVITY_RESULT
  • ActivityManager. INTENT_SENDER_SERVICE
  • ActivityManager. INTENT_SENDER_FORGROUND_SERVICE
  • ActivityManager. INTENT_SENDER_BROADCAST

(2) ActivityManagerService.getIntentSenderWithFeature( )的源码如下:

核心源码路径 : frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.javapublic IIntentSender getIntentSenderWithFeature(int type, String packageName, String featureId,IBinder token, String resultWho, int requestCode, Intent[] intents,String[] resolvedTypes, int flags, Bundle bOptions, int userId) {enforceNotIsolatedCaller("getIntentSender");return getIntentSenderWithFeatureAsApp(type, packageName, featureId, token, resultWho,requestCode, intents, resolvedTypes, flags, bOptions, userId,Binder.getCallingUid());}public IIntentSender getIntentSenderWithFeatureAsApp(int type, String packageName,String featureId, IBinder token, String resultWho, int requestCode, Intent[] intents,String[] resolvedTypes, int flags, Bundle bOptions, int userId, int owningUid) {if (intents != null) {if (intents.length < 1) { //必须指定至少一个intentthrow new IllegalArgumentException("Intents array length must be >= 1");}for (int i=0; i<intents.length; i++) {Intent intent = intents[i];if (intent != null) {if (intent.hasFileDescriptors()) { //不能携带文件描述符throw new IllegalArgumentException("File descriptors passed in Intent");}//广播类的PendingIntent中,定义的intent不能携带 FLAG_RECEIVER_BOOT_UPGRADE(启动并升级) 的标签if (type == ActivityManager.INTENT_SENDER_BROADCAST &&(intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) {throw new IllegalArgumentException("Can't use FLAG_RECEIVER_BOOT_UPGRADE here");}intents[i] = new Intent(intent);}}if (resolvedTypes != null && resolvedTypes.length != intents.length) {throw new IllegalArgumentException("Intent array length does not match resolvedTypes length");}}if (bOptions != null) {if (bOptions.hasFileDescriptors()) {throw new IllegalArgumentException("File descriptors passed in options");}}int origUserId = userId;userId = mUserController.handleIncomingUser(Binder.getCallingPid(), owningUid, userId,type == ActivityManager.INTENT_SENDER_BROADCAST,ALLOW_NON_FULL, "getIntentSender", null);if (origUserId == UserHandle.USER_CURRENT) {// We don't want to evaluate this until the pending intent is// actually executed.  However, we do want to always do the// security checking for it above.userId = UserHandle.USER_CURRENT;}try {if (owningUid != 0 && owningUid != SYSTEM_UID) {final int uid = AppGlobals.getPackageManager().getPackageUid(packageName,MATCH_DEBUG_TRIAGED_MISSING, UserHandle.getUserId(owningUid));if (!UserHandle.isSameApp(owningUid, uid)) {String msg = "Permission Denial: getIntentSender() from pid="+ Binder.getCallingPid()+ ", uid=" + owningUid+ ", (need uid=" + uid + ")"+ " is not allowed to send as package " + packageName;Slog.w(TAG, msg);throw new SecurityException(msg);}}if (type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT) {return mAtmInternal.getIntentSender(type, packageName, featureId, owningUid,userId, token, resultWho, requestCode, intents, resolvedTypes, flags,bOptions);}// 继续调用 PendingIntentController 类的getIntentSender()来获取IIntentSenderreturn mPendingIntentController.getIntentSender(type, packageName, featureId,owningUid, userId, token, resultWho, requestCode, intents, resolvedTypes,flags, bOptions);} catch (RemoteException e) {throw new SecurityException(e);}}

       通过对 intent 等信息的过滤后,函数的最后继续调用 PendingIntentController 类的 getIntentSender() 方法来获取 IIntentSender 对象(请看下面(4)), 在介绍 getIntentSender( )之前,首先介绍一下,系统把每个 PendingIntent 对象都封装成 PendingIntentRecord 对象,然后通过PendingIntentController 对象来统一管理 PendingIntentRecord 对象,从而达到管理 PendingIntent .

 (3) PendingIntentRecord 对象源码分析

核心代码路径 : frameworks/base/services/core/java/com/android/server/am/PendingIntentRecord.javapublic final class PendingIntentRecord extends IIntentSender.Stub {final PendingIntentController controller;final Key key;  //内部类对象final int uid;public final WeakReference<PendingIntentRecord> ref; boolean sent = false;boolean canceled = false;// PendingIntentRecord对象的内部类,主要用于存储PendingIntent 对象中的参数final static class Key {final int type;final String packageName;final String featureId;final IBinder activity;final String who;final int requestCode;final Intent requestIntent; //获取最后一个intent,一般情况下都是指定一个intentfinal String requestResolvedType;final SafeActivityOptions options;Intent[] allIntents;    //获取所有的intent,如果设定了多个intentString[] allResolvedTypes;final int flags;final int hashCode;final int userId;

(4)  PendingIntentController 类简介, 以及 getIntentSender() 方法来获取 IIntentSender 对象

核心路径:  frameworks/base/services/core/java/com/android/server/am/PendingIntentController.java/*** 该类主要的职责就是协助 ActivityManagerService 管理所有的 PendingIntent*/public class PendingIntentController {/** 保存所有可用的IntentSenderRecord 对象. */final HashMap<PendingIntentRecord.Key, WeakReference<PendingIntentRecord>> mIntentSenderRecords= new HashMap<>();......public PendingIntentRecord getIntentSender(int type, String packageName,@Nullable String featureId, int callingUid, int userId, IBinder token, String resultWho,int requestCode, Intent[] intents, String[] resolvedTypes, int flags, Bundle bOptions) {synchronized (mLock) {if (DEBUG_MU) Slog.v(TAG_MU, "getIntentSender(): uid=" + callingUid);if (intents != null) {for (int i = 0; i < intents.length; i++) {intents[i].setDefusable(true);}}Bundle.setDefusable(bOptions, true);//根据PendingIntent中设置的flag,从而决定该PendingIntent对象是取消或更新等操作final boolean noCreate = (flags & PendingIntent.FLAG_NO_CREATE) != 0;final boolean cancelCurrent = (flags & PendingIntent.FLAG_CANCEL_CURRENT) != 0;final boolean updateCurrent = (flags & PendingIntent.FLAG_UPDATE_CURRENT) != 0;flags &= ~(PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_CANCEL_CURRENT| PendingIntent.FLAG_UPDATE_CURRENT);//把PendingIntent的信息传入PendingIntentRecord内部类Key对象中PendingIntentRecord.Key key = new PendingIntentRecord.Key(type, packageName, featureId,token, resultWho, requestCode, intents, resolvedTypes, flags,SafeActivityOptions.fromBundle(bOptions), userId);WeakReference<PendingIntentRecord> ref;ref = mIntentSenderRecords.get(key);PendingIntentRecord rec = ref != null ? ref.get() : null;//遍历 列表,如果该 PendingIntentRecord 已经存在,则需要根据 flags 来进一步处理if (rec != null) {if (!cancelCurrent) { // 没有设置 PendingIntent.FLAG_CANCEL_CURRENT,即 PendingIntentRecord 不取消if (updateCurrent) {//设置了PendingIntent.FLAG_UPDATE_CURRENT ,则更新 PendingIntentRecord if (rec.key.requestIntent != null) {rec.key.requestIntent.replaceExtras(intents != null ?intents[intents.length - 1] : null);}if (intents != null) {intents[intents.length - 1] = rec.key.requestIntent;rec.key.allIntents = intents;rec.key.allResolvedTypes = resolvedTypes;} else {rec.key.allIntents = null;rec.key.allResolvedTypes = null;}}// 更新完毕,返回 PendingIntentRecordreturn rec;}// 设置 PendingIntent.FLAG_CANCEL_CURRENT,即 PendingIntent 取消makeIntentSenderCanceled(rec);mIntentSenderRecords.remove(key);decrementUidStatLocked(rec);}if (noCreate) {return rec;}//如果列表中没有找到匹配的PendingIntentRecord,则创建新的PendingIntentRecordrec = new PendingIntentRecord(this, key, callingUid);//把新的PendingIntentRecord,存入列表mIntentSenderRecords.put(key, rec.ref);incrementUidStatLocked(rec);// 返回新的PendingIntentRecordreturn rec;}}//取消 PendingIntent private void makeIntentSenderCanceled(PendingIntentRecord rec) {rec.canceled = true; //设置 取消 标志,这样下次遍历列表时,直接跳出if判断,如上final RemoteCallbackList<IResultReceiver> callbacks = rec.detachCancelListenersLocked();if (callbacks != null) {final Message m = PooledLambda.obtainMessage(PendingIntentController::handlePendingIntentCancelled, this, callbacks);mH.sendMessage(m);}final AlarmManagerInternal ami = LocalServices.getService(AlarmManagerInternal.class);ami.remove(new PendingIntent(rec));}

      到此,  不管是新创建的,还是更新的PendingIntentRecord ,这样原路返回给  ActivityManagerService.java, 最后到 PendingIntent.java,即前面 new PendingIntent(target) 中需要的 IIntentSender 对象, 这样就成功创建了PendingIndent.过程如下:

3.  PendingIntent.send() 源码分析

       在通知的场景中,第三方应用通过监听到新通知,获取到通知携带的PendingIntent 对象,然后执行PendingIntent.send()方法,即可实现PendingIntent中intent指定的动作.send() 经过一系列的调用,最终调用 sendAndReturnResult() 方法,源码分析如下:

public int sendAndReturnResult(Context context, int code, @Nullable Intent intent,@Nullable OnFinished onFinished, @Nullable Handler handler,@Nullable String requiredPermission, @Nullable Bundle options)throws CanceledException {try {String resolvedType = intent != null ?intent.resolveTypeIfNeeded(context.getContentResolver()): null;if (context != null && isActivity()) {ActivityOptions activityOptions = options != null ? new ActivityOptions(options): ActivityOptions.makeBasic();activityOptions.setCallerDisplayId(context.getDisplayId());options = activityOptions.toBundle();}//通过ActivityManagerService的sendIntentSender()获取IIntentSender,其中mTarget是PendingIntentRecordreturn ActivityManager.getService().sendIntentSender(mTarget, mWhitelistToken, code, intent, resolvedType,onFinished != null? new FinishedDispatcher(this, onFinished, handler): null,requiredPermission, options);} catch (RemoteException e) {throw new CanceledException(e);}}

(1) ActivityManagerService.sendIntentSender() 源码分析

public int sendIntentSender(IIntentSender target, IBinder allowlistToken, int code,Intent intent, String resolvedType,IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {if (target instanceof PendingIntentRecord) {//继续调用PendingIntentRecord.sendWithResult()return ((PendingIntentRecord)target).sendWithResult(code, intent, resolvedType,allowlistToken, finishedReceiver, requiredPermission, options);} else {if (intent == null) {Slog.wtf(TAG, "Can't use null intent with direct IIntentSender call");intent = new Intent(Intent.ACTION_MAIN);}try {target.send(code, intent, resolvedType, allowlistToken, null,requiredPermission, options);} catch (RemoteException e) {}if (finishedReceiver != null) {try {finishedReceiver.performReceive(intent, 0,null, null, false, false, UserHandle.getCallingUserId());} catch (RemoteException e) {}}return 0;}}

(2) PendingIntentRecord.sendWithResult() 源码分析

sendWithResult()最终调用 sendInner() 方法,源码如下:

public int sendInner(int code, Intent intent, String resolvedType, IBinder allowlistToken,IIntentReceiver finishedReceiver, String requiredPermission, IBinder resultTo,String resultWho, int requestCode, int flagsMask, int flagsValues, Bundle options) {if (intent != null) intent.setDefusable(true);if (options != null) options.setDefusable(true);TempAllowListDuration duration = null;Intent finalIntent = null;Intent[] allIntents = null;String[] allResolvedTypes = null;SafeActivityOptions mergedOptions = null;synchronized (controller.mLock) {if (canceled) {return ActivityManager.START_CANCELED;}sent = true;//检查PendingIntent标签,如果设置了FLAG_ONE_SHOT,表示清除该PendingIntent对象,//即把 IIntentSender 对象清除,接着再从列表中清除if ((key.flags & PendingIntent.FLAG_ONE_SHOT) != 0) {controller.cancelIntentSender(this, true);}//获取最后一个Intent,对于只设置一个intent的场景也适用finalIntent = key.requestIntent != null ? new Intent(key.requestIntent) : new Intent();final boolean immutable = (key.flags & PendingIntent.FLAG_IMMUTABLE) != 0;if (!immutable) {if (intent != null) {int changes = finalIntent.fillIn(intent, key.flags);if ((changes & Intent.FILL_IN_DATA) == 0) {resolvedType = key.requestResolvedType;}} else {resolvedType = key.requestResolvedType;}flagsMask &= ~Intent.IMMUTABLE_FLAGS;flagsValues &= flagsMask;finalIntent.setFlags((finalIntent.getFlags() & ~flagsMask) | flagsValues);} else {resolvedType = key.requestResolvedType;}final ActivityOptions opts = ActivityOptions.fromBundle(options);if (opts != null) {finalIntent.addFlags(opts.getPendingIntentLaunchFlags());}mergedOptions = key.options;if (mergedOptions == null) {mergedOptions = new SafeActivityOptions(opts);} else {mergedOptions.setCallerOptions(opts);}if (mAllowlistDuration != null) {duration = mAllowlistDuration.get(allowlistToken);}if (key.type == ActivityManager.INTENT_SENDER_ACTIVITY&& key.allIntents != null && key.allIntents.length > 1) {allIntents = new Intent[key.allIntents.length];allResolvedTypes = new String[key.allIntents.length];System.arraycopy(key.allIntents, 0, allIntents, 0, key.allIntents.length);if (key.allResolvedTypes != null) {System.arraycopy(key.allResolvedTypes, 0, allResolvedTypes, 0,key.allResolvedTypes.length);}allIntents[allIntents.length - 1] = finalIntent;allResolvedTypes[allResolvedTypes.length - 1] = resolvedType;}}final int callingUid = Binder.getCallingUid();final int callingPid = Binder.getCallingPid();final long origId = Binder.clearCallingIdentity();int res = START_SUCCESS;try {if (duration != null) {StringBuilder tag = new StringBuilder(64);tag.append("setPendingIntentAllowlistDuration,reason:");tag.append(duration.reason == null ? "" : duration.reason);tag.append(",pendingintent:");UserHandle.formatUid(tag, callingUid);tag.append(":");if (finalIntent.getAction() != null) {tag.append(finalIntent.getAction());} else if (finalIntent.getComponent() != null) {finalIntent.getComponent().appendShortString(tag);} else if (finalIntent.getData() != null) {tag.append(finalIntent.getData().toSafeString());}controller.mAmInternal.tempAllowlistForPendingIntent(callingPid, callingUid,uid, duration.duration, duration.type, duration.reasonCode, tag.toString());} else if (key.type == ActivityManager.INTENT_SENDER_FOREGROUND_SERVICE&& options != null) {BroadcastOptions brOptions = new BroadcastOptions(options);if (brOptions.getTemporaryAppAllowlistDuration() > 0) {controller.mAmInternal.tempAllowlistForPendingIntent(callingPid, callingUid,uid, brOptions.getTemporaryAppAllowlistDuration(),brOptions.getTemporaryAppAllowlistType(),brOptions.getTemporaryAppAllowlistReasonCode(),brOptions.getTemporaryAppAllowlistReason());}}boolean sendFinish = finishedReceiver != null;int userId = key.userId;if (userId == UserHandle.USER_CURRENT) {userId = controller.mUserController.getCurrentOrTargetUserId();}//暂时允许接收者和服务从后台打开活动final boolean allowTrampoline = uid != callingUid&& controller.mAtmInternal.isUidForeground(callingUid);//根据不同类型的PendingIntent,执行不相应的操作switch (key.type) {case ActivityManager.INTENT_SENDER_ACTIVITY:try {if (key.allIntents != null && key.allIntents.length > 1) {//启动通过ActivityManagerService 启动Activity,res = controller.mAtmInternal.startActivitiesInPackage(uid, callingPid, callingUid, key.packageName, key.featureId,allIntents, allResolvedTypes, resultTo, mergedOptions, userId,false /* validateIncomingUser */,this /* originatingPendingIntent */,mAllowBgActivityStartsForActivitySender.contains(allowlistToken));} else {res = controller.mAtmInternal.startActivityInPackage(uid, callingPid,callingUid, key.packageName, key.featureId, finalIntent,resolvedType, resultTo, resultWho, requestCode, 0,mergedOptions, userId, null, "PendingIntentRecord",false /* validateIncomingUser */,this /* originatingPendingIntent */,mAllowBgActivityStartsForActivitySender.contains(allowlistToken));}} catch (RuntimeException e) {Slog.w(TAG, "Unable to send startActivity intent", e);}break;case ActivityManager.INTENT_SENDER_ACTIVITY_RESULT:controller.mAtmInternal.sendActivityResult(-1, key.activity, key.who,key.requestCode, code, finalIntent);break;case ActivityManager.INTENT_SENDER_BROADCAST:try {final boolean allowedByToken =mAllowBgActivityStartsForBroadcastSender.contains(allowlistToken);final IBinder bgStartsToken = (allowedByToken) ? allowlistToken : null;//启动通过ActivityManagerService 发送广播,int sent = controller.mAmInternal.broadcastIntentInPackage(key.packageName,key.featureId, uid, callingUid, callingPid, finalIntent,resolvedType, finishedReceiver, code, null, null,requiredPermission, options, (finishedReceiver != null), false,userId, allowedByToken || allowTrampoline, bgStartsToken);if (sent == ActivityManager.BROADCAST_SUCCESS) {sendFinish = false;}} catch (RuntimeException e) {Slog.w(TAG, "Unable to send startActivity intent", e);}break;case ActivityManager.INTENT_SENDER_SERVICE:case ActivityManager.INTENT_SENDER_FOREGROUND_SERVICE:try {final boolean allowedByToken =mAllowBgActivityStartsForServiceSender.contains(allowlistToken);final IBinder bgStartsToken = (allowedByToken) ? allowlistToken : null;//启动通过ActivityManagerService 启动Servicecontroller.mAmInternal.startServiceInPackage(uid, finalIntent, resolvedType,key.type == ActivityManager.INTENT_SENDER_FOREGROUND_SERVICE,key.packageName, key.featureId, userId,allowedByToken || allowTrampoline, bgStartsToken);} catch (RuntimeException e) {Slog.w(TAG, "Unable to send startService intent", e);} catch (TransactionTooLargeException e) {res = ActivityManager.START_CANCELED;}break;}if (sendFinish && res != ActivityManager.START_CANCELED) {try {finishedReceiver.performReceive(new Intent(finalIntent), 0,null, null, false, false, key.userId);} catch (RemoteException e) {}}} finally {Binder.restoreCallingIdentity(origId);}return res;}

PendingIntent 最终还是通过 ActivityManagerService 来启动相应的组件.到此, Activity类型的 PendingIntent 就分析完毕了.

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

完整的模型验证套路

读取图片 from PIL import Imageimg_path "../Yennefer_of_Vengerberg.jpg" image Image.open(img_path) print(image)转换成灰度图&#xff08;可选&#xff09; image image.convert(L) image.show()转换成RGB格式 image image.convert(RGB)因为png格式是四…

区块链是怎么存储数据的?

每个块都是有大小限制的新的数据存储单元&#xff0c;当前数据不到上限&#xff0c;那么都可以添加进块。当前数据达到了上限&#xff0c;那么就得分表/分块&#xff0c;超限的那部分数据就需要等待下个区块存储 存储的数据&#xff1a;和mysql一样&#xff0c;文本数据直接存储…

从vue小白到高手,从一个内容管理网站开始实战开发第八天,登录功能后台功能设计--业务逻辑层基础接口和基础服务实现

上一篇我们介绍了项目后续要使用到的工具类,关于工具类的创建可以查看 从vue小白到高手,从一个内容管理网站开始实战开发第七天,登录功能后台功能设计--通用分页、枚举以及相关工具类-CSDN博客文章浏览阅读2次。本次内容主要介绍了项目后续用到的部分工具类,这些工具类,在…

适用于 Windows 的 12 个最佳免费磁盘分区管理器软件

分区是与其他部分分开的硬盘驱动器部分。它使您能够将硬盘划分为不同的逻辑部分。分区软件是一种工具&#xff0c;可帮助您执行基本选项&#xff0c;例如创建、调整大小和删除物理磁盘的分区。许多此类程序允许您更改磁盘片的标签以便于识别数据。 适用于 Windows 的 12 个最佳…

C++笔记之cout高亮输出以及纯C++实现一个彩色时钟

C笔记之cout高亮输出以及纯C实现一个彩色时钟 code review! 文章目录 C笔记之cout高亮输出以及纯C实现一个彩色时钟一.cout高亮输出1.1.运行1.2.代码一1.3.代码二1.4.重置终端的文本格式到默认设置说明 二.纯C实现一个彩色时钟2.1.运行2.2.main.cc2.3.cout带颜色打印输出技巧…

C++设计模式-- 2.代理模式

文章目录 代理模式 代理模式 代理模式的定义&#xff1a;为其他对象提供一种代理以控制对这个对象的访问。在某些情况下&#xff0c;一个对象不适合 或不能直接引用另一个对象&#xff0c;而代理对象可以在客户端和目标对象之间起到中介的作用。 代理模式有以下三种角色&…

中本聪15年前剧本重演!“比特币上市首日”成绩斐然,ETF交易量冲破46亿美元!

2024年1月11日&#xff0c;美国证券交易委员会&#xff08;SEC&#xff09;以3-2投票结果批准比特币现货ETF。 2009年1月11日&#xff0c;中本聪向比特币早期开发者哈尔芬尼(Hal Finney)转出10枚比特币&#xff0c;使他成为世界上第一个透过交易获得比特币的人。随后&#xff0…

Python的内置函数 def __init__和__str__用法

__init__() 当使用类名&#xff08;&#xff09;创建对象时&#xff0c;会自动执行以下操作 __init__()是对象的的内置方法&#xff0c;是专门用来定义一个类 具有哪些属性的方法 class Person:def __init__(self):print("这是一个初始化方法")result Person() …

(26)Linux 进程通信之共享内存(共享储存空间)

共享内存是System V版本的最后一个进程间通信方式。共享内存&#xff0c;顾名思义就是允许两个不相关的进程访问同一个逻辑内存&#xff0c;共享内存是两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常为同一段物理内存。进程可以将同一…

3D scanner with DLPC3478

https://www.bilibili.com/video/BV1vJ411J7ih?p3&vd_source109fb20ee1f39e5212cd7a443a0286c5 因数&#xff1a; 分别率波长pattern速度 DMD 与 DLPC匹配 3D scanner是结构光的概念走的 Internal pattern, 是DLPC内部提供图像给DMD External Pattern, 外部FPGA /MCU…

查看SQL Server的表字段类型、长度、描述以及是否可为null

文章目录 初步理解小步测试组合一下参考文章有更详细评述 继续理解得到大部分信息 本文参考&#xff1a;https://blog.csdn.net/josjiang1/article/details/80558068。 也可以直接点击这里文章链接&#xff1a; sql server查询表结构&#xff08;字段名&#xff0c;数据类型&a…

借款还款记录账本,借款还款账务处理

生活中&#xff0c;难免会有一些金钱上的往来。有时候&#xff0c;我们会因为忙碌或者其他原因&#xff0c;忘记了借款或还款的具体情况。现在【晨曦记账本】可以帮你可以清楚的掌握自己的借款和还款情况&#xff0c;无论是借款总额、还款总额&#xff0c;还是逾期未还的金额&a…