PMS是Android系統(tǒng)服務(wù)中的包管理機(jī)制,服務(wù)于APK的完整生命周期,主要包括APK的解析,安裝,運(yùn)行,卸載。
PMS整個(gè)生命周期比較龐大,可以先從安裝和卸載入手,看PMS是如何工作。
APK的安裝
下面這段代碼估計(jì)大家都很熟悉,就是用做在應(yīng)用中做自更新或者安裝其他應(yīng)用使用
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Uri apkUri = FileProvider.getUriForFile(context, UIUtils.getContext().getPackageName(), file);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
} else {
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
InstallStart
application/vnd.android.package-archive
上面這段主要做隱試匹配,會(huì)匹配到 com.android.packageInstaller進(jìn)程中InstallStart(InstallStart是一個(gè)Activity)
<activity android:name=".InstallStart"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:exported="true"
android:excludeFromRecents="true">
<intent-filter android:priority="1">
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.INSTALL_PACKAGE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="content" />
<data android:mimeType="application/vnd.android.package-archive" />
</intent-filter>
<intent-filter android:priority="1">
<action android:name="android.intent.action.INSTALL_PACKAGE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="package" />
<data android:scheme="content" />
</intent-filter>
<intent-filter android:priority="1">
<action android:name="android.content.pm.action.CONFIRM_INSTALL" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
InstallStart其實(shí)也沒(méi)干什么正事,一般來(lái)說(shuō)就三點(diǎn)
- 對(duì)TargetSDKVersion進(jìn)行校驗(yàn)
- 對(duì)
Manifest.permission.REQUEST_INSTALL_PACKAGES進(jìn)行校驗(yàn) - 對(duì)傳遞進(jìn)來(lái)的uri進(jìn)行校驗(yàn),如果以Schema以Content開(kāi)頭,就跳轉(zhuǎn)到InstallStaging中。
斷點(diǎn)看一下吧

我們傳遞進(jìn)來(lái)uri的值
content://com.android.externalstorage.documents/document/primary%3Aapp-release.apk
InstallStaging
先來(lái)看看這個(gè)Activity長(zhǎng)什么樣子

InstallStaging中主要邏輯在onResume()方法中開(kāi)啟異步任務(wù)
@Override
protected void onResume() {
super.onResume();
// This is the first onResume in a single life of the activity
if (mStagingTask == null) {
// File does not exist, or became invalid
if (mStagedFile == null) {
// Create file delayed to be able to show error
try {
mStagedFile = TemporaryFileManager.getStagedFile(this);
} catch (IOException e) {
showError();
return;
}
}
mStagingTask = new StagingAsyncTask();
mStagingTask.execute(getIntent().getData());
}
}
具體看下StagingAsyncTask的任務(wù)
private final class StagingAsyncTask extends AsyncTask<Uri, Void, Boolean> {
@Override
protected Boolean doInBackground(Uri... params) {
if (params == null || params.length <= 0) {
return false;
}
Uri packageUri = params[0];
// 這里的packageUri 是 content://com.android.externalstorage.documents/document/primary%3Aapp-release.apk
try (InputStream in = getContentResolver().openInputStream(packageUri)) {
// Despite the comments in ContentResolver#openInputStream the returned stream can
// be null.
if (in == null) {
return false;
}
try (OutputStream out = new FileOutputStream(mStagedFile)) {
byte[] buffer = new byte[1024 * 1024];
int bytesRead;
while ((bytesRead = in.read(buffer)) >= 0) {
// Be nice and respond to a cancellation
if (isCancelled()) {
return false;
}
out.write(buffer, 0, bytesRead);
}
}
} catch (IOException | SecurityException | IllegalStateException e) {
Log.w(LOG_TAG, "Error staging apk from content URI", e);
return false;
}
return true;
}
@Override
protected void onPostExecute(Boolean success) {
if (success) {
// Now start the installation again from a file
Intent installIntent = new Intent(getIntent());
installIntent.setClass(InstallStaging.this, DeleteStagedFileOnResult.class);
// 這里需要注意,已經(jīng)將content開(kāi)頭的地址轉(zhuǎn)換成了file開(kāi)頭的文件地址
installIntent.setData(Uri.fromFile(mStagedFile));
if (installIntent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
installIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
}
installIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivity(installIntent);
InstallStaging.this.finish();
} else {
// 這里如果出錯(cuò)的話(huà)會(huì)出現(xiàn)一個(gè)比較熟悉的界面
showError();
}
}
}
其實(shí)就是將APK文件拷貝一份,具體拷貝到哪里呢
拷貝到這里 /data/user/com.android.packageInstaller/no_backup/
拷貝完了之后跳轉(zhuǎn)到DeleteStagedFileOnResult
ps 這里如果拷貝錯(cuò)誤的話(huà)會(huì)出現(xiàn)一個(gè)比較熟悉界面
image0
DeleteStagedFileOnResult
這個(gè)類(lèi)貌似是android 10新增的,在android9還沒(méi)看到這個(gè)類(lèi),內(nèi)容比較簡(jiǎn)單
public class DeleteStagedFileOnResult extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
Intent installIntent = new Intent(getIntent());
// 跳轉(zhuǎn)到PackageInstallerActivity,這個(gè)頁(yè)面很熟悉了
installIntent.setClass(this, PackageInstallerActivity.class);
installIntent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivityForResult(installIntent, 0);
}
}
// 這里是重點(diǎn),主要是用來(lái)接收安裝的成功或失敗,不管咋樣,都把剛復(fù)制的文件干掉
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
File sourceFile = new File(getIntent().getData().getPath());
sourceFile.delete();
setResult(resultCode, data);
finish();
}
}
PackageInstallerActivity
還是先看看這個(gè)頁(yè)面長(zhǎng)什么樣子

