安全模式简述

android平台,在长按power / menu键时会快速进入一个模式选择,部分定制的平台是直接进入安装模式,也可以定制成公司需要的一些特定功能模式,比如报警 ...

power 也属于全局的特殊按键,同样在PhoneWindowManager.java中被捕获处理。

Power键(long)监听流程

InputDispatcher.cpp  中InputDispatcher::notifyKey分发按键
void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
#if DEBUG_INBOUND_EVENT_DETAILSALOGD("notifyKey - eventTime=%lld, deviceId=%d, source=0x%x, policyFlags=0x%x, action=0x%x, ""flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, downTime=%lld",args->eventTime, args->deviceId, args->source, args->policyFlags,args->action, args->flags, args->keyCode, args->scanCode,args->metaState, args->downTime);
#endifif (!validateKeyEvent(args->action)) {return;}uint32_t policyFlags = args->policyFlags;int32_t flags = args->flags;int32_t metaState = args->metaState;if ((policyFlags & POLICY_FLAG_VIRTUAL) || (flags & AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY)) {policyFlags |= POLICY_FLAG_VIRTUAL;flags |= AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY;}if (policyFlags & POLICY_FLAG_FUNCTION) {metaState |= AMETA_FUNCTION_ON;}policyFlags |= POLICY_FLAG_TRUSTED;int32_t keyCode = args->keyCode;if (metaState & AMETA_META_ON && args->action == AKEY_EVENT_ACTION_DOWN) {int32_t newKeyCode = AKEYCODE_UNKNOWN;if (keyCode == AKEYCODE_DEL) {newKeyCode = AKEYCODE_BACK;} else if (keyCode == AKEYCODE_ENTER) {newKeyCode = AKEYCODE_HOME;}if (newKeyCode != AKEYCODE_UNKNOWN) {AutoMutex _l(mLock);struct KeyReplacement replacement = {keyCode, args->deviceId};mReplacedKeys.add(replacement, newKeyCode);keyCode = newKeyCode;metaState &= ~AMETA_META_ON;}} else if (args->action == AKEY_EVENT_ACTION_UP) {// In order to maintain a consistent stream of up and down events, check to see if the key// going up is one we've replaced in a down event and haven't yet replaced in an up event,// even if the modifier was released between the down and the up events.AutoMutex _l(mLock);struct KeyReplacement replacement = {keyCode, args->deviceId};ssize_t index = mReplacedKeys.indexOfKey(replacement);if (index >= 0) {keyCode = mReplacedKeys.valueAt(index);mReplacedKeys.removeItemsAt(index);metaState &= ~AMETA_META_ON;}}KeyEvent event;event.initialize(args->deviceId, args->source, args->action,flags, keyCode, args->scanCode, metaState, 0,args->downTime, args->eventTime);mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags);bool needWake;{ // acquire lockmLock.lock();if (shouldSendKeyToInputFilterLocked(args)) {mLock.unlock();policyFlags |= POLICY_FLAG_FILTERED;if (!mPolicy->filterInputEvent(&event, policyFlags)) {return; // event was consumed by the filter}mLock.lock();}int32_t repeatCount = 0;KeyEntry* newEntry = new KeyEntry(args->eventTime,args->deviceId, args->source, policyFlags,args->action, flags, keyCode, args->scanCode,metaState, repeatCount, args->downTime);needWake = enqueueInboundEventLocked(newEntry);mLock.unlock();} // release lockif (needWake) {mLooper->wake();}
}
    mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags); 截获按键并处理,也即NativeInputManager::interceptKeyBeforeQueueing
void NativeInputManager::interceptKeyBeforeQueueing(const KeyEvent* keyEvent,uint32_t& policyFlags) {// Policy:// - Ignore untrusted events and pass them along.// - Ask the window manager what to do with normal events and trusted injected events.// - For normal events wake and brighten the screen if currently off or dim.if (mInteractive) {policyFlags |= POLICY_FLAG_INTERACTIVE;}if ((policyFlags & POLICY_FLAG_TRUSTED)) {nsecs_t when = keyEvent->getEventTime();JNIEnv* env = jniEnv();jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent);jint wmActions;if (keyEventObj) {wmActions = env->CallIntMethod(mServiceObj,gServiceClassInfo.interceptKeyBeforeQueueing,keyEventObj, policyFlags);if (checkAndClearExceptionFromCallback(env, "interceptKeyBeforeQueueing")) {wmActions = 0;}android_view_KeyEvent_recycle(env, keyEventObj);env->DeleteLocalRef(keyEventObj);} else {ALOGE("Failed to obtain key event object for interceptKeyBeforeQueueing.");wmActions = 0;}handleInterceptActions(wmActions, when, /*byref*/ policyFlags);} else {if (mInteractive) {policyFlags |= POLICY_FLAG_PASS_TO_USER;}}
}

