Android Activity的启动流程(Android-10)

news2025/6/16 10:26:56

前言

在Android开发中,我们经常会用到startActivity(Intent)方法,但是你知道startActivity(Intent)后Activity的启动流程吗?今天就专门讲一下最基础的startActivity(Intent)看一下Activity的启动流程,同时由于Launcher的启动后续和这里基本类似,就记录在一起。注意本章都是基于Android-10来讲解的。

客户端startActivity启动流程

startActivity启动流程本质上就是:向ActivityManagerService发送启动请求。由于发送端基本是在当前用户App进程或者Launcher进程(从桌面启动新的App)。除了SystemServer启动Launcher外,都需要借助AIDL发送请求。

1:客户端启动流程

startActivity方法在Activity,startActivity有两个重载方法,最终都会调用startActivityForResult(Intent,int, Bundle)方法,代码如下:

public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
            @Nullable Bundle options) {
     if (mParent == null) {
        //...
        Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity(
                    this, mMainThread.getApplicationThread(), mToken, this,
                    intent, requestCode, options);
        //...
     } else {

        if (options != null) {
             mParent.startActivityFromChild(this, intent, requestCode, options);
         } else {
             mParent.startActivityFromChild(this, intent, requestCode);
         }
     }
}

其中mParent是当前Activity的父Activity,目前我只能想到TabActivity场景下才会存在一个Activity中包含Activity的情况,虽然它已经被Fragment替代了。所以基本上代码都是走mParent == null的流程,调用Instrumentation的execStartActivity方法。当然,在startActivityFromChild 方法里也是如此(不难发现,该方法已经被废弃了),源码如下,俩个重载的方法均已经废弃:

    /** @deprecated */
    @Deprecated
    public void startActivityFromChild(@NonNull Activity child, Intent intent, int requestCode) {
        throw new RuntimeException("Stub!");
    }

    /** @deprecated */
    @Deprecated
    public void startActivityFromChild(@NonNull Activity child, Intent intent, int requestCode, @Nullable Bundle options) {
        throw new RuntimeException("Stub!");
    }

我们继续看execStartActivity方法的关键代码:

public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
     //...
     try {
            intent.migrateExtraStreamToClipData(who);
            intent.prepareToLeaveProcess(who);
            	//备注一
            int result = ActivityTaskManager.getService().startActivity(whoThread,
                    who.getBasePackageName(), who.getAttributionTag(), intent,
                    intent.resolveTypeIfNeeded(who.getContentResolver()), token,
                    target != null ? target.mEmbeddedID : null, requestCode, 0, null, options);
	      //备注二
            checkStartActivityResult(result, intent);
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
        }
        return null;
}

其中,备注一处就是获取ActivityTaskManager服务并向其发送请求。备注二处会根据备注一处的返回结果检查Activity的启动状态,源码如下:

public static void checkStartActivityResult(int res, Object intent) {
        if (!ActivityManager.isStartResultFatalError(res)) {
            //如果没法发生启动错误,直接返回
            return;
        }

        switch (res) {
            case ActivityManager.START_INTENT_NOT_RESOLVED:
            case ActivityManager.START_CLASS_NOT_FOUND:
                if (intent instanceof Intent && ((Intent)intent).getComponent() != null)
                    throw new ActivityNotFoundException(
                            "Unable to find explicit activity class "
                            + ((Intent)intent).getComponent().toShortString()
                            + "; have you declared this activity in your AndroidManifest.xml?");
                throw new ActivityNotFoundException(
                        "No Activity found to handle " + intent);
            case ActivityManager.START_PERMISSION_DENIED:
                throw new SecurityException("Not allowed to start activity "
                        + intent);
            case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:
                throw new AndroidRuntimeException(
                        "FORWARD_RESULT_FLAG used while also requesting a result");
            //...
            default:
                throw new AndroidRuntimeException("Unknown error code "
                        + res + " when starting " + intent);
        }
}

可以看到checkStartActivityResult在Activity里会根据启动失败的原因并抛出异常:注意里面的case ActivityManager.START_CLASS_NOT_FOUND,你一定知道如果没有在AndroidManifest.xml文件里声明Activity,就会抛出异常。没错,就是根据AMS返回的错误码,在这里抛出的异常。

当然,检查并不是在这里。checkStartActivityResult不负责任何启动Activity以及检查配置文件的工作,它之负责根据AMS的返回结果抛出异常。接下来回到备注一,看一下重点工作 ActivityTaskManager的getService方法。

public static IActivityTaskManager getService() {
        return IActivityTaskManagerSingleton.get();
    }

@UnsupportedAppUsage(trackingBug = 129726065)
private static final Singleton<IActivityTaskManager> IActivityTaskManagerSingleton =
            new Singleton<IActivityTaskManager>() {
                @Override
                protected IActivityTaskManager create() {
                    final IBinder b = ServiceManager.getService(Context.ACTIVITY_TASK_SERVICE);
                    return IActivityTaskManager.Stub.asInterface(b);
            }
     };

代码比较简单,一个匿名内部类IActivityTaskManagerSingleton,里面的create 方法返回了一个IActivityTaskManager对象,根据方法里的内容就能猜出它个AIDL,该AIDL文件可在IActivityTaskManager源码里查看。

接着看一个ActivityTaskManagerSingleton.get()方法,get方法在它的父类Singleton中定义:

public abstract class Singleton<T> {

    @UnsupportedAppUsage
    public Singleton() {
    }

    @UnsupportedAppUsage
    private T mInstance;

    protected abstract T create();

    @UnsupportedAppUsage
    public final T get() {
        synchronized (this) {
            if (mInstance == null) {
                mInstance = create();
            }
            return mInstance;
        }
    }
}

很简单的代码,一个懒汉式单例。如果实例为空,就调用create()方法,也就是上文中匿名内部类中复写的方法。到这里,一切就明了了:这里就准备开始跨进程通讯了ActivityTaskManager.getService方法就是客户端(发起创建Activity的当前App进程)向服务端(AMS)获取获取Binder对象用于跨进程通讯。而关键代码就是IActivityTaskManager.Stub.asInterface(b)。(注意IActivityTaskManager只是个AIDL文件,是为了自动生成代码的工具,更多关于AIDL的知识请看Android里的多进程和跨进程通讯方式)

接下来看final IBinder b = ServiceManager.getService(Context.ACTIVITY_TASK_SERVICE);,看它是如何获得Binder实例的:

