android恢复出厂设置流程分析
原文出自:http://blog.csdn.net/wdaming1986/article/details/11988531
最近看恢复出厂的一个问题,以前也查过这方面的流程,所以这里整理一些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"));
- // Intent handling is asynchronous -- assume it will happen soon.
- }
- }
- };
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"));// Intent handling is asynchronous -- assume it will happen soon.}}};
通过上述的代码,可以看出,实际上点击清除全部内容的时候,如果前面勾选上格式哈SD卡,就会执行mEraseSdCard为true里面的逻辑,如果没有勾选,就执行mEraseSdCard=false的逻辑,其实就是发送一个广播,
- <span style="font-size: 14px;">“android.intent.action.MASTER_CLEAR”</span>
<span style="font-size:14px;">“android.intent.action.MASTER_CLEAR”</span>
Step 2:这个广播接受的地方,参见AndroidManifest.xml中的代码,如下:
- <receiver android:name="com.android.server.MasterClearReceiver"
- android:permission="android.permission.MASTER_CLEAR"
- android:priority="100" >
- <intent-filter>
- <!-- For Checkin, Settings, etc.: action=MASTER_CLEAR -->
- <action android:name="android.intent.action.MASTER_CLEAR" />
- <!-- MCS always uses REMOTE_INTENT: category=MASTER_CLEAR -->
- <action android:name="com.google.android.c2dm.intent.RECEIVE" />
- <category android:name="android.intent.category.MASTER_CLEAR" />
- </intent-filter>
- </receiver>
<receiver android:name="com.android.server.MasterClearReceiver"android:permission="android.permission.MASTER_CLEAR"android:priority="100" ><intent-filter><!-- For Checkin, Settings, etc.: action=MASTER_CLEAR --><action android:name="android.intent.action.MASTER_CLEAR" /><!-- MCS always uses REMOTE_INTENT: category=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 !!!");
- // The reboot call is blocking, so we need to do it on another thread.
- 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();
- }
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 !!!");// The reboot call is blocking, so we need to do it on another thread.Thread thr = new Thread("Reboot") {@Overridepublic 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);
- // Block until the ordered broadcast has completed.
- condition.block();
- bootCommand(context, "--wipe_data\n--locale=" + Locale.getDefault().toString());
- }
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() {@Overridepublic void onReceive(Context context, Intent intent) {condition.open();}}, null, 0, null, null);// Block until the ordered broadcast has completed.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(); // In case we need it
- COMMAND_FILE.delete(); // In case it's not writable
- LOG_FILE.delete();
- FileWriter command = new FileWriter(COMMAND_FILE);
- try {
- command.write(arg);
- command.write("\n");
- } finally {
- command.close();
- }
- // Having written the command file, go ahead and reboot
- PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
- pm.reboot("recovery");
- throw new IOException("Reboot failed (no permissions?)");
- }
private static void bootCommand(Context context, String arg) throws IOException {RECOVERY_DIR.mkdirs(); // In case we need itCOMMAND_FILE.delete(); // In case it's not writableLOG_FILE.delete();FileWriter command = new FileWriter(COMMAND_FILE);try {command.write(arg);command.write("\n");} finally {command.close();}// Having written the command file, go ahead and rebootPowerManager 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) {
- }
- }
public void reboot(String reason) {try {mService.reboot(false, reason, true);} catch (RemoteException e) {}}
这个调用到了PowerManagerService.java这个类的reboot方法中了:
- @Override // Binder call
- 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);
- }
- }
@Override // Binder callpublic 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);
- }
- }
- }
- };
- // ShutdownThread must run on a looper capable of displaying the UI.
- Message msg = Message.obtain(mHandler, runnable);
- msg.setAsynchronous(true);
- mHandler.sendMessage(msg);
- // PowerManager.reboot() is documented not to return so just wait for the inevitable.
- if (wait) {
- synchronized (runnable) {
- while (true) {
- try {
- runnable.wait();
- } catch (InterruptedException e) {
- }
- }
- }
- }
- }
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() {@Overridepublic void run() {synchronized (this) {if (shutdown) {ShutdownThread.shutdown(mContext, confirm);} else {ShutdownThread.reboot(mContext, reason, confirm);}}}};// ShutdownThread must run on a looper capable of displaying the UI.Message msg = Message.obtain(mHandler, runnable);msg.setAsynchronous(true);mHandler.sendMessage(msg);// PowerManager.reboot() is documented not to return so just wait for the inevitable.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);
- }
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()这个方法;
- <span style="font-size: 14px;">static void shutdownInner(final Context context, boolean confirm) {
- // ensure that only one thread is trying to power down.
- // any additional calls are just returned
- 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);//blocking back key
- sConfirmDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
- /*if (!context.getResources().getBoolean(
- com.android.internal.R.bool.config_sf_slowBlur)) {
- sConfirmDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
- }*/
- /* To fix video+UI+blur flick issue */
- sConfirmDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
- }
- closer.dialog = sConfirmDialog;
- sConfirmDialog.setOnDismissListener(closer);
- if (!sConfirmDialog.isShowing()) {
- sConfirmDialog.show();
- }
- } else {
- beginShutdownSequence(context);
- }
- }</span>
<span style="font-size:14px;">static void shutdownInner(final Context context, boolean confirm) {// ensure that only one thread is trying to power down.// any additional calls are just returnedsynchronized (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);//blocking back keysConfirmDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);/*if (!context.getResources().getBoolean(com.android.internal.R.bool.config_sf_slowBlur)) {sConfirmDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND);}*//* To fix video+UI+blur flick issue */sConfirmDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);}closer.dialog = sConfirmDialog;sConfirmDialog.setOnDismissListener(closer);if (!sConfirmDialog.isShowing()) {sConfirmDialog.show();}} else {beginShutdownSequence(context);}}</span>
看beginShutdownSequence()这个方法吧,重点调用到这个方法里面去了,来瞅瞅这个方法:
- <span style="font-size: 14px;">private static void beginShutdownSequence(Context context) {
- synchronized (sIsStartedGuard) {
- if (sIsStarted) {
- Log.e(TAG, "ShutdownThread is already running, returning.");
- return;
- }
- sIsStarted = true;
- }
- // start the thread that initiates shutdown
- 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;
- }
- }
- // throw up an indeterminate system dialog to indicate radio is
- // shutting down.
- 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) {
- // sInstance.mPowerManager.setBacklightBrightness(PowerManager.BRIGHTNESS_DIM);
- } 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);
- /* To fix video+UI+blur flick issue */
- pd.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
- pd.show();
- }
- sInstance.mHandler.postDelayed(mDelayDim, screenTurnOffTime );
- }
- }
- // make sure we never fall asleep again
- sInstance.mCpuWakeLock = null;
- try {
- sInstance.mCpuWakeLock = sInstance.mPowerManager.newWakeLock(
- 。。。 。。。
- }</span>
<span style="font-size:14px;">private static void beginShutdownSequence(Context context) {synchronized (sIsStartedGuard) {if (sIsStarted) {Log.e(TAG, "ShutdownThread is already running, returning."); return;}sIsStarted = true;}// start the thread that initiates shutdownsInstance.mContext = context;sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);sInstance.mHandler = new Handler() {}; bPlayaudio = true;if (!bConfirmForAnimation) {if (!sInstance.mPowerManager.isScreenOn()) {bPlayaudio = false;}}// throw up an indeterminate system dialog to indicate radio is// shutting down.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) {
// sInstance.mPowerManager.setBacklightBrightness(PowerManager.BRIGHTNESS_DIM);} 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);/* To fix video+UI+blur flick issue */pd.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);pd.show();}sInstance.mHandler.postDelayed(mDelayDim, screenTurnOffTime ); }}// make sure we never fall asleep againsInstance.mCpuWakeLock = null;try {sInstance.mCpuWakeLock = sInstance.mPowerManager.newWakeLock(。。。 。。。
}</span>
这段代码有句话会影响关机动画播放不完
“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;
- }
- }
String cust = SystemProperties.get("ro.operator.optr");if (cust != null) {if (cust.equals("CUST")) {mShutOffAnimation = true;}}
然后重点看 sInstance.start();这个方法,就走到了run()方法里满了;
Step 6: 来看看ShutDownThread.java这个类的run()方法;
- <span style="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();
- }
- }</span>
<span style="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();}}</span>
重点看running()这个方法:
下面这个方法比较长,来分析一下:
- <span style="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) {
- // We don't allow apps to cancel this, so ignore the result.
- actionDone();
- }
- };
- /*
- * Write a system property in case the system_server reboots before we
- * get to the actual hardware restart. If that happens, we'll retry at
- * the beginning of the SystemServer startup.
- */
- {
- String reason = (mReboot ? "1" : "0") + (mRebootReason != null ? mRebootReason : "");
- SystemProperties.set(SHUTDOWN_ACTION_PROPERTY, reason);
- }
- /*
- * If we are rebooting into safe mode, write a system property
- * indicating so.
- */
- if (mRebootSafeMode) {
- SystemProperties.set(REBOOT_SAFEMODE_PROPERTY, "1");
- }
- Log.i(TAG, "Sending shutdown broadcast...");
- // First send the high-level shut down broadcast.
- mActionDone = false;
- /// M: 2012-05-20 ALPS00286063 @{
- mContext.sendBroadcast(new Intent("android.intent.action.ACTION_PRE_SHUTDOWN"));
- /// @} 2012-05-20
- 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) {
- }
- }
- }
- // Also send ACTION_SHUTDOWN_IPO in IPO shut down flow
- 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) {
- // power off auto test, don't modify
- 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) {
- }
- }
- }
- // power off auto test, don't modify
- // Shutdown radios.
- Log.i(TAG, "Shutting down radios...");
- shutdownRadios(MAX_RADIO_WAIT_TIME);
- // power off auto test, don't modify
- Log.i(TAG, "Shutting down MountService...");
- if ( (mShutdownFlow == IPO_SHUTDOWN_FLOW) && (command.equals("1")||command.equals("3")) ) {
- Log.i(TAG, "bypass MountService!");
- } else {
- // Shutdown MountService to ensure media is in a safe state
- 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();
- }
- };
- // Set initial variables and time out time.
- 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) {
- }
- }
- }
- }
- // power off auto test, don't modify
- //mountSerivce ���
- Log.i(TAG, "MountService shut done...");
- // [MTK] fix shutdown animation timing issue
- //==================================================================
- 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) {
- // vibrate before shutting down
- Vibrator vibrator = new SystemVibrator();
- try {
- vibrator.vibrate(SHUTDOWN_VIBRATE_MS);
- } catch (Exception e) {
- // Failure to vibrate shouldn't interrupt shutdown. Just log it.
- Log.w(TAG, "Failed to vibrate during shutdown.", e);
- }
- // vibrator is asynchronous so we need to wait to avoid shutting down too soon.
- try {
- Thread.sleep(SHUTDOWN_VIBRATE_MS);
- } catch (InterruptedException unused) {
- }
- }
- // Shutdown power
- // power off auto test, don't modify
- 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);
- //To void previous UI flick caused by shutdown animation stopping before BKL turning off
- 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).");
- }
- //SystemProperties.set("ctl.stop","bootanim");
- }
- synchronized (sIsStartedGuard) {
- sIsStarted = false;
- }
- sInstance.mPowerManager.setBacklightBrightnessOff(false);
- sInstance.mCpuWakeLock.acquire(2000);
- synchronized (mShutdownThreadSync) {
- try {
- mShutdownThreadSync.wait();
- } catch (InterruptedException e) {
- }
- }
- } else {
- rebootOrShutdown(mReboot, mRebootReason);
- }
- }</span>
<span style="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) {// We don't allow apps to cancel this, so ignore the result.actionDone();}};/** Write a system property in case the system_server reboots before we* get to the actual hardware restart. If that happens, we'll retry at* the beginning of the SystemServer startup.*/{String reason = (mReboot ? "1" : "0") + (mRebootReason != null ? mRebootReason : "");SystemProperties.set(SHUTDOWN_ACTION_PROPERTY, reason);}/** If we are rebooting into safe mode, write a system property* indicating so.*/if (mRebootSafeMode) {SystemProperties.set(REBOOT_SAFEMODE_PROPERTY, "1");}Log.i(TAG, "Sending shutdown broadcast...");// First send the high-level shut down broadcast.mActionDone = false;/// M: 2012-05-20 ALPS00286063 @{mContext.sendBroadcast(new Intent("android.intent.action.ACTION_PRE_SHUTDOWN"));/// @} 2012-05-20mContext.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) {}}}// Also send ACTION_SHUTDOWN_IPO in IPO shut down flowif (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) {// power off auto test, don't modifyLog.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) {}}}// power off auto test, don't modify// Shutdown radios.Log.i(TAG, "Shutting down radios...");shutdownRadios(MAX_RADIO_WAIT_TIME);// power off auto test, don't modifyLog.i(TAG, "Shutting down MountService...");if ( (mShutdownFlow == IPO_SHUTDOWN_FLOW) && (command.equals("1")||command.equals("3")) ) {Log.i(TAG, "bypass MountService!");} else {// Shutdown MountService to ensure media is in a safe stateIMountShutdownObserver 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();}};// Set initial variables and time out time.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) {}}}}// power off auto test, don't modify//mountSerivce ���Log.i(TAG, "MountService shut done...");// [MTK] fix shutdown animation timing issue//==================================================================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) {// vibrate before shutting downVibrator vibrator = new SystemVibrator();try {vibrator.vibrate(SHUTDOWN_VIBRATE_MS);} catch (Exception e) {// Failure to vibrate shouldn't interrupt shutdown. Just log it.Log.w(TAG, "Failed to vibrate during shutdown.", e);}// vibrator is asynchronous so we need to wait to avoid shutting down too soon.try {Thread.sleep(SHUTDOWN_VIBRATE_MS);} catch (InterruptedException unused) {}}// Shutdown power// power off auto test, don't modifyLog.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);//To void previous UI flick caused by shutdown animation stopping before BKL turning off 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).");} //SystemProperties.set("ctl.stop","bootanim");}synchronized (sIsStartedGuard) {sIsStarted = false;}sInstance.mPowerManager.setBacklightBrightnessOff(false); sInstance.mCpuWakeLock.acquire(2000); synchronized (mShutdownThreadSync) {try {mShutdownThreadSync.wait();} catch (InterruptedException e) {}}} else {rebootOrShutdown(mReboot, mRebootReason);}}</span>
这个方法做了一些列的操作,会关闭一些操作,如:
- shutdownRadios(MAX_RADIO_WAIT_TIME);
- mount.shutdown(observer);
- stMgr.shutdown(mContext);
重点看 rebootOrShutdown(mReboot, mRebootReason);这个方法;准备重启的方法;
Step 7:来看看rebootOrShutdown()这个方法:
- <span style="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) {
- // vibrate before shutting down
- Vibrator vibrator = new SystemVibrator();
- try {
- vibrator.vibrate(SHUTDOWN_VIBRATE_MS);
- } catch (Exception e) {
- // Failure to vibrate shouldn't interrupt shutdown. Just log it.
- Log.w(TAG, "Failed to vibrate during shutdown.", e);
- }
- // vibrator is asynchronous so we need to wait to avoid shutting down too soon.
- try {
- Thread.sleep(SHUTDOWN_VIBRATE_MS);
- } catch (InterruptedException unused) {
- }
- }
- delayForPlayAnimation();
- // Shutdown power
- // power off auto test, don't modify
- Log.i(TAG, "Performing low-level shutdown...");
- //PowerManagerService.lowLevelShutdown();
- //add your func: HDMI off
- //add for MFR
- 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);
- //add your func: HDMI off
- //unmout data/cache partitions while performing shutdown
- SystemProperties.set("ctl.start", "shutdown");
- /* sleep for a long time, prevent start another service */
- try {
- Thread.currentThread().sleep(Integer.MAX_VALUE);
- } catch ( Exception e) {
- Log.e(TAG, "Shutdown rebootOrShutdown Thread.currentThread().sleep exception!");
- }
- }</span>
<span style="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) {// vibrate before shutting downVibrator vibrator = new SystemVibrator();try {vibrator.vibrate(SHUTDOWN_VIBRATE_MS);} catch (Exception e) {// Failure to vibrate shouldn't interrupt shutdown. Just log it.Log.w(TAG, "Failed to vibrate during shutdown.", e);}// vibrator is asynchronous so we need to wait to avoid shutting down too soon.try {Thread.sleep(SHUTDOWN_VIBRATE_MS);} catch (InterruptedException unused) {}}delayForPlayAnimation();// Shutdown power// power off auto test, don't modifyLog.i(TAG, "Performing low-level shutdown...");//PowerManagerService.lowLevelShutdown();//add your func: HDMI off//add for MFRtry {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);//add your func: HDMI off//unmout data/cache partitions while performing shutdownSystemProperties.set("ctl.start", "shutdown");/* sleep for a long time, prevent start another service */try {Thread.currentThread().sleep(Integer.MAX_VALUE);} catch ( Exception e) {Log.e(TAG, "Shutdown rebootOrShutdown Thread.currentThread().sleep exception!"); }}</span>
关机震动也在这个方法里面;这个方法重点看PowerManagerService.lowLevelReboot(reason);
Log.i(TAG, "Rebooting, reason: " + reason);这句log也很重要,可以有助于我们分析代码;
Step 8:下面来看看PowerManagerServices.java这个类的lowLevelReboot()这个方法:
- <span style="font-size: 18px;">public static void lowLevelReboot(String reason) throws IOException {
- nativeReboot(reason);
- }</span>
<span style="font-size:18px;">public static void lowLevelReboot(String reason) throws IOException {nativeReboot(reason);}</span>
这个方法调用到了native里面,后面的操作我就不分析了。。。
大致流程是:
关机,然后开机,底层判断节点后进入恢复出厂模式,recevory.img释放完全后,进入开机的流程。。。
以后有进展再补充这部分的流程,整个过程大致就是这个样子了,里面的细节有好多没有分析,大家可以自行研究。。。,抛砖引玉的目的达到了。
android恢复出厂设置流程分析相关推荐
- android 恢复出厂设置流程分析,基于Android系统快速恢复出厂设置方法实现.doc
基于Android系统快速恢复出厂设置方法实现 基于Android系统快速恢复出厂设置方法实现 摘 要:针对使用Android系统的智能电视进行恢复出厂设置时重置速度慢的情况进行了研究和分析,从其重置 ...
- Android恢复出厂设置流程分析【Android源码解析十】
最近看恢复出厂的一个问题,以前也查过这方面的流程,所以这里整理一些AP+framework层的流程: 在setting-->备份与重置--->恢复出厂设置--->重置手机---> ...
- Android6.0 Reset恢复出厂设置流程分析
点击Settings应用中的恢复出厂设置按钮后流程分析: 先使用grep命令搜索"恢复出厂设置"字符串,找到相应的布局文件: packages/apps/Settings/res/ ...
- android 恢复出厂设置 界面,android恢复出厂设置流程概括
恢复出厂设置流程概括 ============================================= 恢复出厂设置流程概括: 一. 设置模块中进行恢复出厂设置操作,系统一共做了两件事: 1 ...
- android 恢复出厂设置 代码,android恢复出厂设置以及系统升级流程
http://www.bangchui.org/simple/?t5938.html ============================================= 恢复出厂设置流程概括: ...
- Android 恢复出厂设置(recovery)
Android 恢复出厂设置基本流程 (1)遥控器/按键板后门键触发,或者应用里面从系统设置里面恢复出厂选项也可触发: // 后面以系统设置的应用触发为例 (2)选择恢复出厂设置之后,就会发送广播&q ...
- android 恢复出厂设置 时间,Android 恢复出厂设置后,时间不能恢复替:2013年1月1日...
Android 恢复出厂设置后,时间不能恢复为:2013年1月1日 前言 欢迎大家我分享和推荐好用的代码段~~声明 欢迎转载,但请保留文章原始出处: CSDN:http ...
- Android 恢复出厂设置(系统时间不修改)
Android恢复出厂设置时,只会将/data和/cache分区进行清除,时间和其他分区不会清除, 时间由rtc硬件模块来进行维护的,时间更新后会将时间信息写入此硬件模块,在系统启动时,RTC硬件驱动 ...
- Android恢复出厂设置代码流程分析
工作中排查到了恢复出厂设置的bug, 有一些细节是需要注意的,于是把这块的代码流程看一下: 代码基于:Android9.0 应用层: 就发送MASTER_CLEAR的广播, 这里没有带参数的 priv ...
最新文章
- 重磅直播|基于激光雷达的感知、定位导航应用
- ORB-SLAM(1) --- 让程序飞起来
- cmake (4)引用子目录的库
- 全球农企对话国际农民丰收节贸易会·万祥军:拜耳谋定领先
- c语言变凉存储性,C语言数据的表示和存储(IEEE 754标准)
- 服务器端利器--双缓冲队列
- 关系数据模型和关系数据库系统
- STM32移植LWIP
- 『001』如何在自己的网页里引入一个聊天机器人(。・∀・)ノ
- Eclipse中启动tomcat: java.lang.OutOfMemoryError: PermGen space的解决方法
- 用python偷懒Arcgis(地类编码转地类名称)
- python 广义线性模型_scikit-learn 1.1 广义线性模型(Generalized Linear Models)
- word文档如何画线条流程图_如何在WORD中画流程图
- docker生态-mysql客户端phpAdmin
- maven读取不到包,项目名爆红
- 收集的一些学习ios的好网站
- 不用远程软件,校园网电脑之间如何远程连接
- 怎么查看php-fpm的错误日志,php fpm如何开启错误日志
- 压测⼯具本地快速安装Jmeter5.x以及基础功能组件介绍线程组和Sampler
- itextpdf简单使用 制作豆瓣日志pdf
热门文章
- 我的不靠谱择业[饮水思源feeling]
- 杂谈:饮水思源与Java仍在但Sun已死
- 04笔记 离散数学——关系——基于离散数学(第3版)_章炯民,陶增乐
- python量化交易:Joinquant_量化交易基础【三】:python基本语法与变量
- %20ld c语言,C语言第二次实验报告 - osc_ldea7g3t的个人空间 - OSCHINA - 中文开源技术交流社区...
- 超松弛迭代法求解二维电磁场有限差分方程(附Matlab代码)
- 遇到的MAVEN各种问题以及解决方案
- 前端--实体,meta,语义化标签1
- 如何判断合法的立即数
- 腾讯被爆内测配送机器人,与阿里顺丰直面物流竞争!