wmActions = env->CallIntMethod(mServiceObj,gServiceClassInfo.interceptKeyBeforeQueueing,keyEventObj, policyFlags);则会call 到

InputManagerService.java中的interceptKeyBeforeQueueing
    private long interceptKeyBeforeQueueing(InputWindowHandle focus,KeyEvent event, int policyFlags) {return mWindowManagerCallbacks.interceptKeyBeforeQueueing(focus, event, policyFlags);}

mWindowManagerCallbacks实际为InputMonitor

SystemServer.java 文件中startOtherServices调用inputManager.setWindowManagerCallbacks(wm.getInputMonitor()),给mWindowManagerCallbacks
赋值,而wm.getInputMonitor()获取的是InputMonitor, 所以mWindowManagerCallbacks.interceptKeyBeforeQueueing最后调用到InputMonitor:interceptKeyBeforeQueueing
InputMonitor.java文件中:
    public long interceptKeyBeforeQueueing(InputWindowHandle focus, KeyEvent event, int policyFlags) {WindowState windowState = focus != null ? (WindowState) focus.windowState : null;return mService.mPolicy.interceptKeyBeforeDispatching(windowState, event, policyFlags);}

而mService.mPolicy.interceptKeyBeforeQueueing则是WindowManagerService的按键策略机制mPolicy来继续捕获,

mPolicy = PolicyManager.makeNewWindowManager // mPolicy为 PhoneWindowManager
PhoneWindowManager::interceptKeyBeforeDispatching其中就会截获KeyEvent.KEYCODE_POWER
    public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {if (!mSystemBooted) {// If we have not yet booted, don't let key events do anything.return 0;}final boolean interactive = (policyFlags & FLAG_INTERACTIVE) != 0;final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;final boolean canceled = event.isCanceled();......//<span style="color:#ff0000;"> Handle special keys</span>.switch (keyCode) {case KeyEvent.KEYCODE_VOLUME_DOWN:......case <span style="color:#ff0000;">KeyEvent.KEYCODE_POWER</span>: {result &= ~ACTION_PASS_TO_USER;isWakeKey = false; // wake-up will be handled separatelyif (down) {<span style="color:#ff0000;">interceptPowerKeyDown</span>(event, interactive);} else {interceptPowerKeyUp(event, interactive, canceled);}break;}}......return result;}

interceptKeyBeforeQueueing里面就会截获一些特设的按键,下面我们看power键的处理

down 表示一直为ACTION_DOWN状态,就会call  interceptPowerKeyDown使用mHandler发送一个MSG_POWER_LONG_PRESS消息,而mHandler(PolicyHandler)
就会在handleMessage处理消息MSG_POWER_LONG_PRESS

POWER_LONG_PRESS的处理

上面处理MSG_POWER_LONG_PRESS消息时,powerLongPress
    private void powerLongPress() {final int behavior = getResolvedLongPressOnPowerBehavior();switch (behavior) {case LONG_PRESS_POWER_NOTHING:break;case LONG_PRESS_POWER_GLOBAL_ACTIONS:mPowerKeyHandled = true;if (!performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false)) {performAuditoryFeedbackForAccessibilityIfNeed();}<span style="background-color: rgb(255, 0, 0);">showGlobalActionsInternal</span>();break;case LONG_PRESS_POWER_SHUT_OFF:case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM:mPowerKeyHandled = true;performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF);break;}}