private static Map<String, IBinder> sCache = new ArrayMap<String, IBinder>();

public static IBinder getService(String name) {
    try {
            IBinder service = sCache.get(name);
            if (service != null) {
            // 有缓存,直接从缓存获取
                return service;
            } else {
            // 没有缓存重新通过ServiceManager获取
                return Binder.allowBlocking(rawGetService(name));
            }
        } catch (RemoteException e) {
            Log.e(TAG, "error in getService", e);
        }
        return null;
}

上面是ServiceManager.getService方法的代码。ServiceManager是Android用来关系系统服务的,Android系统给我们提供了很多服务,每一个服务都有一个对应的类(AMS,WMS等),开发过程中我们就是通过这些类来获得我们所需要的服务。而ServiceManager则通过一个ArrayMap来缓存这些服务。这里也是如此:getService(Context.ACTIVITY_TASK_SERVICE)不难看出是要获得ActivityTaskManagerService的。看一下ActivityTaskManagerService:
在这里插入图片描述

它继承了IActivityTaskManager.Stub!!!就和上文中我们的AIDL文件对应上了。可以肯定ActivityTaskManagerService就是这次AIDL跨进程通讯的服务端了。

为了以防万一,我们还是要追踪一下ActivityTaskManagerService是如何添加到ServiceManager中的。这时,我们就要了解一下Android系统的启动流程了:ActivityTaskManagerService作为系统服务,是在SystemServer中被启动的:

 ActivityTaskManagerService atm = mSystemServiceManager.startService(
                ActivityTaskManagerService.Lifecycle.class).getService();
mActivityManagerService = ActivityManagerService.Lifecycle.startService(
                mSystemServiceManager, atm);

其中SystemServiceManager.startService是通过反射创建Service的:

public <T extends SystemService> T startService(Class<T> serviceClass) {
        try {
            final String name = serviceClass.getName();
            //..
            final T service;
            try {
            		//通过反射创建实例
                Constructor<T> constructor = serviceClass.getConstructor(Context.class);
                service = constructor.newInstance(mContext);
            } catch (InstantiationException ex) {
                throw new RuntimeException("Failed to create service " + name
                        + ": service could not be instantiated", ex);
            } 
            //...
		//启动服务
            startService(service);
            return service;
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
        }
}

代码很简单,就是通过反射创建服务并启动。我们接着看启动服务的startService(service);方法:

public void startService(@NonNull final SystemService service) {
        //..
        try {
            service.onStart();
        } catch (RuntimeException ex) {
            throw new RuntimeException("Failed to start service " + service.getClass().getName()
                    + ": onStart threw an exception", ex);
        }
       
}

这里可以看出,明明我们创建的是ActivityTaskManagerServiceLifecycle,但是上面这段代码却是接收一个SystemService并调用它的onStart方法?原因只有一个,ActivityTaskManagerServiceLifecycle是SystemService的子类:

public static final class Lifecycle extends SystemService {
        private final ActivityTaskManagerService mService;

        public Lifecycle(Context context) {
            super(context);
            mService = new ActivityTaskManagerService(context);
        }

        @Override
        public void onStart() {
            publishBinderService(Context.ACTIVITY_TASK_SERVICE, mService);
            mService.start();
        }
        //...
}

看一下源码也确实如此,那么紧接着看SystemService在Lifecycle中的实现中的一行代码:publishBinderService(Context.ACTIVITY_TASK_SERVICE, mService);。这里有出现了Context.ACTIVITY_TASK_SERVICE。就是在这里将ActivityTaskManagerService添加到ServiceManager中的。查看源码可发现SystemService有多个publishBinderService重载,并最终都调用了:

protected final void publishBinderService(String name, IBinder service,
            boolean allowIsolated, int dumpPriority) {
        ServiceManager.addService(name, service, allowIsolated, dumpPriority);
    }

没错,这里就调用了ServiceManager.addService方法了。最终调用方法在ServiceManager中:



public static void addService(String name, IBinder service, boolean allowIsolated,
            int dumpPriority) {
        try {
            getIServiceManager().addService(name, service, allowIsolated, dumpPriority);
        } catch (RemoteException e) {
            Log.e(TAG, "error in addService", e);
        }
}

private static IServiceManager getIServiceManager() {
        if (sServiceManager != null) {
            return sServiceManager;
        }

        // Find the service manager
        sServiceManager = ServiceManagerNative
                .asInterface(Binder.allowBlocking(BinderInternal.getContextObject()));
        return sServiceManager;
}

至此,服务的获取也明确了,ActivityTaskManagerService就是在系统启动时创建的。

2:服务端接收请求流程

接下来,就是继续看startActivity方法了,客户端Instrumentation调用,服务端ActivityTaskManagerService响应。ActivityTaskManagerService中有多个startActivity重载方法。经过不断调用,最终会调用startActivityAsUser方法:

int startActivityAsUser(IApplicationThread caller, String callingPackage,
            Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
            int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, int userId,
            boolean validateIncomingUser) {
        enforceNotIsolatedCaller("startActivityAsUser");

        userId = getActivityStartController().checkTargetUser(userId, validateIncomingUser,
                Binder.getCallingPid(), Binder.getCallingUid(), "startActivityAsUser");

        return getActivityStartController().obtainStarter(intent, "startActivityAsUser")
                .setCaller(caller)
                .setCallingPackage(callingPackage)
                .setResolvedType(resolvedType)
                .setResultTo(resultTo)
                .setResultWho(resultWho)
                .setRequestCode(requestCode)
                .setStartFlags(startFlags)
                .setProfilerInfo(profilerInfo)
                .setActivityOptions(bOptions)
                .setMayWait(userId)
                .execute(); //启动

    }

