我们已知要想启动一个Activity的时候直接调用startActivity()或ActivityForResult()方法,实际上startActivity()也是调用了startActivityForResult(),因此,我们从startActivity()开始一步一步的分析。
一、startActivity()的执行过程
首先来看下startActivity()源码:
@Override public void startActivity(Intent intent) { startActivity(intent, null); } @Override public void startActivity(Intent intent, Bundle options) { if (options != null) { startActivityForResult(intent, -1, options); } else { // Note we want to go through this call for compatibility with // applications that may have overridden the method. startActivityForResult(intent, -1); } } public void startActivityForResult(Intent intent, int requestCode) { startActivityForResult(intent, requestCode, null); } public void startActivityForResult(Intent intent, int requestCode, Bundle options) { if (mParent == null) { Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity( this, mMainThread.getApplicationThread(), mToken, this, intent, requestCode, options); if (ar != null) { mMainThread.sendActivityResult( mToken, mEmbeddedID, requestCode, ar.getResultCode(), ar.getResultData()); } if (requestCode >= 0) { // If this start is requesting a result, we can avoid making // the activity visible until the result is received. Setting // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the // activity hidden during this time, to avoid flickering. // This can only be done when a result is requested because // that guarantees we will get information back when the // activity is finished, no matter what happens to it. mStartedActivity = true; } } else { if (options != null) { mParent.startActivityFromChild(this, intent, requestCode, options); } else { // Note we want to go through this method for compatibility with // existing applications that may have overridden it. mParent.startActivityFromChild(this, intent, requestCode); } } }可以看到我们客户端在启动一个Activity时最多会涉及相关的四个方法,就是当我们直接调用startActivity()时,通过层层调用最后调用了startActivityForResult(Intent intent, int requestCode, Bundle options)这个方法,下面我们就重点研究这个方法:
先挑简单的说,注意if (requestCode >= 0)判断,当我们调用startActivity()时,默认的requestCode就是-1,所以此时mStartActivity=false,也就是告诉系统当那个被启动的Activity被finish()后前一个Activity不需要收到消息,即onActivityResult()里不会收到任何消息,所以当我们调用startActivityForResult()的时候其requestCode一定要保证大于等于0,否则将不会收到预期效果。
上面简单说了下startActivity()和startActivityForResult()在使用时注意的细节,接下来就说下它到底是怎么启动的。
我们知道在startActivityForResult()中首先对 if (mParent == null)进行判断,这个mParent是什么东东呢?它实际上也是一个Activity实例,现在假设我们要启动的一个Activity是自app安装以来首次启动,那么此时mParent为null,他就会执行以下代码:
if (mParent == null) { Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity( this, mMainThread.getApplicationThread(), mToken, this, intent, requestCode, options); if (ar != null) { mMainThread.sendActivityResult( mToken, mEmbeddedID, requestCode, ar.getResultCode(), ar.getResultData()); } if (requestCode >= 0) { // If this start is requesting a result, we can avoid making // the activity visible until the result is received. Setting // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the // activity hidden during this time, to avoid flickering. // This can only be done when a result is requested because // that guarantees we will get information back when the // activity is finished, no matter what happens to it. mStartedActivity = true; } }首先执行的就是Instrumentation.ActivityResult ar =mInstrumentation.execStartActivity(this,mMainThread.getApplicationThread(), mToken, this,intent, requestCode, options);看起来比较复杂,我们把它剥开来分析,首先是要执行mInstrumentation.execStartActivity(....),看名字直译过来就是“执行启动Activity”,那么mInstrumentation参数是哪里来的呢?什么时候被赋值的呢?我们先不急,先看下execStartActivity()中一个比较重要的参数就是mMainThread.getApplicationThread(),那么这个mMainThread又是从哪里来的呢?什么时候被赋值的呢?其实我们在Activity源码里可以找到蛛丝马迹,mInstrumentation和mMainThread都是在Activity的attach()方法中被赋值的,mMainThread也就是整个app的主线程(UI线程),这个是本文稍后的重点讲解,而mInstrumentation就是一个Instrumentation实例,Instrumentation按照其源码注释的意思就是:
/** * Base class for implementing application instrumentation code. When running * with instrumentation turned on, this class will be instantiated for you * before any of the application code, allowing you to monitor all of the * interaction the system has with the application. An Instrumentation * implementation is described to the system through an AndroidManifest.xml's * <instrumentation> tag. */其大意就是这是为实现应用“应用仪表”代码的基类(这么翻译好拗口啊。。),当用这个仪表打开时,这个类会在我们应用的所有代码之前被实例化,允许我们监督所有的系统和本应用之间的交互,对于Instrumentaion的实现,我们需要在AndroidMainfest.xml文件里添加描述。-----以上是直接翻译过来的,用我们的话来说,这个类呢就是用来监督别人的,这种手段用在单元测试方面比较多。
上面说了那么多废话,话说回来我们该看看attach()方法了。以下便是源码:
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) { attachBaseContext(context); mFragments.attachActivity(this); mWindow = PolicyManager.makeNewWindow(this); mWindow.setCallback(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(); mMainThread = aThread; mInstrumentation = instr; mToken = token; mIdent = ident; mApplication = application; mIntent = intent; mComponent = intent.getComponent(); mActivityInfo = info; mTitle = title; mParent = parent; mEmbeddedID = id; mLastNonConfigurationInstances = lastNonConfigurationInstances; mWindow.setWindowManager(null, mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); if (mParent != null) { mWindow.setContainer(mParent.getWindow()); } mWindowManager = mWindow.getWindowManager(); mCurrentConfig = config; }对于attach(),就以前的知识范围来讲与其说Activity的onCreate()方法是app的入口,倒不如说attach()是app的入口(当然这里只是突出attach的地位,并不是说attach就是app的入口,如果硬要讨论程序的入口,依次往上推就是Application的onCreate、ActivityThread的main()、Linux的init()了)。其实仔细看attach()方法就是一个赋值的过程,下面我们来具体看下:
首先是attachBaseContext(context);这个赋值就是一个Activity的上下文赋值,他就是给ContextWrapper的Context对象的mBase变量赋值。然后就是mFragments.attachActivity(this);这个mFragments是一个FragmentManagerImp实例,它执行的结果就是将Fragment和Activity联系起来。接下来就是mWindow的创建,他实际上就是一个PhoneWindow实例,关于此实例详见我的另一篇博文View的绘制过程 ,然后就是一系列的赋值了,这里这出赋值清单如下:
ActivityThread mMainThread = aThread; //在这里赋值主线程 Instrumentation mInstrumentation = instr; IBinder mToken = token; int mIdent = ident; Application mApplication = application; Intent mIntent = intent; ComponentName mComponent = intent.getComponent(); ActivityInfo mActivityInfo = info; CharSequence mTitle = title; Activity mParent = parent; //就是在这里赋值mParent String mEmbeddedID = id; NonConfigurationInstances mLastNonConfigurationInstances = lastNonConfigurationInstances;好了,那么我们知道了主线程实例以及Application实例、IBinder实现都是在attach()中赋值的,那么attach()方法是在什么时候被调用的呢?答案就是:attach()在ActivityThread中调用。那么问题来了,第一个入口Activity是在什么时候调用的呢?这就涉及到APK的启动过程了,这篇文章详细看APK的启动过程分析 。好了,现在我们回到mInstrumentation.execStartActivity(this,mMainThread.getApplicationThread(), mToken, this,intent, requestCode, options);这个方法的调用上来,首先来看看mMainThread.getApplicationThread(),这个是ActivityThread中的方法,用来获取ApplicationThread的,其实getApplicationThread()方法就直接returnmAppThread ,并无其他操作,mApplicationThread也是在ActivityThread中作为全局变量直接创建的final ApplicationThread mAppThread = new ApplicationThread();
好了,接下来就看看mInstruction.execStartActivity()方法吧,此方法当然是在Instructation()里了,源码如下:
public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, Fragment target, Intent intent, int requestCode, Bundle options) { IApplicationThread whoThread = (IApplicationThread) contextThread; if (mActivityMonitors != null) { synchronized (mSync) { final int N = mActivityMonitors.size(); for (int i=0; i<N; i++) { final ActivityMonitor am = mActivityMonitors.get(i); if (am.match(who, null, intent)) { am.mHits++; if (am.isBlocking()) { return requestCode >= 0 ? am.getResult() : null; } break; } } } } try { intent.setAllowFds(false); intent.migrateExtraStreamToClipData(); int result = ActivityManagerNative.getDefault() .startActivity(whoThread, intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target != null ? target.mWho : null, requestCode, 0, null, null, options); checkStartActivityResult(result, intent); } catch (RemoteException e) { } return null; }可以看到,该方法的第二个参数是IBinder,但是我们在Activity中调用mInstrumentation他的时候传递的是一各ApplicationThread对象,为什么可以从ApplicationThread强转成IBinder呢?因为ApplicationThread是继承ApplicationThreadNative,此类实现了Binder,Binder实现了IBinder接口,就是这么回事!接着看,IBinder实例contextThread(ApplicationThread对象)又被强转成了IApplicationThread对象,为何可以这么任性的转?因为AplicationThreadNative不紧继承了Binder类还实现了IApplicationThread接口,就是这么回事!关于Application和系统的“交流”我们会另外开启一篇文章!好,继续往下看ActivityManagerNative.getDefault()
.startActivity(...)此方法是启动一个Activity的核心代码,ActivityManagerNative.getDefault()返回的是一个IActivityManager接口实例,它是由ActivityManagerProxy实现的。我们直接去看ActivityManagerProxy的startActivity()吧:
public int startActivity(IApplicationThread caller, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, String profileFile, ParcelFileDescriptor profileFd, Bundle options) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeStrongBinder(caller != null ? caller.asBinder() : null); intent.writeToParcel(data, 0); data.writeString(resolvedType); data.writeStrongBinder(resultTo); data.writeString(resultWho); data.writeInt(requestCode); data.writeInt(startFlags); data.writeString(profileFile); if (profileFd != null) { data.writeInt(1); profileFd.writeToParcel(data, Parcelable.PARCELABLE_WRITE_RETURN_VALUE); } else { data.writeInt(0); } if (options != null) { data.writeInt(1); options.writeToParcel(data, 0); } else { data.writeInt(0); } mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0); reply.readException(); int result = reply.readInt(); reply.recycle(); data.recycle(); return result; }此startActivity()方法主要就是通过Binder机制完成了客户端向服务器端的通信,即调用mRemote.transact()方法完成通信,那么 这个ActivityManagerProxy的mRemote实例是在什么时候赋值的呢?这就涉及到android.util包下的一个Singleton类了,上面我们说到在 mInstruction.execStartActivity()方法中ActivityManagerNative.getDefault().startActivity(...)是关键,此处的getDefault()方法虽然只做了一件事,那就是返回一个IActivityManager实例:
/** * Retrieve the system's default/global activity manager. */ static public IActivityManager getDefault() { return gDefault.get(); }虽然上面的代码很简单,但却是很关键的,首先我们看下gDefault是个什么对象以及怎么创建的:
private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() { protected IActivityManager create() { IBinder b = ServiceManager.getService("activity"); if (false) { Log.v("ActivityManager", "default service binder = " + b); } IActivityManager am = asInterface(b); if (false) { Log.v("ActivityManager", "default service = " + am); } return am; } };也许你会问,gDefault是一个Singleton类实例,此处直接创建的gDefault实例时重写了其create()方法,但是在调用get()方法时是如何获取到IActivityManager实例的呢?我们来看下Singleton类的源码,他是一个抽象类,就两个方法,一个get()一个onCreate():
/** * Singleton helper class for lazily initialization. * * Modeled after frameworks/base/include/utils/Singleton.h * * @hide */ public abstract class Singleton<T> { private T mInstance; protected abstract T create(); public final T get() { synchronized (this) { if (mInstance == null) { mInstance = create(); } return mInstance; } } }看代码就一目了然,原来我们在 调用ActivityManagerNative.getDefault()时,Singleton的get()方法如果检查到其实例为空就会去执行create()方法并返回,此时就会调用到创建gDefault对象时重写的onCreate()方法,此方法就是重中之重了,正如上面的代码所示,他会先 执行 IBinder b = ServiceManager.getService("activity");获取一个IBinder实例,至于ServiceManager如何通过getService()获取IBinder实例这不是我们的重点讨论。然后就调用ActivityThreadNative方法的asInterface()方法:IActivityManager am = asInterface(b);注意此时这个b是一个IBinder实例,接下来我们看下asInterface()做了什么事情:
/** * Cast a Binder object into an activity manager interface, generating * a proxy if needed. */ static public IActivityManager asInterface(IBinder obj) { if (obj == null) { return null; } IActivityManager in = (IActivityManager)obj.queryLocalInterface(descriptor); if (in != null) { return in; } return new ActivityManagerProxy(obj); }asInterface()方法在必要的情况下会创建一个代理,也就是ActivityManagerProxy,在创建此代理时传递了一个IBinder实例进去,去他的构造方法看会发现, 此obj直接赋值给了mRemote,至此mRemote对象赋值完毕,那么客户端通过 mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0);告诉服务端要启动一个Activity也就完成了。接下来就该ActivityThread闪亮登场了,客户端已经向服务端请求了要启动一个Activity,那么服务器端说让ActivityThread来完成启动,然后ActivityThread就开始要准备启动一个Activity了,实际上在执行ApplicationThreadProxy的schedulePauseActivity()方法时,就是在用IPC原理远程调用ApplicationThread的schedulePauseActivity()方法,其实经过ActivityThread的内部类H(就是一个Handler),最后一步步调用到了handleLaunchActivity()方法,我们看下此类源码:
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) { // If we are getting ready to gc after going to the background, well // we are back active so skip it. unscheduleGcIdler(); if (r.profileFd != null) { mProfiler.setProfiler(r.profileFile, r.profileFd); mProfiler.startProfiling(); mProfiler.autoStopProfiler = r.autoStopProfiler; } // Make sure we are running with the most recent config. handleConfigurationChanged(null, null); if (localLOGV) Slog.v( TAG, "Handling launch of " + r); //看这里,此处就生产了一个Activity实例 Activity a = performLaunchActivity(r, customIntent); if (a != null) { r.createdConfig = new Configuration(mConfiguration); Bundle oldState = r.state; handleResumeActivity(r.token, false, r.isForward, !r.activity.mFinished && !r.startsNotResumed); if (!r.activity.mFinished && r.startsNotResumed) { // The activity manager actually wants this one to start out // paused, because it needs to be visible but isn't in the // foreground. We accomplish this by going through the // normal startup (because activities expect to go through // onResume() the first time they run, before their window // is displayed), and then pausing it. However, in this case // we do -not- need to do the full pause cycle (of freezing // and such) because the activity manager assumes it can just // retain the current state it has. try { r.activity.mCalled = false; mInstrumentation.callActivityOnPause(r.activity); // We need to keep around the original state, in case // we need to be created again. But we only do this // for pre-Honeycomb apps, which always save their state // when pausing, so we can not have them save their state // when restarting from a paused state. For HC and later, // we want to (and can) let the state be saved as the normal // part of stopping the activity. if (r.isPreHoneycomb()) { r.state = oldState; } if (!r.activity.mCalled) { throw new SuperNotCalledException( "Activity " + r.intent.getComponent().toShortString() + " did not call through to super.onPause()"); } } catch (SuperNotCalledException e) { throw e; } catch (Exception e) { if (!mInstrumentation.onException(r.activity, e)) { throw new RuntimeException( "Unable to pause activity " + r.intent.getComponent().toShortString() + ": " + e.toString(), e); } } r.paused = true; } } else { // If there was an error, for any reason, tell the activity // manager to stop us. try { ActivityManagerNative.getDefault() .finishActivity(r.token, Activity.RESULT_CANCELED, null); } catch (RemoteException ex) { // Ignore } } }对于上面的方法,我们直接看重点performLaunchActivity()这个方法的调用,因为正是该方法生成了一个Activity实例,接下来我们看下该方法是如何完成Activity的创建的:
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { // System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")"); ActivityInfo aInfo = r.activityInfo; if (r.packageInfo == null) { r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo, Context.CONTEXT_INCLUDE_CODE); } ComponentName component = r.intent.getComponent(); if (component == null) { component = r.intent.resolveActivity( mInitialApplication.getPackageManager()); r.intent.setComponent(component); } if (r.activityInfo.targetActivity != null) { component = new ComponentName(r.activityInfo.packageName, r.activityInfo.targetActivity); } Activity activity = null; try { java.lang.ClassLoader cl = r.packageInfo.getClassLoader(); activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); StrictMode.incrementExpectedActivityCount(activity.getClass()); r.intent.setExtrasClassLoader(cl); 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 (localLOGV) Slog.v(TAG, "Performing launch of " + r); if (localLOGV) Slog.v( TAG, r + ": app=" + app + ", appName=" + app.getPackageName() + ", pkg=" + r.packageInfo.getPackageName() + ", comp=" + r.intent.getComponent().toShortString() + ", dir=" + r.packageInfo.getAppDir()); if (activity != null) { Context appContext = createBaseContextForActivity(r, activity); CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager()); Configuration config = new Configuration(mCompatConfiguration); if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity " + r.activityInfo.name + " with config " + config); activity.attach(appContext, this, getInstrumentation(), r.token, r.ident, app, r.intent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config); if (customIntent != null) { activity.mIntent = customIntent; } r.lastNonConfigurationInstances = null; activity.mStartedActivity = false; int theme = r.activityInfo.getThemeResource(); if (theme != 0) { activity.setTheme(theme); } activity.mCalled = false; mInstrumentation.callActivityOnCreate(activity, r.state); if (!activity.mCalled) { throw new SuperNotCalledException( "Activity " + r.intent.getComponent().toShortString() + " did not call through to super.onCreate()"); } r.activity = activity; r.stopped = true; if (!r.activity.mFinished) { activity.performStart(); r.stopped = false; } if (!r.activity.mFinished) { if (r.state != null) { mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state); } } if (!r.activity.mFinished) { activity.mCalled = false; mInstrumentation.callActivityOnPostCreate(activity, r.state); if (!activity.mCalled) { throw new SuperNotCalledException( "Activity " + r.intent.getComponent().toShortString() + " did not call through to super.onPostCreate()"); } } } r.paused = true; mActivities.put(r.token, r); } 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 activity = null; try { //注意,这个r.packageInfo是一个LoadedApk实例, java.lang.ClassLoader cl = r.packageInfo.getClassLoader(); activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); StrictMode.incrementExpectedActivityCount(activity.getClass()); r.intent.setExtrasClassLoader(cl); 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); } }可以看到生成Activity实例的代码又糊掉了Instrumentation里去了,我们去看看该类的newActivity():
/** * Perform instantiation of the process's {@link Activity} object. The * default implementation provides the normal system behavior. * * @param cl The ClassLoader with which to instantiate the object. * @param className The name of the class implementing the Activity * object. * @param intent The Intent object that specified the activity class being * instantiated. * * @return The newly instantiated Activity object. */ public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { return (Activity)cl.loadClass(className).newInstance(); }
从上可以看出来,一个Activity的实例是被反射出来的,而不是new出来的T_T,以后有人问Activity实例时怎么创建的,你就回答是系统反射出来的好了,哈哈~。以上说了那么多,其实最关键的是客户端如何通知服务端来进行Activity启动,这种通信机制后面会另外开一片文章说道。
以上说了半天Activity的诞生,都是源于Instrumentmentation的execStartActivity()的int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mWho : null,
requestCode, 0, null, null, options);的执行,分析完之后发现他还返回了一个参数,这个参数有什么用呢?当然有用了,接下来会调用方法来对Activity启动的结构进行告知用户,如果启动出问题了就报异常,启动成功就成功吧:
/*package*/ static void checkStartActivityResult(int res, Object intent) { //如果启动成功直接return掉 if (res >= ActivityManager.START_SUCCESS) { return; } //开始报一系列异常 switch (res) { case ActivityManager.START_INTENT_NOT_RESOLVED: //新手最熟悉这种异常吧。。通常忘记在AndroidMainfest.xml文件中添加Activity描述时会异常 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"); case ActivityManager.START_NOT_ACTIVITY: throw new IllegalArgumentException( "PendingIntent is not an activity"); default: throw new AndroidRuntimeException("Unknown error code " + res + " when starting " + intent); } }
最后贴出一张关于ApplicationThread的关系图,此图参考http://blog.csdn.net/myarrow/article/details/14223493这位兄弟的博客。我觉得这张图画得和本博客得思路讲解一致,故拿过来贴在此处,本人就懒得去画啦~
好了,关于Activity的启动方式我们已经说完了,下一篇文章就说说Activity启动的四种模式和Activity栈吧!总的来说,在ActivityThread中一个Activity被启动时相关方法调用层次时酱紫的:schedule->handle->perform->activity/Insturmation。