Android异常处理流程

Android异常处理流程

前面的几篇文章都是讲解的android中的窗口显示机制,包括Activity窗口加载绘制流程,Dialog窗口加载绘制流程,PopupWindow窗口加载绘制流程,Toast窗口加载绘制流程等等。整个Android的界面显示的原理都是类似的,都是通过Window对象控制View组件,实现加载与绘制流程。

这篇文章休息一下,不在讲解Android的窗口绘制机制,穿插的讲解一下Android系统的异常处理流程。O(∩_∩)O哈哈~

开发过android项目的童鞋对android系统中错误弹窗,force close应该不陌生了,当我们的App异常崩溃时,就会弹出一个force close的弹窗,告诉我们App崩溃,以及一下简易的错误信息:
这里写图片描述

那么这里的force close弹窗是如何弹出的呢?

还有我们在开发App的过程中,经常会自定义Application,自定义UncaughtExceptionHandler实现App的全局异常处理,那么这里的UncaughtExceptionHandler是如何实现对异常的全局处理的呢?(可参考: 在Android中自定义捕获Application全局异常

带着这两个问题,我们开始今天的异常流程分析。

在android应用进程的启动流程中我们在经过一系列的操作之后会调用RuntimeInit.zygoteInit方法(可参考:Android应用程序进程启动过程的源代码分析

而我们也是从这里开始分析我们的Android系统异常处理流程,好了,让我们先来看一下zygoteInit方法的具体实现:

1
2
3
4
5
6
7
8
9
10
11
public static final void zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader)
throws ZygoteInit.MethodAndArgsCaller {
if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application from zygote");

Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "RuntimeInit");
redirectLogStreams();

commonInit();
nativeZygoteInit();
applicationInit(targetSdkVersion, argv, classLoader);
}

可以看到在方法体中我们调用了commonInit方法,这个方法是用于初始化操作的,继续看一下commonInit方法的实现:

1
2
3
4
5
private static final void commonInit() {
...
Thread.setDefaultUncaughtExceptionHandler(new UncaughtHandler());
...
}

可以看到在这里我们调用了Thread.setDefaultUncaughtExceptionHandler方法,这样当我们的进程出现异常的时候,异常信息就会被我们新创建的UncaughtHandler所捕获。

看过我们前面写过的关于Android全局异常处理文章的童鞋应该知道,我们实现对Android异常全局处理的操作也是通过设置Thread.setDefaultUncaughtExceptionHandler来实现的,具体可参考: 在Android中自定义捕获Application全局异常
所以Android系统默认的异常信息都会被这里的UncaughtHandler所捕获并被其uncaughtException方法回调,所以若我们不重写Thread.setDefaultUncaughtExceptionHandler方法,那么这里的UncaughtHandler就是我们默认的异常处理操作 这样我们看一下UncaughtHandler的具体实现:

Toast加载绘制流程

Toast加载绘制流程

前面我们分析了Activity、Dialog、PopupWindow的加载绘制流程,相信大家对整个Android系统中的窗口绘制流程已经有了一个比较清晰的认识了,这里最后再给大家介绍一下Toast的加载绘制流程。

其实Toast窗口和Activity、Dialog、PopupWindow有一个不太一样的地方,就是Toast窗口是属于系统级别的窗口,他和输入框等类似的,不属于某一个应用,即不属于某一个进程,所以自然而然的,一旦涉及到Toast的加载绘制流程就会涉及到进程间通讯,看过前面系列文章的同学应该知道,Android间的进程间通讯采用的是Android特有的Binder机制,所以Toast的加载绘制流程也会涉及到Binder进程间通讯。

Toast的显示流程其实内部还是通过Window的窗口机制实现加载绘制的,只不过由于是系统级别的窗口,在显示过程中涉及到了进程间通讯等机制。

下面我们来具体看一下Toast窗口的简单使用。

1
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();

上面的代码是Toast的典型使用方式,通过makeText方法创建出一个Toast对象,然后调用show方法将Toast窗口显示出来。

下面我们来看一下makeText方法的具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
Toast result = new Toast(context);

LayoutInflater inflate = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
tv.setText(text);

result.mNextView = v;
result.mDuration = duration;

return result;
}

方法体不是很长,在makeText方法中,我们首先通过Toast对象的构造方法,创建了一个新的Toast对象,这样我们就先来看一下Toast的构造方法做了哪些事。

1
2
3
4
5
6
7
8
public Toast(Context context) {
mContext = context;
mTN = new TN();
mTN.mY = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.toast_y_offset);
mTN.mGravity = context.getResources().getInteger(
com.android.internal.R.integer.config_toastDefaultGravity);
}

可以看到这里初始化了Toast对象的成员变量mContext和mTN,这里的mContext是一个Context类型的成员变量,那mTN是什么东西呢?

1
private static class TN extends ITransientNotification.Stub

从类的源码定义来看,我们知道TN是一个继承自ITransientNotification.Stub的类,这里我们暂时只用知道他的继承关系就好了,知道其是一个Binder对象,可以用于进程间通讯,然后回到我们的makeText方法,在调用了Toast的构造方法创建了Toast对象之后,我们又通过context.getSystemService方法获取到LayoutInflater,然后通过调用LayoutInflater的inflate方法加载到了Toast的布局文件:

PopupWindow加载绘制流程

PopupWindow加载绘制流程

在前面的几篇文章中我们分析了Activity与Dialog的加载绘制流程,取消绘制流程,相信大家对Android系统的窗口绘制机制有了一个感性的认识了,这篇文章我们将继续分析一下PopupWindow加载绘制流程。

在分析PopupWindow之前,我们将首先说一下什么是PopupWindow?理解一个类最好的方式就是看一下这个类的定义,这里我们摘要了一下Android系统中PopupWindow的类的说明:

A popup window that can be used to display an arbitrary view. The popup window is a floating container that appears on top of the current
activity.

一个PopupWindow能够被用于展示任意的View,PopupWindow是一个悬浮的容易展示在当前Activity的上面。
简单来说PopupWindow就是一个悬浮在Activity之上的窗口,可以用展示任意布局文件。

在说明PopupWindow的加载绘制机制之前,我们还是先写一个简单的例子用于说明一下PopupWindow的简单用法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static View showPopupWindowMenu(Activity mContext, View anchorView, int layoutId) {
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(layoutId, null);
popupWindow = new PopupWindow(view, DisplayUtil.dip2px(mContext, 148), WindowManager.LayoutParams.WRAP_CONTENT);
popupWindow.setBackgroundDrawable(mContext.getResources().getDrawable(R.drawable.menu_bg));
popupWindow.setFocusable(true);
popupWindow.setOutsideTouchable(true);

int[] location = new int[2];
anchorView.getLocationOnScreen(location);
popupWindow.setAnimationStyle(R.style.popwin_anim_style);
popupWindow.showAtLocation(anchorView, Gravity.NO_GRAVITY,
location[0] - popupWindow.getWidth() + anchorView.getWidth() - DisplayUtil.dip2px(mContext, 12),
location[1] + anchorView.getHeight() - DisplayUtil.dip2px(mContext, 10));

popupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
@Override
public void onDismiss() {
popupWindow = null;
}
});
return view;
}

可以看到我们首先通过LayoutInflater对象将布局文件解析到内存中View对象,然后创建了一个PopupWindow对象,可以看到传递了三个参数,一个是View对象,一个是PopupWindow的宽度和高度。

这里就是PopupWindow的初始化流程的开始了,好吧,我们来看一下PopupWindow的构造方法的实现:

1
2
3
public PopupWindow(View contentView, int width, int height) {
this(contentView, width, height, false);
}

可以看到这里调用了PopupWindow的重载构造方法,好吧,继续看一下这个重载构造方法的实现逻辑:

Dialog取消绘制流程

Dialog取消绘制流程

