笔记:Context

Context 是上下文对象,是 Android 常用类 Activity、Service 和 Application 都间接继承 Context ,Context 是一个抽象类,内部定义了很多方法和静态常量,具体实现类是 ContextImpl
在这里插入图片描述
ContextImpl 和 ContextWrapper 继承子 Context ,ContextWrapper 内部包含了一个 Context 类型的对象 mBase对象,这个对象指向 ContextImpl ,ContextWrapper 是装饰类,它对 ContextImpl 进行了包装,ContextThemeWrapper、Service、Application 都继承自 ContextWrapper 都可以通过 mBase 来使用 Context 的方法,同时它们也是装饰了,ContextThemeWrapper 中包含了和主题相关的方法

Application Context 创建过程

Android 应用程序启动的最后一步,也是应用程序的入口是 RuntimeInit.java 类通过反射调用其静态 main 方法并创建 ActivityThread 对象
位置:\frameworks\base\core\java\android\app\ActivityThread.java

	public static void main(String[] args) {
        ......

        Looper.prepareMainLooper();	// 创建一个消息循环

        ......
        ActivityThread thread = new ActivityThread(); 	// 创建 ActivityThread 对象
        thread.attach(false, startSeq);					// 并调用attach方法

        ......
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
	private void attach(boolean system, long startSeq) {
        sCurrentActivityThread = this;
        mSystemThread = system;
        if (!system) {
            ......
            final IActivityManager mgr = ActivityManager.getService();
            try {
                mgr.attachApplication(mAppThread, startSeq);
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
            ......
        } else {
           ......
            try {
                mInstrumentation = new Instrumentation();
                mInstrumentation.basicInit(this);
                ContextImpl context = ContextImpl.createAppContext(
                        this, getSystemContext().mPackageInfo);
                mInitialApplication = context.mPackageInfo.makeApplication(true, null);
                mInitialApplication.onCreate();
            } catch (Exception e) {
                throw new RuntimeException(
                        "Unable to instantiate Application():" + e.toString(), e);
            }
        }
		.......
    }

system 表示是否是系统进程,这个只有在 Framework 进程启动时才会调用到,直接调用了 attachApplication 方法,通过 Binder进程间通讯的方式,向 AMS 发送了一个消息
位置:\frameworks\base\services\core\java\com\android\server\am\ActivityManagerService.java

	@Override
    public final void attachApplication(IApplicationThread thread, long startSeq) {
        if (thread == null) {
            throw new SecurityException("Invalid application interface");
        }
        synchronized (this) {
            int callingPid = Binder.getCallingPid();
            final int callingUid = Binder.getCallingUid();
            final long origId = Binder.clearCallingIdentity();
            attachApplicationLocked(thread, callingPid, callingUid, startSeq);  // 调用 attachApplicationLocked 方法
            Binder.restoreCallingIdentity(origId);
        }
    }

	@GuardedBy("this")
    private boolean attachApplicationLocked(@NonNull IApplicationThread thread,
            int pid, int callingUid, long startSeq) {

        ......
            if (app.isolatedEntryPoint != null) {
                // This is an isolated process which should just call an entry point instead of
                // being bound to an application.
                thread.runIsolatedEntryPoint(app.isolatedEntryPoint, app.isolatedEntryPointArgs);
            } else if (app.instr != null) {
                thread.bindApplication(processName, appInfo, providers,
                        app.instr.mClass,
                        profilerInfo, app.instr.mArguments,
                        app.instr.mWatcher,
                        app.instr.mUiAutomationConnection, testMode,
                        mBinderTransactionTrackingEnabled, enableTrackAllocation,
                        isRestrictedBackupMode || !normalMode, app.persistent,
                        new Configuration(getGlobalConfiguration()), app.compat,
                        getCommonServicesLocked(app.isolated),
                        mCoreSettingsObserver.getCoreSettingsLocked(),
                        buildSerial, isAutofillCompatEnabled);
            } else {
                thread.bindApplication(processName, appInfo, providers, null, profilerInfo,
                        null, null, null, testMode,
                        mBinderTransactionTrackingEnabled, enableTrackAllocation,
                        isRestrictedBackupMode || !normalMode, app.persistent,
                        new Configuration(getGlobalConfiguration()), app.compat,
                        getCommonServicesLocked(app.isolated),
                        mCoreSettingsObserver.getCoreSettingsLocked(),
                        buildSerial, isAutofillCompatEnabled);
            }
        ......
        return true;
    }

可以看到这里面又调用了 IApplicationThread.bindApplication 方法,IApplicationThread 是 ActivityThread 中 ApplicationThread binder 对象的客户端
位置:\frameworks\base\core\java\android\app\ActivityThread.java

	private class ApplicationThread extends IApplicationThread.Stub {
		......
		public final void bindApplication(String processName, ApplicationInfo appInfo,
                List<ProviderInfo> providers, 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, boolean autofillCompatibilityEnabled) {

            if (services != null) {
                if (false) {
                    // Test code to make sure the app could see the passed-in services.
                    for (Object oname : services.keySet()) {
                        if (services.get(oname) == null) {
                            continue; // AM just passed in a null service.
                        }
                        String name = (String) oname;

                        // See b/79378449 about the following exemption.
                        switch (name) {
                            case "package":
                            case Context.WINDOW_SERVICE:
                                continue;
                        }

                        if (ServiceManager.getService(name) == null) {
                            Log.wtf(TAG, "Service " + name + " should be accessible by this app");
                        }
                    }
                }

                // Setup the service cache in the ServiceManager
                ServiceManager.initServiceCache(services);
            }

            setCoreSettings(coreSettings);

            AppBindData data = new AppBindData();
            data.processName = processName;
            data.appInfo = appInfo;
            data.providers = providers;
            data.instrumentationName = instrumentationName;
            data.instrumentationArgs = instrumentationArgs;
            data.instrumentationWatcher = instrumentationWatcher;
            data.instrumentationUiAutomationConnection = instrumentationUiConnection;
            data.debugMode = debugMode;
            data.enableBinderTracking = enableBinderTracking;
            data.trackAllocation = trackAllocation;
            data.restrictedBackupMode = isRestrictedBackupMode;
            data.persistent = persistent;
            data.config = config;
            data.compatInfo = compatInfo;
            data.initProfilerInfo = profilerInfo;
            data.buildSerial = buildSerial;
            data.autofillCompatibilityEnabled = autofillCompatibilityEnabled;
            sendMessage(H.BIND_APPLICATION, data);
        }
        ......
	}

最后调用了ActivityThread.sendMessage(),sendMessage 发送了一个 Handler 的异步消息,最后被 handleMessage 方法处理
在这里插入图片描述

private void handleBindApplication(AppBindData data) {
        ......
        data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
        ......
        final InstrumentationInfo ii;
        // 创建 Instrumentation
        if (data.instrumntationName != null) {
            try {
                ii = new ApplicationPackageManager(null, getPackageManager())
                        .getInstrumentationInfo(data.instrumentationName, 0);
            } catch (PackageManager.NameNotFoundException e) {
                throw new RuntimeException(
                        "Unable to find instrumentation info for: " + data.instrumentationName);
            }

            // Warn of potential ABI mismatches.
            if (!Objects.equals(data.appInfo.primaryCpuAbi, ii.primaryCpuAbi)
                    || !Objects.equals(data.appInfo.secondaryCpuAbi, ii.secondaryCpuAbi)) {
                Slog.w(TAG, "Package uses different ABI(s) than its instrumentation: "
                        + "package[" + data.appInfo.packageName + "]: "
                        + data.appInfo.primaryCpuAbi + ", " + data.appInfo.secondaryCpuAbi
                        + " instrumentation[" + ii.packageName + "]: "
                        + ii.primaryCpuAbi + ", " + ii.secondaryCpuAbi);
            }

            mInstrumentationPackageName = ii.packageName;
            mInstrumentationAppDir = ii.sourceDir;
            mInstrumentationSplitAppDirs = ii.splitSourceDirs;
            mInstrumentationLibDir = getInstrumentationLibrary(data.appInfo, ii);
            mInstrumentedAppDir = data.info.getAppDir();
            mInstrumentedSplitAppDirs = data.info.getSplitAppDirs();
            mInstrumentedLibDir = data.info.getLibDir();
        } else {
            ii = null;
        }

        .......
        
        try {
            
            app = data.info.makeApplication(data.restrictedBackupMode, null); // 创建 Application 对象

            // Propagate autofill compat state
            app.setAutofillCompatibilityEnabled(data.autofillCompatibilityEnabled);

            mInitialApplication = app;

            .......
            try {
                mInstrumentation.onCreate(data.instrumentationArgs);
            }
            catch (Exception e) {
                throw new RuntimeException(
                    "Exception thrown in onCreate() of "
                    + data.instrumentationName + ": " + e.toString(), e);
            }
            try {
                mInstrumentation.callApplicationOnCreate(app);
            } catch (Exception e) {
                if (!mInstrumentation.onException(app, e)) {
                    throw new RuntimeException(
                      "Unable to create application " + app.getClass().getName()
                      + ": " + e.toString(), e);
                }
            }
        } finally {
            if (data.appInfo.targetSdkVersion < Build.VERSION_CODES.O_MR1
                    || StrictMode.getThreadPolicy().equals(writesAllowedPolicy)) {
                StrictMode.setThreadPolicy(savedPolicy);
            }
        }
		......
    }

handleBindApplication 方法首先通过反射创建了 Instrumentation 对象,并执行了 init 方法初始化,然后调用 LoadedApk 类的 makeApplication 方法来创建一个 Application 对象,在 LoadedApk 类中会把创建的 Application 对象保存在 mApplication 变量中,并返回这个变量赋值给 ActivityThread 类中的 mInitialApplication 变量
位置:\frameworks\base\core\java\android\app\LoadedApk.java

	public Application makeApplication(boolean forceDefaultAppClass,
            Instrumentation instrumentation) {
        // 检查是否已经创建
        if (mApplication != null) {
            return mApplication;
        }

        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "makeApplication");

        Application app = null;
		// 设置 Application 的类名
        String appClass = mApplicationInfo.className;
        if (forceDefaultAppClass || (appClass == null)) {
            appClass = "android.app.Application";
        }

        try {
        	// 创建 Application 及其 Context
            java.lang.ClassLoader cl = getClassLoader();
            if (!mPackageName.equals("android")) {
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                        "initializeJavaContextClassLoader");
                initializeJavaContextClassLoader();
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            }
            // 1.创建Context
            ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
            // 2.创建 Application
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);
            // 3.将两个对象关联起来
            appContext.setOuterContext(app);
        } catch (Exception e) {
            if (!mActivityThread.mInstrumentation.onException(app, e)) {
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                throw new RuntimeException(
                    "Unable to instantiate application " + appClass
                    + ": " + e.toString(), e);
            }
        }
        // 4.保存创建的Application
        mActivityThread.mAllApplications.add(app);
        mApplication = app;

        if (instrumentation != null) {
            try {
            	// 调用 Application 中的 onCreate() 方法
                instrumentation.callApplicationOnCreate(app);
            } catch (Exception e) {
                if (!instrumentation.onException(app, e)) {
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    throw new RuntimeException(
                        "Unable to create application " + app.getClass().getName()
                        + ": " + e.toString(), e);
                }
            }
        }

        .......
        return app;
    }

