Android进阶 四大组件的工作过程(四):ContentProvider的工作过程

news2024/11/26 10:39:08

Android进阶 四大组件的工作工程(四):ContentProvider的工作过程

在这里插入图片描述

导语

本篇是介绍四大组件的最后一篇文章,前三篇文章里我们已经介绍了Activity,Service以及Broadcast的工作流程,那么这篇文章我们就来介绍内容提供器ContentProvider的工作流程。

前几篇文章:

  1. Android进阶 四大组件的工作过程(一):Activity的工作过程
  2. Android进阶 四大组件的工作过程(二):Service的工作过程
  3. Android进阶 四大组件的工作过程(三):广播的注册,发送和接收过程

ContextImpl到AMS的调用

内容提供者ContentProvider一般是用于跨进程间的通信的,目前我使用的话一般是会配合Room数据库进行使用。

一般来说我们在使用ContentProvider是这样的:

binding.btAdd.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String name = binding.edName.getText().toString();
                Book book = new Book(Uid,name);
                User user = new User(Uid,name,true);
                Uid++;
                ContentValues values = new ContentValues();
                values.put("book_id",book.uid);
                values.put("book_name",book.name);
                values.put("user_id",user.uid);
                values.put("user_name",user.name);
                values.put("user_isMale",user.isMaile);
                getContentResolver().insert(BookProvider.USER_CONTENT_URI,values);
                getContentResolver().insert(BookProvider.BOOK_CONTENT_URI,values);


            }
        });

可以看到我们需要先调用getContentResolver来获取内容解析器,然后用内容解析器来插入数据,那我们就从这里入手,看getContentResolver方法:

    public ContentResolver getContentResolver() {
        return mBase.getContentResolver();
    }

可以看到这里还是和之前的三大组件一样,会调用到ContentWrapper的ContextImpl的getContentResolver方法,所以我们接下来看ContextImpl的getContentResolver方法:

	private final ApplicationContentResolver mContentResolver;
	.......
    public ContentResolver getContentResolver() {
        return mContentResolver;
    }

可以看到,这里是会返回ContextImpl的成员变量,类型为ApplicationContentResolver,这个类型也是ContextImpl的内部类,它继承于ContentResolver内容解析器:

 private static final class ApplicationContentResolver extends ContentResolver 

那最后回到方法的调用,也就是说我们最后调用insert或者query等方法是会调用ApplicationContentResolver的对应方法的,这里我们以query方法为例,先来看它的query,是在
它的父类ContentResolver中实现的:

public final @Nullable Cursor query(final @RequiresPermission.Read @NonNull Uri uri,
            @Nullable String[] projection, @Nullable Bundle queryArgs,
            @Nullable CancellationSignal cancellationSignal) {
        Objects.requireNonNull(uri, "uri");

        try {
            if (mWrapped != null) { //1---------1
                return mWrapped.query(uri, projection, queryArgs, cancellationSignal);
            }
        } catch (RemoteException e) {
            return null;
        }

        IContentProvider unstableProvider = acquireUnstableProvider(uri);//2------2
        if (unstableProvider == null) {
            return null;
        }
        IContentProvider stableProvider = null;
        Cursor qCursor = null;
        try {
            ......
            try {
                qCursor = unstableProvider.query(mContext.getAttributionSource(), uri, projection,queryArgs, remoteCancellationSignal);//3----------3
            } catch (DeadObjectException e) {
                // The remote process has died...  but we only hold an unstable
                // reference though, so we might recover!!!  Let's try!!!!
                // This is exciting!!1!!1!!!!1
                unstableProviderDied(unstableProvider);
                stableProvider = acquireProvider(uri);
                if (stableProvider == null) {
                    return null;
                }
                qCursor = stableProvider.query(mContext.getAttributionSource(), uri, projection,
                        queryArgs, remoteCancellationSignal);
            }
           .............
    }

这里在注释一处会先判断是否有装饰类,这个装饰类实际上是我们可以自定义创建的,它是在ContentProvider的构造方法中被初始化的,这里我们一般也不传自定义的装饰类;所以接下来就会来到注释二处,这里调用了acquireUnstableProvider来获得内容提供者对象;然后在注释三处调用了获得的内容提供者的query方法;

我们先来看注释二处它是怎么获得内容提供者的,这里会最终调用到ApplicationContentResolver的acquireUnstableProvider方法:

protected IContentProvider acquireUnstableProvider(Context c, String auth) {
            return mMainThread.acquireProvider(c,
                    ContentProvider.getAuthorityWithoutUserId(auth),
                    resolveUserIdFromAuthority(auth), false);
        }

这里调用到了自身对应进程的ActivityThread的acquireProvider方法:

public final IContentProvider acquireProvider(
            Context c, String auth, int userId, boolean stable) {
        final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);//1----------1
        if (provider != null) { 
            return provider;
        }
        ContentProviderHolder holder = null;
        final ProviderKey key = getGetProviderKey(auth, userId);
        try {
            synchronized (key) {
                holder = ActivityManager.getService().getContentProvider(//2------2
                        getApplicationThread(), c.getOpPackageName(), auth, userId, stable);
                if (holder != null && holder.provider == null && !holder.mLocal) {
                    synchronized (key.mLock) {
                        if (key.mHolder != null) {
                            if (DEBUG_PROVIDER) {
                                Slog.i(TAG, "already received provider: " + auth);
                            }
                        } else {                            key.mLock.wait(ContentResolver.CONTENT_PROVIDER_READY_TIMEOUT_MILLIS);
                        }
                        holder = key.mHolder;
                    }
                    if (holder != null && holder.provider == null) {
                        // probably timed out
                        holder = null;
                    }
                }
            }
        }
 		.........
        holder = installProvider(c, holder, holder.info,
                true /*noisy*/, holder.noReleaseNeeded, stable);//3------3
        return holder.provider;
    }

这里在注释一处会调用自身ActivityThread的acquireExistingProvider方法,从自身的mProviderMap成员变量中查找有没有目标ContentProvider,这个mProviderMap变量是一个ArrayMap,键为ProviderKey,值为ProviderClientRecord,显然是用于存储(缓存)ContentProvider的一个容器。

如果没有找到目标的ContentProvider,接下来就会调用注释二处的AMS的getContentProvider方法来获取holder,然后注释三处调用installProvider来安装Provider,最后返回holder的provider。

那么接下来就跳转到了注释二处AMS的getContentProvider方法,进入第二阶段。

AMS到ContentProvider的启动

上一节说到了现在跳转到了AMS的getContentProvider方法,我们先来看这个方法:

    public final ContentProviderHolder getContentProvider(
            IApplicationThread caller, String callingPackage, String name, int userId,
            boolean stable) {
        traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "getContentProvider: ", name);
        try {
            return mCpHelper.getContentProvider(caller, callingPackage, name, userId, stable);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        }
    }

这里调转到了中间帮助类的getContentProvider方法:

ContentProviderHolder getContentProvider(IApplicationThread caller, String callingPackage,
            String name, int userId, boolean stable) {
        mService.enforceNotIsolatedCaller("getContentProvider");
		..............
        final int callingUid = Binder.getCallingUid();
		..............
        return getContentProviderImpl(caller, name, null, callingUid, callingPackage,
                null, stable, userId);
    }

这里继续跳转到了ContentProviderHolder的getContentProviderImpl方法:

private ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
            String name, IBinder token, int callingUid, String callingPackage, String callingTag,
            boolean stable, int userId) {
			............
                synchronized (this) {
                    if (!mSystemProvidersInstalled && cpi.applicationInfo.isSystemApp()
                            && "system".equals(cpi.processName)) {
                        throw new IllegalStateException("Cannot access system provider: '"
                                + cpi.authority + "' before system providers are installed!");
                    }
                }
				...........
                        ProcessRecord proc = mService.getProcessRecordLocked(
                                cpi.processName, cpr.appInfo.uid);//1-------1
                        IApplicationThread thread;
                        if (proc != null && (thread = proc.getThread()) != null
                                && !proc.isKilled()) {
                            if (ActivityManagerDebugConfig.DEBUG_PROVIDER) {
                                Slog.d(TAG, "Installing in existing process " + proc);
                            }
                            final ProcessProviderRecord pr = proc.mProviders;
                            if (!pr.hasProvider(cpi.name)) {
                                checkTime(startTime, "getContentProviderImpl: scheduling install");
                                pr.installProvider(cpi.name, cpr);//2------2
                                try {
                                    thread.scheduleInstallProvider(cpi);//3------3
                                } catch (RemoteException e) {
                                }
                            }
                        } else {
                            checkTime(startTime, "getContentProviderImpl: before start process");
                            proc = mService.startProcessLocked(//4-------4
                                    cpi.processName, cpr.appInfo, false, 0,
                                    new HostingRecord(HostingRecord.HOSTING_TYPE_CONTENT_PROVIDER,
                                        new ComponentName(
                                                cpi.applicationInfo.packageName, cpi.name)),
                                    Process.ZYGOTE_POLICY_FLAG_EMPTY, false, false);
                            checkTime(startTime, "getContentProviderImpl: after start process");
                            if (proc == null) {
                                Slog.w(TAG, "Unable to launch app "
                                        + cpi.applicationInfo.packageName + "/"
                                        + cpi.applicationInfo.uid + " for provider " + name
                                        + ": process is bad");
                                return null;
                            }
                        }
                        cpr.launchingApp = proc;
                        mLaunchingProviders.add(cpr);
                    } finally {
                        Binder.restoreCallingIdentity(origId);
                    }
                }

                ...........
        }
        return cpr.newHolder(conn, false);
    }

这个方法比较长,我们就只截取它比较重要的部分。在注释一处,获得了内容调用者的指定进程,如果这个进程存在且应用程序线程对象thread不为null,并且进程记录对象proc没有被杀死(isKilled()返回false),则会调用注释二处的代码安装Provider并且调用注释三的ActivityThread的scheduleInstallProvider方法;如果不存在的话则会调用注释四处的startProcessLocked方法启动所需要的进程,这个过程在应用进程的启动过程中介绍过了,不过这里还是假设应用程序进程不存在,我们接下来看注释四处的方法,它最终会调用到ActivityThread的main方法,具体可以看我的这一篇文章:

Android应用程序进程的启动过程

这里也贴出流程图:
在这里插入图片描述
总之最后调用到了ActivityThread的main方法:

    public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

        // Install selective syscall interception
        AndroidOs.install();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        // Call per-process mainline module initialization.
        initializeMainlineModules();

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();//1----1
        long startSeq = 0;
        if (args != null) {
            for (int i = args.length - 1; i >= 0; --i) {
                if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                    startSeq = Long.parseLong(
                            args[i].substring(PROC_START_SEQ_IDENT.length()));
                }
            }
        }
        ActivityThread thread = new ActivityThread();//2----2
        thread.attach(false, startSeq);//3----3

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
		.........
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

