一、Android 呼吸灯的使用

在讲呼吸灯实现流程之前,我们先看一下如何使用它。
     Android提供了呼吸灯的接口,我们可以通过该接口,控制呼吸灯的闪烁频率和占空比。具体代码如下:

[java] view plaincopyprint?
  1. package com.example.test;
  2. import android.os.Bundle;
  3. import android.view.View;
  4. import android.widget.Button;
  5. import android.widget.EditText;
  6. import android.app.Activity;
  7. import android.app.Notification;
  8. import android.app.NotificationManager;
  9. public class MainActivity extends Activity {
  10. Button working;
  11. EditText ledOn;
  12. EditText ledOff;
  13. final int ID_LED=19871103;
  14. @Override
  15. protected void onCreate(Bundle savedInstanceState) {
  16. super.onCreate(savedInstanceState);
  17. setContentView(R.layout.activity_main);
  18. ledOn = (EditText)findViewById(R.id.LedOn);
  19. ledOff = (EditText)findViewById(R.id.LedOff);
  20. working = (Button)findViewById(R.id.bu1);
  21. working.setOnClickListener(new View.OnClickListener() {
  22. @Override
  23. public void onClick(View arg0) {
  24. // TODO Auto-generated method stub
  25. NotificationManager nm=(NotificationManager)getSystemService(NOTIFICATION_SERVICE);
  26. Notification notification = new Notification();
  27. notification.ledARGB = 0xffff0000; //这里是颜色,我们可以尝试改变,理论上0xFFff0000是红色
  28. //              notification.ledOnMS = 350;
  29. //              notification.ledOffMS = 300;
  30. notification.ledOnMS = Integer.parseInt((ledOn.getText().toString()));
  31. notification.ledOffMS = Integer.parseInt((ledOff.getText().toString()));
  32. notification.flags = Notification.FLAG_SHOW_LIGHTS;
  33. nm.notify(ID_LED, notification);
  34. }
  35. });
  36. //nm.cancel(ID_LED);
  37. }
  38. }
package com.example.test;import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;public class MainActivity extends Activity {Button working;EditText ledOn;EditText ledOff;    final int ID_LED=19871103;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);ledOn = (EditText)findViewById(R.id.LedOn);ledOff = (EditText)findViewById(R.id.LedOff);working = (Button)findViewById(R.id.bu1);working.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View arg0) {// TODO Auto-generated method stubNotificationManager nm=(NotificationManager)getSystemService(NOTIFICATION_SERVICE);Notification notification = new Notification();notification.ledARGB = 0xffff0000; //这里是颜色,我们可以尝试改变,理论上0xFFff0000是红色
//              notification.ledOnMS = 350;
//              notification.ledOffMS = 300;notification.ledOnMS = Integer.parseInt((ledOn.getText().toString()));notification.ledOffMS = Integer.parseInt((ledOff.getText().toString()));notification.flags = Notification.FLAG_SHOW_LIGHTS;nm.notify(ID_LED, notification);}});//nm.cancel(ID_LED);}
}

通过该程序,便能自由控制呼吸灯的占空比与频率。

二、Android上层呼吸灯的实现

1、NotificationManager

(1).在apk中我们填充了结构notification,并调用了nm.notify。很显然,找到了关键点NotificationManager,对应文件为:
              frameworks/base/core/java/android/app/NotificationManager.java

(2).在NotificationManager.java中找到了我们的调用方法notify。 对应如下:

[plain] view plaincopyprint?
  1. public void notify(int id, Notification notification)
  2. 107    {
  3. 108        notify(null, id, notification);
  4. 109    }
  5. 110
  6. 111    /**
  7. 112     * Post a notification to be shown in the status bar. If a notification with
  8. 113     * the same tag and id has already been posted by your application and has not yet been
  9. 114     * canceled, it will be replaced by the updated information.
  10. 115     *
  11. 116     * @param tag A string identifier for this notification.  May be {@code null}.
  12. 117     * @param id An identifier for this notification.  The pair (tag, id) must be unique
  13. 118     *        within your application.
  14. 119     * @param notification A {@link Notification} object describing what to
  15. 120     *        show the user. Must not be null.
  16. 121     */
  17. 122    public void notify(String tag, int id, Notification notification)
  18. 123    {
  19. 124        int[] idOut = new int[1];
  20. 125        INotificationManager service = getService();
  21. 126        String pkg = mContext.getPackageName();
  22. 127        if (notification.sound != null) {
  23. 128            notification.sound = notification.sound.getCanonicalUri();
  24. 129        }
  25. 130        if (localLOGV) Log.v(TAG, pkg + ”: notify(“ + id + ”, ” + notification + ”)”);
  26. 131        try {
  27. 132            service.enqueueNotificationWithTag(pkg, tag, id, notification, idOut,
  28. 133                    UserHandle.myUserId());
  29. 134            if (id != idOut[0]) {
  30. 135                Log.w(TAG, “notify: id corrupted: sent ” + id + ”, got back ” + idOut[0]);
  31. 136            }
  32. 137        } catch (RemoteException e) {
  33. 138        }
  34. 139    }
public void notify(int id, Notification notification)
107    {
108        notify(null, id, notification);
109    }
110
111    /**
112     * Post a notification to be shown in the status bar. If a notification with
113     * the same tag and id has already been posted by your application and has not yet been
114     * canceled, it will be replaced by the updated information.
115     *
116     * @param tag A string identifier for this notification.  May be {@code null}.
117     * @param id An identifier for this notification.  The pair (tag, id) must be unique
118     *        within your application.
119     * @param notification A {@link Notification} object describing what to
120     *        show the user. Must not be null.
121     */
122    public void notify(String tag, int id, Notification notification)
123    {
124        int[] idOut = new int[1];
125        INotificationManager service = getService();
126        String pkg = mContext.getPackageName();
127        if (notification.sound != null) {
128            notification.sound = notification.sound.getCanonicalUri();
129        }
130        if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
131        try {
132            service.enqueueNotificationWithTag(pkg, tag, id, notification, idOut,
133                    UserHandle.myUserId());
134            if (id != idOut[0]) {
135                Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]);
136            }
137        } catch (RemoteException e) {
138        }
139    }

抓取到关键点:enqueueNotificationWithTag。它位于类NotificationManagerService.java中,具体位置如下:

frameworks/base/services/java/com/android/server/NotificationManagerService.java
      (3).NotificationManagerService

进入类NotificationManagerService.java,找到我们在NotificationManager.java中调用的方法:enqueueNotificationWithTag,如下:

[plain] view plaincopyprint?
  1. public void enqueueNotificationWithTag(String pkg, String tag, int id, Notification notification,
  2. 956            int[] idOut, int userId)
  3. 957    {
  4. 958        enqueueNotificationInternal(pkg, Binder.getCallingUid(), Binder.getCallingPid(),
  5. 959                tag, id, notification, idOut, userId);
  6. 960    }
  7. 961
  8. 962    private final static int clamp(int x, int low, int high) {
  9. 963        return (x < low) ? low : ((x > high) ? high : x);
  10. 964    }
  11. 965
  12. 966    // Not exposed via Binder; for system use only (otherwise malicious apps could spoof the
  13. 967    // uid/pid of another application)
  14. 968    public void enqueueNotificationInternal(String pkg, int callingUid, int callingPid,
  15. 969            String tag, int id, Notification notification, int[] idOut, int userId)
  16. 970    {
  17. 971        if (DBG) {
  18. 972            Slog.v(TAG, “enqueueNotificationInternal: pkg=” + pkg + ” id=” + id + ” notification=” + notification);
  19. 973        }
  20. 974        checkCallerIsSystemOrSameApp(pkg);
  21. 975        final boolean isSystemNotification = (“android”.equals(pkg));
  22. 976
  23. 977        userId = ActivityManager.handleIncomingUser(callingPid,
  24. 978                callingUid, userId, true, false, “enqueueNotification”, pkg);
  25. 979        UserHandle user = new UserHandle(userId);
  26. 980
  27. 981        // Limit the number of notifications that any given package except the android
  28. 982        // package can enqueue.  Prevents DOS attacks and deals with leaks.
  29. 983        if (!isSystemNotification) {
 public void enqueueNotificationWithTag(String pkg, String tag, int id, Notification notification,
956            int[] idOut, int userId)
957    {
958        enqueueNotificationInternal(pkg, Binder.getCallingUid(), Binder.getCallingPid(),
959                tag, id, notification, idOut, userId);
960    }
961
962    private final static int clamp(int x, int low, int high) {
963        return (x < low) ? low : ((x > high) ? high : x);
964    }
965
966    // Not exposed via Binder; for system use only (otherwise malicious apps could spoof the
967    // uid/pid of another application)
968    public void enqueueNotificationInternal(String pkg, int callingUid, int callingPid,
969            String tag, int id, Notification notification, int[] idOut, int userId)
970    {
971        if (DBG) {
972            Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id + " notification=" + notification);
973        }
974        checkCallerIsSystemOrSameApp(pkg);
975        final boolean isSystemNotification = ("android".equals(pkg));
976
977        userId = ActivityManager.handleIncomingUser(callingPid,
978                callingUid, userId, true, false, "enqueueNotification", pkg);
979        UserHandle user = new UserHandle(userId);
980
981        // Limit the number of notifications that any given package except the android
982        // package can enqueue.  Prevents DOS attacks and deals with leaks.
983        if (!isSystemNotification) {

很显然我们进入了:enqueueNotificationInternal,该函数太长,不复制了就,~_~。这个函数中实现了不少功能,如是否播放声音,是否震动。最后找到控制呼吸灯的位置在这个方法中:

[plain] view plaincopyprint?
  1. 1321            if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0
  2. 1322                    && canInterrupt) {
  3. 1323                mLights.add(r);
  4. 1324                updateLightsLocked();
  5. 1325            } else {
  6. 1326                if (old != null
  7. 1327                        && ((old.notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0)) {
  8. 1328                    updateLightsLocked();
  9. 1329                }
  10. 1330            }
1321            if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0
1322                    && canInterrupt) {
1323                mLights.add(r);
1324                updateLightsLocked();
1325            } else {
1326                if (old != null
1327                        && ((old.notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0)) {
1328                    updateLightsLocked();
1329                }
1330            }

很显然,关键点就是:updateLightsLocked()。进入之后会有一系列判断、赋值之类操作。之后进入:

[plain] view plaincopyprint?
  1. private LightsService.Light mNotificationLight;
  2. 1602            if (mNotificationPulseEnabled) {
  3. 1603                // pulse repeatedly
  4. 1604                ///M: log lights information
  5. 1605                Log.d(TAG, “notification setFlashing ledOnMS = ”+ledOnMS + ” ledOffMS = ”+ ledOffMS);
  6. 1606                mNotificationLight.setFlashing(ledARGB, LightsService.LIGHT_FLASH_TIMED,
  7. 1607                        ledOnMS, ledOffMS);
  8. 1608                ///M:
  9. 1609            } else {
  10. 1610                // pulse only once
  11. 1611                mNotificationLight.pulse(ledARGB, ledOnMS);
  12. 1612            }
               private LightsService.Light mNotificationLight;1602            if (mNotificationPulseEnabled) {
1603                // pulse repeatedly
1604                ///M: log lights information
1605                Log.d(TAG, "notification setFlashing ledOnMS = "+ledOnMS + " ledOffMS = "+ ledOffMS);
1606                mNotificationLight.setFlashing(ledARGB, LightsService.LIGHT_FLASH_TIMED,
1607                        ledOnMS, ledOffMS);
1608                ///M:
1609            } else {
1610                // pulse only once
1611                mNotificationLight.pulse(ledARGB, ledOnMS);
1612            }

然后,我们开始进入LightsService。

(4).LightsService

LightsService的位置如下:
                   frameworks/base/services/java/com/android/server/LightsService.java
      在LightsService中,通过方法:setFlashing 调用:setLightLocked,最终到达了JNI的setLight_native;

[plain] view plaincopyprint?
  1. 116        private void setLightLocked(int color, int mode, int onMS, int offMS, int brightnessMode) {
  2. 117            if (color != mColor || mode != mMode || onMS != mOnMS || offMS != mOffMS) {
  3. 118                if (DEBUG) Slog.v(TAG, “setLight #” + mId + ”: color=#”
  4. 119                        + Integer.toHexString(color));
  5. 120                mColor = color;
  6. 121                mMode = mode;
  7. 122                mOnMS = onMS;
  8. 123                mOffMS = offMS;
  9. 124                setLight_native(mNativePointer, mId, color, mode, onMS, offMS, brightnessMode);
  10. 125            }
  11. 126        }
116        private void setLightLocked(int color, int mode, int onMS, int offMS, int brightnessMode) {
117            if (color != mColor || mode != mMode || onMS != mOnMS || offMS != mOffMS) {
118                if (DEBUG) Slog.v(TAG, "setLight #" + mId + ": color=#"
119                        + Integer.toHexString(color));
120                mColor = color;
121                mMode = mode;
122                mOnMS = onMS;
123                mOffMS = offMS;
124                setLight_native(mNativePointer, mId, color, mode, onMS, offMS, brightnessMode);
125            }
126        }

2.JNI

通过方法setLight_native,进入到了JNI层中,位置如下:
                      frameworks/base/services/jni/com_android_server_LightsService.cpp
      在方法:setLight_native中,一样的进行了相关的判断、接受上层赋值.之后根据参数,调用了对应的set_light:

[plain] view plaincopyprint?
  1. 127    ALOGD(“setLight_native: light=%d, colorARGB=0x%x, flashMode=%d, onMS=%d, offMS=%d, brightnessMode=%d”,
  2. 128 light, colorARGB, flashMode, onMS, offMS, brightnessMode);
  3. 129
  4. 130#if defined(MTK_AAL_SUPPORT)
  5. 131    if (light == LIGHT_INDEX_BACKLIGHT) {
  6. 132        if (AALClient::getInstance().setBacklightColor(colorARGB & 0x00ffffff) == 0)
  7. 133            return;
  8. 134        ALOGW(“Fail to set backlight from AAL service”);
  9. 135    }
  10. 136#endif
  11. 137
  12. 138    devices->lights[light]->set_light(devices->lights[light], &state);
127    ALOGD("setLight_native: light=%d, colorARGB=0x%x, flashMode=%d, onMS=%d, offMS=%d, brightnessMode=%d",
128 light, colorARGB, flashMode, onMS, offMS, brightnessMode);
129
130#if defined(MTK_AAL_SUPPORT)
131    if (light == LIGHT_INDEX_BACKLIGHT) {
132        if (AALClient::getInstance().setBacklightColor(colorARGB & 0x00ffffff) == 0)
133            return;
134        ALOGW("Fail to set backlight from AAL service");
135    }
136#endif
137
138    devices->lights[light]->set_light(devices->lights[light], &state);

最后通过set_light转入了HAL层。

3.HAL

呼吸灯的HAL层对应位置如下:
                    mediatek/hardware/liblights/lights.c
    我们在NotificationManager下来的set_light对应为:
                    open_lights下的:

[plain] view plaincopyprint?
  1. 584    }
  2. 585    else if (0 == strcmp(LIGHT_ID_NOTIFICATIONS, name)) {
  3. 586        set_light = set_light_notifications;
  4. 587    }
584    }
585    else if (0 == strcmp(LIGHT_ID_NOTIFICATIONS, name)) {
586        set_light = set_light_notifications;
587    }

进入set_light_notifications,通过如下调用:
                  set_light_notifications —-> handle_speaker_battery_locked —->  set_speaker_light_locked
       在函数set_speaker_light_locked中,最后判断我们要控制的led是red,green还是blue,从我的范例上看,我传入的led参数为0xffff0000,对应为red。于是,进入如下的          red:

[plain] view plaincopyprint?
  1. 465    if (red) {
  2. 466        blink_green(0, 0, 0);
  3. 467        blink_blue(0, 0, 0);
  4. 468        blink_red(red, onMS, offMS);
  5. 469    }
  6. 470    else if (green) {
  7. 471        blink_red(0, 0, 0);
  8. 472        blink_blue(0, 0, 0);
  9. 473        blink_green(green, onMS, offMS);
  10. 474    }
  11. 475    else if (blue) {
  12. 476        blink_red(0, 0, 0);
  13. 477        blink_green(0, 0, 0);
  14. 478        blink_blue(blue, onMS, offMS);
  15. 479    }
  16. 480    else {
  17. 481        blink_red(0, 0, 0);
  18. 482        blink_green(0, 0, 0);
  19. 483        blink_blue(0, 0, 0);
  20. 484    }
465    if (red) {
466        blink_green(0, 0, 0);
467        blink_blue(0, 0, 0);
468        blink_red(red, onMS, offMS);
469    }
470    else if (green) {
471        blink_red(0, 0, 0);
472        blink_blue(0, 0, 0);
473        blink_green(green, onMS, offMS);
474    }
475    else if (blue) {
476        blink_red(0, 0, 0);
477        blink_green(0, 0, 0);
478        blink_blue(blue, onMS, offMS);
479    }
480    else {
481        blink_red(0, 0, 0);
482        blink_green(0, 0, 0);
483        blink_blue(0, 0, 0);
484    }

进入了red函数之后,重点如下:
                 上层传下来的的level值为0,则直接关闭RED_LED_FILE(char const*const RED_LED_FILE = “/sys/class/leds/red/brightness”)

[plain] view plaincopyprint?
  1. 248 if (nowStatus == 0) {
  2. 249         write_int(RED_LED_FILE, 0);
  3. 250 }
248    if (nowStatus == 0) {
249         write_int(RED_LED_FILE, 0);
250 }

上层传下的来的参数onMS和offMS都有值,则呼吸灯闪烁:

[plain] view plaincopyprint?
  1. 251 else if (nowStatus == 1) {
  2. 252//           write_int(RED_LED_FILE, level); // default full brightness
  3. 253     write_str(RED_TRIGGER_FILE, “timer”);
  4. 254     while (((access(RED_DELAY_OFF_FILE, F_OK) == -1) || (access(RED_DELAY_OFF_FILE, R_OK|W_OK) == -1)) && i<10) {
  5. 255         ALOGD(“RED_DELAY_OFF_FILE doesn’t exist or cannot write!!\n”);
  6. 256         led_wait_delay(5);//sleep 5ms for wait kernel LED class create led delay_off/delay_on node of fs
  7. 257         i++;
  8. 258     }
  9. 259     write_int(RED_DELAY_OFF_FILE, offMS);
  10. 260     write_int(RED_DELAY_ON_FILE, onMS);
  11. 261 }
251    else if (nowStatus == 1) {
252//           write_int(RED_LED_FILE, level); // default full brightness
253     write_str(RED_TRIGGER_FILE, "timer");
254     while (((access(RED_DELAY_OFF_FILE, F_OK) == -1) || (access(RED_DELAY_OFF_FILE, R_OK|W_OK) == -1)) && i<10) {
255         ALOGD("RED_DELAY_OFF_FILE doesn't exist or cannot write!!\n");
256         led_wait_delay(5);//sleep 5ms for wait kernel LED class create led delay_off/delay_on node of fs
257         i++;
258     }
259     write_int(RED_DELAY_OFF_FILE, offMS);
260     write_int(RED_DELAY_ON_FILE, onMS);
261 }

其他情况下,我们直接就点亮红色呼吸灯:

[plain] view plaincopyprint?
  1. 262 else {
  2. 263     write_str(RED_TRIGGER_FILE, “none”);
  3. 264         write_int(RED_LED_FILE, 255); // default full brightness
  4. 265 }
262   else {
263     write_str(RED_TRIGGER_FILE, "none");
264         write_int(RED_LED_FILE, 255); // default full brightness
265 }

4.小结

到处Andoid上层的呼吸灯基本上就这个。
      在HAL中最后,点亮,关闭和闪烁呼吸灯,点亮和关闭呼吸灯都是直接操作设备接口: RED_LED_FILE = “/sys/class/leds/red/brightness”;
      闪烁则相对复杂一些,接下来驱动部分就以闪烁为为范例进行讲解。

Android 呼吸灯流程分析(一)相关推荐

  1. Android 呼吸灯流程分析

    一.Android呼吸灯Driver实现 1.注册驱动 代码位置:mediatek/kernel/drivers/leds/leds_drv.c [plain] view plaincopy 602s ...

  2. android物理键盘灯控制,Android按键灯流程分析

    调用代码为: alps\frameworks\base\services\java\com\Android\server\LightsService.java 使用方法: private Lights ...

  3. Android 呼吸灯控制逻辑

    Android 呼吸灯控制 android中灯光除闪光灯外是由Lights统一控制的,使用adb shell dumpsys lights我们可以看到当前灯光的一些状态 Service: aidl ( ...

  4. Android SDCard UnMounted 流程分析(三)

    前篇地址 Android SDCard UnMounted 流程分析(一) Android SDCard UnMounted 流程分析(二) 前一篇讲到SDCard unmout onEvent 发送 ...

  5. android camera2 API流程分析

    Android camera2 API流程分析 Android5.0之后,新推出来了一个类,android.hardware.camera2,与原来的camera的类实现照相和拍视频的流程有所不同,原 ...

  6. Android UI绘制流程分析(三)measure

    源码版本Android 6.0 请参阅:http://androidxref.com/6.0.1_r10 本文目的是分析从Activity启动到走完绘制流程并显示在界面上的过程,在源码展示阶段为了使跟 ...

  7. Android -- Wifi启动流程分析

    Android -- Wifi启动流程分析 Android网络各个模式中,Wifi应该是目前最常用的一种网络方式了:下面就简单介绍下Android中Wifi的启动流程. 当我在Setting菜单里点击 ...

  8. 【SemiDrive源码分析】【X9芯片启动流程】27 - AP1 Android Preloader启动流程分析(加载atf、tos、bootloader镜像后进入BL31环境)

    [SemiDrive源码分析][X9芯片启动流程]27 - AP1 Android Preloader启动流程分析(加载atf.tos.bootloader镜像后进入BL31环境) 一.Android ...

  9. android 呼吸灯权限_小米新机搭载炫彩呼吸灯酷到爆;三星顶级旗舰Note 10正式官宣...

    各位,早上好!即日起,魅族Meizu Pay京津冀互联互通卡免费开卡,你期待吗? 1,苦等436天!国产超优秀ROM终于换代 7月2日,有网友曝光了魅族Flyme 8的海报图片,图片显示:你所期待的我 ...

  10. android呼吸灯动画,Android高德地图自定义定位蓝点实现呼吸灯功能

    还是先上个图吧: cluster.gif 说下实现的原理,首先这个定位小蓝点是由两张图片组成的,最底层的一张白色圆形图片以及上一层的蓝色圆形图片,只要不停的对蓝色图片进行透明度动画操作就可以实现这个效 ...

最新文章

  1. 用Python解密2021年最新富豪榜,马云居然连前三都没进
  2. Linux扫盲篇:CentOS、Ubuntu、Gento
  3. OFBiz + Opentaps 目录管理 四. 产品(二)可配置产品
  4. 【深度强化学习】DQN训练超级玛丽闯关
  5. Ajax(jquery)
  6. 关于vs2012、tfs2012、windows server 2008r2一些记录
  7. 905. 按奇偶排序数组
  8. Avalonia跨平台入门第七篇之RadioButton的模板
  9. idea中使用逆向工程----三部曲
  10. Centos、Ubuntu的区别
  11. Android自定义控件学习(五)-------自定义绘图
  12. 开始用Flutter做游戏吧
  13. 最大最小标准化 数值_分享丨实验数据标准化处理方法
  14. android ui设计最新字体,手机ui设计常用字体一般有哪些,UI设计中的字体有什么规范要求...
  15. 关于TRACERT和TTL
  16. 菜鸟学IT之Hadoop综合大作业
  17. Liunx配置tomcat
  18. hibernate中lazy的使用
  19. 关于runtime error '429'解决方案
  20. python发明小故事简写_科学发明小故事20字

热门文章

  1. 精读解析 Entire Space Multi-Task Model(ESMM) 阿里2018年CVR预测
  2. 仅允许用户radmin使用su命令
  3. 服务器2008系统usb驱动,Windows Server 2008 R2驱动程序整合USB3实用程序
  4. Mycat快速入门(六): Mycat管理命令和监控
  5. cao方法matlab程序,偏最小二乘法 matlab程序 [转]
  6. Spring源码杂集
  7. CTF 你就是长不了
  8. 扫描探针显微术入门(3)
  9. Texmacs使用注意事项
  10. 35岁的大龄程序员都去哪里了?