1.首先调用 ContextImpl.createAppContext 静态方法创建 ContextImpl 实例

2.调用 newApplication 方法并传入 ContextImpl 实例和之前设置的 appClass,来创建该类名对应的一个 Application实例,在 newApplication 方法内部,创建完 Application 实例后,会调用其 attach 方法并传入 ContextImpl 实例,在 attach 方法中又会调用其父类 ContextWrapper(装饰类) 中的 attachBaseContext() 方法,将 ContextImpl 对象设置给内部实例变量 mBase, 这样 Application 内部就持有了Context的实现类ContextImpl实例
位置:\frameworks\base\core\java\android\app\Instrumentation.java

	static public Application newApplication(Class<?> clazz, Context context)
            throws InstantiationException, IllegalAccessException, 
            ClassNotFoundException {
        Application app = (Application)clazz.newInstance(); // 创建 Application 类,相当于 new 关键字
        app.attach(context);	// 调用 Application 的 attach 方法
        return app;
    }

位置:\frameworks\base\core\java\android\app\Application.java

    /* package */ final void attach(Context context) {
        attachBaseContext(context); // 直接调用 attachBaseContext 方法,attachBaseContext 方法是在其父类 ContextWrapper 实现的
        mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
    }

位置:\frameworks\base\core\java\android\content\ContextWrapper.java

    protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        mBase = base; // 将 ContextImpl 对象设置给内部实例变量 mBase
    }

3.最后调用 ContextImpl 的实例方法 setOuterContext,传入这个 Application 实例,这样 ContextImpl 实例也会持有 Context 的包装类 ContextWrapper 实例

4.创建完一个 Application 对象后,将其保存在 mApplication 实例变量中,由于一个应用程序进程中的 LoadedApk 对象也是一个单例,因此一个进程中只能创建一个 Application 对象。

Application 得到 Context

都知道得到 Application 的 Context 是通过 getApplicationContext() 方法获得到的,这个方法是在 Application 的父类 ContextWrapper 中实现的
在这里插入图片描述

Activity 创建 Context

从上面得知,创建 Application 对象是通过 ActivityManagerService 中的 attachApplicationLocked 方法中的 thread.bindApplication 方法去创建并绑定到 AMS 中,在 thread.bindApplication 之后会调用 mStackSupervisor.attachApplicationLocked(app) 方法去启动应用的 Activity,详细见这篇文章:https://blog.csdn.net/weixin_44128558/article/details/132140588 之后经过层层调用会调用到 ActivityThread 中的 performLaunchActivity 方法
位置:\frameworks\base\core\java\android\app\ActivityThread.java

	/**  Core implementation of activity launch. */
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ......
        
        ContextImpl appContext = createBaseContextForActivity(r); //1.创建 Context
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);  // 2.创建一个新的Activity
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess();
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } 
        
        ......
        
        try {
            ......

            if (activity != null) {
                ......
                appContext.setOuterContext(activity); //3.将Activity 和 Context 关联起来
                activity.attach(appContext, this, getInstrumentation(), r.token,	//4.调用attach 将 ContextImpl 对象设置给内部实例变量 mBase
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback);

                ......
                if (r.isPersistable()) { // 调用 activity 的 OnCreate 方法
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState); 
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
                ......
                r.activity = activity;	//5.保存创建的Activity
            }
            ......
        } 
        .......

        return activity;
    }

	private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
        ......

        ContextImpl appContext = ContextImpl.createActivityContext(
                this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);

        ......
        return appContext;
    }

