• Tiny4412 LED 硬件服务


    1.Android系统中启动框架

     

    2.首先实现驱动程序

    #include <linux/kernel.h>
    #include <linux/module.h>
    #include <linux/miscdevice.h>
    #include <linux/device.h>
    #include <linux/fs.h>
    #include <linux/types.h>
    #include <linux/moduleparam.h>
    #include <linux/slab.h>
    #include <linux/ioctl.h>
    #include <linux/cdev.h>
    #include <linux/delay.h>
     
    #include <linux/gpio.h>
    #include <mach/gpio.h>
    #include <plat/gpio-cfg.h>
    
    static int led_gpios[] = {
        EXYNOS4212_GPM4(0),
        EXYNOS4212_GPM4(1),
        EXYNOS4212_GPM4(2),
        EXYNOS4212_GPM4(3),
    };
    
    static int led_open(struct inode *inode, struct file *file)
    {
        /* 配置GPIO为输出引脚 */
        int i;
        for (i = 0; i < 4; i++)
            s3c_gpio_cfgpin(led_gpios[i], S3C_GPIO_OUTPUT);
        
        return 0;
    }
    
    /* app : ioctl(fd, cmd, arg) */
    static long led_ioctl(struct file *filp, unsigned int cmd,
            unsigned long arg)
    {
        /* 根据传入的参数设置GPIO */
        /* cmd : 0-off, 1-on */
        /* arg : 0-3, which led */
    
        if ((cmd != 0) && (cmd != 1))
            return -EINVAL;
        
        if (arg > 4)
            return -EINVAL;
        
        gpio_set_value(led_gpios[arg], !cmd);
        
        return 0;
    }
    
    static struct file_operations leds_ops = {
        .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
        .open   =   led_open,     
        .unlocked_ioctl    = led_ioctl,
        
    };
    
    static int major;
    static struct class *cls;
    
    int leds_init(void)
    {
        major = register_chrdev(0, "leds", &leds_ops);
    
        /* 为了让系统udev,mdev给我们创建设备节点 */
        /* 创建类, 在类下创建设备 : /sys */
        cls = class_create(THIS_MODULE, "leds");
        device_create(cls, NULL, MKDEV(major, 0), NULL, "leds"); /* /dev/leds */
        
        return 0;
    }
    
    void leds_exit(void)
    {
        device_destroy(cls, MKDEV(major, 0));
        class_destroy(cls);
        unregister_chrdev(major, "leds");
    }
    
    module_init(leds_init);
    module_exit(leds_exit);
    
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("www.100ask.net");

    放入内核 drivers/char
    修改 drivers/char/Makefile,添加:
    obj-y += leds_4412.o

    重新编译内核

    或者将其编译成模块

    #Makefile
    obj-m := leds_4412.o
    KDIR := /home/cent/work/androidL_Tiny4412/linux-3.0.86
    all:
        make -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=arm
    .PHONY: clean
    clean:
        rm  -f  *.ko  *.o  *.mod.c  *.bak  *.order *.symvers *~

    执行 make后 用adb push 命令将其上传到开发板的/data/local 目录,adb shell 使用 insmod 命令将驱动模块加载到内核。

    3.实现HAL层

    //led_hal.h
    
    
    #ifndef ANDROID_LED_INTERFACE_H
    #define ANDROID_LED_INTERFACE_H
    
    #include <stdint.h>
    #include <sys/cdefs.h>
    #include <sys/types.h>
    
    #include <hardware/hardware.h>
    
    __BEGIN_DECLS
    
    struct led_device_t {
        struct hw_device_t common;
    
        int (*led_open)(struct led_device_t* dev);
        int (*led_ctrl)(struct led_device_t* dev, int which, int status);
    };
    
    
    __END_DECLS
    
    #endif  // ANDROID_LED_INTERFACE_H
    //led_hal.c
    
    #define LOG_TAG "LedHal"
    
    
    /* 1. 实现一个名为HMI的hw_module_t结构体 */
    
    /* 2. 实现一个open函数, 它返回led_device_t结构体 */
    
    /* 3. 实现led_device_t结构体 */
    
    /* 参考 hardwarelibhardwaremodulesvibratorvibrator.c
     */
    
    #include <hardware/vibrator.h>
    #include <hardware/hardware.h>
    
    #include <cutils/log.h>
    
    #include <stdio.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <errno.h>
    
    #include <hardware/led_hal.h>
    
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <sys/ioctl.h>
    #include <utils/Log.h>
    
    
    static int fd;
    
    
    /** Close this device */
    static int led_close(struct hw_device_t* device)
    {
        close(fd);
        return 0;
    }
    
    static int led_open(struct led_device_t* dev)
    {
        fd = open("/dev/leds", O_RDWR);
        ALOGI("led_open : %d", fd);
        if (fd >= 0)
            return 0;
        else
            return -1;
    }
    
    static int led_ctrl(struct led_device_t* dev, int which, int status)
    {
        int ret = ioctl(fd, status, which);
        ALOGI("led_ctrl : %d, %d, %d", which, status, ret);
        return ret;
    }
    
    
    
    
    static struct led_device_t led_dev = {
        .common = {
            .tag   = HARDWARE_DEVICE_TAG,
            .close = led_close,
        },
        .led_open  = led_open,
        .led_ctrl  = led_ctrl,
    };
    
    static int led_device_open(const struct hw_module_t* module, const char* id,
            struct hw_device_t** device)
    {
        *device = &led_dev;
        return 0;
    }
    
    
    static struct hw_module_methods_t led_module_methods = {
        .open = led_device_open,
    };
    
    struct hw_module_t HAL_MODULE_INFO_SYM = {
        .tag = HARDWARE_MODULE_TAG,
        .id = "led",
        .methods = &led_module_methods,
    };
    #Android.mk
    LOCAL_PATH := $(call my-dir)
    include $(CLEAR_VARS)
    LOCAL_MODULE := led.default
    LOCAL_MODULE_RELATIVE_PATH := hw
    LOCAL_C_INCLUDES := hardware/libhardware
    LOCAL_SRC_FILES := led_hal.c
    LOCAL_SHARED_LIBRARIES := liblog
    LOCAL_MODULE_TAGS := eng
    
    include $(BUILD_SHARED_LIBRARY)
    把新文件放到Android源码中, 所在目录:
    hardware/libhardware/include/hardware/led_hal.h
    hardware/libhardware/modules/led/led_hal.c
    hardware/libhardware/modules/led/Android.mk
     
    编译:
    $ mmm hardware/libhardware/modules/led
     
    4.实现JAVA硬件服务JNI层
    //com_android_server_LedService.cpp
    
    #define LOG_TAG "LedService"
    
    #include "jni.h"
    #include "JNIHelp.h"
    #include "android_runtime/AndroidRuntime.h"
    
    #include <utils/misc.h>
    #include <utils/Log.h>
    
    #include <stdio.h>
    
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <sys/ioctl.h>
    #include <hardware/led_hal.h>
    
    
    namespace android
    {
    
    static led_device_t* led_device;
    
    jint ledOpen(JNIEnv *env, jobject cls)
    {
        jint err;
        hw_module_t* module;
        hw_device_t* device;
    
        ALOGI("native ledOpen ...");
    
        /* 1. hw_get_module */
        err = hw_get_module("led", (hw_module_t const**)&module);
        if (err == 0) {
            /* 2. get device : module->methods->open */
            err = module->methods->open(module, NULL, &device);
            if (err == 0) {
                /* 3. call led_open */
                led_device = (led_device_t *)device;
                return led_device->led_open(led_device);
            } else {
                return -1;
            }
        }
        
        return -1;    
    }
    
    void ledClose(JNIEnv *env, jobject cls)
    {
        //ALOGI("native ledClose ...");
        //close(fd);
    }
    
    
    jint ledCtrl(JNIEnv *env, jobject cls, jint which, jint status)
    {
        ALOGI("native ledCtrl %d, %d", which, status);
        return led_device->led_ctrl(led_device, which, status);
    }
    
    
    static const JNINativeMethod methods[] = {
        {"native_ledOpen", "()I", (void *)ledOpen},
        {"native_ledClose", "()V", (void *)ledClose},
        {"native_ledCtrl", "(II)I", (void *)ledCtrl},
    };
        
    
    int register_android_server_LedService(JNIEnv *env)
    {
        return jniRegisterNativeMethods(env, "com/android/server/LedService",
                methods, NELEM(methods));
    }
    
    }

    放到Android源码frameworks/base/services/core/jni/com_android_server_LedService.cpp位置

    修改frameworks/base/services/core/jni/onload.cpp

    添加:

      int register_android_server_Watchdog(JNIEnv* env);
    +int register_android_server_LedService(JNIEnv *env);

      register_android_server_VibratorService(env);
    +register_android_server_LedService(env);

    修改 frameworks/base/services/core/jni/Android.mk :
      $(LOCAL_REL_DIR)/com_android_server_VibratorService.cpp
    + $(LOCAL_REL_DIR)/com_android_server_LedService.cpp
     
    5.实现AIDL代码
    package android.os;
    
    /** {@hide} */
    interface ILedService
    {
        int ledCtrl(int which, int status);
    }

    把 ILedService.aidl 放入 frameworks/base/core/java/android/os

     修改 frameworks/base/Android.mk  添加一行
             core/java/android/os/IVibratorService.aidl
    +        core/java/android/os/ILedService.aidl
     
     
     mmm frameworks/base
    它会生成: ./out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/os/ILedService.java
     
    6.实现LedService JAVA层代码
    package com.android.server;
    import android.os.ILedService;
    
    public class LedService extends ILedService.Stub {
        private static final String TAG = "LedService";
    
        /* call native c function to access hardware */
        public int ledCtrl(int which, int status) throws android.os.RemoteException
        {
            return native_ledCtrl(which, status);
        }
    
        public LedService() {
            native_ledOpen();
        }
    
        public static native int native_ledOpen();
        public static native void native_ledClose();
        public static native int native_ledCtrl(int which, int status);
    }

    frameworks/base/services/java/com/android/server/SystemServer.java中注册LedService

    Slog.i(TAG, "Vibrator Service");
    vibrator = new VibratorService(context);
    ServiceManager.addService("vibrator", vibrator);

    +Slog.i(TAG, "Led Service");
    +ServiceManager.addService("led", new LedService());

    上传到Android源码目录中
    frameworks/base/services/java/com/android/server/SystemServer.java
     
    不需要修改 frameworks/base/services/core/Android.mk
    它的内容里已经把该目录下所有JAVA文件自动包含进去了:
    LOCAL_SRC_FILES +=
        $(call all-java-files-under,java)
     
    编译:
    $ mmm frameworks/base/services
     
    7.Android应用程序中使用
     
    package cc.icen.leddemo;
    
    import android.os.RemoteException;
    import android.app.Activity;
    import android.os.Bundle;
    import android.widget.Button;
    import android.view.View;
    import android.widget.CheckBox;
    import android.widget.Toast;
    import android.os.ILedService;
    import android.os.ServiceManager;
    import android.util.Log;
    
    public class MainActivity extends Activity {
    
        private boolean ledon = false;
        private Button button = null;
        private CheckBox checkBoxLed1 = null;
        private CheckBox checkBoxLed2 = null;
        private CheckBox checkBoxLed3 = null;
        private CheckBox checkBoxLed4 = null;
        private ILedService iLedService = null;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            button = (Button) findViewById(R.id.BUTTON);
    
            iLedService = ILedService.Stub.asInterface(ServiceManager.getService("led"));
            if(iLedService == null)
                Log.d("tian","iLedService is null!!!!");
                
    
            checkBoxLed1 = (CheckBox)findViewById(R.id.LED1);
            checkBoxLed2 = (CheckBox)findViewById(R.id.LED2);
            checkBoxLed3 = (CheckBox)findViewById(R.id.LED3);
            checkBoxLed4 = (CheckBox)findViewById(R.id.LED4);
    
            button.setOnClickListener(new MyButtonListener());
    
        }
    
        class MyButtonListener implements View.OnClickListener {
            @Override
            public void onClick(View v) {
                ledon = !ledon;
                if (ledon) {
                    button.setText("ALL OFF");
                    checkBoxLed1.setChecked(true);
                    checkBoxLed2.setChecked(true);
                    checkBoxLed3.setChecked(true);
                    checkBoxLed4.setChecked(true);
    
                    try {
                        for (int i = 0; i < 4; i++)
                            iLedService.ledCtrl(i, 1);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
                else {
                    button.setText("ALL ON");
                    checkBoxLed1.setChecked(false);
                    checkBoxLed2.setChecked(false);
                    checkBoxLed3.setChecked(false);
                    checkBoxLed4.setChecked(false);
    
                    try {
                        for (int i = 0; i < 4; i++)
                            iLedService.ledCtrl(i, 0);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        public void onCheckboxClicked(View view) {
            // Is the view now checked?
            boolean checked = ((CheckBox) view).isChecked();
    
            try {
                // Check which checkbox was clicked
                switch(view.getId()) {
                    case R.id.LED1:
                        if (checked) {
                            // Put some meat on the sandwich
                            Log.d("tian","1");
                            //Toast.makeText(getApplicationContext(), "LED1 on", Toast.LENGTH_SHORT).show();
                            iLedService.ledCtrl(0, 1);
                            Log.d("tian","2");
                        }
                        else {
                            // Remove the meat
                            //Toast.makeText(getApplicationContext(), "LED1 off", Toast.LENGTH_SHORT).show();
                            iLedService.ledCtrl(0, 0);
                        }
                        break;
                    case R.id.LED2:
                        if (checked) {
                            // Put some meat on the sandwich
                            //Toast.makeText(getApplicationContext(), "LED2 on", Toast.LENGTH_SHORT).show();
                            iLedService.ledCtrl(1, 1);
                        }
                        else {
                            // Remove the meat
                            //Toast.makeText(getApplicationContext(), "LED2 off", Toast.LENGTH_SHORT).show();
                            iLedService.ledCtrl(1, 0);
                        }
                        break;
    
                    case R.id.LED3:
                        if (checked) {
                            // Put some meat on the sandwich
                            //Toast.makeText(getApplicationContext(), "LED3 on", Toast.LENGTH_SHORT).show();
                            iLedService.ledCtrl(2, 1);
                        }
                        else {
                            // Remove the meat
                            //Toast.makeText(getApplicationContext(), "LED3 off", Toast.LENGTH_SHORT).show();
                            iLedService.ledCtrl(2, 0);
                        }
                        break;
    
                    case R.id.LED4:
                        if (checked) {
                            // Put some meat on the sandwich
                            //Toast.makeText(getApplicationContext(), "LED4 on", Toast.LENGTH_SHORT).show();
                            iLedService.ledCtrl(3, 1);
                        }
                        else {
                            // Remove the meat
                            //Toast.makeText(getApplicationContext(), "LED4 off", Toast.LENGTH_SHORT).show();
                            iLedService.ledCtrl(3, 0);
                        }
                        break;
                    // TODO: Veggie sandwich
                }
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    
    
    }
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
        android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity"
        android:orientation="vertical"
        >
    
    
        <Button
            android:id="@+id/BUTTON"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="@string/app_name"
            />
    
        <CheckBox
            android:id="@+id/LED1"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="@string/app_name"
            android:onClick="onCheckboxClicked"
            />
    
        <CheckBox
            android:id="@+id/LED2"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="@string/app_name"
            android:onClick="onCheckboxClicked"
            />
    
        <CheckBox
            android:id="@+id/LED3"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="@string/app_name"
            android:onClick="onCheckboxClicked"
            />
    
        <CheckBox
            android:id="@+id/LED4"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="@string/app_name"
            android:onClick="onCheckboxClicked"
            />
    
    </LinearLayout>
    #Android.mk
    LOCAL_PATH := $(call my-dir)
      include $(CLEAR_VARS)
       
      # Build all java files in the java subdirectory
      LOCAL_SRC_FILES := $(call all-subdir-java-files)
       
      # Name of the APK to build
      LOCAL_PACKAGE_NAME := LEDDemo
       
      # Tell it to build an APK
      include $(BUILD_PACKAGE)
     
     

    android应用如何访问C库

    安卓应用是由java编写的,而在android底层,如安卓底层的硬件驱动程序,都是由C/C++编写的,那应用层java是必须通过某种手段才能使用底层提供的硬件资源。毫无疑问,在java看要调用底层C实现的代码,中间必须要有一层接口,那就是JNI。本例要实现的就是JAVA如何访问底层C库。

    假设JAVA应用需要实现的功能是控制底层硬件LED设备,包括开,关及控制操作。需要实现下述流程:

    1 实现一个JAVA硬件控制类,声明本地的Native方法(操作硬件的接口),并使用static模块加载C的动态库。实现代码如下

    public class HardControl{
        //声明本地的Native方法
        public static native int ledCtrl(int which, int status);
        public static native int ledOpen();
        public static native void ledClose();
    
        //我们要在这个java程序里面加载C库,定义一个static模块
        static {
            //用System方法来加载C库
            try { //添加捕获异常代码的快捷键为:ctrl+alt+T(前提先用鼠标选中需要捕获的代码)
                System.loadLibrary("hardcontrol"); //参数为C库的库名
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    注意,加载C库的方法为System.loadLibrary.

     
     2 JAVA本地native方法与C库接口的关联
     
     
    #include <jni.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    #include <android/log.h> /* liblog */
    
    
    jint ledOpen(JNIEnv *env, jobject cls)
    {
    
        __android_log_print(ANDROID_LOG_DEBUG, "LEDDemo",  "native_ledOpen"); //调用这句就可以打印了。
    
        return 0;
    }
    
    
    void ledClose(JNIEnv *env, jobject cls)
    {
        __android_log_print(ANDROID_LOG_DEBUG, "LEDDemo",  "native_ledClose"); //调用这句就可以打印了。
    
    }
    
    void ledCtrl(JNIEnv *env, jobject cls, jint which, jint status)
    {
        __android_log_print(ANDROID_LOG_DEBUG, "LEDDemo",  "native_ledCtrl:%d %d", which, status); //调用这句就可以打印了。
    
        return 0;
    }
    
    
    static const JNINativeMethod methods[] = {
            {"ledOpen", "()I", (void *)ledOpen}, //() inside empty=> no params, 'I' means: return type is int
            {"ledClose", "()V", (void *)ledClose},//() inside empty=> no params, 'V' means: return type is void
            {"ledCtrl", "(II)I", (void *)ledCtrl}, //()inside two I => means two params, types is int, ouside () is 'I' ==> return type is int
                
    };
    
    
    /* System.LoadLibrary */
    JNI_OnLoad(JavaVM *jvm, void *reserved)
    {
        JNIEnv *env;
        jclass cls;
        if((*jvm)->GetEnv(jvm, (void**)&env, JNI_VERSION_1_4) != JNI_OK) {
            return JNI_ERR;; //jni version not supported
        }
    
        //find the class that realize the local native method: *.java, here is HardControl
        //PS:here need to indification the package & class name.
        // "." --> "/"
        cls = (*env)->FindClass(env, "com/example/zhm/hardlibrary/HardControl");
        if( cls == NULL )
        {
            return JNI_ERR;
        }
    
        //find the class then register their methods .  Java's method <==correspond with ==> C's method
        if((*env)->RegisterNatives(env, cls, methods, sizeof(methods)/sizeof(methods[0])) < 0 ) // 3: means there are 3 funcs in methods.
        {
            
        }
    
    }
    如果某个硬件资源只能被某一个应用使用,可以使用下面的方法访问硬件:JAVA APP--->JNI_OnLoad()加载C库---->将JAVA三个地方法与C库函数进行关联并注册---->调用JAVA本地Native方法就可以访问C库的C接口------>进而访问硬件驱动中的open, read, write,从进访问硬件。但是,以上场景仅限于只有一个APP使用这个硬件资源,如果有多个应用想要使用某个硬件时,如果还按上面方法,必须会造成硬件资源的冲突,所以此时需要有一种框架来解决这个问题。解决方案就是访问硬件资源的程序只能并且只有一个,我们称之为System Server, 其它要访问这个硬件资源的APP必须要给Server发请求,由Server间接的操作硬件,从而实现资源的访问。这个就称之为硬件访问服务。

    关于硬件访问服务需要注意以下几点:
    1 System Server是由JAVA编写的,所以它要想访问硬件,必须要加载JNI的C库(Loadlibrary).
    2 C库的JNI_Onload函数里要注册本地方法,分别调用各个硬件的函数来注册本地方法。比如LED,振动器,有串口。。。等等。。
    3 System Server:
    (1)对每个硬件都要添加服务,add service
     前提需要实现的是:对每个硬件构造service,使用本地Native方法
    (2)对于(1)添加的服务就是向service_manager.c注册,比如serialservice, vibratorservice, ledservice等。如果JAVA应用程序需要使用某些Service的时候,就需要通过这个Service_manager查询及获取相应的Service。

    4 最终APP怎么使用?
    (1)APP使用之前需要获得这个服务getService
    (2)最后就是使用这个服务了。执行Service的方法

    以后修改硬件驱动的时候,把驱动文件放在hal里面,如hal_led.c,有几个好处:
    (1)容易修改
    (2)很多公司不愿意开放其硬件操作,他们只提供so文件,出于保密的目的。试想一下,如果把硬件操作源代码放到JNI文件里,如果要修改,需要编译整个工程,此外,硬件源代码暴露出来了,保密性不好。


    分析一下:
    以上操作涉及到三个进程, 
    1 SystemServer进程:它提供的功能如下:
    ---a: 它向service_manager.c注册服务
    ---b: 加载硬件Service JNI 的C库
    ---C: 接收其它app的硬件操作请求,访问硬件资源
    2 Service_Manager进程:负责硬件资源各种Service的注册添加,以及接怍JAVA应用app的各种service查询请求及资源的获取。
    3 JAVA应用APP进程,它其实是一个客户端,它首先向Service_Manager查询获得某一个Service, 最后,把这个Service发送给SystemServer进程以请求相应的服务,
      而以上三个进程之间的内部通信,主要依靠Android内核的Binder Driver系统进行内部进程间通信。这个Binder并不是linux内核自带的,是google公司对linux内核进行修改添加的一个驱动程序,可实现更加高效的进程间通信。
     
    思考:如何实现一个硬件访问服务。
    1 编写JNI和HAL,以led为例,先编写com_android_server_ledservice.cpp,用于注册JNI本地方法。再编写hal_led.c,里面就是实现open,read,write等硬件访问接口。
    2 修改onload.cpp,调它调用com_android_server_ledservice.cpp内实现的函数,
    3 修改system server.java, 
       new ledservice()
       add ledservice()
    4 编写LEDService.java,用于调用本地方法,实现硬件操作
    5 编写ILEDService.java接口给app使用。
     
    Android硬件抽象层模块开发
     
     
    Android硬件访问服务开发
     

  • 相关阅读:
    vue-router replace 浏览器前进后退记忆栈不记住当前菜单页面
    vue-router active-class 进入当前菜单的样式
    vue-router createWebHashHistory
    浅析如何升级npm及更新npm之后报错(node:15920) ExperimentalWarning: The fs.promises API is experimental 的解决
    浅析nvm介绍、安装与使用以及遇到的问题解决
    npm安装vue-cli报错internal/modules/cjs/loader.js(Error: Cannot find module 'D:Program odejs ode_global ode_modulesvue-cliinvue')
    浅析如何升级vue-cli以及使用npm卸载包时遇到问题:npm ERR! code EEXIST(npm ERR! File exists: D:Program odejs ode_globalvue-list.cmd
    浅析如何实现根据图片自动切换背景色功能:提取图片主题色方案探索
    sync.Pool is much slower than using channel, so why should we use sync.Pool?
    golange benchmark运行
  • 原文地址:https://www.cnblogs.com/CoderTian/p/5759154.html
Copyright © 2020-2023  润新知