注意看ActivityStartController. obtainStarter方法!其中,最后的execute最终是执行的ActivityStarter的execute代码:

 /**
     * Starts an activity based on the request parameters provided earlier.
     * @return The starter result.
     */
    int execute() {
        try {
            // TODO(b/64750076): Look into passing request directly to these methods to allow
            // for transactional diffs and preprocessing.
            if (mRequest.mayWait) {
                return startActivityMayWait(mRequest.caller, mRequest.callingUid,
                        mRequest.callingPackage, mRequest.realCallingPid, mRequest.realCallingUid,
                        mRequest.intent, mRequest.resolvedType,
                        mRequest.voiceSession, mRequest.voiceInteractor, mRequest.resultTo,
                        mRequest.resultWho, mRequest.requestCode, mRequest.startFlags,
                        mRequest.profilerInfo, mRequest.waitResult, mRequest.globalConfig,
                        mRequest.activityOptions, mRequest.ignoreTargetSecurity, mRequest.userId,
                        mRequest.inTask, mRequest.reason,
                        mRequest.allowPendingRemoteAnimationRegistryLookup,
                        mRequest.originatingPendingIntent, mRequest.allowBackgroundActivityStart);
            } else {
                return startActivity(mRequest.caller, mRequest.intent, mRequest.ephemeralIntent,
                        mRequest.resolvedType, mRequest.activityInfo, mRequest.resolveInfo,
                        mRequest.voiceSession, mRequest.voiceInteractor, mRequest.resultTo,
                        mRequest.resultWho, mRequest.requestCode, mRequest.callingPid,
                        mRequest.callingUid, mRequest.callingPackage, mRequest.realCallingPid,
                        mRequest.realCallingUid, mRequest.startFlags, mRequest.activityOptions,
                        mRequest.ignoreTargetSecurity, mRequest.componentSpecified,
                        mRequest.outActivity, mRequest.inTask, mRequest.reason,
                        mRequest.allowPendingRemoteAnimationRegistryLookup,
                        mRequest.originatingPendingIntent, mRequest.allowBackgroundActivityStart);
            }
        } finally {
            onExecutionComplete();
        }
    }

其中,startActivity有三个的重载方法,经过不断的调用,最后会调用:startActivity(final ActivityRecord r, ActivityRecord sourceRecord, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask, ActivityRecord[] outActivity, boolean restrictedBgActivity) 方法:

 private int startActivity(final ActivityRecord r, ActivityRecord sourceRecord,
                IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
                int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
                ActivityRecord[] outActivity, boolean restrictedBgActivity) {
     try {
         mService.mWindowManager.deferSurfaceLayout();
            result = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor,
                    startFlags, doResume, options, inTask, outActivity, restrictedBgActivity);
     } 
 }

大致的流程图如下:
在这里插入图片描述

但是,到了Launcher启动Activity时变终止了,最终在这里合流。

public class ActivityStartController {
     .... 
     ActivityStarter obtainStarter(Intent intent, String reason) {
        return mFactory.obtain().setIntent(intent).setReason(reason);
    }
}

所以,整个客户端的请求和处理流程大致如下:

在这里插入图片描述

接下来,就要开始创建了。

服务端处理流程

我们一步步分析看服务端的处理流程。

1:检验、初始化等基础工作

ActivityStarter有多个startActivity的重载方法。最终执行的关键代码如下:

private int startActivity(IApplicationThread caller, Intent intent, Intent ephemeralIntent,
            String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,
            String callingPackage, int realCallingPid, int realCallingUid, int startFlags,
            SafeActivityOptions options,
            boolean ignoreTargetSecurity, boolean componentSpecified, ActivityRecord[] outActivity,
            TaskRecord inTask, boolean allowPendingRemoteAnimationRegistryLookup,
            PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart) {
        //...
        //创建错误码,默认是成功的
        int err = ActivityManager.START_SUCCESS;
        // Pull the optional Ephemeral Installer-only bundle out of the options early.
        final Bundle verificationBundle
                = options != null ? options.popAppVerificationBundle() : null;

        //..
        final int userId = aInfo != null && aInfo.applicationInfo != null
                ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0;

        //..            
        final int launchFlags = intent.getFlags();

        //..
        //开始处理错误
        //找不到可以处理给定Intent的类
        if (err == ActivityManager.START_SUCCESS && intent.getComponent() == null) {
            err = ActivityManager.START_INTENT_NOT_RESOLVED;
        }

        //找不到Intent中指定的Ativity类,就是对应我们上文中没有在配置文件里配置Activity
        if (err == ActivityManager.START_SUCCESS && aInfo == null) {
            err = ActivityManager.START_CLASS_NOT_FOUND;
        }

        //..

        final ActivityStack resultStack = resultRecord == null
                ? null : resultRecord.getActivityStack();

        //如果错误码被改变,不为成功,则返回给客户端启动失败        
        if (err != START_SUCCESS) {
            if (resultRecord != null) {
                resultStack.sendActivityResultLocked(
                        -1, resultRecord, resultWho, requestCode, RESULT_CANCELED, null);
            }
            SafeActivityOptions.abort(options);
            return err;
        }

        // 检查启动activity的权限
        boolean abort = !mSupervisor.checkStartAnyActivityPermission(intent, aInfo, resultWho,
                requestCode, callingPid, callingUid, callingPackage, ignoreTargetSecurity,
                inTask != null, callerApp, resultRecord, resultStack);
        abort |= !mService.mIntentFirewall.checkStartActivity(intent, callingUid,
                callingPid, resolvedType, aInfo.applicationInfo);
        abort |= !mService.getPermissionPolicyInternal().checkStartActivity(intent, callingUid,
                callingPackage);

        // 检查是否允许后台启动activity
        if (!abort) {
            try {
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                        "shouldAbortBackgroundActivityStart");
                restrictedBgActivity = shouldAbortBackgroundActivityStart(callingUid,
                        callingPid, callingPackage, realCallingUid, realCallingPid, callerApp,
                        originatingPendingIntent, allowBackgroundActivityStart, intent);
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            }
        }

        //..

        // 判断如果有权限需要哦用户审阅才能运行Activity
        if (aInfo != null) {
            // 获取此包使用的某些权限是否需要用户审核才能运行任何应用程序组件
            if (mService.getPackageManagerInternalLocked().isPermissionsReviewRequired(
                    aInfo.packageName, userId)) {
                IIntentSender target = mService.getIntentSenderLocked(
                        ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage,
                        callingUid, userId, null, null, 0, new Intent[]{intent},
                        new String[]{resolvedType}, PendingIntent.FLAG_CANCEL_CURRENT
                                | PendingIntent.FLAG_ONE_SHOT, null);

                Intent newIntent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS);

                //..
            }
        }

    
        //创建ActivityRecord
        ActivityRecord r = new ActivityRecord(mService, callerApp, callingPid, callingUid,
                callingPackage, intent, resolvedType, aInfo, mService.getGlobalConfiguration(),
                resultRecord, resultWho, requestCode, componentSpecified, voiceSession != null,
                mSupervisor, checkedOptions, sourceRecord);
        //..
        //创建ActivityStack
        final ActivityStack stack = mRootActivityContainer.getTopDisplayFocusedStack();
        //..
        // 进入启动activity的流程(
        final int res = startActivity(r, sourceRecord, voiceSession, voiceInteractor, startFlags,
                true /* doResume */, checkedOptions, inTask, outActivity, restrictedBgActivity);
        mSupervisor.getActivityMetricsLogger().notifyActivityLaunched(res, outActivity[0]);
        return res;
    }

该方法主要做了一下两件事:

  1. 执行初步的配置、权限等基本的检查工作;
  2. 创建ActivityRecord对象,并获取ActivityStack对象。

第一步的检查包括Intent的检查,包括里面Activity的类型检查。还要进行权限,是否允许后台启动等检查。第二部主要是创建Activity启动的辅助类,ActivityRecord负责存储一些关于Activity组件的相关信息。是应用层Activity组件在AMS中的代表,每一个在应用中启动的Activity,在AMS中都有一个ActivityRecord实例来与之对应。ActivityRecord则由TaskRecord通过一个ArrayList管理,而ActivityStack则用来管理TaskRecord。它们之间的关系大致如下图片所示:

在这里插入图片描述
紧接着又会调用startActivity的重载方法:

private int startActivity(final ActivityRecord r, ActivityRecord sourceRecord,
                IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
                int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
                ActivityRecord[] outActivity, boolean restrictedBgActivity) {
        int result = START_CANCELED;
        final ActivityStack startedActivityStack;
        try {
            //准备启动,暂停页面布局绘制
            mService.mWindowManager.deferSurfaceLayout();
            result = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor,
                    startFlags, doResume, options, inTask, outActivity, restrictedBgActivity);
        } finally {
            final ActivityStack currentStack = r.getActivityStack();
            startedActivityStack = currentStack != null ? currentStack : mTargetStack;

            if (ActivityManager.isStartResultSuccessful(result)) {
                if (startedActivityStack != null) {
                    //处理Activity信息,并确保显示,更新方向等配置信息
                    final ActivityRecord currentTop =
                            startedActivityStack.topRunningActivityLocked();
                    if (currentTop != null && currentTop.shouldUpdateConfigForDisplayChanged()) {
                        mRootActivityContainer.ensureVisibilityAndConfig(
                                currentTop, currentTop.getDisplayId(),
                                true /* markFrozenIfConfigChanged */, false /* deferResume */);
                    }
                }
            } else {
                //如果失败了,请取消Activity与栈的关联。
                //让Activity处于不完整状态可能会导致问题,
                //例如在没有窗口容器的情况下执行操作。
                final ActivityStack stack = mStartActivity.getActivityStack();
                if (stack != null) {
                    stack.finishActivityLocked(mStartActivity, RESULT_CANCELED,
                            null /* intentResultData */, "startActivity", true /* oomAdj */);
                }

                //执行失败的逻辑,移除堆信息
                if (startedActivityStack != null && startedActivityStack.isAttached()
                        && startedActivityStack.numActivities() == 0
                        && !startedActivityStack.isActivityTypeHome()) {
                    startedActivityStack.remove();
                }
            }
            //重新开始绘制
            mService.mWindowManager.continueSurfaceLayout();
        }

        postStartActivityProcessing(r, result, startedActivityStack);

        return result;
    }

代码的详细解读写在了注释里面。这里关键的步骤就是调用startActivityUnchecked。

2:任务栈的创建和获取