PackageInstallerActivity中邏輯比較多
先看看在onCreate方法中初始化的主要變量
// 這個(gè)是向應(yīng)用的進(jìn)程提供功能,但是功能最終都有PMS提供,當(dāng)前的進(jìn)程還是處在com.android.packageInstaller中
mPm = getPackageManager();
// AIDL接口,用于和PMS進(jìn)行通信
mIpm = AppGlobals.getPackageManager();
// 動(dòng)態(tài)權(quán)限檢測(cè)
mAppOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
// 提供安裝,卸載的功能
mInstaller = mPm.getPackageInstaller();
// 誰(shuí)喚起了這次安裝,如果是從應(yīng)用安裝,那么就是應(yīng)用相關(guān)的信息,如果是從文件管理器點(diǎn)擊安裝,那么都是空的
mCallingPackage = intent.getStringExtra(EXTRA_CALLING_PACKAGE);
mSourceInfo = intent.getParcelableExtra(EXTRA_ORIGINAL_SOURCE_INFO);
mOriginatingUid = intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID,
PackageInstaller.SessionParams.UID_UNKNOWN);
看一下傳過(guò)來(lái)的apk文件地址

file:///data/user_de/0/com.android.packageinstaller/no_backup/package5541502234249167159.apk
得到地址后,對(duì)APK文件開(kāi)始解析,具體實(shí)現(xiàn)在processPackageUri方法中
ApkAssets apkAssets = ApkAssets.loadFromPath("/data/user_de/0/com.android.packageinstaller/no_backup/package5541502234249167159.apk")
XmlResourceParser parser = apkAssets.openXml(ANDROID_MANIFEST_FILENAME);
構(gòu)造資源路徑
parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
// 這里的assets資源路徑總共有兩個(gè)
// ApkAssets{path=/system/framework/framework-res.apk},應(yīng)用中有很多使用系統(tǒng)資源
// ApkAssets{path=/data/user_de/0/com.android.packageinstaller/no_backup/package4780013443826073855.apk}
final Resources res = new Resources(assets, mMetrics, null);
最后看下解析的結(jié)果
activitys // 數(shù)量和類(lèi)名
applicationInfo
baseCodePath
mCompileSdkVersion
mVersionName
mVersionCode
manifestPackageName
packageName
permissionGroups
permissions
use32bitAbi = false
codePath = /data/user_de/0/com.android.packageinstaller/no_backup/package4780013443826073855.apk
providers
receivers
.....
基本上得到在清單文件注冊(cè)的所有信息
點(diǎn)擊繼續(xù)之后會(huì)出現(xiàn)確認(rèn)安裝界面