这里在注释一处会先创建一个消息队列管理者Looper,然后在注释二处创建ActivityThread对象,最后在注释三处调用attach方法进行一些参数的配置,我们主要就来看这个attach方法:

    private void attach(boolean system, long startSeq) {
		.........
        mSystemThread = system;
        if (!system) {
         	...........
            final IActivityManager mgr = ActivityManager.getService();//1------1
            try {
                mgr.attachApplication(mAppThread, startSeq);//2------2
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
            // Watch for getting close to heap limit.
            BinderInternal.addGcWatcher(new Runnable() {
                @Override public void run() {
                    if (!mSomeActivitiesChanged) {
                        return;
                    }
 					.........
                    if (dalvikUsed > ((3*dalvikMax)/4)) {
                      ...........
        } else {
					...........
            try {
                mInstrumentation = new Instrumentation();
                mInstrumentation.basicInit(this);
                ContextImpl context = ContextImpl.createAppContext(
                        this, getSystemContext().mPackageInfo);//3------3
                mInitialApplication = context.mPackageInfo.makeApplicationInner(true, null);//4-------4
                mInitialApplication.onCreate();
            } catch (Exception e) {
                throw new RuntimeException(
                        "Unable to instantiate Application():" + e.toString(), e);
            }
        }
        ...........
    }

这里在注释一处获得了AMS,并且在注释二中调用到了AMS的attachApplication方法,注释三处创建出了ContextImpl方法,注释四处创建了Application对象;这里我们主要看注释二处的方法,这个方法最后会调用attachApplicationLocked方法:

private boolean attachApplicationLocked(@NonNull IApplicationThread thread,
            int pid, int callingUid, long startSeq) {
		.........
                thread.bindApplication(processName, appInfo,//1-------1
                        app.sdkSandboxClientAppVolumeUuid, app.sdkSandboxClientAppPackage,
                        providerList,
                        instr2.mClass,
                        profilerInfo, instr2.mArguments,
                        instr2.mWatcher,
                        instr2.mUiAutomationConnection, testMode,
                        mBinderTransactionTrackingEnabled, enableTrackAllocation,
                        isRestrictedBackupMode || !normalMode, app.isPersistent(),
                        new Configuration(app.getWindowProcessController().getConfiguration()),
                        app.getCompat(), getCommonServicesLocked(app.isolated),
                        mCoreSettingsObserver.getCoreSettingsLocked(),
                        buildSerial, autofillOptions, contentCaptureOptions,
                        app.getDisabledCompatChanges(), serializedSystemFontMap,
                        app.getStartElapsedTime(), app.getStartUptime());
		.........
        return true;
    }

这里只要看注释一处的方法,会调用到ActivityThread的bindApplication方法:

        public final void bindApplication(String processName, ApplicationInfo appInfo,
                String sdkSandboxClientAppVolumeUuid, String sdkSandboxClientAppPackage,
                ProviderInfoList providerList, ComponentName instrumentationName,
                ProfilerInfo profilerInfo, Bundle instrumentationArgs,
                IInstrumentationWatcher instrumentationWatcher,
                IUiAutomationConnection instrumentationUiConnection, int debugMode,
                boolean enableBinderTracking, boolean trackAllocation,
                boolean isRestrictedBackupMode, boolean persistent, Configuration config,
                CompatibilityInfo compatInfo, Map services, Bundle coreSettings,
                String buildSerial, AutofillOptions autofillOptions,
                ContentCaptureOptions contentCaptureOptions, long[] disabledCompatChanges,
                SharedMemory serializedSystemFontMap,
                long startRequestedElapsedTime, long startRequestedUptime) {
			...........
            data.initProfilerInfo = profilerInfo;
            data.buildSerial = buildSerial;
            data.autofillOptions = autofillOptions;
            data.contentCaptureOptions = contentCaptureOptions;
            data.disabledCompatChanges = disabledCompatChanges;
            data.mSerializedSystemFontMap = serializedSystemFontMap;
            data.startRequestedElapsedTime = startRequestedElapsedTime;
            data.startRequestedUptime = startRequestedUptime;
            sendMessage(H.BIND_APPLICATION, data);//1------1
        }

这里给自身的Handler发送了H.BIND_APPLICATION标志,用Handler来处理,最后会触发handleBindApplication方法:

    private void handleBindApplication(AppBindData data) {
       .........
        final InstrumentationInfo ii;
        if (data.instrumentationName != null) {
            ii = prepareInstrumentation(data);
        } else {
            ii = null;
        }

        final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);//1---
        mConfigurationController.updateLocaleListFromAppContext(appContext);

  		..........
        Application app;
        final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
        final StrictMode.ThreadPolicy writesAllowedPolicy = StrictMode.getThreadPolicy();
        try {
            // If the app is being launched for full backup or restore, bring it up in
            // a restricted environment with the base application class.
            app = data.info.makeApplicationInner(data.restrictedBackupMode, null);//2----
			
            // Propagate autofill compat state
            app.setAutofillOptions(data.autofillOptions);

            // Propagate Content Capture options
            app.setContentCaptureOptions(data.contentCaptureOptions);
            sendMessage(H.SET_CONTENT_CAPTURE_OPTIONS_CALLBACK, data.appInfo.packageName);

            mInitialApplication = app;
			............
            if (!data.restrictedBackupMode) {
                if (!ArrayUtils.isEmpty(data.providers)) {
                    installContentProviders(app, data.providers);//3-------3
                }
            }
           ...........
        }
    }

在注释一处创建出了ContextImpl对象,继续再注释二处创建出了Application对象,最后在注释三处调用installContentProvider方法来安装内容提供器,所以我们看注释三处的方法:

private void installContentProviders(
            Context context, List<ProviderInfo> providers) {
        final ArrayList<ContentProviderHolder> results = new ArrayList<>();

        for (ProviderInfo cpi : providers) {
            ........
            ContentProviderHolder cph = installProvider(context, null, cpi,//1-----1
                    false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
            if (cph != null) {
                cph.noReleaseNeeded = true;
                results.add(cph);
            }
        }

        try {
            ActivityManager.getService().publishContentProviders(
                getApplicationThread(), results);//2--------2
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }

在注释一处遍历当前应用进程的ProviderInfo列表,得到每个Content Provider的ProviderInfo信息,并调用installProvider方法来启动Content Provider,在注释三处又会将这些Content Provider存储在AMS的mProviderMap中,以便后续调用。接下来看注释一处的方法:

private ContentProviderHolder installProvider(Context context,
            ContentProviderHolder holder, ProviderInfo info,
            boolean noisy, boolean noReleaseNeeded, boolean stable) {
        ContentProvider localProvider = null;
        IContentProvider provider;
        .............
            try {
                final java.lang.ClassLoader cl = c.getClassLoader();
                LoadedApk packageInfo = peekPackageInfo(ai.packageName, true);
                if (packageInfo == null) {
                    // System startup case.
                    packageInfo = getSystemContext().mPackageInfo;
                }
                localProvider = packageInfo.getAppFactory()
                        .instantiateProvider(cl, info.name);//1-------1
                provider = localProvider.getIContentProvider();
               ..........
                localProvider.attachInfo(c, info);//2-------2
            } catch (java.lang.Exception e) {
                if (!mInstrumentation.onException(null, e)) {
                    throw new RuntimeException(
                            "Unable to get provider " + info.name
                            + ": " + e.toString(), e);
                }
                return null;
            }
        } else {
            provider = holder.provider;
            if (DEBUG_PROVIDER) Slog.v(TAG, "Installing external provider " + info.authority + ": "
                    + info.name);
        }

        ContentProviderHolder retHolder;
        ..............
        return retHolder;
    }

在注释一处,用ClassLoader实例化了一个localProvider,接着在注释二处进行参数的配置:

    private void attachInfo(Context context, ProviderInfo info, boolean testing) {
			..........
            ContentProvider.this.onCreate();
        }
    }

这个方法最后就会调用onCreate回调方法完成内容提供者的创建。

总结

总的来说还是ContextImpl到AMS的路数,不过这次ActivityThread的戏份比较少。
在这里插入图片描述

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

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

相关文章

【数据结构与算法分析】一文搞定插入排序、交换排序、简单选择排序、合并排序的代码实现并给出详细讲解

文章目录 排序相关的基本概念排序算法及其实现插入排序直接插入排序折半插入排序希尔排序 交换排序冒泡排序快速排序 合并排序归并排序简单选择排序 算法比较 排序相关的基本概念 排序&#xff1a;将数组中所有元素按照某一顺序(从小到大或从大到小)重新排列的过程。排序算法的…

DJ2-5 内容分发网络 CDN

目录 单一的大规模数据中心 内容分发网络 CDN 单一的大规模数据中心 存在三个问题&#xff1a; ① 如果客户远离数据中心&#xff0c;服务器到客户的分组将跨越许多通信链路并很可能通过许多 ISP&#xff0c;给用户带来恼人的时延。 ② 流行的视频很可能经过相同的通信链路…

[C++11] 智能指针

长路漫漫&#xff0c;唯剑作伴。 目录 长路漫漫&#xff0c;唯剑作伴。 为什么需要智能指针 RAII 使用RAII思想管理内存 重载 * 和-> 总结一下智能指针的原理&#xff1a; C的智能指针和拷贝问题 auto_ptr (C98) ​编辑 auto_ptr的实现原理…

EmGUCV中类函数 FastFeatureDetector使用详解

FastFeatureDetector Class 释义&#xff1a;FAST&#xff08;加速检测特&#xff09;关键点检测器&#xff0c;源自 E. Rosten ("Machine learning for high-speed corner detection, 2006). 继承关系&#xff1a;Emgu.CV.Features2D.FastFeatureDetector 派生&#xff…

记录好项目D5

记录好项目 你好呀&#xff0c;这里是我专门记录一下从某些地方收集起来的项目&#xff0c;对项目修改&#xff0c;进行添砖加瓦&#xff0c;变成自己的闪亮项目。修修补补也可以成为毕设哦 本次的项目是 商品信息管理系统 技术栈&#xff1a;SpringBoot Mybatis Thymelea…

MATLAB|主动噪声和振动控制算法——对较大的次级路径变化具有鲁棒性

\ &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭…

细谈容器化技术实现原理--以Docker为例

目录 一、Docker解决了什么 二、容器的发展过程 三、容器基础 3.1. 容器实现的原理&#xff1a; ⚠️原理详解&#xff1a; 3.1.1. Namespace 3.1.2. Cgroups 3.1.3. chroot 四、Volume 4.1. Docker是如何做到把一个宿主机上的目录或者文件&#xff0c;挂载到容器里面…

4. 数组更新检测

4.1 v-for更新监测 当v-for遍历的目标结构改变, Vue触发v-for的更新 情况1: 数组翻转 情况2: 数组截取 情况3: 更新值 口诀: 数组变更方法, 就会导致v-for更新, 页面更新 数组非变更方法, 返回新数组, 就不会导致v-for更新, 可采用覆盖数组或this.$set() 这些方法会触发数组改…

基于深度学习的高精度鸽子检测识别系统(PyTorch+Pyside6+YOLOv5模型)

摘要&#xff1a;基于深度学习的高精度鸽子检测识别系统可用于日常生活中或野外来检测与定位鸽子目标&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的鸽子目标检测识别&#xff0c;另外支持结果可视化与图片或视频检测结果的导出。本系统采用YOLOv5目标检测模型…

XSS注入——反射性XSS

xss注入的攻击步骤&#xff1a; 1.查找可能存在的注入点&#xff08;搜索框&#xff0c;留言板&#xff0c;注册&#xff09; 2.观察输出显示位置&#xff1a; html&#xff1a; 尖括号外部&#xff0c; 尖括号内部: 引号内部》闭合&#xff0…

Django | 基于pycharm的django配置52张全流程截图,红星给你一步一步的男妈妈式教学

演示版本&#xff1a;【windows系统】python3.10pycharm2023.1.2django4.2.2 &#xff08;本教程全程在虚拟机中演示&#xff0c;读者无需配置虚拟机&#xff0c;直接按教程安装即可&#xff09; 目录 1.搞到必要的安装包 2.事先准备 3.安装chrome浏览器&#xff08;也可以…

国产MCU-CW32F030开发学习--按键检测

国产MCU-CW32F030开发学习–按键检测 bsp_key 按键驱动程序用于扫描独立按键&#xff0c;具有软件滤波机制&#xff0c;采用 FIFO 机制保存键值。可以检测 如下事件&#xff1a; 按键按下。 按键弹起。 长按键。 长按时自动连发。 我们将按键驱动分为两个部分来介绍&#xff…

C语言学习笔记:顺序结构

✨博文作者&#xff1a;烟雨孤舟 &#x1f496; 喜欢的可以 点赞 收藏 关注哦~~ ✍️ 作者简介: 一个热爱大数据的学习者 ✍️ 笔记简介&#xff1a;作为大数据爱好者&#xff0c;以下是个人总结的学习笔记&#xff0c;如有错误&#xff0c;请多多指教&#xff01; 目录 程序与…

《面试1v1》Spring基础

&#x1f345; 作者简介&#xff1a;王哥&#xff0c;CSDN2022博客总榜Top100&#x1f3c6;、博客专家&#x1f4aa; &#x1f345; 技术交流&#xff1a;定期更新Java硬核干货&#xff0c;不定期送书活动 &#x1f345; 王哥多年工作总结&#xff1a;Java学习路线总结&#xf…

浅谈微前端

本文呢是我梳理的一个扫盲文&#xff0c;由于最近团队准备使用微前端对项目进行改造&#xff0c;所以我呢就先浅了解一下&#xff1a; 微前端到底是什么&#xff1f; 为什么要使用微前端&#xff1f; 都有哪些微前端方案&#xff1f; 微前端有什么不好的地方吗&#xff1f; 通过…

48 最佳实践-性能最佳实践-Guest-Idle-Haltpoll

文章目录 48 最佳实践-性能最佳实践-Guest-Idle-Haltpoll48.1 概述48.2 操作指导 48 最佳实践-性能最佳实践-Guest-Idle-Haltpoll 48.1 概述 为了保证公平性及降低功耗&#xff0c;当虚拟机vCPU空闲时&#xff0c;虚拟机将执行WFx/HLT指令退出到宿主机中&#xff0c;并触发上…

计算机视觉 - 基于黄金模板比较技术的缺陷检测

一、黄金模板比较概述 基于黄金模板比对的检测是一种常见的视觉应用。当进行缺陷检查而其他缺陷检测方法是不可行的时候,使用金模板比较。另外当物体的表面或物体的形状非常复杂时,此技术特别有用。 虽然说黄金模板比较的技术的思路很简单,但是真正落地实施确不是一件十分容…

广告数仓:数仓搭建(二)

系列文章目录 广告数仓&#xff1a;采集通道创建 广告数仓&#xff1a;数仓搭建 广告数仓&#xff1a;数仓搭建(二) 文章目录 系列文章目录前言DWD层创建1.建表广告事件事实表 2.数据装载初步解析日志解析IP和UA标注无效流量编写脚本 总结 前言 这次我们完成数仓剩下的内容 D…

Web服务器群集:Web基础与HTTP协议

目录 一、理论 1.Web基础 2.HTTP协议 二、实验 1.浏览本地HTML页面 三、总结 一、理论 1.Web基础 &#xff08;1&#xff09;域名和DNS ① 域名 网络是基于TCP/IP 协议进行通信和连接的&#xff0c;每一台主机都有一个唯一的标识&#xff08;固定的IP地 址&#xff0…

【Java面试】什么是SpringMVC?它的工作流程是什么样子的?

文章目录 什么是MVC&#xff1f;MVC组件组件前端控制器DispatcherServlet处理器映射器HandlerMapping处理器适配器HandlAdapter视图解析器ViewResolver处理器Handler视图View 工作原理具体执行流程 什么是MVC&#xff1f; M&#xff1a;model&#xff0c;模型层&#xff0c;包…