可见,与 Application 创建 Context 流程类似
1.直接调用 createBaseContextForActivity 方法返回一个 ContextImpl 对象

	private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
        ......
        ContextImpl appContext = ContextImpl.createActivityContext(
                this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);

        ......
        return appContext;
    }

createBaseContextForActivity 方法里调用 ContextImpl.createActivityContext 静态方法创建 ContextImpl 实例并返回
位置:\frameworks\base\core\java\android\app\ContextImpl.java

static ContextImpl createActivityContext(ActivityThread mainThread,
            LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId,
            Configuration overrideConfiguration) {
        ......
        
        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityInfo.splitName,
                activityToken, null, 0, classLoader);
        
        ......

        final ResourcesManager resourcesManager = ResourcesManager.getInstance();

        // Create the base resources for which all configuration contexts for this Activity
        // will be rebased upon.
        context.setResources(resourcesManager.createBaseActivityResources(activityToken,
                packageInfo.getResDir(),
                splitDirs,
                packageInfo.getOverlayDirs(),
                packageInfo.getApplicationInfo().sharedLibraryFiles,
                displayId,
                overrideConfiguration,
                compatInfo,
                classLoader));
        context.mDisplay = resourcesManager.getAdjustedDisplay(displayId,
                context.getResources());
        return context;
    }