上几篇文章中我们分析了Dialog的加载绘制流程,也分析了Acvityi的加载绘制流程,说白了Android系统中窗口的展示都是通过Window对象控制,通过ViewRootImpl对象执行绘制操作来完成的,那么窗口的取消绘制流程是怎么样的呢?这篇文章就以Dialog为例说明Window窗口是如何取消绘制的。

有的同学可能会问前几篇文章介绍Activity的加载绘制流程的时候为何没有讲Activity的窗口取消流程,这里说明一下。那是因为当时说明的重点是Activity的加载与绘制流程,而取消绘制流程由于混杂在Activity的生命周期管理,可能不太明显,所以这里将Window窗口的取消绘制流程放在Dialog中,其实他们的取消绘制流程都是相似的,看完Dialog的取消绘制流程之后,再看一下Activity的取消绘制流程就很简单了。

还记得我们上一篇文章关于Dialog的例子么?我们通过AlertDialog.Builder创建了一个AlertDialog,并通过Activity中的按钮点击事件来显示这个AlertDialog,而在AlertDialog中定义了一个“知道了”按钮,点击这个按钮就会触发alertDialog.cancel方法,通过执行这个方法,我们的alertDialog就不在显示了,很明显的,cancel方法执行过程中就执行了取消绘制的逻辑,这里我们先看一下我们的例子核心代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
title.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this.getApplication());
builder.setIcon(R.mipmap.ic_launcher);
builder.setMessage("this is the content view!!!");
builder.setTitle("this is the title view!!!");
builder.setView(R.layout.activity_second);
builder.setPositiveButton("知道了", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
alertDialog.cannel();
}
});
alertDialog = builder.create();
alertDialog.show();
}
});

这里的title就是我们自己的Activity中的一个TextView,通过注册这个TextView的点击事件,来显示一个AlertDialog,通过注册AlertDialog中按钮的点击事件,执行alertDialog的cancel方法。

好吧,看一下Dialog的cannel方法的具体实现:

1
2
3
4
5
6
7
8
public void cancel() {
if (!mCanceled && mCancelMessage != null) {
mCanceled = true;
// Obtain a new message so this dialog can be re-used
Message.obtain(mCancelMessage).sendToTarget();
}
dismiss();
}

可以看到方法体中,若当前Dialog没有取消,并且设置了取消message,则调用Message.obtain(mCancel).sendToTarget(),前面已经分析过这里的sendToTarget方法会回调我们注册的异步消息处理逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
public void setOnCancelListener(final OnCancelListener listener) {
if (mCancelAndDismissTaken != null) {
throw new IllegalStateException(
"OnCancelListener is already taken by "
+ mCancelAndDismissTaken + " and can not be replaced.");
}
if (listener != null) {
mCancelMessage = mListenersHandler.obtainMessage(CANCEL, listener);
} else {
mCancelMessage = null;
}
}

可以看到如果我们在初始化AlertDialog.Builder时,设置了setOnCancelListener,那么我们就会执行mListenersHandler的异步消息处理,好吧,这里看一下mListenersHandler的定义:

Dialog加载绘制流程

Dialog加载绘制流程

前面两篇文章,我们分析了Activity的布局文件加载、绘制流程,算是对整个Android系统中界面的显示流程有了一个大概的了解,其实Android系统中所有的显示控件(注意这里是控件,而不是组件)的加载绘制流程都是类似的,包括:Dialog的加载绘制流程,PopupWindow的加载绘制流程,Toast的显示原理等,上一篇文章中,我说在介绍了Activity界面的加载绘制流程之后,就会分析一下剩余几个控件的显示控制流程,这里我打算先分析一下Dialog的加载绘制流程。