showGlobalActionsInternal会构造GlobalActions对象并显示

    void showGlobalActionsInternal() {sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);if (mGlobalActions == null) {mGlobalActions = new <span style="color:#ff0000;">GlobalActions</span>(mContext, mWindowManagerFuncs);}final boolean keyguardShowing = isKeyguardShowingAndNotOccluded();<span style="color:#ff0000;">mGlobalActions.showDialog</span>(keyguardShowing, isDeviceProvisioned());if (keyguardShowing) {// since it took two seconds of long press to bring this up,// poke the wake lock so they have some time to see the dialog.mPowerManager.userActivity(SystemClock.uptimeMillis(), false);}}

GlobalActions对话框的显示

GlobalActions主要是针对 “ 静音,重启,关机,飞行模式 ” 等全局按键的特殊快捷功能弹出的一些Dialog对话框。
接着上面mGlobalActions.showDialog,继续分析showDialog
    public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) {mKeyguardShowing = keyguardShowing;mDeviceProvisioned = isDeviceProvisioned;if (mDialog != null) {mDialog.dismiss();mDialog = null;// Show delayed, so that the dismiss of the previous dialog completesmHandler.sendEmptyMessage(<span style="color:#ff0000;">MESSAGE_SHOW</span>);} else {handleShow();}}

mHandler中处理MESSAGE_SHOW消息

    private Handler mHandler = new Handler() {public void handleMessage(Message msg) {switch (msg.what) {case MESSAGE_DISMISS:if (mDialog != null) {mDialog.dismiss();mDialog = null;}break;case MESSAGE_REFRESH:refreshSilentMode();mAdapter.notifyDataSetChanged();break;case MESSAGE_SHOW:<span style="color:#ff0000;">handleShow</span>();break;}}}

handleShow主要是创建Dialog对话框并显示

    private void handleShow() {awakenIfNecessary();mDialog = <span style="color:#ff0000;">createDialog</span>();prepareDialog();// If we only have 1 item and it's a simple press action, just do this action.if (mAdapter.getCount() == 1&& mAdapter.getItem(0) instanceof SinglePressAction&& !(mAdapter.getItem(0) instanceof LongPressAction)) {((SinglePressAction) mAdapter.getItem(0)).onPress();} else {WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes();attrs.setTitle("GlobalActions");mDialog.getWindow().setAttributes(attrs);<span style="color:#ff0000;"> mDialog.show();</span>mDialog.getWindow().getDecorView().setSystemUiVisibility(View.STATUS_BAR_DISABLE_EXPAND);}}

其中createDialog 创建的GlobalActionsDialog对话框内容比较丰富,创建一些Action ( SilentModeToggleAction/SilentModeTriStateAction, ToggleAction,) 保存在mItems里面, 其实我们也可以发现关机行为处理的PowerAction(ToggleAction 行为)也被添加到mItems ArrayList中。

    private GlobalActionsDialog createDialog() {// Simple toggle style if there's no vibrator, otherwise use a tri-stateif (!mHasVibrator) {mSilentModeAction = new SilentModeToggleAction();} else {mSilentModeAction = new SilentModeTriStateAction(mContext, mAudioManager, mHandler);}mAirplaneModeOn = new ToggleAction(R.drawable.ic_lock_airplane_mode,R.drawable.ic_lock_airplane_mode_off,R.string.global_actions_toggle_airplane_mode,R.string.global_actions_airplane_mode_on_status,R.string.global_actions_airplane_mode_off_status) {void onToggle(boolean on) {if (mHasTelephony && Boolean.parseBoolean(SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) {mIsWaitingForEcmExit = true;// Launch ECM exit dialogIntent ecmDialogIntent =new Intent(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null);ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);mContext.startActivity(ecmDialogIntent);} else {changeAirplaneModeSystemSetting(on);}}@Overrideprotected void changeStateFromPress(boolean buttonOn) {if (!mHasTelephony) return;// In ECM mode airplane state cannot be changedif (!(Boolean.parseBoolean(SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE)))) {mState = buttonOn ? State.TurningOn : State.TurningOff;mAirplaneState = mState;}}public boolean showDuringKeyguard() {return true;}public boolean showBeforeProvisioning() {return false;}};onAirplaneModeChanged();mItems = new ArrayList<Action>();String[] defaultActions = mContext.getResources().getStringArray(com.android.internal.R.array.config_globalActionsList);ArraySet<String> addedKeys = new ArraySet<String>();for (int i = 0; i < defaultActions.length; i++) {String actionKey = defaultActions[i];if (addedKeys.contains(actionKey)) {// If we already have added this, don't add it again.continue;}if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) {<span style="background-color: rgb(255, 0, 0);">mItems.add(new PowerAction())</span>;} else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) {mItems.add(mAirplaneModeOn);} else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) {if (Settings.Global.getInt(mContext.getContentResolver(),Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 && isCurrentUserOwner()) {mItems.add(getBugReportAction());}} else if (GLOBAL_ACTION_KEY_SILENT.equals(actionKey)) {if (mShowSilentToggle) {mItems.add(mSilentModeAction);}} else if (GLOBAL_ACTION_KEY_USERS.equals(actionKey)) {if (SystemProperties.getBoolean("fw.power_user_switcher", false)) {addUsersToMenu(mItems);}} else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) {mItems.add(getSettingsAction());} else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) {mItems.add(getLockdownAction());} else {Log.e(TAG, "Invalid global action key " + actionKey);}// Add here so we don't add more than one.addedKeys.add(actionKey);}mAdapter = new MyAdapter();AlertParams params = new AlertParams(mContext);params.mAdapter = mAdapter;params.mOnClickListener = this;params.mForceInverseBackground = true;GlobalActionsDialog dialog = new GlobalActionsDialog(mContext, params);dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.dialog.getListView().<span style="color:#ff0000;">setItemsCanFocus</span>(true);dialog.getListView().<span style="color:#ff0000;">setLongClickable</span>(true);dialog.getListView().<span style="color:#ff0000;">setOnItemLongClickListener</span>(<span style="color:#000099;">new AdapterView.OnItemLongClickListener() {@Overridepublic boolean onItemLongClick(AdapterView<?> parent, View view, int position,long id) {final Action action = mAdapter.getItem(position);if (action instanceof LongPressAction) {return ((LongPressAction) action).onLongPress();}return false;}</span>});dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);dialog.setOnDismissListener(this);return dialog;}

在创建GlobalActionsDialog后给各个view设置监听,setItemsCanFocus,setLongClickable,setOnItemLongClickListener,其中setOnItemLongClickListener

就是针对长按键的处理 ((LongPressAction) action).onLongPress(),如果不想处理power长按动作可以dialog.getListView().setOnItemLongClickListener(null)来到达要求。
        public boolean onLongPress() {mWindowManagerFuncs.rebootSafeMode(true);return true;}

而mWindowManagerFuncs有就是WindowManagerService,rebootSafeMode就会调到

    // Called by window manager policy.  Not exposed externally.@Overridepublic void rebootSafeMode(boolean confirm) {ShutdownThread.rebootSafeMode(mContext, confirm);}

ShutdownThread进入安全模式

WindowManagerService 调用rebootSafeMode之后,就转到ShutdownThread 线程来处理

    public static void rebootSafeMode(final Context context, boolean confirm) {mReboot = true;mRebootSafeMode = true;mRebootReason = null;<span style="color:#ff0000;">shutdownInner</span>(context, confirm);}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;}}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();}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) {<span style="color:#ff0000;">beginShutdownSequence</span>(context);}}).setNegativeButton(com.android.internal.R.string.no, null).create();closer.dialog = sConfirmDialog;sConfirmDialog.setOnDismissListener(closer);sConfirmDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);sConfirmDialog.show();} else {<span style="color:#ff0000;">beginShutdownSequence</span>(context);}}