private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
            ActivityRecord[] outActivity, boolean restrictedBgActivity) {
        //初始化Activity配置    
        //此处会直接将mDoResume设置为true
        //在该方法末尾会用到
        setInitialState(r, options, inTask, doResume, startFlags, sourceRecord, voiceSession,
                voiceInteractor, restrictedBgActivity);

        final int preferredWindowingMode = mLaunchParams.mWindowingMode;
        //计算Activity的Flag,也就是mLaunchFlags
        computeLaunchingTaskFlags();
        computeSourceStack();
        
        //将mLaunchFlags设置给mIntent
        mIntent.setFlags(mLaunchFlags);

        //判断Activity是否可以放入到一个已经存在的任务栈
        //如果返回null则标识需要新的任务栈
        ActivityRecord reusedActivity = getReusableIntentActivity();

        //..

        //如果有可以复用的ActivityRecord
        if (reusedActivity != null) {
            //..
            //判断是否需要清空任务栈栈顶
            final boolean clearTopAndResetStandardLaunchMode =
                    (mLaunchFlags & (FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED))
                            == (FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
                    && mLaunchMode == LAUNCH_MULTIPLE;

            //将ActivityRecord加入到TaskRecord
            if (mStartActivity.getTaskRecord() == null && !clearTopAndResetStandardLaunchMode) {
                mStartActivity.setTask(reusedActivity.getTaskRecord());
            }

           
            // 判断是否要清除TaskRecord
            if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
                    || isDocumentLaunchesIntoExisting(mLaunchFlags)
                    || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
                //获取可复用的TaskRecord
                final TaskRecord task = reusedActivity.getTaskRecord();

                
                final ActivityRecord top = task.performClearTaskForReuseLocked(mStartActivity,
                        mLaunchFlags);

                //重新进行赋值
                if (reusedActivity.getTaskRecord() == null) {
                    reusedActivity.setTask(task);
                }

                //..
            }

            mRootActivityContainer.sendPowerHintForLaunchStartIfNeeded
                    (false /* forceSend */, reusedActivity);
            //将被复用的Task防置到前台
            reusedActivity = setTargetStackAndMoveToFrontIfNeeded(reusedActivity);

            final ActivityRecord outResult =
                    outActivity != null && outActivity.length > 0 ? outActivity[0] : null;

            // When there is a reused activity and the current result is a trampoline activity,
            // set the reused activity as the result.
            if (outResult != null && (outResult.finishing || outResult.noDisplay)) {
                outActivity[0] = reusedActivity;
            }

            if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) != 0) {
                //START_FLAG_ONLY_IF_NEEDED:只需要使task放到前台,无需去启动,(eg. 从桌面点击图标恢复task的情况)。
                resumeTargetStackIfNeeded();
                return START_RETURN_INTENT_TO_CALLER;
            }

            if (reusedActivity != null) {
                setTaskFromIntentActivity(reusedActivity);

                if (!mAddingToTask && mReuseTask == null) {
                     //将Stack调用到前台
                    resumeTargetStackIfNeeded();
                    //..

                    return mMovedToFront ? START_TASK_TO_FRONT : START_DELIVERED_TO_TOP;
                }
            }
        }

        // ..

        // 接下来判断要启动的Activity和当前栈顶的是否一样
        //并且判断时序需要启动
        final ActivityStack topStack = mRootActivityContainer.getTopDisplayFocusedStack();
        final ActivityRecord topFocused = topStack.getTopActivity();
        final ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(mNotTop);
        //判断是否需要启动Activity
        final boolean dontStart = top != null && mStartActivity.resultTo == null
                && top.mActivityComponent.equals(mStartActivity.mActivityComponent)
                && top.mUserId == mStartActivity.mUserId
                && top.attachedToProcess()
                && ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
                || isLaunchModeOneOf(LAUNCH_SINGLE_TOP, LAUNCH_SINGLE_TASK))
                && (!top.isActivityTypeHome() || top.getDisplayId() == mPreferredDisplayId);

        if (dontStart) {
            //无需重启,直接将当前activity显示到前台
            topStack.mLastPausedActivity = null;
            if (mDoResume) {
                mRootActivityContainer.resumeFocusedStacksTopActivities();
            }
            //..

            //返回启动结果,结束
            return START_DELIVERED_TO_TOP;
        }

        boolean newTask = false;
        final TaskRecord taskToAffiliate = (mLaunchTaskBehind && mSourceRecord != null)
                ? mSourceRecord.getTaskRecord() : null;

        
        int result = START_SUCCESS;
        // 判断是否需要一个新的任务栈
        // 并分情况设置任务栈
        if (mStartActivity.resultTo == null && mInTask == null && !mAddingToTask
                && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
            newTask = true;
            result = setTaskFromReuseOrCreateNewTask(taskToAffiliate);
        } else if (mSourceRecord != null) {
            result = setTaskFromSourceRecord();
        } else if (mInTask != null) {
            result = setTaskFromInTask();
        } else {
            result = setTaskToCurrentTopOrCreateNewTask();
        }
        //根据设置任务栈的返回值判断是否发生错误
        //如果发生了错误,那么就直接返回
        if (result != START_SUCCESS) {
            return result;
        }


        //..
        //这里我们只关注mDoResume为true的情况
        //因为在我们上文中startActivity方法中有如下调用:
        //final int res = startActivity(r, sourceRecord, voiceSession, voiceInteractor, startFlags,
        //        true /* doResume */, checkedOptions, inTask, outActivity, restrictedBgActivity);
        //其中的doResume,在该方法开头第一步,就会将mDoResume设置为true
        if (mDoResume) {
            final ActivityRecord topTaskActivity =
                    mStartActivity.getTaskRecord().topRunningActivityLocked();
            //如果当前任务栈没有获得焦点走这里
            //我们按照主流程会走else        
            if (!mTargetStack.isFocusable()
                    || (topTaskActivity != null && topTaskActivity.mTaskOverlay
                    && mStartActivity != topTaskActivity)) {
                //..
            } else {
            
                if (mTargetStack.isFocusable()
                        && !mRootActivityContainer.isTopDisplayFocusedStack(mTargetStack)) {
                    mTargetStack.moveToFront("startActivityUnchecked");
                }
                mRootActivityContainer.resumeFocusedStacksTopActivities(
                        mTargetStack, mStartActivity, mOptions);
            }
        } else if (mStartActivity != null) {
            //..
        }
        //..

        return START_SUCCESS;
    }

该方法代码量很大,关键的节点已经写在注释里面了。但是它的工作其实也很简单:主要就是准备好任务栈,并且将acticityRecord加入到TaskRecord中。而它的复杂支出就在于复用等一系列的判断和准备工作。

这里补充一下getReusableIntentActivity判断任务栈复用的关键代码:

 private ActivityRecord getReusableIntentActivity() {
        //android会尽量的将Activity放入到已经存在的任务栈中
        //尤其是使用了singleTask和singleInstance模式启动的Activity
        //而对于使用了FLAG_ACTIVITY_NEW_TASK也会尽量保持这种风格


        //以下集中情况才会允许插入到已经存在的任务栈里
        //standard启动模式而且intent且添加FLAG_ACTIVITY_NEW_TASK
        //使用singleTask和singleInstance启动的Activity
        boolean putIntoExistingTask = ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0 &&
                (mLaunchFlags & FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
                || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK);
        
        ActivityRecord intentActivity = null;
        if (mOptions != null && mOptions.getLaunchTaskId() != -1) {
            final TaskRecord task = mRootActivityContainer.anyTaskForId(mOptions.getLaunchTaskId());
            intentActivity = task != null ? task.getTopActivity() : null;
        } else if (putIntoExistingTask) {
            //..
            //省略复用的逻辑
        }

        //不能复用桌面Activity
        if (intentActivity != null
                && (mStartActivity.isActivityTypeHome() || intentActivity.isActivityTypeHome())
                && intentActivity.getDisplayId() != mPreferredDisplayId) {
            intentActivity = null;
        }

        return intentActivity;
    }

此时,任务栈已经准备好了。紧接着关注一下 mRootActivityContainer.resumeFocusedStacksTopActivities(mTargetStack, mStartActivity, mOptions);也就是RootActivityContainer.resumeFocusedStacksTopActivities方法:

boolean resumeFocusedStacksTopActivities(
            ActivityStack targetStack, ActivityRecord target, ActivityOptions targetOptions) {

        //..
        boolean result = false;
        if (targetStack != null && (targetStack.isTopStackOnDisplay()
                || getTopDisplayFocusedStack() == targetStack)) {
            //如果Activity的目标栈正处于前台,那么直接调用方法
            //将ActivityRecord(target)放置到顶部
            result = targetStack.resumeTopActivityUncheckedLocked(target, targetOptions);
        }

        for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
            boolean resumedOnDisplay = false;
            final ActivityDisplay display = mActivityDisplays.get(displayNdx);
            //..

            //如果目标栈没有显示在屏幕上
            //则获取当前的ActivityStack,将ActivityRecord(target)放置到顶部
            if (!resumedOnDisplay) {
                final ActivityStack focusedStack = display.getFocusedStack();
                if (focusedStack != null) {
                    focusedStack.resumeTopActivityUncheckedLocked(target, targetOptions);
                }
            }
        }

        return result;
    }

该段代码的目的就是为了让目标的Activity的表述ActivityRecord(target)放置在前台。当然,是通过ActivityStack.resumeTopActivityUncheckedLocked方法实现的。注意此时:放置我们要启动Activity的ActivityRecord的TaskRecord已经放在了前台,同时该ActivityRecord处在顶部。

3:通知ActivityThread创建

接下来关注关键的步骤,调用了resumeTopActivityInnerLocked方法:


private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) {
        if (!mService.isBooting() && !mService.isBooted()) {
            // Not ready yet!
            return false;
        }
        //找到当前Task里最顶层的ActivityRecord
        //注意不是Activity的任务栈,而是ActivityTask
        //可以理解为是启动我们Activity(B)的Activity(A)
        //A可能是不同App的,比如Launcher,也可能是和B同一应用内
        //还记得上段代码的任务吗:放置我们要启动Activity的ActivityRecord的TaskRecord已经放在了前台
        //同时该ActivityRecord处在顶部。
        //这个next就是我们要启动的了

        ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */);
        //..

        //判断是否还有其他RESUMED状态的Activity
        if (mResumedActivity == next && next.isState(RESUMED)
                && display.allResumedActivitiesComplete()) {
            // Make sure we have executed any pending transitions, since there
            // should be nothing left to do at this point.
            executeAppTransition(options);
            if (DEBUG_STATES) Slog.d(TAG_STATES,
                    "resumeTopActivityLocked: Top activity resumed " + next);
            return false;
        }
        //..
        //重点一
        //调用ActivityDisplay.pauseBackStacks方法暂停后台Stack的Activity
        boolean pausing = getDisplay().pauseBackStacks(userLeaving, next, false);
        //暂停掉当前Stacks里处于活动状态的Activity
        if (mResumedActivity != null) {
            if (DEBUG_STATES) Slog.d(TAG_STATES,
                    "resumeTopActivityLocked: Pausing " + mResumedActivity);
            pausing |= startPausingLocked(userLeaving, false, next, false);
        }
        

        //判断要启动的Activity所要运行的进程是否已经存在
        if (next.attachedToProcess()) {

            try {
                //重点二
                //当Activity之前启动过,直接resume
                final ClientTransaction transaction =
                        ClientTransaction.obtain(next.app.getThread(), next.appToken);
               //..
                mService.getLifecycleManager().scheduleTransaction(transaction);

                if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Resumed "
                        + next);
            } catch (Exception e) {
            	   // Whoops, need to restart this activity!
               //..
               //重点三
               mStackSupervisor.startSpecificActivityLocked(next, true, false);
            }

            //.. 
        } else {
            //进程没有运行,走启动进程的流程
            
            //..
		//重点四
            //创建进程的的逻辑就不展开了,创建完成之后之后最终还是会走后续的流程
            mStackSupervisor.startSpecificActivityLocked(next, true, true);
        }

        return true;
    }

代码注释比较详细了,这里解读一下关键点:

  1. 重点一:暂停掉处在前台的Activity,该步骤最终是通过startPausingLocked方法实现的,在这里调用了当前正在显示的Activity的onPause方法。
  2. 重点二:如果之前Activity启动过,直接resume,否则的话抛出异常,进入重点三
  3. 重点三:向ActivityThread发送创建Activity的请求。
  4. 重点四:如果Activity所要运行的线程还未创建,那么就会请求Zygote创建进程(创建过程就不展开说了)。而在进程创建完之后。Zygote会通过Binder通知ATMS(ActivityTaskManagerService),紧接着又会通过类似重点二的方式请求ActivityThread创建。

接着,我们看看是如何通知ActivityThread创建Activity的。分析重点三处的代码,它会通过ActivityStackSupervisor.startSpecificActivityLocked调用ActivityStackSupervisor.realStartActivityLocked方法:

boolean realStartActivityLocked(ActivityRecord r, WindowProcessController proc,
            boolean andResume, boolean checkConfig) throws RemoteException {

        //..
            try {
                //..

                // 创建启动Activity的任务
                final ClientTransaction clientTransaction = ClientTransaction.obtain(
                        proc.getThread(), r.appToken);

                final DisplayContent dc = r.getDisplay().mDisplayContent;
                // 非常重要:添加 LaunchActivityItem callback(备注一)
                clientTransaction.addCallback(LaunchActivityItem.obtain(new Intent(r.intent),
                        System.identityHashCode(r), r.info,
                        mergedConfiguration.getGlobalConfiguration(),
                        mergedConfiguration.getOverrideConfiguration(), r.compat,
                        r.launchedFromPackage, task.voiceInteractor, proc.getReportedProcState(),
                        r.icicle, r.persistentState, results, newIntents,
                        dc.isNextTransitionForward(), proc.createProfilerInfoIfNeeded(),
                                r.assistToken));

                //..
                // 执行启动Activity的任务
                mService.getLifecycleManager().scheduleTransaction(clientTransaction);

                //
            } //..

        return true;
    }

请记住备注一,这里添加了个LaunchActivityItem作为CallBack,它很重要。但是在讲解它之前,我们首先看ClientTransaction.obtain方法:

public static ClientTransaction obtain(IApplicationThread client, IBinder activityToken) {
        ClientTransaction instance = ObjectPool.obtain(ClientTransaction.class);
        if (instance == null) {
            instance = new ClientTransaction();
        }
        instance.mClient = client;
        instance.mActivityToken = activityToken;

        return instance;
    }

代码很简单,就是初始化ClientTransaction,关键点在于mClient成员变量。它是一个IApplicationThread类型的,没错,它是AIDL。而它的具体实现在实现是ActivityThread.ApplicationThread,是ActivityThread的一个内部类。紧接着看mService.getLifecycleManager().scheduleTransaction(transaction);,这段代码调用了ClientLifecycleManager.scheduleTransaction:

void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
	  //..
        transaction.schedule();
        //..
    }

紧接着调用ClientTransaction.schedule方法

public void schedule() throws RemoteException {
        mClient.scheduleTransaction(this);
    }

而这个mClient,就是我们的AIDL客户端(AMS)。使用它向我们的服务端(ActivityThread)发送请求:它实现就在ActivityThread$ApplicationThread中。

ActivityThread接受请求开始创建

接下来就是ActivityThread处理请求了:

@Override
        public void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
            ActivityThread.this.scheduleTransaction(transaction);
        }

由于 ActivityThread继承自ClientTransactionHandler,所以scheduleTransaction会调用ClientTransactionHandler. scheduleTransaction方法:

void scheduleTransaction(ClientTransaction transaction) {
        transaction.preExecute(this);
        sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction);
    }

而sendMessage定义在ActivityThread中,它有多个重载方法,最终会执行:

private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
        if (DEBUG_MESSAGES) {
            Slog.v(TAG,
                    "SCHEDULE " + what + " " + mH.codeToString(what) + ": " + arg1 + " / " + obj);
        }
        Message msg = Message.obtain();
        msg.what = what;
        msg.obj = obj;
        msg.arg1 = arg1;
        msg.arg2 = arg2;
        if (async) {
            msg.setAsynchronous(true);
        }
        mH.sendMessage(msg);
    }

很明显这是一个Handler,接着看mH的具体实现:

public void handleMessage(Message msg) {
    //..
    switch (msg.what) {
        //..
        //执行生命周期的调度工作
         case EXECUTE_TRANSACTION:
            final ClientTransaction transaction = (ClientTransaction) msg.obj;
            mTransactionExecutor.execute(transaction);
            //..
            break;
        //..
    }

    //..
}

很明显,AMS使用Binder请求ActivityThread创建Activity,然后ActivityThread通过Handler机制向TransactionExecutor发送创建的请求。

紧接着看TransactionExecutor的execute方法:

public void execute(ClientTransaction transaction) {
        //..
        executeCallbacks(transaction);

        //..
}

关键方法就是调用了executeCallbacks:

    public void executeCallbacks(ClientTransaction transaction) {
    	  final List<ClientTransactionItem> callbacks = transaction.getCallbacks();
        //..
        final int size = callbacks.size();
        for (int i = 0; i < size; ++i) {
            //..

            item.execute(mTransactionHandler, token, mPendingActions);
            item.postExecute(mTransactionHandler, token, mPendingActions);
            //..
        }
    }

没错,在这里就用到了CallBack了,这里面必定有刚刚备注一里提到的LaunchActivityItem。我们直接看它的execute方法:

 public void execute(ClientTransactionHandler client, IBinder token,
            PendingTransactionActions pendingActions) {
        //..
        client.handleLaunchActivity(r, pendingActions, null /* customIntent */);
        Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
    }

调用了ClientTransactionHandler的handleLaunchActivity方法。还记得上文说的ActivityThread继承自ClientTransactionHandler。其实这个ClientTransactionHandler就可以认为是ActivityThread,其中handleLaunchActivity的实现如下:

public Activity handleLaunchActivity(ActivityClientRecord r,
            PendingTransactionActions pendingActions, Intent customIntent) {
      
        final Activity a = performLaunchActivity(r, customIntent);

}

只留下关键代码,调用了performLaunchActivity方法:

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ActivityInfo aInfo = r.activityInfo;
        //..
        //创建上下文环境
        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        try {
        	//重点一:
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess();
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to instantiate activity " + component
                    + ": " + e.toString(), e);
            }
        }

        try {
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);

            //..

            if (activity != null) {
                //..
                //重点二:
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback,
                        r.assistToken);

            
                //设置主题
                int theme = r.activityInfo.getThemeResource();
                if (theme != 0) {
                    activity.setTheme(theme);
                }

                //重点三:
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
                //..
            }
           //..

        } catch (SuperNotCalledException e) {
            throw e;

        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to start activity " + component
                    + ": " + e.toString(), e);
            }
        }

        return activity;
    }

最最最重要的来了,我们关注代码注释里的几点: 重点一:通过反射创建Activity; 重点二:调用了Activity.attach()方法; 重点三:通过Instrumentation对象执行Activity的onCreate()方法。

到这里,Activity就算是创建完成了。

总结

到这里,Activity的创建就完成了。放一个极简版序列图:

在这里插入图片描述
其中Ams为ActivityManagerService,Android最近一些较新的版本为了解决ActivityManagerService臃肿的问题引入了ActivityTaskManagerService来分担Ams工作,就是图中的Atms。

Activity的关系大致如下图:

如上图所示,除了启动Activity的进程和Activity运行的进程可能为同一进程,它们互为不同进程。当要创建一个Activity时:

  1. 如果Activity所依赖的进程已经存在,那么流程为:Step:1->2。如果启动进程和运行进程是一个,那么该过程只涉及到两个进程(启动进程和AMS进程),否则就是三个(启动进程、运行进程和AMS进程)
  2. 如果Activity所依赖的进程不存在,那么流程为:Step:1->3->4->2。

本文只讲述了请求创建的流程,其中省略了很多其他操作。包括,Activity生命周期的调用(都是由Instrumentation对象来调用的)、进程的创建、activity的attach等其他重要操作。相关知识可以由于能力有限,大家可自行查看Android源码自行了解。

最后推荐一些比较好的博客,供参考学习:

  1. ActivityRecord、TaskRecord、ActivityStack以及Activity启动模式详解
  2. 四大组件之ActivityRecord
  3. 深入理解ActivityRecord、TaskRecord、ActivityStack的作用及关系
  4. framework之Activity启动流程
  5. Android启动流程源码解析

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

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

相关文章

竞赛保研 多目标跟踪算法 实时检测 - opencv 深度学习 机器视觉

文章目录 0 前言2 先上成果3 多目标跟踪的两种方法3.1 方法13.2 方法2 4 Tracking By Detecting的跟踪过程4.1 存在的问题4.2 基于轨迹预测的跟踪方式 5 训练代码6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习多目标跟踪 …

工业平板定制方案_基于联发科、紫光展锐平台的工业平板电脑方案

工业平板主板采用联发科MT6762平台方案&#xff0c;搭载Android 11.0操作系统&#xff0c; 主频最高2.0GHz&#xff0c;效能有大幅提升;采用12nm先进工艺&#xff0c;具有低功耗高性能的特点。 该工业平板主板搭载了IMG GE8320图形处理器&#xff0c;最高主频为680MHz, 支持108…

Vue-24、Vue过滤器

1、效果 2、过滤器实现 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>过滤器</title><script type"text/javascript" src"https://cdn.jsdelivr.net/npm/vue2/dist/vue.…

有序矩阵中第 K 小的元素

题目链接 有序矩阵中第 K 小的元素 题目描述 注意点 每行和每列元素均按升序排序找到一个内存复杂度优于 O(n) 的解决方案 解答思路 使用二分查找&#xff0c;思路为&#xff1a; &#xff08;1&#xff09;因为左上角的元素值更小&#xff0c;右下角的元素值更大&#xf…