在看看點(diǎn)擊安裝之后的邏輯
private void startInstall() {
// Start subactivity to actually install the application
Intent newIntent = new Intent();
// 應(yīng)用的ApplicationInfo
newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
mPkgInfo.applicationInfo);
// mPackageURI = file:///data/user_de/0/com.android.packageinstaller/no_backup/package479667155487808554.apk
newIntent.setData(mPackageURI);
// 跳轉(zhuǎn)到InstallInstalling中
newIntent.setClass(this, InstallInstalling.class);
String installerPackageName = getIntent().getStringExtra(
Intent.EXTRA_INSTALLER_PACKAGE_NAME);
if (mOriginatingURI != null) {
newIntent.putExtra(Intent.EXTRA_ORIGINATING_URI, mOriginatingURI);
}
if (mReferrerURI != null) {
newIntent.putExtra(Intent.EXTRA_REFERRER, mReferrerURI);
}
if (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) {
newIntent.putExtra(Intent.EXTRA_ORIGINATING_UID, mOriginatingUid);
}
if (installerPackageName != null) {
newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME,
installerPackageName);
}
if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
}
newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI);
startActivity(newIntent);
finish();
}
邏輯比較簡(jiǎn)單,就是將解析出來(lái)的ApplicationInfo和之前傳遞過(guò)來(lái)的PackageURI繼續(xù)傳遞到InstallInstalling中.
這里應(yīng)該會(huì)跟現(xiàn)在網(wǎng)上已經(jīng)有的描述不一樣,android 10在應(yīng)用安裝過(guò)程不會(huì)展示需要系統(tǒng)權(quán)限
InstallInstalling
這里差不多是在應(yīng)用安裝在com.android.packageinstaller進(jìn)程的最后一步。
首先注冊(cè)了注冊(cè)一個(gè)接受應(yīng)用安裝成功或者失敗的廣播
try {
mInstallId = InstallEventReceiver
.addObserver(this, EventResultPersister.GENERATE_NEW_ID,
this::launchFinishBasedOnResult);
} catch (EventResultPersister.OutOfIdsException e) {
launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
}
這里需要重點(diǎn)看一下這個(gè)廣播
@NonNull private static EventResultPersister getReceiver(@NonNull Context context) {
synchronized (sLock) {
if (sReceiver == null) {
sReceiver = new EventResultPersister(
// 這里返回了一個(gè)File 具體路徑是
// /data/user_de/0/com.android.packageinstaller/no_backup/install_results.xml
// install_results.xml中記錄的每次安裝的installId,這次installId每次安裝的時(shí)候遞增
TemporaryFileManager.getInstallStateFile(context));
}
}
return sReceiver;
}
我們安裝應(yīng)用的時(shí)候通常會(huì)有進(jìn)度呢,到現(xiàn)在還沒(méi)看到進(jìn)度的回調(diào)呢?
進(jìn)度的回調(diào)肯定是PMS回調(diào)給InstallInstalling來(lái)展示的,接著往下看,果然在后面建立了于PMS通信
try {
// 這種SessionID的系統(tǒng)應(yīng)用和SystemServer的服務(wù)中很常見(jiàn),因?yàn)橄到y(tǒng)服務(wù)要服務(wù)于所有應(yīng)用,所以每個(gè)應(yīng)用調(diào)用系統(tǒng)的服務(wù)的時(shí)候,在每個(gè)系統(tǒng)服務(wù)那里都會(huì)有一個(gè)sessionID的列表,標(biāo)識(shí)著與各種應(yīng)用的連接
mSessionId = getPackageManager().getPackageInstaller().createSession(params);
} catch (IOException e) {
launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
}
mSessionCallback = new InstallSessionCallback();
// InstallSessionCallback這里就是PMS的進(jìn)度回調(diào)
@Override
public void onProgressChanged(int sessionId, float progress) {
if (sessionId == mSessionId) {
ProgressBar progressBar = requireViewById(R.id.progress);
progressBar.setMax(Integer.MAX_VALUE);
progressBar.setProgress((int) (Integer.MAX_VALUE * progress));
}
}
接著就是將APK傳遞給PMS,怎么傳遞呢,直接傳遞路徑?NO
PackageInstaller.Session session;
try {
// 通過(guò)上一步創(chuàng)建的sessionID,創(chuàng)建一個(gè)安裝的會(huì)話(huà)
session = getPackageManager().getPackageInstaller().openSession(mSessionId);
} catch (IOException e) {
return null;
}
session.setStagingProgress(0);
try {
File file = new File(mPackageURI.getPath());
try (InputStream in = new FileInputStream(file)) {
long sizeBytes = file.length();
// 將字節(jié)流寫(xiě)入PMS中,具體寫(xiě)到什么位置了呢?下文有介紹
try (OutputStream out = session
.openWrite("PackageInstaller", 0, sizeBytes)) {
byte[] buffer = new byte[1024 * 1024];
while (true) {
int numRead = in.read(buffer);
if (numRead == -1) {
session.fsync(out);
break;
}
if (isCancelled()) {
session.close();
break;
}
out.write(buffer, 0, numRead);
if (sizeBytes > 0) {
float fraction = ((float) numRead / (float) sizeBytes);
session.addProgress(fraction);
}
}
}
}
return session;
} catch (IOException | SecurityException e) {
Log.e(LOG_TAG, "Could not write package", e);
session.close();
return null;
} finally {
synchronized (this) {
isDone = true;
notifyAll();
}
}
APK傳輸給PMS之后下一步肯定是通知PMS我傳遞完了,你可以開(kāi)始安裝了,接著看下怎么通知了
Intent broadcastIntent = new Intent(BROADCAST_ACTION);
broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
broadcastIntent.setPackage(getPackageName());
// 還記得這個(gè)mInstallId之前在哪里創(chuàng)建的嗎,忘記了往前翻翻,這里將mInstallId也傳遞給了PMS,PMS拿到這個(gè)mInstallID,在應(yīng)用安裝完成后就可以發(fā)送一個(gè)廣播,在把這個(gè)mInstallID回寫(xiě),packageinstaller進(jìn)程就可以單獨(dú)去更新這個(gè)被安裝應(yīng)用的狀態(tài)
broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId);
// PendingIntent 延時(shí)意圖,滿(mǎn)足某個(gè)條件的時(shí)候才觸發(fā)
PendingIntent pendingIntent = PendingIntent.getBroadcast(
InstallInstalling.this,
mInstallId,
broadcastIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
session.commit(pendingIntent.getIntentSender());
mCancelButton.setEnabled(false);
setFinishOnTouchOutside(false);
說(shuō)了半天其實(shí)和PMS還沒(méi)有半毛錢(qián)關(guān)系,只是說(shuō)安裝前的準(zhǔn)備工作,收攏總結(jié)一下在com.android.packageinstaller中流程的工作

PMS中的安裝流程
在介紹PMS之前,先介紹PNS
接著上面IPC調(diào)用開(kāi)始分析
session.commit(pendingIntent.getIntentSender());
對(duì)應(yīng)的遠(yuǎn)程調(diào)用在 PackageInstallerSession中的commit方法
@Override
public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) {
if (hasParentSessionId()) {
throw new IllegalStateException(
"Session " + sessionId + " is a child of multi-package session "
+ mParentSessionId + " and may not be committed directly.");
}
if (!markAsCommitted(statusReceiver, forTransfer)) {
return;
}
... // 省略部分代碼
...
// 發(fā)送了一個(gè)MSG_COMMIT
mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
}
這個(gè)Handler通過(guò)Callback的方式處理消息,在SystemServer進(jìn)程斷點(diǎn)看一下

在handleCommit方法中終于調(diào)用PMS中的installStage方法
// 安裝信息都在ActiveInstallSession中,ActiveInstallSession具體有哪些值呢
// mInstallPackageName = com.android.packageinstaller
// mInstallUid = 10048
// mPackageName = com.android.sourcecodedebug
// mStageDir = /data/app/vmdl1397850738.tmp
// .....
// 之前說(shuō)過(guò)跨進(jìn)程將APK寫(xiě)入到PMS中,其實(shí)就是寫(xiě)入到 /data/app/vmdl1397850738.tmp,它是個(gè)文件夾,里面有兩個(gè)文件,后面的數(shù)字是隨機(jī)生成的,不同的應(yīng)用不一樣
// /data/app/vmdl1397850738.tmp/lib 估計(jì)能猜出有啥作用
// /data/app/vmdl1397850738.tmp/base.apk
void installStage(ActiveInstallSession activeInstallSession) {
final Message msg = mHandler.obtainMessage(INIT_COPY);
final InstallParams params = new InstallParams(activeInstallSession);
msg.obj = params;
// 這個(gè)mHandler是PMS中的核心,下文會(huì)細(xì)說(shuō)
// 發(fā)送了一個(gè)INIT_COPY消息
mHandler.sendMessage(msg);
}
PackageHandler是處理PMS中的所有邏輯的核心,應(yīng)用的安裝,卸載都會(huì)觸發(fā)到這個(gè)Handler,消息的種類(lèi)太多,先接著上文的INIC_COPY消息來(lái)看
// 看著好像比較簡(jiǎn)單,直接調(diào)用了InstallParams的startCopy方法,看名字像是做了一次拷貝,其實(shí)不完全是
case INIT_COPY: {
HandlerParams params = (HandlerParams) msg.obj;
if (params != null) {
params.startCopy();
}
break;
}
InstallParams.startCopy中就兩個(gè)方法調(diào)用
- handleStartCopy
- handleReturnCode
先看handleStartCopy
/*
*
* 方法很長(zhǎng),省略了部分代碼,關(guān)鍵其實(shí)就做了兩件事
1 輕量解析 /data/app/vmdl1397850738.tmp/base.apk文件,將各種信息包裝成PackageInfoLite
2 確定應(yīng)用的安裝位置,三方應(yīng)用,系統(tǒng)應(yīng)用,InstantApp都有對(duì)應(yīng)的規(guī)則
* {@link PackageHelper#RECOMMEND_INSTALL_INTERNAL} to install on internal storage, 等于1
* {@link PackageHelper#RECOMMEND_INSTALL_EXTERNAL} to install on external media, 等于2
* {@link PackageHelper#RECOMMEND_FAILED_INSUFFICIENT_STORAGE} for storage errors, 等于-1
*/
public void handleStartCopy() {
int ret = PackageManager.INSTALL_SUCCEEDED;
....
....
PackageInfoLite pkgLite = null;
pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mContext,
origin.resolvedPath, installFlags, packageAbiOverride);
/*
* 判斷空間夠不夠,不光是APK文件,還有從APK解壓釋放的so文件
*
*/
if (!origin.staged && pkgLite.recommendedInstallLocation
== PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
// TODO: focus freeing disk space on the target device
final StorageManager storage = StorageManager.from(mContext);
final long lowThreshold = storage.getStorageLowBytes(
Environment.getDataDirectory());
final long sizeBytes = PackageManagerServiceUtils.calculateInstalledSize(
origin.resolvedPath, packageAbiOverride);
if (sizeBytes >= 0) {
try {
mInstaller.freeCache(null, sizeBytes + lowThreshold, 0, 0);
pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mContext,
origin.resolvedPath, installFlags, packageAbiOverride);
} catch (InstallerException e) {
Slog.w(TAG, "Failed to free cache", e);
}
}
}
if (ret == PackageManager.INSTALL_SUCCEEDED) {
// 解析完就可以知道應(yīng)用的類(lèi)型,獲取PMS推薦的安裝目錄,一般都是1 內(nèi)置存儲(chǔ)
int loc = pkgLite.recommendedInstallLocation;
.....
.....
}
mRet = ret;
}
接著看handlerReturnCode
@Override
void handleReturnCode() {
if (mVerificationCompleted && mEnableRollbackCompleted) {
.....
.....
if (mRet == PackageManager.INSTALL_SUCCEEDED) {
// copyAPK并沒(méi)有開(kāi)始復(fù)制,
mRet = mArgs.copyApk();
}
processPendingInstall(mArgs, mRet);
}
}
copyApk()并沒(méi)有真正的復(fù)制,只是將FileInstallArgs對(duì)象中的兩個(gè)成員變量復(fù)制,兩個(gè)變量值都是一樣的
/data/app/vmdl424643763.tmp