rebootSafeMode会接着调shutdownInner,最终beginShutdownSequence call   pms电源管理服务来关机重启

    private static void beginShutdownSequence(Context context) {synchronized (sIsStartedGuard) {if (sIsStarted) {Log.d(TAG, "Shutdown sequence already running, returning.");return;}sIsStarted = true;}// throw up an indeterminate system dialog to indicate radio is// shutting down.ProgressDialog 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.show();sInstance.mContext = context;<span style="color:#ff0000;">sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE)</span>;// make sure we never fall asleep againsInstance.mCpuWakeLock = null;try {sInstance.mCpuWakeLock = sInstance.mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG + "-cpu");<span style="color:#ff0000;">sInstance.mCpuWakeLock.setReferenceCounted(false);sInstance.mCpuWakeLock.acquire()</span>;} catch (SecurityException e) {Log.w(TAG, "No permission to acquire wake lock", e);sInstance.mCpuWakeLock = null;}// also make sure the screen stays on for better user experiencesInstance.mScreenWakeLock = null;if (sInstance.mPowerManager.isScreenOn()) {try {sInstance.mScreenWakeLock = sInstance.mPowerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG + "-screen");sInstance.mScreenWakeLock.setReferenceCounted(false);sInstance.mScreenWakeLock.acquire();} catch (SecurityException e) {Log.w(TAG, "No permission to acquire wake lock", e);sInstance.mScreenWakeLock = null;}}// start the thread that initiates shutdownsInstance.mHandler = new Handler() {};sInstance.start();}

至此整个长按 power键的处理流程结束。

android系统重启功能定制 ,安全模式相关流程,安全模式流程图。

Android 5.1 长按power键流程分析相关推荐

  1. Android rom开发:长按Power键关机/重启优化,告别长时间等待

    最近在项目上碰到了这样的问题:在某个apk界面长按power键来选择关机或者重启,apk会出现重启现象,并且更加尴尬的是,在另外的方案上面对比后发现没有问题,明明白白地显示这是系统的锅. 好吧,改!仔 ...

  2. android7.0 电源(Power)键流程

    对于Power键的分析文章较多,本文从android7.0源码的角度大致分析下电源键的流程!参考博主连接:http://blog.csdn.net/gaugamela/article/details/ ...

  3. Android 系统修改长按关机键功能

    系统环境 瑞星微 px30 android 8.1系统 要实现的功能 原本长按power键的功能是弹出一个选择对话框,关机和重启功能,长按两个选择项会提示是否要进入安全模式,现将它改成长按恢复出厂功能 ...

  4. Android 5.1长按电源键添加重启功能

    原址:http://blog.csdn.net/zhoumushui 现在长按Power键只有一个关机键,需要添加一个重启,以下是我的添加步骤: 1.在frameworks/base/core/res ...

  5. RK3588长按power键强制关机的按键时间设置

    RK3588长按power键强制关机的按键时间设置 本文适用于RK3588+RK806(电源管理芯片)的方案,power(电源)键是直接控制RK806来控制RK3588的上下电,可以实现短按开机.长按 ...

  6. 高通Android智能平台环境搭建_编译流程分析

    高通Android智能平台环境搭建_编译流程分析 高通平台环境搭建,编译,系统引导流程分析 TOC \o \h \z \u 1. 高通平台android开发总结. 7 1.1 搭建高通平台环境开发环境 ...

  7. android长按home键流程

    home键在KeyEvent中的键值为3. public static final int KEYCODE_HOME            = 3; 当用户按下home键的时候(包括长按),程序会进入 ...

  8. Android 系统(211)---Power键不亮屏分析方法

    Power键不亮屏分析方法 亮屏流程 (1)  以下是列出的整个按键唤醒的log关键点,每条都有粗体字说明其含义以及该注意的关键字: (2)  一条一条依次检查,直到如果发现某条log找不到,那问题就 ...

  9. Android系统启动顺序(按下power键后所做的的工作)

    Android是一个基于Linux的开源操作系统.所有的Android设备都是运行在ARM处理器(ARM 源自进阶精简指令集机器,源自ARM架构)上,除了英特尔的Xolo设备(http://xolo. ...

最新文章

  1. 妙用Telnet快速收发电子邮件(转载)
  2. boost::mp11::mp_set_push_front相关用法的测试程序
  3. PHP格式化全国省市区列表
  4. 为什么要使用反射机制
  5. 项目背景怎么描述_培训回顾 |第六届“互联网+”之创业大赛项目计划书撰写
  6. mysql 插入前查重_插入新数据是直接查重,如果有重复则不插入数据
  7. web框架Django一
  8. Rabbit MQ 安装
  9. jQuery图片预加载(延迟加载)之插件Lazy Load
  10. 计算机主板在网卡分配错位,华硕主板如何在bios里关闭网卡启动
  11. RS485转OPC UA
  12. 计算机文件夹加密文件,如何加密计算机文件夹4种加密文件夹的方法
  13. 百度API接口+图灵机器人=语音助手
  14. Planner – 项目管理软件 - 小众软件
  15. PTA不变初心数(17分)简单答案版
  16. Endnote 导入enw文件无响应及解决方法
  17. 解决微信App支付服务端,App上提示“商户支付下单id非法”
  18. 双网卡设置一个外网一个内网_双网卡同时上网,内网外网同时启用的解放办法...
  19. Chrome开启多线程下载
  20. 记一次失败的实战渗透

热门文章

  1. 论文中的参考文献序号自动链接到对应的参考文献
  2. 模型推荐丨政务大数据项目案例模型分享
  3. 如何给电脑硬盘分区?
  4. 谁浇了李彦宏一瓶冷水?
  5. 蓝桥杯 土地的面积计算
  6. 手游服务器技术的选择
  7. PHP中利用PHPMailer配合QQ邮箱实现发邮件
  8. java 实现邮件带附件发送
  9. 2015年南通二级建造师考试报名通知
  10. 30 行Python代码实现蚂蚁森林自动收能量(附送源码)