dp专题13 零钱兑换II

本题链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 题目&#xff1a; 思路&#xff1a; 根据题意&#xff0c;这是一道很裸的背包问题&#xff0c;其中这里是返回 背包方案数 的。 我们可以直接推出公式 &#xff1a; dp [ j ] dp[ j - coins[ i ] ] 在我之前…

User-Agent(用户代理)是什么?

User-Agent&#xff08;用户代理&#xff09;是什么&#xff1f; User-Agent 即用户代理&#xff0c;简称“UA”&#xff0c;它是一个特殊字符串头。网站服务器通过识别 “UA”来确定用户所使用的操作系统版本、CPU 类型、浏览器版本等信息。而网站服务器则通过判断 UA 来给客…

onlyoffice源码编译

环境准备 官网要求CPU dual core 2 GHz or better RAM at least 2 GB, but depends of the host OS. More is better HDD at least 40 GB of free space SWAP at least 4 GB, but depends of the host OS. More is better SoftwareOS 64-bit Ubuntu 16.04 The solution has be…

Linux命令之pwd,cd,ls,cat,more,less,head,tail文件目录类命令的使用

一、实验题 1、在桌面打开终端&#xff0c;查看当前目录 2、改变目录位置至当前目录的父目录 3、改变目录位置至用户的家目录 4、利用绝对路径改变目录到/usr/local目录下 5、列出当前目录下的文件及目录 6、列出包括以“.”开始的隐藏文件在内的所有文件 7、列出当前目录下所…

SQL实践:利用tag检索文件的多种情况讨论(二)

在上一篇文章SQL实践&#xff1a;利用tag检索文件的多种情况讨论中&#xff0c;我们介绍了在使用外键的方式为数据关联tag后&#xff0c;如何筛选&#xff1a; 如何筛选包含某一个tag的数据如何筛选包含且只包含某一个tag的数据如何筛选包含多个指定tag的数据 这篇文章主要是…

Mysql 安装通过mysql installer安装+配置环境+连接可视化工具

注意&#xff1a;不适合纯小白&#xff0c;小白建议移步别的大佬MySQL详细安装教程 目录 注意&#xff1a;不适合纯小白&#xff0c;小白建议移步别的大佬MySQL详细安装教程 前言 准备工作 一、Mysql下载 二、MySQL installer 安装以及系统环境配置 三、检验MySQL 四、可…

docker-consul部署

目录 一、环境 二、consul服务器 三、registrator服务器 四、consul-template 一、环境 consul服务器 192.168.246.10 运行consul服务、nginx服务、consul-template守护进程 registrator服务器 192.168.246.11 运行registrator容器、运行ngi…

Docker RTMP服务器搭建与视频流推送示例(流媒体服务器tiangolo/nginx-rtmp,推流客户端ffmpeg)

文章目录 RTMP服务器搭建与视频流推送第一部分&#xff1a;搭建RTMP服务器&#xff08;流媒体服务器&#xff09;1.1 安装Docker1.2 搭建RTMP服务器 第二部分&#xff1a;使用ffmpeg进行视频推流&#xff08;推流客户端&#xff09;2.1 安装ffmpeg2.2 使用ffmpeg推流 第三部分&…

matlab 直道转向时方向盘最小转角算法

1、内容简介 略 33-可以交流、咨询、答疑 2、内容说明 汽车主动转向&#xff0c;直道转向时方向盘最小转角算法&#xff0c;一个m脚本和simulink的计算结果 略 3、仿真分析 略 4、参考论文 汽车主动转向关键技术研究

ElasticSearch概述+SpringBoot 集成ES

ES概述 开源的、高扩展的、分布式全文检索引擎【站内搜索】 解决问题 1.搜索词是一个整体时&#xff0c;不能拆分&#xff08;mysql整体连续&#xff09; 2.效率会低&#xff0c;不会用到索引&#xff08;mysql索引失效&#xff09; 解决方式 进行数据的存储&#xff08;只存储…

自动驾驶轨迹规划之碰撞检测(二)

欢迎大家关注我的B站&#xff1a; 偷吃薯片的Zheng同学的个人空间-偷吃薯片的Zheng同学个人主页-哔哩哔哩视频 (bilibili.com) 目录 1.基于凸优化 2.具身足迹 3. ESDF 自动驾驶轨迹规划之碰撞检测&#xff08;一&#xff09;-CSDN博客 大家可以先阅读之前的博客 1.基于…

FFMPEG解码实时流,支持cpu、gpu解码

官网下载的ffmpeg目前只能下载到X64版本的库&#xff0c;具体编译请参考windows编译ffmpeg源码&#xff08;32位库&#xff09;_windows 32位ffmpeg动态库-CSDN博客 直接上代码 int VideoDecodeModule::Open(std::string strUrl) {AVFormatContext *pFormatCtx nullptr;AVCo…

nestjs之JWT认证实现流程

nestjs的jwt认证利用了 Passport.js 的认证机制。要根据这个源码实现您自己的 AuthGuard&#xff0c;需要理解几个关键部分&#xff1a;如何集成 Passport.js、如何处理认证结果&#xff0c;以及如何使用 NestJS 的依赖注入系统。 先自定义一个策略函数类 // wsy.strategy.ts …

idea 安装免费Ai工具 codeium

目录 概述 ide安装 使用 chat问答 自动写代码 除此外小功能 概述 这已经是我目前用的最好免费的Ai工具了&#xff0c;当然你要是有钱最好还是用点花钱的&#xff0c;比如copilot&#xff0c;他可以在idea全家桶包括vs&#xff0c;还有c/c的vs上运行&#xff0c;还贼强&am…

宝塔发布网站问题汇总和记录

1、添加网站站点后打不开 解决办法&#xff0c;关闭防跨站攻击2 2、laravel项目部署到linux的时候出现The stream or file "/home/www/storage/logs/laravel.log" could not be opened in append mode 给目录加权限 chmod -R 777 storage 3、Class "Redis"…

5G阅信在汽车销售行业的应用与优势

5G阅信在汽车销售行业的应用与优势包括&#xff1a;提升客户体验&#xff0c;提供更快速、稳定的网络服务&#xff1b;实时数据传输&#xff0c;更好地了解客户需求&#xff1b;增强现实应用&#xff0c;提供更真实、直观的购车体验&#xff1b;创新营销方式&#xff0c;如短视…