最終的實(shí)現(xiàn)在processPendingInstall方法中,通過(guò)PackageHandler post一個(gè)Runnable
private void processInstallRequestsAsync(boolean success,
List<InstallRequest> installRequests) {
mHandler.post(() -> {
if (success) {
for (InstallRequest request : installRequests) {
// 對(duì)安裝目錄做一下清理
request.args.doPreInstall(request.installResult.returnCode);
}
synchronized (mInstallLock) {
// 重點(diǎn),安裝過(guò)程中最核心的方法
installPackagesTracedLI(installRequests);
}
for (InstallRequest request : installRequests) {
// 安裝完成之后的工作
request.args.doPostInstall(
request.installResult.returnCode, request.installResult.uid);
}
}
});
}
installPackagesTracedLi方法代碼比較多,主要執(zhí)行了下面四個(gè)邏輯
1 全量解析APK文件
2 校驗(yàn)APK簽名,如果之前有安裝過(guò)會(huì)和之前的APK簽名做比對(duì)
3 釋放apk文件中的so庫(kù),釋放到/data/app/vmdl1397850738.tmp/lib**
4 對(duì)目錄和文件充新命名,將/data/app/vmdl903343092.tmp重新命名為/data/app/包名/,將/data/app/vmdl1397850738.tmp/base.apk重新命名為/data/app/包名/base.apk**5 重新命名后的地址賦值給PackageParser.Package
6 更新mPackages和mSettings中的應(yīng)用信息
最后一個(gè)目錄比較熟悉吧!
這里描述的安裝流程和現(xiàn)有的很多不一致的地方,主要是android 10的源代碼在PMS安裝這一塊改動(dòng)挺大,摒棄了PackageHandler中的MCS_BOUND消息。