这里真正创建了 Context

2. 调用 newActivity 方法并传入 ContextImpl 实例,实际调用的是 AppComponentFactory 的 instantiateActivity() 方法,该方法通过反射方式创建 Activity
位置:frameworks\base\core\java\android\app\Instrumentation.java

	public Activity newActivity(ClassLoader cl, String className,
            Intent intent)
            throws InstantiationException, IllegalAccessException,
            ClassNotFoundException {
        String pkg = intent != null && intent.getComponent() != null
                ? intent.getComponent().getPackageName() : null;
        return getFactory(pkg).instantiateActivity(cl, className, intent);
    }

位置:frameworks\base\core\java\android\app\AppComponentFactory.java

	public @NonNull Activity instantiateActivity(@NonNull ClassLoader cl, @NonNull String className,
            @Nullable Intent intent)
            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        return (Activity) cl.loadClass(className).newInstance();
    }

3. 与 Application 相同,调用 ContextImpl 的实例方法 setOuterContext,传入这个 Activity 实例,这样 ContextImpl 实例也会持有 Context 的包装类 ContextWrapper 实例

4. 再调用 Activity 的 attach ,也 Application 类似,该方法中会调用其父类 ContextWrapper 中的 attachBaseContext 方法,将 ContextImpl 对象设置给内部实例变量 mBase
位置:\frameworks\base\core\java\android\app\Activity.java

	final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback) {
        attachBaseContext(context); // 将 ContextImpl 对象设置给内部实例变量 mBase

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this, window, activityConfigCallback);  //创建 PhoneWindow 应用程序的窗口
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);				// 设置回调接口,有关屏幕事件回调
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
        if (info.uiOptions != 0) {
            mWindow.setUiOptions(info.uiOptions);
        }
        mUiThread = Thread.currentThread();		//创建刷新ui的线程

       ......

        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        mWindowManager = mWindow.getWindowManager(); // 得到 WindowManager 并赋给 mWindowManager 成员变量,这样 Activity 可以通过 getWindowManager 方法来获得 WindowManager
        mCurrentConfig = config;

        mWindow.setColorMode(info.colorMode);

        setAutofillCompatibilityEnabled(application.isAutofillCompatibilityEnabled());
        enableAutofillCompatibilityIfNeeded();
    }

	@Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(newBase);
        if (newBase != null) {
            newBase.setAutofillClient(this);
        }
    }

首先调用 attachBaseContext 方法,attachBaseContext 方法中又直接调用其父类 (ContextThemeWrapper) 的 attachBaseContext 方法
在这里插入图片描述
ContextThemeWrapper 又会调用其父类 (ContextThemeWrapper) 的 attachBaseContext 方法,将 ContextImpl 对象设置给内部实例变量 mBase (与上面 Application 相同)

之后会创建 PhoneWindow ,PhoneWindow 会接受很多事件,比如点击,菜单弹出,屏幕焦点变化等,这些都会转发给与 PhoneWindow 关联的 Activity,通过 Window.Callback 回调接口实现,当前的 Activity 通过 Window 的 setCallback 方法 传递给 PhoneWindow

5.创建完一个 Activity 对象后,将其保存在 mApplication 实例变量中

Service 创建 Context

Service 的 Context创建过程与 Activity 的 Context 创建过程类似,是在 Service 的启动过程中被创建,Service 是通过 ActiviytThread 的内部类 ApplicationThread 调用 scheduleCreateService 方法来启动
在这里插入图片描述
与 Activity 类似,最后将创建的 ContextImpl 一路传递到 Context

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

macOS上编译android的ffmpeg及ffmpeg.c