可能有的同学问这里为什么没有Fragment?其实严格意义上来说Fragment并不是一个显示控件,而只是一个显示组件。为什么这么说呢?其实像我们的Activity,Dialog,PopupWindow以及Toast类的内部都管理维护着一个Window对象,这个Window对象不但是一个View组件的集合管理对象,它也实现了组件的加载与绘制流程,而我们的Fragment组件如果看过源码的话,严格意义上来说,只是一个View组件的集合并通过控制变量实现了其特定的生命周期,但是其由于并没有维护Window类型的成员变量,所以其不具备组件的加载与绘制功能,因此其不能单独的被绘制出来,这也是我把它称之为组件而不是控件的原因。(在分析完这几个控件的加载绘制流程之后,有时间的话,也会分析一下Fragment的相关源码)

好吧,开始我们今天关于Dialog的讲解,相信大家在平时的开发过程中经常会使用到Dialog弹窗,使用Dialog可以在Activity弹出弹窗,确认消息等。为了更好的分析Dialog的源码,我们这里暂时写一个简单的demo,看一下Dialog的使用实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
title.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {

AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setIcon(R.mipmap.ic_launcher);
builder.setMessage("this is the content view!!!");
builder.setTitle("this is the title view!!!");
builder.setView(R.layout.activity_second);
builder.setPositiveButton("知道了", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
alertDialog.dismiss();
}
});
alertDialog = builder.create();
alertDialog.show();
}
});

我们在Activity中获取一个textView组件,并监听TextView的点击事件,并在点击事件中,初始化一个AlertDialog弹窗,并执行AlertDialog的show方法展示弹窗,在弹窗中定义一个按钮,并监听弹窗按钮的点击事件,若用户点击了弹窗的按钮,则执行AlertDialog的dismiss方法,取消展示AlertDialog。好吧,我们来看一下这个弹窗弹出的展示结果:
这里写图片描述
可以看到我们定义的icon,title,message和button都已经显示出来了,这时候我们点击弹窗按钮知道了,这时候弹窗就会消失了。

一般我们使用Dialog的大概流程都是这样的,可能定制Dialog的时候有一些定制化的操作,但是基本操作流程还是这样的。

那么我们先来看一下AlertDialog.Builder的构造方法,这里的Builder是AlertDialog的内部类,用于封装AlertDialog的构造过程,看一下Builder的构造方法:

1
2
3
public Builder(Context context) {
this(context, resolveDialogTheme(context, 0));
}

好吧,这里调用的是Builder的重载构造方法:

Activity布局绘制流程(转)

Activity布局绘制流程(转)

这篇文章是承接上一篇文章(Android布局加载流程:android源码解析(十七)–>Activity布局加载流程)来写的,大家都知道Activity在Android体系中扮演者一个界面展示的角色,通过上一篇文章的分析,我们知道Activity是通过Window来控制界面的展示的,一个Window对象就是一个窗口对象,而每个Activity中都有一个相应的Window对象,所以说一个Activity对象也就可以说是一个窗口对象,而Window只是控制着界面布局文件的加载过程,那么界面布局文件的绘制流程是如何的呢?这篇文章主要就是顺着上篇文章的思路,看一下在android系统中Activity的布局文件是如何绘制的。

顺便在这里多说几句,android中所有能显示的东西都是通过Window对象实现了,无论Activity,Dialog,PopupWindow,Toast等。后期我可能也会讲一下Dialog,PopupWindow,Toast等组件的显示过程。

前面有一篇文章中我们介绍过Activity的启动流程,可参考:android源码解析之(十四)–>Activity启动流程
在执行ActivityThread的handleLauncherActivity方法中通过Window对象控制了布局文件的加载流程,而Android体系在执行Activity的onResume方法之前会回调ActivityThread的handleResumeActivity方法:

Activity布局加载流程(转)

Activity布局加载流程(转)

好吧,终于要开始讲讲Activity的布局加载流程了,大家都知道在Android体系中Activity扮演了一个界面展示的角色,这也是它与android中另外一个很重要的组件Service最大的不同,但是这个展示的界面的功能是Activity直接控制的么?界面的布局文件是如何加载到内存并被Activity管理的?android中的View是一个怎样的概念?加载到内存中的布局文件是如何绘制出来的?

要想回答这些问题,我们就需要对android的界面加载与绘制流程有所了解,这里我们先来学习一下Activity的布局加载的流程。而至于Acitivty的布局绘制流程我们在下一篇中在做介绍。

其实Activity对界面布局的管理是都是通过Window对象来实现的,Window对象,顾名思义就是一个窗口对象,而Activity从用户角度就是一个个的窗口实例,因此不难想象每个Activity中都对应着一个Window对象,而这个Window对象就是负责加载显示界面的。至于window对象是如何展示不同的界面的,那是通过定义不同的View组件实现不同的界面展示。

废话不多说了,不知道大家是否还记得我们讲过的Activity的启动流程么?不熟悉的童鞋可以参考: android源码解析之(十四)–>Activity启动流程 ,在文章中我们介绍到当ActivityManagerService接收到启动Activity的请求之后会通过IApplicationThread进程间通讯告知ApplicationThread并执行handleLauncherActivity方法,这里我们可以下其具体实现:

应用进程Context创建流程(转)

应用进程Context创建流程(转)

今天讲讲应用进程Context的创建流程,相信大家平时在开发过程中经常会遇到对Context对象的使用,Application是Context,Activity是Context,Service也是Context,所以有一个经典的问题是一个App中一共有多少个Context?

这个问题的答案是Application + N个Activity + N个Service。

还有就是我们平时在使用Context过程中许多时候由于使用不当,可能会造成内存泄露的情况等等,这个也是需要我们注意的。这里有篇不错的文章:
Android Context 是什么?

好吧,什么叫应用进程Context呢?这是指的是Application所代表的Context的创建流程,还记得我们前几篇写的应用进程创建流程么?
android源码解析之(十一)–>应用进程启动流程
最后我们得出结论,应用进程的起始方法是ActivityThread.main方法,好吧,

由于还未讲解Service相关知识,这里暂时讲解一下Activity与Application中Context对象的创建过程。

首先我们就从ActivityThread.main方法开始看一下Application的创建流程。。。

1
2
3
4
5
6
public static void main(String[] args) {
...
ActivityThread thread = new ActivityThread();
thread.attach(false);
...
}

这里我们发现在方法体中我们创建了一个ActivityThread对象并执行了attach方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
private void attach(boolean system) {
sCurrentActivityThread = this;
mSystemThread = system;
if (!system) {
ViewRootImpl.addFirstDrawHandler(new Runnable() {
@Override
public void run() {
ensureJitEnabled();
}
});
android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
UserHandle.myUserId());
RuntimeInit.setApplicationObject(mAppThread.asBinder());
final IActivityManager mgr = ActivityManagerNative.getDefault();
try {
mgr.attachApplication(mAppThread);
} catch (RemoteException ex) {
// Ignore
}
// Watch for getting close to heap limit.
BinderInternal.addGcWatcher(new Runnable() {
@Override public void run() {
if (!mSomeActivitiesChanged) {
return;
}
Runtime runtime = Runtime.getRuntime();
long dalvikMax = runtime.maxMemory();
long dalvikUsed = runtime.totalMemory() - runtime.freeMemory();
if (dalvikUsed > ((3*dalvikMax)/4)) {
if (DEBUG_MEMORY_TRIM) Slog.d(TAG, "Dalvik max=" + (dalvikMax/1024)
+ " total=" + (runtime.totalMemory()/1024)
+ " used=" + (dalvikUsed/1024));
mSomeActivitiesChanged = false;
try {
mgr.releaseSomeActivities(mAppThread);
} catch (RemoteException e) {
}
}
}
});
} else {
...
}
}

这里看一下重点实现,我们可以发现在方法体中调用了ActivityManagerNative.getDefault().attachApplication(mAppThread)
看过我的前几篇文章的童鞋应该知道这里就是一个Binder进程间通讯,其实上执行的是ActivityManagerService.attachApplication方法,具体的可以参考前几篇文章的介绍,好吧,既然这样我们看一下ActivityManagerService.attachApplication方法的具体实现。

activity销毁流程(转)

activity销毁流程(转)

继续我们的源码解析,上一篇文章我们介绍了Activity的启动流程,一个典型的场景就是Activity a 启动了一个Activity b,他们的生命周期回调方法是:
onPause(a) –> onCreate(b) –> onStart(b) –> onResume(b) –> onStop(a)
而我们根据源码也验证了这样的生命周期调用序列,那么Activity的销毁流程呢?它的生命周期的调用顺序又是这样的呢?

这里我们我做一个简单的demo,让一个Activity a启动Activity b,然后在b中调用finish()方法,它们的生命周期执行顺序是:

onPause(b)
onRestart(a)
onStart(a)
onResume(a)
onStop(b)
onDestory(b)

好吧,根据我们测试的生命周期方法的回调过程开始对Activity销毁流程的分析,一般而言当我们需要销毁Activity的时候都会调用其自身的finish方法,所以我们的流程开始是以finish方法开始的。


一:请求销毁当前Activity

MyActivity.finish() Activity.finish() ActivityManagerNative.getDefault().finishActivity() ActivityManagerService.finishActivity() ActivityStack.requestFinishActivityLocked() ActivityStack.finishActivityLocked() ActivityStack.startPausingLocked()
activity启动流程(转)

activity启动流程(转)

好吧,终于要开始讲解Activity的启动流程了,Activity的启动流程相对复杂一下,涉及到了Activity中的生命周期方法,涉及到了Android体系的CS模式,涉及到了Android中进程通讯Binder机制等等,

首先介绍一下Activity,这里引用一下Android guide中对Activity的介绍:

An activity represents a single screen with a user interface. For example, an email application might have one activity that shows a list of new emails, another activity to compose an email, and another activity for reading emails. Although the activities work together to form a cohesive user experience in the email application, each one is independent of the others. As such, a different application can start any one of these activities (if the email application allows it). For example, a camera application can start the activity in the email application that composes new mail, in order for the user to share a picture.

英文不太好,这里就不献丑了,这里介绍的Activity的大概意思就是说,activity在Android系统中代表的就是一个屏幕,一个App就是由许多个不同的Acitivty组成的,并且不同进程之间的Activity是可以相互调用的。

在介绍Activity的启动流程之前,我们先介绍几个概念:

  • Activity的生命周期

protected void onCreate(Bundle savedInstanceState);
protected void onRestart();
protected void onStart();
protected void onResume();
protected void onPause();
protected void onStop();
protected void onDestory();
以上为Activity生命周期中的各个时期的回调方法,在不同的方法中我们可以执行不同的逻辑。
关于Activity生命周期的详细介绍可以参考: Android activity的生命周期

  • Activity的启动模式

activity启动时可以设置不同的启动模式,主要是:standrand,singleTop,singleTask,instance等四种启动模式,不同的启动模式在启动Activity时会执行不同的逻辑,系统会按不同的启动模式将Activity存放到不同的activity栈中。
关于Activity启动模式的详细介绍,可以参考: Android任务和返回栈完全解析

  • Activity的启动进程

在Manifest.xml中定义Activity的时候,Activity默认是属于进程名称为包名的进程的,当然这时候是可以指定Activity的启动进程,所以在Activity启动时首先会检测当前Activity所属的进程是否已经启动,若进程没有启动,则首先会启动该进程,并在该进程启动之后才会执行Activity的启动过程。

  • Intent启动Activity的方式

    Intent启动Activity分为两种,显示启动和隐士启动,显示启动就是在初始化Intent对象的时候直接引用需要启动的Activity的字节码,显示引用的好处就是可以直接告诉Intent对象启动的Activity对象不需要执行intent filter索引需要启动哪一个Activity,但是显示引用不能启动其他进程的Activity对象,因为无法获取其他进程的Activity对象的字节码,而隐式启动则可以通过配置Intent Filter启动其他进程的Activity对象,因此在应用内,我们一般都是使用显示启动的方式启动Activity,而如果需要启动其他应用的Activity时,一般使用隐式启动的方式。


:D 一言句子获取中...