最近看恢复出厂的一个问题,以前也查过这方面的流程,所以这里整理一些AP+framework层的流程;
在setting-->备份与重置--->恢复出厂设置--->重置手机--->清除全部内容--->手机关机--->开机--->进行恢复出厂的操作--->开机流程;
Step 1:前面找settings中的布局我就省略了,这部分相对简单一些,直接到清除全部内容这个按钮的操作,
对应的java类是setting中的MasterClearConfirm.java这个类,
-
private Button.OnClickListener mFinalClickListener = new Button.OnClickListener() {
-
-
public void onClick(View v) {
-
if (Utils.isMonkeyRunning()) {
-
return;
-
}
-
-
if (mEraseSdCard) {
-
Intent intent = new Intent(ExternalStorageFormatter.FORMAT_AND_FACTORY_RESET);
-
intent.setComponent(ExternalStorageFormatter.COMPONENT_NAME);
-
getActivity().startService(intent);
-
} else {
-
getActivity().sendBroadcast(new Intent("android.intent.action.MASTER_CLEAR"));
-
-
}
-
}
-
};
通过上述的代码,可以看出,实际上点击清除全部内容的时候,如果前面勾选上格式哈SD卡,就会执行mEraseSdCard为true里面的逻辑,如果没有勾选,就执行mEraseSdCard=false的逻辑,其实就是发送一个广播,
-
"font-size:14px;">“android.intent.action.MASTER_CLEAR”
Step 2:这个广播接受的地方,参见AndroidManifest.xml中的代码,如下:
-
<receiver android:name="com.android.server.MasterClearReceiver"
-
android:permission="android.permission.MASTER_CLEAR"
-
android:priority="100" >
-
<intent-filter>
-
-
<action android:name="android.intent.action.MASTER_CLEAR" />
-
-
-
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
-
<category android:name="android.intent.category.MASTER_CLEAR" />
-
intent-filter>
-
receiver>
找这个MasterClearReceiver.java这个receiver,下面来看看这个onReceiver()里面做了什么操作:
-
public void onReceive(final Context context, final Intent intent) {
-
if (intent.getAction().equals(Intent.ACTION_REMOTE_INTENT)) {
-
if (!"google.com".equals(intent.getStringExtra("from"))) {
-
Slog.w(TAG, "Ignoring master clear request -- not from trusted server.");
-
return;
-
}
-
}
-
-
Slog.w(TAG, "!!! FACTORY RESET !!!");
-
-
Thread thr = new Thread("Reboot") {
-
@Override
-
public void run() {
-
try {
-
RecoverySystem.rebootWipeUserData(context);
-
Log.wtf(TAG, "Still running after master clear?!");
-
} catch (IOException e) {
-
Slog.e(TAG, "Can't perform master clear/factory reset", e);
-
}
-
}
-
};
-
thr.start();
-
}
这个里面主要的操作是:RecoverySystem.rebootWipeUserData(context);准备做重启的动作,告诉手机要清除userData的数据;
Step 3:接着来看看RecoverySystem.rebootWipeUserData()这个方法做了哪些操作:
-
public static void rebootWipeUserData(Context context) throws IOException {
-
final ConditionVariable condition = new ConditionVariable();
-
-
Intent intent = new Intent("android.intent.action.MASTER_CLEAR_NOTIFICATION");
-
context.sendOrderedBroadcastAsUser(intent, UserHandle.OWNER,
-
android.Manifest.permission.MASTER_CLEAR,
-
new BroadcastReceiver() {
-
@Override
-
public void onReceive(Context context, Intent intent) {
-
condition.open();
-
}
-
}, null, 0, null, null);
-
-
-
condition.block();
-
-
bootCommand(context, "--wipe_data\n--locale=" + Locale.getDefault().toString());
-
}
这个里面的广播可以先忽略不计,重点来看看bootCommand()这个方法,注意这个参数“--wipe_data\n--locale=”
-
private static void bootCommand(Context context, String arg) throws IOException {
-
RECOVERY_DIR.mkdirs();
-
COMMAND_FILE.delete();
-
LOG_FILE.delete();
-
-
FileWriter command = new FileWriter(COMMAND_FILE);
-
try {
-
command.write(arg);
-
command.write("\n");
-
} finally {
-
command.close();
-
}
-
-
-
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-
pm.reboot("recovery");
-
-
throw new IOException("Reboot failed (no permissions?)");
-
}
这个方法的操作大致是“写节点/cache/recovery/command”,把传递过来的字符串写进去;然后调用PowerManager进行重启操作,reboot();
Step 4:接着我们来看看PowerManager的reboot方法做了哪些操作:
-
public void reboot(String reason) {
-
try {
-
mService.reboot(false, reason, true);
-
} catch (RemoteException e) {
-
}
-
}
这个调用到了PowerManagerService.java这个类的reboot方法中了:
-
@Override
-
public void reboot(boolean confirm, String reason, boolean wait) {
-
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null);
-
-
final long ident = Binder.clearCallingIdentity();
-
try {
-
shutdownOrRebootInternal(false, confirm, reason, wait);
-
} finally {
-
Binder.restoreCallingIdentity(ident);
-
}
-
}
重点来看看shutdownOrRebootInternal()这个方法,
-
private void shutdownOrRebootInternal(final boolean shutdown, final boolean confirm,
-
final String reason, boolean wait) {
-
if (mHandler == null || !mSystemReady) {
-
throw new IllegalStateException("Too early to call shutdown() or reboot()");
-
}
-
-
Runnable runnable = new Runnable() {
-
@Override
-
public void run() {
-
synchronized (this) {
-
if (shutdown) {
-
ShutdownThread.shutdown(mContext, confirm);
-
} else {
-
ShutdownThread.reboot(mContext, reason, confirm);
-
}
-
}
-
}
-
};
-
-
-
Message msg = Message.obtain(mHandler, runnable);
-
msg.setAsynchronous(true);
-
mHandler.sendMessage(msg);
-
-
-
if (wait) {
-
synchronized (runnable) {
-
while (true) {
-
try {
-
runnable.wait();
-
} catch (InterruptedException e) {
-
}
-
}
-
}
-
}
-
}
由于传递过来的shutdown为false,所以执行ShutdownThread.reboot(mContext, reason, confirm);reason:recevory
下面调用到ShutdownThread
Step 5:这个追踪ShutdownThread.reboot()这个方法,这就有点像破案电影,一点一点查找罪犯的难点;
来窥视一下这个类:
-
public static void reboot(final Context context, String reason, boolean confirm) {
-
mReboot = true;
-
mRebootSafeMode = false;
-
mRebootReason = reason;
-
Log.d(TAG, "reboot");
-
shutdownInner(context, confirm);
-
}
这个里面做的操作就是给这个变量mRebootReason复制“recevory”,重点调用shutdownInner()这个方法;
-
"font-size:14px;">static void shutdownInner(final Context context, boolean confirm) {
-
-
-
synchronized (sIsStartedGuard) {
-
if (sIsStarted) {
-
Log.d(TAG, "Request to shutdown already running, returning.");
-
return;
-
}
-
}
-
-
Log.d(TAG, "Notifying thread to start radio shutdown");
-
bConfirmForAnimation = confirm;
-
final int longPressBehavior = context.getResources().getInteger(
-
com.android.internal.R.integer.config_longPressOnPowerBehavior);
-
final int resourceId = mRebootSafeMode
-
? com.android.internal.R.string.reboot_safemode_confirm
-
: (longPressBehavior == 2
-
? com.android.internal.R.string.shutdown_confirm_question
-
: com.android.internal.R.string.shutdown_confirm);
-
-
Log.d(TAG, "Notifying thread to start shutdown longPressBehavior=" + longPressBehavior);
-
-
if (confirm) {
-
final CloseDialogReceiver closer = new CloseDialogReceiver(context);
-
if (sConfirmDialog != null) {
-
sConfirmDialog.dismiss();
-
}
-
if (sConfirmDialog == null) {
-
Log.d(TAG, "PowerOff dialog doesn't exist. Create it first");
-
sConfirmDialog = new AlertDialog.Builder(context)
-
.setTitle(mRebootSafeMode
-
? com.android.internal.R.string.reboot_safemode_title
-
: com.android.internal.R.string.power_off)
-
.setMessage(resourceId)
-
.setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() {
-
public void onClick(DialogInterface dialog, int which) {
-
beginShutdownSequence(context);
-
if (sConfirmDialog != null) {
-
sConfirmDialog = null;
-
}
-
}
-
})
-
.setNegativeButton(com.android.internal.R.string.no, new DialogInterface.OnClickListener() {
-
public void onClick(DialogInterface dialog, int which) {
-
synchronized (sIsStartedGuard) {
-
sIsStarted = false;
-
}
-
if (sConfirmDialog != null) {
-
sConfirmDialog = null;
-
}
-
}
-
})
-
.create();
-
sConfirmDialog.setCancelable(false);
-
sConfirmDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
-
-
-
-
-
-
sConfirmDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
-
}
-
-
closer.dialog = sConfirmDialog;
-
sConfirmDialog.setOnDismissListener(closer);
-
-
if (!sConfirmDialog.isShowing()) {
-
sConfirmDialog.show();
-
}
-
} else {
-
beginShutdownSequence(context);
-
}
-
}
看beginShutdownSequence()这个方法吧,重点调用到这个方法里面去了,来瞅瞅这个方法:
-
"font-size:14px;">private static void beginShutdownSequence(Context context) {
-
synchronized (sIsStartedGuard) {
-
if (sIsStarted) {
-
Log.e(TAG, "ShutdownThread is already running, returning.");
-
return;
-
}
-
sIsStarted = true;
-
}
-
-
-
sInstance.mContext = context;
-
sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
-
sInstance.mHandler = new Handler() {
-
};
-
-
bPlayaudio = true;
-
if (!bConfirmForAnimation) {
-
if (!sInstance.mPowerManager.isScreenOn()) {
-
bPlayaudio = false;
-
}
-
}
-
-
-
-
beginAnimationTime = 0;
-
boolean mShutOffAnimation = false;
-
-
try {
-
if (mIBootAnim == null) {
-
mIBootAnim = MediatekClassFactory.createInstance(IBootAnimExt.class);
-
}
-
} catch (Exception e) {
-
e.printStackTrace();
-
}
-
-
int screenTurnOffTime = mIBootAnim.getScreenTurnOffTime();
-
mShutOffAnimation = mIBootAnim.isCustBootAnim();
-
Log.e(TAG, "mIBootAnim get screenTurnOffTime : " + screenTurnOffTime);
-
-
String cust = SystemProperties.get("ro.operator.optr");
-
-
if (cust != null) {
-
if (cust.equals("CUST")) {
-
mShutOffAnimation = true;
-
}
-
}
-
-
synchronized (mEnableAnimatingSync) {
-
-
if(!mEnableAnimating) {
-
-
} else {
-
if (mShutOffAnimation) {
-
Log.e(TAG, "mIBootAnim.isCustBootAnim() is true");
-
bootanimCust();
-
} else {
-
pd = new ProgressDialog(context);
-
pd.setTitle(context.getText(com.android.internal.R.string.power_off));
-
pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
-
pd.setIndeterminate(true);
-
pd.setCancelable(false);
-
pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
-
-
pd.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
-
pd.show();
-
}
-
sInstance.mHandler.postDelayed(mDelayDim, screenTurnOffTime );
-
}
-
}
-
-
-
sInstance.mCpuWakeLock = null;
-
try {
-
sInstance.mCpuWakeLock = sInstance.mPowerManager.newWakeLock(
-
。。。 。。。
-
}
这段代码有句话会影响关机动画播放不完
“sInstance.mHandler.postDelayed(mDelayDim, screenTurnOffTime ); ”
解决办法
(1)“可以把这个screenTurnOffTime时间乘以2,这个时间看log是5000毫秒,就是5秒,乘以2就是10秒,大概就能播放完全关机动画了。”
(2)把这句话注释掉,但是有可能会引起问题,导致恢复出厂设置的时候没有进行恢复出厂的操作。目前正在追踪此问题;
这段代码中还有影响关机动画是否走客制化的关机动画,如果ro.operator.optr这个属性配置的是CUST,则会走客制化的关机动画,否则走系统默认的关机动画;
-
String cust = SystemProperties.get("ro.operator.optr");
-
-
-
if (cust != null) {
-
if (cust.equals("CUST")) {
-
mShutOffAnimation = true;
-
}
-
}
然后重点看 sInstance.start();这个方法,就走到了run()方法里满了;
Step 6: 来看看ShutDownThread.java这个类的run()方法;
-
"font-size:14px;">public void run() {
-
checkShutdownFlow();
-
while (mShutdownFlow == IPO_SHUTDOWN_FLOW) {
-
stMgr.saveStates(mContext);
-
stMgr.enterShutdown(mContext);
-
running();
-
}
-
if (mShutdownFlow != IPO_SHUTDOWN_FLOW) {
-
stMgr.enterShutdown(mContext);
-
running();
-
}
-
}
重点看running()这个方法:
下面这个方法比较长,来分析一下:
-
"font-size:14px;">public void running() {
-
if(sPreShutdownApi != null){
-
try {
-
sPreShutdownApi.onPowerOff();
-
} catch (RemoteException e) {
-
Log.e(TAG, "onPowerOff exception" + e.getMessage());
-
}
-
}else{
-
Log.w(TAG, "sPreShutdownApi is null");
-
}
-
-
command = SystemProperties.get("sys.ipo.pwrdncap");
-
-
BroadcastReceiver br = new BroadcastReceiver() {
-
@Override public void onReceive(Context context, Intent intent) {
-
-
actionDone();
-
}
-
};
-
-
-
-
-
-
-
{
-
String reason = (mReboot ? "1" : "0") + (mRebootReason != null ? mRebootReason : "");
-
SystemProperties.set(SHUTDOWN_ACTION_PROPERTY, reason);
-
}
-
-
-
-
-
-
if (mRebootSafeMode) {
-
SystemProperties.set(REBOOT_SAFEMODE_PROPERTY, "1");
-
}
-
-
Log.i(TAG, "Sending shutdown broadcast...");
-
-
-
mActionDone = false;
-
-
mContext.sendBroadcast(new Intent("android.intent.action.ACTION_PRE_SHUTDOWN"));
-
-
mContext.sendOrderedBroadcastAsUser((new Intent()).setAction(Intent.ACTION_SHUTDOWN).putExtra("_mode", mShutdownFlow),
-
UserHandle.ALL, null, br, mHandler, 0, null, null);
-
-
final long endTime = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME;
-
synchronized (mActionDoneSync) {
-
while (!mActionDone) {
-
long delay = endTime - SystemClock.elapsedRealtime();
-
if (delay <= 0) {
-
Log.w(TAG, "Shutdown broadcast ACTION_SHUTDOWN timed out");
-
if (mShutdownFlow == IPO_SHUTDOWN_FLOW) {
-
Log.d(TAG, "change shutdown flow from ipo to normal: ACTION_SHUTDOWN timeout");
-
mShutdownFlow = NORMAL_SHUTDOWN_FLOW;
-
}
-
break;
-
}
-
try {
-
mActionDoneSync.wait(delay);
-
} catch (InterruptedException e) {
-
}
-
}
-
}
-
-
-
if (mShutdownFlow == IPO_SHUTDOWN_FLOW) {
-
mActionDone = false;
-
mContext.sendOrderedBroadcast(new Intent("android.intent.action.ACTION_SHUTDOWN_IPO"), null,
-
br, mHandler, 0, null, null);
-
final long endTimeIPO = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME;
-
synchronized (mActionDoneSync) {
-
while (!mActionDone) {
-
long delay = endTimeIPO - SystemClock.elapsedRealtime();
-
if (delay <= 0) {
-
Log.w(TAG, "Shutdown broadcast ACTION_SHUTDOWN_IPO timed out");
-
if (mShutdownFlow == IPO_SHUTDOWN_FLOW) {
-
Log.d(TAG, "change shutdown flow from ipo to normal: ACTION_SHUTDOWN_IPO timeout");
-
mShutdownFlow = NORMAL_SHUTDOWN_FLOW;
-
}
-
break;
-
}
-
try {
-
mActionDoneSync.wait(delay);
-
} catch (InterruptedException e) {
-
}
-
}
-
}
-
}
-
-
if (mShutdownFlow != IPO_SHUTDOWN_FLOW) {
-
-
Log.i(TAG, "Shutting down activity manager...");
-
-
final IActivityManager am =
-
ActivityManagerNative.asInterface(ServiceManager.checkService("activity"));
-
if (am != null) {
-
try {
-
am.shutdown(MAX_BROADCAST_TIME);
-
} catch (RemoteException e) {
-
}
-
}
-
}
-
-
-
-
Log.i(TAG, "Shutting down radios...");
-
shutdownRadios(MAX_RADIO_WAIT_TIME);
-
-
-
Log.i(TAG, "Shutting down MountService...");
-
if ( (mShutdownFlow == IPO_SHUTDOWN_FLOW) && (command.equals("1")||command.equals("3")) ) {
-
Log.i(TAG, "bypass MountService!");
-
} else {
-
-
IMountShutdownObserver observer = new IMountShutdownObserver.Stub() {
-
public void onShutDownComplete(int statusCode) throws RemoteException {
-
Log.w(TAG, "Result code " + statusCode + " from MountService.shutdown");
-
if (statusCode < 0) {
-
mShutdownFlow = NORMAL_SHUTDOWN_FLOW;
-
}
-
actionDone();
-
}
-
};
-
-
-
-
-
mActionDone = false;
-
final long endShutTime = SystemClock.elapsedRealtime() + MAX_SHUTDOWN_WAIT_TIME;
-
synchronized (mActionDoneSync) {
-
try {
-
final IMountService mount = IMountService.Stub.asInterface(
-
ServiceManager.checkService("mount"));
-
if (mount != null) {
-
mount.shutdown(observer);
-
} else {
-
Log.w(TAG, "MountService unavailable for shutdown");
-
}
-
} catch (Exception e) {
-
Log.e(TAG, "Exception during MountService shutdown", e);
-
}
-
while (!mActionDone) {
-
long delay = endShutTime - SystemClock.elapsedRealtime();
-
if (delay <= 0) {
-
Log.w(TAG, "Shutdown wait timed out");
-
if (mShutdownFlow == IPO_SHUTDOWN_FLOW) {
-
Log.d(TAG, "change shutdown flow from ipo to normal: MountService");
-
mShutdownFlow = NORMAL_SHUTDOWN_FLOW;
-
}
-
break;
-
}
-
try {
-
mActionDoneSync.wait(delay);
-
} catch (InterruptedException e) {
-
}
-
}
-
}
-
}
-
-
-
-
Log.i(TAG, "MountService shut done...");
-
-
-
try {
-
SystemProperties.set("service.shutanim.running","1");
-
Log.i(TAG, "set service.shutanim.running to 1");
-
-
} catch (Exception ex) {
-
Log.e(TAG, "Failed to set 'service.shutanim.running' = 1).");
-
}
-
-
-
if (mShutdownFlow == IPO_SHUTDOWN_FLOW) {
-
if (SHUTDOWN_VIBRATE_MS > 0) {
-
-
Vibrator vibrator = new SystemVibrator();
-
try {
-
vibrator.vibrate(SHUTDOWN_VIBRATE_MS);
-
} catch (Exception e) {
-
-
Log.w(TAG, "Failed to vibrate during shutdown.", e);
-
}
-
-
-
try {
-
Thread.sleep(SHUTDOWN_VIBRATE_MS);
-
} catch (InterruptedException unused) {
-
}
-
}
-
-
-
-
Log.i(TAG, "Performing ipo low-level shutdown...");
-
-
delayForPlayAnimation();
-
-
if (sInstance.mScreenWakeLock != null && sInstance.mScreenWakeLock.isHeld()) {
-
sInstance.mScreenWakeLock.release();
-
sInstance.mScreenWakeLock = null;
-
}
-
-
sInstance.mHandler.removeCallbacks(mDelayDim);
-
stMgr.shutdown(mContext);
-
stMgr.finishShutdown(mContext);
-
-
-
if (pd != null) {
-
pd.dismiss();
-
pd = null;
-
} else if (beginAnimationTime > 0) {
-
try {
-
SystemProperties.set("service.bootanim.exit","1");
-
Log.i(TAG, "set 'service.bootanim.exit' = 1).");
-
} catch (Exception ex) {
-
Log.e(TAG, "Failed to set 'service.bootanim.exit' = 1).");
-
}
-
-
}
-
-
synchronized (sIsStartedGuard) {
-
sIsStarted = false;
-
}
-
-
sInstance.mPowerManager.setBacklightBrightnessOff(false);
-
sInstance.mCpuWakeLock.acquire(2000);
-
-
synchronized (mShutdownThreadSync) {
-
try {
-
mShutdownThreadSync.wait();
-
} catch (InterruptedException e) {
-
}
-
}
-
} else {
-
rebootOrShutdown(mReboot, mRebootReason);
-
}
-
}
这个方法做了一些列的操作,会关闭一些操作,如:
-
shutdownRadios(MAX_RADIO_WAIT_TIME);
-
mount.shutdown(observer);
-
stMgr.shutdown(mContext);
重点看 rebootOrShutdown(mReboot, mRebootReason);这个方法;准备重启的方法;
Step 7:来看看rebootOrShutdown()这个方法:
-
"font-size:14px;">public static void rebootOrShutdown(boolean reboot, String reason) {
-
if (reboot) {
-
Log.i(TAG, "Rebooting, reason: " + reason);
-
if ( (reason != null) && reason.equals("recovery") ) {
-
delayForPlayAnimation();
-
}
-
try {
-
PowerManagerService.lowLevelReboot(reason);
-
} catch (Exception e) {
-
Log.e(TAG, "Reboot failed, will attempt shutdown instead", e);
-
}
-
} else if (SHUTDOWN_VIBRATE_MS > 0) {
-
-
Vibrator vibrator = new SystemVibrator();
-
try {
-
vibrator.vibrate(SHUTDOWN_VIBRATE_MS);
-
} catch (Exception e) {
-
-
Log.w(TAG, "Failed to vibrate during shutdown.", e);
-
}
-
-
-
try {
-
Thread.sleep(SHUTDOWN_VIBRATE_MS);
-
} catch (InterruptedException unused) {
-
}
-
}
-
-
delayForPlayAnimation();
-
-
-
Log.i(TAG, "Performing low-level shutdown...");
-
-
-
-
try {
-
if (ImHDMI == null)
-
ImHDMI=MediatekClassFactory.createInstance(IHDMINative.class);
-
} catch (Exception e) {
-
e.printStackTrace();
-
}
-
ImHDMI.hdmiPowerEnable(false);
-
try {
-
if (mTvOut == null)
-
mTvOut =MediatekClassFactory.createInstance(ITVOUTNative.class);
-
} catch (Exception e) {
-
e.printStackTrace();
-
}
-
-
mTvOut.tvoutPowerEnable(false);
-
-
-
-
SystemProperties.set("ctl.start", "shutdown");
-
-
-
try {
-
Thread.currentThread().sleep(Integer.MAX_VALUE);
-
} catch ( Exception e) {
-
Log.e(TAG, "Shutdown rebootOrShutdown Thread.currentThread().sleep exception!");
-
}
-
}
关机震动也在这个方法里面;这个方法重点看PowerManagerService.lowLevelReboot(reason);
Log.i(TAG, "Rebooting, reason: " + reason);这句log也很重要,可以有助于我们分析代码;
Step 8:下面来看看PowerManagerServices.java这个类的lowLevelReboot()这个方法:
-
"font-size:18px;">public static void lowLevelReboot(String reason) throws IOException {
-
nativeReboot(reason);
-
}
这个方法调用到了native里面,后面的操作我就不分析了。。。
大致流程是:
关机,然后开机,底层判断节点后进入恢复出厂模式,recevory.img释放完全后,进入开机的流程。。。
以后有进展再补充这部分的流程,整个过程大致就是这个样子了,里面的细节有好多没有分析,大家可以自行研究。。。,抛砖引玉的目的达到了。