在设置界面点卸载按钮执行如下代码
public static final String ACTION_UNINSTALL_PACKAGE = "android.intent.action.UNINSTALL_PACKAGE";
void uninstallPkg(String packageName, boolean allUsers, boolean andDisable) {
Uri packageUri = Uri.parse("package:" + packageName);
Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri);
uninstallIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, allUsers);
mFragment.startActivityForResult(uninstallIntent, requestUninstallCode);
}
跳转到PackageInstaller应用卸载界面:
\PackageInstaller\src\com\android\packageinstaller\UninstallUninstalling.java
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//****省略代码
// 弹出确认是否需要卸载的提示框UninstallUninstallingFragment:
DialogFragment dialog = new UninstallUninstallingFragment();
dialog.setCancelable(false);
dialog.show(transaction, "dialog");
//注册等待卸载完成的广播和回调
mUninstallId = UninstallEventReceiver.addObserver(this,
EventResultPersister.GENERATE_NEW_ID, this);
Intent broadcastIntent = new Intent(BROADCAST_ACTION);
broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mUninstallId);
broadcastIntent.setPackage(getPackageName());
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, mUninstallId,
broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT
| PendingIntent.FLAG_MUTABLE);
int flags = allUsers ? PackageManager.DELETE_ALL_USERS : 0;
flags |= keepData ? PackageManager.DELETE_KEEP_DATA : 0;
try {
//开始卸载
ActivityThread.getPackageManager().getPackageInstaller().uninstall(
new VersionedPackage(mAppInfo.packageName,
PackageManager.VERSION_CODE_HIGHEST),
getPackageName(), flags, pendingIntent.getIntentSender(),
user.getIdentifier());
//****省略代码
}
}
通过IPackageInstaller.java这个接口实现跨进程通信,此时的客户端为PackageInstaller.apk,服务端为 PackageInstallerService.java,就直接调用到PackageInstallerService.java中的uninstall方法中去了。代码如下:
frameworks\base\core\java\android\content\pm\PackageInstaller.java
public void uninstall(@NonNull VersionedPackage versionedPackage, @DeleteFlags int flags,
@NonNull IntentSender statusReceiver) {
Objects.requireNonNull(versionedPackage, "versionedPackage cannot be null");
try {
mInstaller.uninstall(versionedPackage, mInstallerPackageName,
flags, statusReceiver, mUserId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
frameworks\base\services\core\java\com\android\server\pm\PackageInstallerService.java
// frameworks/base/services/core/java/com/android/server/pm/PackageInstallerService.java
@Override
public void uninstall(VersionedPackage versionedPackage, String callerPackageName, int flags,
IntentSender statusReceiver, int userId) {
// 先获取 UID
final int callingUid = Binder.getCallingUid();
// 检查执行权限
mPermissionManager.enforceCrossUserPermission(callingUid, userId, true, true, "uninstall");
//创建PackageDeleteObserverAdapter对象,用于接收响应删除的结果
final PackageDeleteObserverAdapter adapter = new PackageDeleteObserverAdapter(mContext,
statusReceiver, versionedPackage.getPackageName(),
canSilentlyInstallPackage, userId);
if(mContext.checkCallingOrSelfPermission(android.Manifest.permission.DELETE_PACKAGES)
== PackageManager.PERMISSION_GRANTED) {
// Sweet, call straight through!
// 调用mPm.deletePackageVersioned
mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags);
......
}
这个方法主要做了3件事情:
-
创建PackageDeleteObserverAdapter对象,用于接收响应删除的结果;
-
检查当前程序是否具有删除权限,如果有的话,就调用 mPm.deletePackageVersioned方法;
-
调用PMS的deletePackageVersioned()方法执行程序的卸载。
-
最后卸载调用了mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags); 所以走到了PackageManagerService的deletePackageVersioned()。
frameworks\base\services\core\java\com\android\server\pm\PackageManagerService.java
@Override
public void deletePackageVersioned(VersionedPackage versionedPackage,
final IPackageDeleteObserver2 observer, final int userId, final int deleteFlags) {
deletePackageVersionedInternal(versionedPackage, observer, userId, deleteFlags, false);
}
deletePackageVersioned调用了deletePackageVersionedInternal(versionedPackage, observer, userId, deleteFlags, false);
private void deletePackageVersionedInternal(VersionedPackage versionedPackage,
final IPackageDeleteObserver2 observer, final int userId, final int deleteFlags,
final boolean allowSilentUninstall) {
//********省略代码
mHandler.post(() -> {
int returnCode;
final PackageSetting ps = mSettings.mPackages.get(internalPackageName);
boolean doDeletePackage = true;
if (ps != null) {
final boolean targetIsInstantApp =
ps.getInstantApp(UserHandle.getUserId(callingUid));
doDeletePackage = !targetIsInstantApp
|| canViewInstantApps;
}
if (doDeletePackage) {
if (!deleteAllUsers) {
//走这里的逻辑
returnCode = deletePackageX(internalPackageName, versionCode,
userId, deleteFlags);
} else {
try {
//删除apk后的回调方法
observer.onPackageDeleted(packageName, returnCode, null);
} catch (RemoteException e) {
Log.i(TAG, "Observer no longer exists.");
} //end catch
}
在deletePackageVersionedInternal()中发送Post事件执行异步删除操作,在Handler事件中调用deletePackageX()方法。
// PackageManagerService.java
int deletePackageX(String packageName, long versionCode, int userId, int deleteFlags) {
....
if (isPackageDeviceAdmin(packageName, removeUser)) {
Slog.w(TAG, "Not removing package " + packageName + ": has active device admin");
return PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER;
}
synchronized (mInstallLock) {
if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageX: pkg=" + packageName + " user=" + userId);
try (PackageFreezer freezer = freezePackageForDelete(packageName, freezeUser,
deleteFlags, "deletePackageX")) {
//调用这个方法
res = deletePackageLIF(packageName, UserHandle.of(removeUser), true, allUsers,
deleteFlags | PackageManager.DELETE_CHATTY, info, true, null);
}
....
}
在deletePackageX()中首先判断要卸载的程序是否是admin管理的,如果是则直接return;如果不是则执行deletePackageLIF()方法继续卸载程序。
// PackageManagerService.java
private boolean deletePackageLIF(@NonNull String packageName, UserHandle user,
boolean deleteCodeAndResources, int[] allUserHandles, int flags,
PackageRemovedInfo outInfo, boolean writeSettings,
PackageParser.Package replacingPackage) {
.....
try {
// 继续调用卸载方法
executeDeletePackageLIF(action, packageName, deleteCodeAndResources,
allUserHandles, writeSettings, replacingPackage);
} catch (SystemDeleteException e) {
if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageLI: system deletion failure", e);
return false;
}
.....
}
执行到executeDeletePackageLIF方法中
// PackageManagerService.java
private void executeDeletePackageLIF(DeletePackageAction action,
String packageName, boolean deleteCodeAndResources,
int[] allUserHandles, boolean writeSettings,
PackageParser.Package replacingPackage) throws SystemDeleteException {
......
//是否是系统app
final boolean systemApp = isSystemApp(ps);
......
// TODO(b/109941548): break reasons for ret = false out into mayDelete method
if (systemApp) {
if (DEBUG_REMOVE) Slog.d(TAG, "Removing system package: " + ps.name);
// When an updated system application is deleted we delete the existing resources
// as well and fall back to existing code in system partition
deleteSystemPackageLIF(action, ps, allUserHandles, flags, outInfo, writeSettings);
} else {
if (DEBUG_REMOVE) Slog.d(TAG, "Removing non-system package: " + ps.name);
// 这里卸载的是三方应用
deleteInstalledPackageLIF(ps, deleteCodeAndResources, flags, allUserHandles,
outInfo, writeSettings, replacingPackage);
}
......
}
在executeDeletePackageLIF函数中根据是否是systemApp调用不同的流程,如果是systemApp,则调用deleteSystemPackageLIF完成卸载;如果非systemApp,则调用deleteInstalledPackageLIF完成卸载,当然在卸载之前,首先会调用AMS的killApplication方法先让这个APP停止运行,deleteInstalledPackageLIF 方法。
// PackageManagerService.java
private void deleteInstalledPackageLIF(PackageSetting ps,
boolean deleteCodeAndResources, int flags, int[] allUserHandles,
PackageRemovedInfo outInfo, boolean writeSettings,
PackageParser.Package replacingPackage) {
......
// Delete package data from internal structures and also remove data if flag is set
//从内部结构中删除包数据,如果设置了标志,也删除数据
removePackageDataLIF(ps, allUserHandles, outInfo, flags, writeSettings);
......
}
removePackageDataLIF()方法
private void removePackageDataLIF(final PackageSetting deletedPs, @NonNull int[] allUserHandles,
PackageRemovedInfo outInfo, int flags, boolean writeSettings) {
String packageName = deletedPs.name;
if (DEBUG_REMOVE) Slog.d(TAG, "removePackageDataLI: " + deletedPs);
// Retrieve object to delete permissions for shared user later on
// 检索对象以稍后删除共享用户的权限
final AndroidPackage deletedPkg = deletedPs.pkg;
//*******省略代码
//删除数据
removePackageLI(deletedPs.name, (flags & PackageManager.DELETE_CHATTY) != 0);
if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) {
final AndroidPackage resolvedPkg;
//*******省略代码
//注释2:删除目录和配置文件
destroyAppDataLIF(resolvedPkg, UserHandle.USER_ALL,
FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL);
destroyAppProfilesLIF(resolvedPkg);
if (outInfo != null) {
outInfo.dataRemoved = true;
}
}
int removedAppId = -1;
// writer
boolean installedStateChanged = false;
if (deletedPs != null) {
if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) {
final SparseBooleanArray changedUsers = new SparseBooleanArray();
synchronized (mLock) {
mDomainVerificationManager.clearPackage(deletedPs.name);
//移除mSetting中保存的数据
mSettings.getKeySetManagerService().removeAppKeySetDataLPw(packageName);
mAppsFilter.removePackage(getPackageSetting(packageName));
removedAppId = mSettings.removePackageLPw(packageName);
if (outInfo != null) {
outInfo.removedAppId = removedAppId;
}
if (!mSettings.isDisabledSystemPackageLPr(packageName)) {
// If we don't have a disabled system package to reinstall, the package is
// really gone and its permission state should be removed.
final SharedUserSetting sus = deletedPs.getSharedUser();
List<AndroidPackage> sharedUserPkgs = sus != null ? sus.getPackages()
: null;
if (sharedUserPkgs == null) {
sharedUserPkgs = Collections.emptyList();
}
//通知PermissionManagerService卸载,更新权限信息
mPermissionManager.onPackageUninstalled(packageName, deletedPs.appId,
deletedPs.pkg, sharedUserPkgs, UserHandle.USER_ALL);
}
//清除设置数据
clearPackagePreferredActivitiesLPw(
deletedPs.name, changedUsers, UserHandle.USER_ALL);
}
if (changedUsers.size() > 0) {
updateDefaultHomeNotLocked(changedUsers);
postPreferredActivityChangedBroadcast(UserHandle.USER_ALL);
}
}
// make sure to preserve per-user disabled state if this removal was just
// a downgrade of a system app to the factory package
if (outInfo != null && outInfo.origUsers != null) {
if (DEBUG_REMOVE) {
Slog.d(TAG, "Propagating install state across downgrade");
}
for (int userId : allUserHandles) {
final boolean installed = ArrayUtils.contains(outInfo.origUsers, userId);
if (DEBUG_REMOVE) {
Slog.d(TAG, " user " + userId + " => " + installed);
}
if (installed != deletedPs.getInstalled(userId)) {
installedStateChanged = true;
}
deletedPs.setInstalled(installed, userId);
if (installed) {
deletedPs.setUninstallReason(UNINSTALL_REASON_UNKNOWN, userId);
}
}
}
}
synchronized (mLock) {
// can downgrade to reader
if (writeSettings) {
// Save settings now
writeSettingsLPrTEMP();
}
if (installedStateChanged) {
mSettings.writeKernelMappingLPr(deletedPs);
}
}
if (removedAppId != -1) {
// A user ID was deleted here. Go through all users and remove it
// from KeyStore.
//此处删除了用户ID。浏览所有用户并将其从KeyStore中删除
removeKeystoreDataIfNeeded(
mInjector.getUserManagerInternal(), UserHandle.USER_ALL, removedAppId);
}
}
removePackageDataLIF用于删除应用的/data/data数据目录,并且从PMS内部数据结构里面清除package的信息。首先调用removePackageLI从PMS内部的数据结构上删除要卸载的package信息。
removePackageLI方法如下:
private void removePackageLI(AndroidPackage pkg, boolean chatty) {
// Remove the parent package setting
PackageSetting ps = getPackageSetting(pkg.getPackageName());
if (ps != null) {
removePackageLI(ps.name, chatty);
} else if (DEBUG_REMOVE && chatty) {
Log.d(TAG, "Not removing package " + pkg.getPackageName() + "; mExtras == null");
}
}
private void removePackageLI(String packageName, boolean chatty) {
if (DEBUG_INSTALL) {
if (chatty)
Log.d(TAG, "Removing package " + packageName);
}
// writer
synchronized (mLock) {
final AndroidPackage removedPackage = mPackages.remove(packageName);
if (removedPackage != null) {
//调用这个方法,删除四大组件相关的信息
cleanPackageDataStructuresLILPw(removedPackage, chatty);
}
}
}
cleanPackageDataStructuresLILPw用于将package的providers、services、receivers、activities等信息去PMS的全局数据结构上移除,cleanPackageDataStructuresLILPw如下 :
private void cleanPackageDataStructuresLILPw(AndroidPackage pkg, boolean chatty) {
//移除所有组件
mComponentResolver.removeAllComponents(pkg, chatty);
mPermissionManager.onPackageRemoved(pkg);
mPackageProperty.removeAllProperties(pkg);
//*****省略代码
}
移除所有组件:
frameworks\base\services\core\java\com\android\server\pm\ComponentResolver.java
void removeAllComponents(AndroidPackage pkg, boolean chatty) {
synchronized (mLock) {
removeAllComponentsLocked(pkg, chatty);
onChanged();
}
}
private void removeAllComponentsLocked(AndroidPackage pkg, boolean chatty) {
int componentSize;
StringBuilder r;
int i;
componentSize = ArrayUtils.size(pkg.getActivities());
r = null;
for (i = 0; i < componentSize; i++) {
ParsedActivity a = pkg.getActivities().get(i);
//移除activity:
mActivities.removeActivity(a, "activity");
//*****省略代码
}
//*****省略代码
componentSize = ArrayUtils.size(pkg.getProviders());
r = null;
for (i = 0; i < componentSize; i++) {
ParsedProvider p = pkg.getProviders().get(i);
mProviders.removeProvider(p); //移除Providers:
//*****省略代码
}
//*****省略代码
componentSize = ArrayUtils.size(pkg.getReceivers());
r = null;
for (i = 0; i < componentSize; i++) {
ParsedActivity a = pkg.getReceivers().get(i);
mReceivers.removeActivity(a, "receiver"); //移除receiver:
//*****省略代码
}
//*****省略代码
componentSize = ArrayUtils.size(pkg.getServices());
r = null;
for (i = 0; i < componentSize; i++) {
ParsedService s = pkg.getServices().get(i);
mServices.removeService(s); //移除Services
}
//*****省略代码
}
在以上removeAllComponentsLocked方法中移除四大组件。
再回到removePackageDataLIF中的调用destroyAppDataLIF和destroyAppProfilesLIF去删除/data/data下面的目录
destroyAppDataLIF方法如下:
private void destroyAppDataLIF(AndroidPackage pkg, int userId, int flags) {
if (pkg == null) {
Slog.wtf(TAG, "Package was null!", new Throwable());
return;
}
destroyAppDataLeafLIF(pkg, userId, flags);
}
private void destroyAppDataLeafLIF(AndroidPackage pkg, int userId, int flags) {
final PackageSetting ps;
synchronized (mLock) {
ps = mSettings.getPackageLPr(pkg.getPackageName());
}
for (int realUserId : resolveUserIds(userId)) {
final long ceDataInode = (ps != null) ? ps.getCeDataInode(realUserId) : 0;
try {
//跳转到Installer.java
mInstaller.destroyAppData(pkg.getVolumeUuid(), pkg.getPackageName(), realUserId,
flags, ceDataInode);
} catch (InstallerException e) {
Slog.w(TAG, String.valueOf(e));
}
mDexManager.notifyPackageDataDestroyed(pkg.getPackageName(), userId);
}
}
通过mInstaller.destroyAppData(pkg.getVolumeUuid(), pkg.getPackageName(), realUserId, flags, ceDataInode)方法跳转到Installer.java
// frameworks/base/services/core/java/com/android/server/pm/Installer.java
public void destroyAppData(String uuid, String packageName, int userId, int flags,
long ceDataInode) throws InstallerException {
if (!checkBeforeRemote()) return;
try {
// 执行守护进程Installd的方法
mInstalld.destroyAppData(uuid, packageName, userId, flags, ceDataInode);
} catch (Exception e) {
throw InstallerException.from(e);
}
}
然后在Installd守护进程执行删除app数据的动作,具体文件和方法:
跨进程调用InstalldNativeService.cpp的destroyAppData方法。
// frameworks/native/cmds/installd/InstalldNativeService.cpp
binder::Status InstalldNativeService::destroyAppData(const std::unique_ptr<std::string>& uuid,
const std::string& packageName, int32_t userId, int32_t flags, int64_t ceDataInode) {
ENFORCE_UID(AID_SYSTEM);
CHECK_ARGUMENT_UUID(uuid);
CHECK_ARGUMENT_PACKAGE_NAME(packageName);
std::lock_guard<std::recursive_mutex> lock(mLock);
const char* uuid_ = uuid ? uuid->c_str() : nullptr;
const char* pkgname = packageName.c_str();
binder::Status res = ok();
if (flags & FLAG_STORAGE_CE) {
// 构造/data/data/packageName的文件路径名
auto path = create_data_user_ce_package_path(uuid_, userId, pkgname, ceDataInode);
// 删除文件内容以及目录(比如Android/data/包名/
if (delete_dir_contents_and_dir(path) != 0) {
res = error("Failed to delete " + path);
}
}
if (flags & FLAG_STORAGE_DE) {
auto path = create_data_user_de_package_path(uuid_, userId, pkgname);
if (delete_dir_contents_and_dir(path) != 0) {
res = error("Failed to delete " + path);
}
destroy_app_current_profiles(packageName, userId);
// TODO(calin): If the package is still installed by other users it's probably
// beneficial to keep the reference profile around.
// Verify if it's ok to do that.
destroy_app_reference_profile(packageName);
}
if (flags & FLAG_STORAGE_EXTERNAL) {
std::lock_guard<std::recursive_mutex> lock(mMountsLock);
for (const auto& n : mStorageMounts) {
auto extPath = n.second;
if (n.first.compare(0, 14, "/mnt/media_rw/") != 0) {
extPath += StringPrintf("/%d", userId);
} else if (userId != 0) {
// TODO: support devices mounted under secondary users
continue;
}
auto path = StringPrintf("%s/Android/data/%s", extPath.c_str(), pkgname);
if (delete_dir_contents_and_dir(path, true) != 0) {
res = error("Failed to delete contents of " + path);
}
path = StringPrintf("%s/Android/media/%s", extPath.c_str(), pkgname);
if (delete_dir_contents_and_dir(path, true) != 0) {
res = error("Failed to delete contents of " + path);
}
path = StringPrintf("%s/Android/obb/%s", extPath.c_str(), pkgname);
if (delete_dir_contents_and_dir(path, true) != 0) {
res = error("Failed to delete contents of " + path);
}
}
}
return res;
}
-
create_data_user_ce_package_path方法构造/data/data/packageName的文件路径名,然后调用delete_dir_contents_and_dir来删除文件内容以及目录(比如Android/data/包名/;
-
Android/media/包名,Android/obb/包名/ 路径下的文件),前面介绍过,/data/data/packageName的文件其实都是符号链接,所以delete_dir_contents_and_dir的实现中都是调用unlinkat去删除这些符号链接;
到此应用程序的app安装文件和使用中产生的数据文件都已被删除,且配置文件已更新完成,此时会回调deletePackageVersioned()中的observer.onPackageDeleted(packageName, returnCode, null) 方法通知删除结果,这里回调的是PackageDeleteObserverAdapter类中(PackageInstallerService的内部类):
//frameworks\base\services\core\java\com\android\server\pm\PackageInstallerService.java
static class PackageDeleteObserverAdapter extends PackageDeleteObserver {
private final Context mContext;
private final IntentSender mTarget;
private final String mPackageName;
private final Notification mNotification;
public PackageDeleteObserverAdapter(Context context, IntentSender target,
String packageName, boolean showNotification, int userId) {
mContext = context;
mTarget = target;
mPackageName = packageName;
//发送通知
if (showNotification) {
mNotification = buildSuccessNotification(mContext,
mContext.getResources().getString(R.string.package_deleted_device_owner),
packageName,
userId);
} else {
mNotification = null;
}
}
@Override
public void onUserActionRequired(Intent intent) {
//***省略代码
}
@Override
public void onPackageDeleted(String basePackageName, int returnCode, String msg) {
if (PackageManager.DELETE_SUCCEEDED == returnCode && mNotification != null) {
NotificationManager notificationManager = (NotificationManager)
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(basePackageName,
SystemMessage.NOTE_PACKAGE_STATE,
mNotification);
}
if (mTarget == null) {
return;
}
final Intent fillIn = new Intent();
fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, mPackageName);
fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
PackageManager.deleteStatusToPublicStatus(returnCode));
fillIn.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE,
PackageManager.deleteStatusToString(returnCode, msg));
fillIn.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, returnCode);
try {
// 通知IntentSender对象接收删除结果
mTarget.sendIntent(mContext, 0, fillIn, null, null);
} catch (SendIntentException ignored) {
}
}
}
在onPackageDeleted()中,首先调用NotificationManager中方法发送notify()通知卸载成功,然后创建Intent对象并回调mTarget.sendIntent(),这里的mTarget对象其实就是卸载最开始时调用uninstall()方法传入的第二个参数,即IntentSender对象,用于接收删除的结果,并发送给在(PackageInstaller\src\com\android\packageinstaller\UninstallUninstalling.java)时已经注册好了的广播UninstallEventReceiver.java
等待卸载完成的广播:
PackageInstaller\src\com\android\packageinstaller\UninstallEventReceiver.java
@Override
public void onReceive(Context context, Intent intent) {
getReceiver(context).onEventReceived(context, intent);
}
@NonNull private static EventResultPersister getReceiver(@NonNull Context context) {
synchronized (sLock) {
if (sReceiver == null) {
sReceiver = new EventResultPersister(
TemporaryFileManager.getUninstallStateFile(context));
}
}
return sReceiver;
}
void onEventReceived(@NonNull Context context, @NonNull Intent intent) {
//省略代码*****
if (observerToCall != null) {
observerToCall.onResult(status, legacyStatus, statusMessage);
} else {
mResults.put(id, new EventResult(status, legacyStatus, statusMessage));
writeState();
}
}
}
然后再回到收到onResult后关闭界面
\PackageInstaller\src\com\android\packageinstaller\UninstallUninstalling.java
@Override
public void onResult(int status, int legacyStatus, @Nullable String message) {
//***省略代码
finish();
}
时序图:
应用层时序图:
系统层时序图: