最近看恢复出厂的一个问题,以前也查过这方面的流程,所以这里整理一些AP+framework层的流程;
在setting-->备份与重置--->恢复出厂设置--->重置手机--->清除全部内容--->手机关机--->开机--->进行恢复出厂的操作--->开机流程;
Step 1:前面找settings中的布局我就省略了,这部分相对简单一些,直接到清除全部内容这个按钮的操作,
对应的java类是setting中的MasterClearConfirm.java这个类,
1. private Button.OnClickListener mFinalClickListener = new Button.OnClickListener() {
2.
3. public void onClick(View v) {
4. if (Utils.isMonkeyRunning()) {
5. return;
6. }
7.
8. if (mEraseSdCard) {
9. new Intent(ExternalStorageFormatter.FORMAT_AND_FACTORY_RESET);
10. intent.setComponent(ExternalStorageFormatter.COMPONENT_NAME);
11. getActivity().startService(intent);
12. else {
13. new Intent("android.intent.action.MASTER_CLEAR"));
14. // Intent handling is asynchronous -- assume it will happen soon.
15. }
16. }
17. };
通过上述的代码,可以看出,实际上点击清除全部内容的时候,如果前面勾选上格式哈SD卡,就会执行mEraseSdCard为true里面的逻辑,如果没有勾选,就执行mEraseSdCard=false的逻辑,其实就是发送一个广播,
1. <span style="font-size:14px;">“android.intent.action.MASTER_CLEAR”</span>
Step 2:这个广播接受的地方,参见AndroidManifest.xml中的代码,如下:
1. <receiver android:name="com.android.server.MasterClearReceiver"
2. android:permission="android.permission.MASTER_CLEAR"
3. android:priority="100" >
4. <intent-filter>
5. <!-- For Checkin, Settings, etc.: action=MASTER_CLEAR -->
6. <action android:name="android.intent.action.MASTER_CLEAR" />
7.
8. <!-- MCS always uses REMOTE_INTENT: category=MASTER_CLEAR -->
9. <action android:name="com.google.android.c2dm.intent.RECEIVE" />
10. <category android:name="android.intent.category.MASTER_CLEAR" />
11. </intent-filter>
12. </receiver>
找这个MasterClearReceiver.java这个receiver,下面来看看这个onReceiver()里面做了什么操作:
1. public void onReceive(final Context context, final Intent intent) {
2. if (intent.getAction().equals(Intent.ACTION_REMOTE_INTENT)) {
3. if (!"google.com".equals(intent.getStringExtra("from"))) {
4. "Ignoring master clear request -- not from trusted server.");
5. return;
6. }
7. }
8.
9. "!!! FACTORY RESET !!!");
10. // The reboot call is blocking, so we need to do it on another thread.
11. new Thread("Reboot") {
12. @Override
13. public void run() {
14. try {
15. RecoverySystem.rebootWipeUserData(context);
16. "Still running after master clear?!");
17. catch (IOException e) {
18. "Can't perform master clear/factory reset", e);
19. }
20. }
21. };
22. thr.start();
23. }
这个里面主要的操作是:RecoverySystem.rebootWipeUserData(context);准备做重启的动作,告诉手机要清除userData的数据;
Step 3:接着来看看RecoverySystem.rebootWipeUserData()这个方法做了哪些操作:
1. public static void rebootWipeUserData(Context context) throws IOException {
2. final ConditionVariable condition = new ConditionVariable();
3.
4. new Intent("android.intent.action.MASTER_CLEAR_NOTIFICATION");
5. context.sendOrderedBroadcastAsUser(intent, UserHandle.OWNER,
6. android.Manifest.permission.MASTER_CLEAR,
7. new BroadcastReceiver() {
8. @Override
9. public void onReceive(Context context, Intent intent) {
10. condition.open();
11. }
12. null, 0, null, null);
13.
14. // Block until the ordered broadcast has completed.
15. condition.block();
16.
17. "--wipe_data\n--locale=" + Locale.getDefault().toString());
18. }
这个里面的广播可以先忽略不计,重点来看看bootCommand()这个方法,注意这个参数“--wipe_data\n--locale=”
1. private static void bootCommand(Context context, String arg) throws IOException {
2. // In case we need it
3. // In case it's not writable
4. LOG_FILE.delete();
5.
6. new FileWriter(COMMAND_FILE);
7. try {
8. command.write(arg);
9. "\n");
10. finally {
11. command.close();
12. }
13.
14. // Having written the command file, go ahead and reboot
15. PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
16. "recovery");
17.
18. throw new IOException("Reboot failed (no permissions?)");
19. }
这个方法的操作大致是“写节点/cache/recovery/command”,把传递过来的字符串写进去;然后调用PowerManager进行重启操作,reboot();
Step 4:接着我们来看看PowerManager的reboot方法做了哪些操作:
1. public void reboot(String reason) {
2. try {
3. false, reason, true);
4. catch (RemoteException e) {
5. }
6. }
这个调用到了PowerManagerService.java这个类的reboot方法中了:
1. @Override // Binder call
2. public void reboot(boolean confirm, String reason, boolean wait) {
3. null);
4.
5. final long ident = Binder.clearCallingIdentity();
6. try {
7. false, confirm, reason, wait);
8. finally {
9. Binder.restoreCallingIdentity(ident);
10. }
11. }
重点来看看shutdownOrRebootInternal()这个方法,
1. private void shutdownOrRebootInternal(final boolean shutdown, final boolean confirm,
2. final String reason, boolean wait) {
3. if (mHandler == null || !mSystemReady) {
4. throw new IllegalStateException("Too early to call shutdown() or reboot()");
5. }
6.
7. new Runnable() {
8. @Override
9. public void run() {
10. synchronized (this) {
11. if (shutdown) {
12. ShutdownThread.shutdown(mContext, confirm);
13. else {
14. ShutdownThread.reboot(mContext, reason, confirm);
15. }
16. }
17. }
18. };
19.
20. // ShutdownThread must run on a looper capable of displaying the UI.
21. Message msg = Message.obtain(mHandler, runnable);
22. true);
23. mHandler.sendMessage(msg);
24.
25. // PowerManager.reboot() is documented not to return so just wait for the inevitable.
26. if (wait) {
27. synchronized (runnable) {
28. while (true) {
29. try {
30. runnable.wait();
31. catch (InterruptedException e) {
32. }
33. }
34. }
35. }
36. }
由于传递过来的shutdown为false,所以执行ShutdownThread.reboot(mContext, reason, confirm);reason:recevory
下面调用到ShutdownThread
Step 5:这个追踪ShutdownThread.reboot()这个方法,这就有点像破案电影,一点一点查找罪犯的难点;
来窥视一下这个类:
1. public static void reboot(final Context context, String reason, boolean confirm) {
2. true;
3. false;
4. mRebootReason = reason;
5. "reboot");
6. shutdownInner(context, confirm);
7. }
这个里面做的操作就是给这个变量mRebootReason复制“recevory”,重点调用shutdownInner()这个方法;
1. <span style="font-size:14px;">static void shutdownInner(final Context context, boolean confirm) {
2. // ensure that only one thread is trying to power down.
3. // any additional calls are just returned
4. synchronized (sIsStartedGuard) {
5. if (sIsStarted) {
6. "Request to shutdown already running, returning.");
7. return;
8. }
9. }
10.
11. "Notifying thread to start radio shutdown");
12. bConfirmForAnimation = confirm;
13. final int longPressBehavior = context.getResources().getInteger(
14. com.android.internal.R.integer.config_longPressOnPowerBehavior);
15. final int resourceId = mRebootSafeMode
16. ? com.android.internal.R.string.reboot_safemode_confirm
17. 2
18. ? com.android.internal.R.string.shutdown_confirm_question
19. : com.android.internal.R.string.shutdown_confirm);
20.
21. "Notifying thread to start shutdown longPressBehavior=" + longPressBehavior);
22.
23. if (confirm) {
24. final CloseDialogReceiver closer = new CloseDialogReceiver(context);
25. if (sConfirmDialog != null) {
26. sConfirmDialog.dismiss();
27. }
28. if (sConfirmDialog == null) {
29. "PowerOff dialog doesn't exist. Create it first");
30. new AlertDialog.Builder(context)
31. .setTitle(mRebootSafeMode
32. ? com.android.internal.R.string.reboot_safemode_title
33. : com.android.internal.R.string.power_off)
34. .setMessage(resourceId)
35. new DialogInterface.OnClickListener() {
36. public void onClick(DialogInterface dialog, int which) {
37. beginShutdownSequence(context);
38. if (sConfirmDialog != null) {
39. null;
40. }
41. }
42. })
43. new DialogInterface.OnClickListener() {
44. public void onClick(DialogInterface dialog, int which) {
45. synchronized (sIsStartedGuard) {
46. false;
47. }
48. if (sConfirmDialog != null) {
49. null;
50. }
51. }
52. })
53. .create();
54. false);//blocking back key
55. sConfirmDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
56. /*if (!context.getResources().getBoolean(
57. com.android.internal.R.bool.config_sf_slowBlur)) {
58. sConfirmDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
59. }*/
60. /* To fix video+UI+blur flick issue */
61. sConfirmDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
62. }
63.
64. closer.dialog = sConfirmDialog;
65. sConfirmDialog.setOnDismissListener(closer);
66.
67. if (!sConfirmDialog.isShowing()) {
68. sConfirmDialog.show();
69. }
70. else {
71. beginShutdownSequence(context);
72. }
73. }</span>
看beginShutdownSequence()这个方法吧,重点调用到这个方法里面去了,来瞅瞅这个方法:
1. <span style="font-size:14px;">private static void beginShutdownSequence(Context context) {
2. synchronized (sIsStartedGuard) {
3. if (sIsStarted) {
4. "ShutdownThread is already running, returning.");
5. return;
6. }
7. true;
8. }
9.
10. // start the thread that initiates shutdown
11. sInstance.mContext = context;
12. sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
13. new Handler() {
14. };
15.
16. true;
17. if (!bConfirmForAnimation) {
18. if (!sInstance.mPowerManager.isScreenOn()) {
19. false;
20. }
21. }
22.
23. // throw up an indeterminate system dialog to indicate radio is
24. // shutting down.
25. 0;
26. boolean mShutOffAnimation = false;
27.
28. try {
29. if (mIBootAnim == null) {
30. class);
31. }
32. catch (Exception e) {
33. e.printStackTrace();
34. }
35.
36. int screenTurnOffTime = mIBootAnim.getScreenTurnOffTime();
37. mShutOffAnimation = mIBootAnim.isCustBootAnim();
38. "mIBootAnim get screenTurnOffTime : " + screenTurnOffTime);
39.
40. "ro.operator.optr");
41.
42. if (cust != null) {
43. if (cust.equals("CUST")) {
44. true;
45. }
46. }
47.
48. synchronized (mEnableAnimatingSync) {
49.
50. if(!mEnableAnimating) {
51. // sInstance.mPowerManager.setBacklightBrightness(PowerManager.BRIGHTNESS_DIM);
52. else {
53. if (mShutOffAnimation) {
54. "mIBootAnim.isCustBootAnim() is true");
55. bootanimCust();
56. else {
57. new ProgressDialog(context);
58. pd.setTitle(context.getText(com.android.internal.R.string.power_off));
59. pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
60. true);
61. false);
62. pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
63. /* To fix video+UI+blur flick issue */
64. pd.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
65. pd.show();
66. }
67. sInstance.mHandler.postDelayed(mDelayDim, screenTurnOffTime );
68. }
69. }
70.
71. // make sure we never fall asleep again
72. null;
73. try {
74. sInstance.mCpuWakeLock = sInstance.mPowerManager.newWakeLock(
75. 。。。 。。。
76. }</span>
这段代码有句话会影响关机动画播放不完
“sInstance.mHandler.postDelayed(mDelayDim, screenTurnOffTime ); ”
解决办法
(1)“可以把这个screenTurnOffTime时间乘以2,这个时间看log是5000毫秒,就是5秒,乘以2就是10秒,大概就能播放完全关机动画了。”
(2)把这句话注释掉,但是有可能会引起问题,导致恢复出厂设置的时候没有进行恢复出厂的操作。目前正在追踪此问题;
这段代码中还有影响关机动画是否走客制化的关机动画,如果ro.operator.optr这个属性配置的是CUST,则会走客制化的关机动画,否则走系统默认的关机动画;
1. String cust = SystemProperties.get("ro.operator.optr");
2.
3.
4. if (cust != null) {
5. if (cust.equals("CUST")) {
6. true;
7. }
8. }
然后重点看 sInstance.start();这个方法,就走到了run()方法里满了;
Step 6:
1. <span style="font-size:14px;">public void run() {
2. checkShutdownFlow();
3. while (mShutdownFlow == IPO_SHUTDOWN_FLOW) {
4. stMgr.saveStates(mContext);
5. stMgr.enterShutdown(mContext);
6. running();
7. }
8. if (mShutdownFlow != IPO_SHUTDOWN_FLOW) {
9. stMgr.enterShutdown(mContext);
10. running();
11. }
12. }</span>
重点看running()这个方法:
下面这个方法比较长,来分析一下:
1. <span style="font-size:14px;">public void running() {
2. if(sPreShutdownApi != null){
3. try {
4. sPreShutdownApi.onPowerOff();
5. catch (RemoteException e) {
6. "onPowerOff exception" + e.getMessage());
7. }
8. else{
9. "sPreShutdownApi is null");
10. }
11.
12. "sys.ipo.pwrdncap");
13.
14. new BroadcastReceiver() {
15. @Override public void onReceive(Context context, Intent intent) {
16. // We don't allow apps to cancel this, so ignore the result.
17. actionDone();
18. }
19. };
20.
21. /*
22. * Write a system property in case the system_server reboots before we
23. * get to the actual hardware restart. If that happens, we'll retry at
24. * the beginning of the SystemServer startup.
25. */
26. {
27. "1" : "0") + (mRebootReason != null ? mRebootReason : "");
28. SystemProperties.set(SHUTDOWN_ACTION_PROPERTY, reason);
29. }
30.
31. /*
32. * If we are rebooting into safe mode, write a system property
33. * indicating so.
34. */
35. if (mRebootSafeMode) {
36. "1");
37. }
38.
39. "Sending shutdown broadcast...");
40.
41. // First send the high-level shut down broadcast.
42. false;
43. /// M: 2012-05-20 ALPS00286063 @{
44. new Intent("android.intent.action.ACTION_PRE_SHUTDOWN"));
45. /// @} 2012-05-20
46. new Intent()).setAction(Intent.ACTION_SHUTDOWN).putExtra("_mode", mShutdownFlow),
47. null, br, mHandler, 0, null, null);
48.
49. final long endTime = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME;
50. synchronized (mActionDoneSync) {
51. while (!mActionDone) {
52. long delay = endTime - SystemClock.elapsedRealtime();
53. if (delay <= 0) {
54. "Shutdown broadcast ACTION_SHUTDOWN timed out");
55. if (mShutdownFlow == IPO_SHUTDOWN_FLOW) {
56. "change shutdown flow from ipo to normal: ACTION_SHUTDOWN timeout");
57. mShutdownFlow = NORMAL_SHUTDOWN_FLOW;
58. }
59. break;
60. }
61. try {
62. mActionDoneSync.wait(delay);
63. catch (InterruptedException e) {
64. }
65. }
66. }
67.
68. // Also send ACTION_SHUTDOWN_IPO in IPO shut down flow
69. if (mShutdownFlow == IPO_SHUTDOWN_FLOW) {
70. false;
71. new Intent("android.intent.action.ACTION_SHUTDOWN_IPO"), null,
72. 0, null, null);
73. final long endTimeIPO = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME;
74. synchronized (mActionDoneSync) {
75. while (!mActionDone) {
76. long delay = endTimeIPO - SystemClock.elapsedRealtime();
77. if (delay <= 0) {
78. "Shutdown broadcast ACTION_SHUTDOWN_IPO timed out");
79. if (mShutdownFlow == IPO_SHUTDOWN_FLOW) {
80. "change shutdown flow from ipo to normal: ACTION_SHUTDOWN_IPO timeout");
81. mShutdownFlow = NORMAL_SHUTDOWN_FLOW;
82. }
83. break;
84. }
85. try {
86. mActionDoneSync.wait(delay);
87. catch (InterruptedException e) {
88. }
89. }
90. }
91. }
92.
93. if (mShutdownFlow != IPO_SHUTDOWN_FLOW) {
94. // power off auto test, don't modify
95. "Shutting down activity manager...");
96.
97. final IActivityManager am =
98. "activity"));
99. if (am != null) {
100. try {
101. am.shutdown(MAX_BROADCAST_TIME);
102. catch (RemoteException e) {
103. }
104. }
105. }
106.
107. // power off auto test, don't modify
108. // Shutdown radios.
109. "Shutting down radios...");
110. shutdownRadios(MAX_RADIO_WAIT_TIME);
111.
112. // power off auto test, don't modify
113. "Shutting down MountService...");
114. if ( (mShutdownFlow == IPO_SHUTDOWN_FLOW) && (command.equals("1")||command.equals("3")) ) {
115. "bypass MountService!");
116. else {
117. // Shutdown MountService to ensure media is in a safe state
118. new IMountShutdownObserver.Stub() {
119. public void onShutDownComplete(int statusCode) throws RemoteException {
120. "Result code " + statusCode + " from MountService.shutdown");
121. if (statusCode < 0) {
122. mShutdownFlow = NORMAL_SHUTDOWN_FLOW;
123. }
124. actionDone();
125. }
126. };
127.
128.
129.
130. // Set initial variables and time out time.
131. false;
132. final long endShutTime = SystemClock.elapsedRealtime() + MAX_SHUTDOWN_WAIT_TIME;
133. synchronized (mActionDoneSync) {
134. try {
135. final IMountService mount = IMountService.Stub.asInterface(
136. "mount"));
137. if (mount != null) {
138. mount.shutdown(observer);
139. else {
140. "MountService unavailable for shutdown");
141. }
142. catch (Exception e) {
143. "Exception during MountService shutdown", e);
144. }
145. while (!mActionDone) {
146. long delay = endShutTime - SystemClock.elapsedRealtime();
147. if (delay <= 0) {
148. "Shutdown wait timed out");
149. if (mShutdownFlow == IPO_SHUTDOWN_FLOW) {
150. "change shutdown flow from ipo to normal: MountService");
151. mShutdownFlow = NORMAL_SHUTDOWN_FLOW;
152. }
153. break;
154. }
155. try {
156. mActionDoneSync.wait(delay);
157. catch (InterruptedException e) {
158. }
159. }
160. }
161. }
162.
163. // power off auto test, don't modify
164. //mountSerivce ���
165. "MountService shut done...");
166. // [MTK] fix shutdown animation timing issue
167. //==================================================================
168. try {
169. "service.shutanim.running","1");
170. "set service.shutanim.running to 1");
171.
172. catch (Exception ex) {
173. "Failed to set 'service.shutanim.running' = 1).");
174. }
175. //==================================================================
176.
177. if (mShutdownFlow == IPO_SHUTDOWN_FLOW) {
178. if (SHUTDOWN_VIBRATE_MS > 0) {
179. // vibrate before shutting down
180. new SystemVibrator();
181. try {
182. vibrator.vibrate(SHUTDOWN_VIBRATE_MS);
183. catch (Exception e) {
184. // Failure to vibrate shouldn't interrupt shutdown. Just log it.
185. "Failed to vibrate during shutdown.", e);
186. }
187.
188. // vibrator is asynchronous so we need to wait to avoid shutting down too soon.
189. try {
190. Thread.sleep(SHUTDOWN_VIBRATE_MS);
191. catch (InterruptedException unused) {
192. }
193. }
194.
195. // Shutdown power
196. // power off auto test, don't modify
197. "Performing ipo low-level shutdown...");
198.
199. delayForPlayAnimation();
200.
201. if (sInstance.mScreenWakeLock != null && sInstance.mScreenWakeLock.isHeld()) {
202. sInstance.mScreenWakeLock.release();
203. null;
204. }
205.
206. sInstance.mHandler.removeCallbacks(mDelayDim);
207. stMgr.shutdown(mContext);
208. stMgr.finishShutdown(mContext);
209.
210. //To void previous UI flick caused by shutdown animation stopping before BKL turning off
211. if (pd != null) {
212. pd.dismiss();
213. null;
214. else if (beginAnimationTime > 0) {
215. try {
216. "service.bootanim.exit","1");
217. "set 'service.bootanim.exit' = 1).");
218. catch (Exception ex) {
219. "Failed to set 'service.bootanim.exit' = 1).");
220. }
221. //SystemProperties.set("ctl.stop","bootanim");
222. }
223.
224. synchronized (sIsStartedGuard) {
225. false;
226. }
227.
228. false);
229. 2000);
230.
231. synchronized (mShutdownThreadSync) {
232. try {
233. mShutdownThreadSync.wait();
234. catch (InterruptedException e) {
235. }
236. }
237. else {
238. rebootOrShutdown(mReboot, mRebootReason);
239. }
240. }</span>
这个方法做了一些列的操作,会关闭一些操作,如:
1. shutdownRadios(MAX_RADIO_WAIT_TIME);
2. mount.shutdown(observer);
3. stMgr.shutdown(mContext);
重点看 rebootOrShutdown(mReboot, mRebootReason);这个方法;准备重启的方法;
Step 7:来看看rebootOrShutdown()这个方法:
1. <span style="font-size:14px;">public static void rebootOrShutdown(boolean reboot, String reason) {
2. if (reboot) {
3. "Rebooting, reason: " + reason);
4. if ( (reason != null) && reason.equals("recovery") ) {
5. delayForPlayAnimation();
6. }
7. try {
8. PowerManagerService.lowLevelReboot(reason);
9. catch (Exception e) {
10. "Reboot failed, will attempt shutdown instead", e);
11. }
12. else if (SHUTDOWN_VIBRATE_MS > 0) {
13. // vibrate before shutting down
14. new SystemVibrator();
15. try {
16. vibrator.vibrate(SHUTDOWN_VIBRATE_MS);
17. catch (Exception e) {
18. // Failure to vibrate shouldn't interrupt shutdown. Just log it.
19. "Failed to vibrate during shutdown.", e);
20. }
21.
22. // vibrator is asynchronous so we need to wait to avoid shutting down too soon.
23. try {
24. Thread.sleep(SHUTDOWN_VIBRATE_MS);
25. catch (InterruptedException unused) {
26. }
27. }
28.
29. delayForPlayAnimation();
30. // Shutdown power
31. // power off auto test, don't modify
32. "Performing low-level shutdown...");
33. //PowerManagerService.lowLevelShutdown();
34. //add your func: HDMI off
35. //add for MFR
36. try {
37. if (ImHDMI == null)
38. class);
39. catch (Exception e) {
40. e.printStackTrace();
41. }
42. false);
43. try {
44. if (mTvOut == null)
45. class);
46. catch (Exception e) {
47. e.printStackTrace();
48. }
49.
50. false);
51. //add your func: HDMI off
52. //unmout data/cache partitions while performing shutdown
53.
54. "ctl.start", "shutdown");
55.
56. /* sleep for a long time, prevent start another service */
57. try {
58. Thread.currentThread().sleep(Integer.MAX_VALUE);
59. catch ( Exception e) {
60. "Shutdown rebootOrShutdown Thread.currentThread().sleep exception!");
61. }
62. }</span>
关机震动也在这个方法里面;这个方法重点看PowerManagerService.lowLevelReboot(reason);
Log.i(TAG, "Rebooting, reason: " + reason);这句log也很重要,可以有助于我们分析代码;
Step 8:下面来看看PowerManagerServices.java这个类的lowLevelReboot()这个方法:
1. <span style="font-size:18px;">public static void lowLevelReboot(String reason) throws IOException {
2. nativeReboot(reason);
3. }</span>
这个方法调用到了native里面,后面的操作我就不分析了。。。
大致流程是:
关机,然后开机,底层判断节点后进入恢复出厂模式,recevory.img释放完全后,进入开机的流程。。。
以后有进展再补充这部分的流程,整个过程大致就是这个样子了,里面的细节有好多没有分析,大家可以自行研究。。。,抛砖引玉的目的达到了。