1 前言 前段时间介绍过使用xcode和qt creator编译调试ffmepg.c&#xff0c;运行平台是在macOS上&#xff0c;本文拟介绍下android平台如何用NDK编译链编译ffmepg库并使用。 macOS上使用qt creator编译调试ffmpeg.c macOS上将ffmpeg.c编译成Framework 大体思路&#xff1a; 其…

LTspice仿真中设置电阻随时间变化的方法

背景&#xff1a; 笔者找了很多资料都没有看到如何设置电阻、电容等参数随时间变化。但在实际模拟中&#xff0c;总会遇到需要模拟这些量的变化。故撰写此文&#xff0c;供大家参考。 除了模拟随时间变化外&#xff0c;同样的思路也可以模拟随其他变量变化 效果展示 设置电…

python3.5如何安装numpy

python3.5如何安装numpy&#xff1f;步骤如下&#xff1a; 1.首先应该将你的Python环境变量设置正确。检验是否正确的方法就是winR&#xff0c;输入cmd 。在窗口中输入python&#xff0c;应该得到如下所示的效果图&#xff1a; 可以在命令框中直接编译python。 2.安装pip&…

乡村振兴与乡村旅游创新:创新乡村旅游产品,提升旅游服务水平,打造特色乡村旅游品牌,助力美丽乡村建设

目录 一、引言 二、乡村旅游产品的创新 &#xff08;一&#xff09;挖掘乡村特色资源 &#xff08;二&#xff09;注重产品体验性 &#xff08;三&#xff09;创新旅游产品形态 三、旅游服务水平的提升 &#xff08;一&#xff09;加强基础设施建设 &#xff08;二&…

ESP32入门:1、VSCode+PlatformIO环境搭建(离线快速安装)

文章目录 背景安装vscode安装配置中文 安装Platform IO安装PIO 新建ESP32工程参考 背景 对于刚接触单片机的同学&#xff0c;使用vscodeplatformIO来学习ESP32是最方便快捷的&#xff0c;比IDF框架简单&#xff0c;且比arduino文件管理性能更好。但是platformIO安装较为麻烦&a…

《中国科技纵横》是什么级别的期刊?是正规期刊吗?能评职称吗?

问题解答&#xff1a; 问&#xff1a;《中国科技纵横》期刊是核心吗&#xff1f; 答&#xff1a;不是&#xff0c;是万方维普收录的正规期刊。 问&#xff1a;《中国科技纵横》知网收录吗&#xff1f; 答&#xff1a;知网不收录&#xff0c;万方维普收录。主管单位&#xf…

ES 生命周期管理

一 .概念 ILM定义了四个生命周期阶段&#xff1a;Hot&#xff1a;正在积极地更新和查询索引。Warm&#xff1a;不再更新索引&#xff0c;但仍在查询。cold&#xff1a;不再更新索引&#xff0c;很少查询。信息仍然需要可搜索&#xff0c;但是如果这些查询速度较慢也可以。Dele…

出吉林大学计算机考研资料适用于计专966/计学941/软专967

本人是24上岸吉大计算机专硕的考生&#xff0c;先上成绩&#xff1a; 出专业课备考过程的相关笔记资料&#xff0c;也可以提供经验分享等&#xff1a; 吉林大学计算机数据结构基础算法ADL汇总&#xff0c;适用于计专966/计学941/软专967综合整理小绿书以及期末题上重难点算法…

字符串和字符串函数(2)

前言&#xff1a; 在字符串和字符串函数&#xff08;1&#xff09;-CSDN博客中&#xff0c;已将将字符串和字符函数的使用了解&#xff0c;并且实现模拟了一些字符串的库函数。 接下来将继续深入学习字符串和字符串函数。并且模拟实现一些较为复杂的函数。 可控制字符…

gpt-4o api申请开发部署应用:一篇全面的指南

利用 GPT-4o API 开发创新应用&#xff1a;一篇全面的指南 OpenAI 的 GPT-4o 是一款集成了音频、视觉和文本处理能力的多模态人工智能模型&#xff0c;它的出现代表了人工智能领域的重大进步。在本篇文章中&#xff0c;我们将详细介绍如何通过 OpenAI API 使用 GPT-4o&#xf…

xcode开发swift允许发送http请求设置

Xcode 现在新建项目默认只支持HTTPS请求&#xff0c;认为HTTP请求不安全&#xff0c;所以不支持。但是开发环境一般都是http模式&#xff0c;所以需要单独配置才可以访问。 需要到项目的设置里面&#xff0c;点击info&#xff0c;如果没有App Transport Security Setting这一项…

【源码】Spring Data JPA原理解析之Repository自定义方法命名规则执行原理(二)

Spring Data JPA系列 1、SpringBoot集成JPA及基本使用 2、Spring Data JPA Criteria查询、部分字段查询 3、Spring Data JPA数据批量插入、批量更新真的用对了吗 4、Spring Data JPA的一对一、LazyInitializationException异常、一对多、多对多操作 5、Spring Data JPA自定…

生信分析进阶3 - pysam操作bam文件统计unique reads和mapped reads高级技巧合辑

pysam操作bam文件统计unique reads和mapped reads高级技巧 1. Linux服务器读取bam文件 服务器查看bam常用方法。 # bam_path&#xff1a; bam文件路径 samtools view -h bam_path|grep -v ^|less -S2. samtools python os库读取bam文件 缺点速度较慢。 import os# 读取ba…

springboot从2.7.2 升级到 3.3.0

文章目录 概要准备报错调整小结后记 概要 时代在进步&#xff0c;springboot已经来到了3.3.0 , 于是我们也打算升级下sbvadmin到3.3&#xff0c; jdk使用21的版本&#xff0c;下面是升级过程中碰到的一些问题&#xff0c;问题不大。 2.7.2 -> 3.3.0 准备 下载jdk21&#…

AdroitFisherman模块安装日志(2024/5/31)

安装指令 pip install AdroitFisherman-0.0.29.tar.gz -v 安装条件 1:Microsoft Visual Studio Build Tools 2:python 3.10.x 显示输出 Using pip 24.0 from C:\Users\12952\AppData\Local\Programs\Python\Python310\lib\site-packages\pip (python 3.10) Processing c:\u…

UML静态图-类图

概述 静态图包含类图、对象图和包图的主要目的是在系统详细设计阶段&#xff0c;帮助系统设计人员以一种可视化的方式来理解系统的内部结构和代码结构&#xff0c;包括类的细节、类的属性和操作、类的依赖关系和调用关系、类的包和包的依赖关系。 一、类图的表示法 类图(Cla…

【linux】自定义快捷命令/脚本

linux自定义快捷命令 场景自定义命令自定义脚本 场景 深度学习经常要切换到自己环境&#xff0c;conda activate mmagic&#xff0c;但是又不想每次重复打这么多字&#xff0c;想使用快捷命令直接切换。 自定义命令 使用别名&#xff08;alias&#xff09;或自定义脚本来创建…

【期末速成】——计算机组成原理(1)

目录 一、什么是计算机的组成 二、冯诺依曼体系结构计算机的特点 三、计算机系统的层次结构 四、机器语言、汇编语言、高级语言, 五、 编译程序、解释程序、汇编程序 六、已知主频、CPI计算程序运行时间 一、什么是计算机的组成 计算机的组成可以分为五个部件和两个信息…

10.Halcon3D点云和MESH的相互转换

1.实现效果 这个案例主要是想告诉我们,如何在点云数据(全是点)和MESH(网格数据)中转换,理论上说可以点云数据可以看作的离散的,而MESH网格数据可以看作是连续的。 上图展示了三个(其实是四个)空间中的3d对象,左边第一个是一个立方体,经过降采样之后的点云,中间的是…

systemctl 添加自定义系统服务

以 “启动、停止、重启” boa web server为例&#xff1a; 1. 编写系统服务脚本 编写一个符合系统服务规范的脚本。这个脚本通常描述了服务的启动、停止、重启等行为。你可以使用shell、C、C、Java等语言来编写这个脚本。 # boa_server_run.sh&#xff1a;#!/bin/bashset -e …