1recovery输入事件及处理分析

本文详细解析了基于Android 4.1的Recovery模式下触摸输入的处理流程,包括触摸事件的初始化、处理以及主线程读取触摸消息的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

代码是基于android4.1的。

1recovery输入事件及处理分析

1.1时序图

Recovery <wbr>support <wbr>touch

1.2代码分析

1.2.1 输入事件初始化

Recovery的入口是recovery.cpp中的main函数,当然会根据参数的不同,进入recovery的模式也就不一样,这里我们就不一一介绍了,我们这里主要看图形界面模式,即有个人机交互的见面,用户可以通过按键选择不同的执行操作。

根据上面的时序图中,我们可以看到,在main函数中,需要做一些界面显示、输入事件的初始化工作。而在这里,我们就主要先关注输入事件的初始化工作,即在main函数中调用了ui.cpp的Init()方法,下面看看其代码:

void RecoveryUI::Init() {

    ev_init(input_callback, NULL); //输入事件初始化,并注册回调函数

    pthread_create(&input_t, NULL, input_thread, NULL); //创建新的线程读取输入事件

}

 

在该方法中,主要完成了两个动作,第一就是初始化话输入事件,注册了回调函数,当有输入事件的时候回调,第二就是创建了一个新的线程,用于读取输入事件的数据。

我们先看看events.c中的ev_init()方法,输入事件初始化,代码如下:

int ev_init(ev_callback input_cb, void *data)

  ......

    dir = opendir("/dev/input");//打开文件

    if(dir != 0) {

        while((de = readdir(dir))) {

            unsigned long ev_bits[BITS_TO_LONGS(EV_MAX)];

            if(strncmp(de->d_name,"event",5)) continue;

            fd = openat(dirfd(dir), de->d_name, O_RDONLY); //打开设备节点

            if(fd < 0) continue;

           

            if (ioctl(fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits) < 0) {//获取节点特性

                close(fd);

                continue;

            }

            //判断是否是按键设备

            if (!test_bit(EV_KEY, ev_bits) && !test_bit(EV_REL, ev_bits) ) {

                close(fd);

                continue;

            }

            //保存设备的信息

            ev_fds[ev_count].fd = fd;

            ev_fds[ev_count].events = POLLIN;

            ev_fdinfo[ev_count].cb = input_cb;

            ev_fdinfo[ev_count].data = data;

            ev_count++;

            ev_dev_count++;

            if(ev_dev_count == MAX_DEVICES) break;

        }

    }

    return 0;

}

#define test_bit(bit, array) \

    ((array)[(bit)/BITS_PER_LONG] & (1 << ((bit) % BITS_PER_LONG)))

代码看起来还是非常简单的,输入设备的设备节点都在/dev/input/目录下,所以需要扫面下面所有的设备节点。

调用openat()打开设备节点,这是linux的系统调用,这里就不说了。

调用ioctl的,并用宏EVIOCGBIT产生参数,获取一个设备的特性,这里特性会说明该设备是按键设备还是触摸设备等,并将特性存在中数组ev_bits中。

定义了test_bit的宏,用于判断ev_bits中的特性是否是我们想要的,在linux input系统中,EV_KEY指的是按键设备,EV_REL值相对坐标,如光标移动。看到这里了,如果我们想要接受触摸消息,那么我将在这里添加EV_ABS的支持。后续会说明添加具体方法。

当打开的设备是我们想要监听的设备的时候,我们将设备节点的文件描述符等信息添加到ev_fds结构体数组中,还有将注册的回调函数input_cb添加到结构体数据ev_fdinfo中。

到此就完成了输入事件的初始化,接下来就看在创建的新线程中读取输入事件。

1.2.2 创建线程读取输入事件

在前面的RecoveryUI::Init() 方法中,我们看到了这么一句:

 pthread_create(&input_t, NULL, input_thread, NULL); //创建新的线程读取输入事件

调用了pthread_create()方法创建新的线程,该方法的原型如下:

int pthread_create(pthread_t*restrict tidp,const pthread_attr_t *restrict_attr,void*(*start_rtn)(void*),void *restrict arg);

若成功则返回0,否则返回出错编号  

返回成功时,由tidp指向的内存单元被设置为新创建线程的线程ID。attr参数用于制定各种不同的线程属性。新创建的线程从start_rtn函数的地址开始运行,该函数只有一个万能指针参数arg,如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg的参数传入。

linux下用C开发多线程程序,Linux系统下的多线程遵循POSIX线程接口,称为pthread。  由 restrict 修饰的指针是最初唯一对指针所指向的对象进行存取的方法,仅当第二个指针基于第一个时,才能对对象进行存取。对对象的存取都限定于基于由 restrict 修饰的指针表达式中。 由 restrict 修饰的指针主要用于函数形参,或指向由 malloc() 分配的内存空间。restrict 数据类型不改变程序的语义。 编译器能通过作出 restrict 修饰的指针是存取对象的唯一方法的假设,更好地优化某些类型的例程。下面看四个参数:

第一个参数为指向线程标识符指针。  

第二个参数用来设置线程属性。  

第三个参数是线程运行函数的起始地址。  

第四个参数是运行函数的参数。  

另外,在编译时注意加上-lpthread参数,以调用链接库。因为pthread并非Linux系统的默认库。

在我们这里用到的代码中,第一个参数的定义为:pthread_t input_t;为指向线程标识符指针。接下来我们主要看新线程的入口函数input_thread()方法,代码如下:

void* RecoveryUI::input_thread(void *cookie)

{

    for (;;) {

        if (!ev_wait(-1)) //查看是否有输入事件

            ev_dispatch();//有输入事件,那么将派发输入事件

    }

    return NULL;

}

在一个循环里面不断的查询是否有输入事件,那么,我们看看events.c中的ev_wait()方法,代码如下:

int ev_wait(int timeout)

{

    int r;

    r = poll(ev_fds, ev_count, timeout);

    if (r <= 0)

        return -1;

    return 0;

}

还记得在输入事件初始化的时候,将按键等我们想监听的设备点信息添加到了ev_fds结构体中,在这里我们将用到了。

系统调用poll()方法,如果ev_fds包含的设备节点中有消息,那么返回的值r将大于0,所以ev_wait()方法的返回值为0。再回头看看input_thread()方法,当ev_wait()返回值等于零的时候,将调用ev_dispatch()派发输入消息。那么我们看看events.c中的该方法代码:

void ev_dispatch(void)

{

    unsigned n;

    int ret;

    for (n = 0; n < ev_count; n++) {

        ev_callback cb = ev_fdinfo[n].cb;

        if (cb && (ev_fds[n].revents & ev_fds[n].events))

            cb(ev_fds[n].fd, ev_fds[n].revents, ev_fdinfo[n].data);//回调

    }

}

找到ev_fdinfo结构体数组中之前注册的回调函数,实际是调用了ui.cpp中的RecoveryUI::input_callback()方法,下面看看其代码:

int RecoveryUI::input_callback(int fd, short revents, void* data)

{

    struct input_event ev;

    int ret;

    ret = ev_get_input(fd, revents, &ev);//读取输入消息

    if (ret)

        return -1;

    if (ev.type == EV_SYN) {

        return 0;

} else if (ev.type == EV_REL) {

 ......

    }

    if (ev.type == EV_KEY && ev.code <= KEY_MAX)//按键消息

        self->process_key(ev.code, ev.value);//处理按键消息

 

    return 0;

}

在之前的调用用,只是通知有输入消息,那么在回调函数中,我们就需要去读取输入消息了,定义了结构体input_event的变量ev,调用了events.c的ev_get_input()方法,看下其代码:

int ev_get_input(int fd, short revents, struct input_event *ev)

{

    int r;

    if (revents & POLLIN) {

        r = read(fd, ev, sizeof(*ev));//读取input消息,数据存在ev变量中

        if (r == sizeof(*ev))

            return 0;

    }

    return -1;

}

 

在该方法中,非常简单,系统调用read()方法去读fd指定的设备节点的数据,并保存在ev变量中。

我们回到input_callback()方法中看,调用ev_get_input()后,读取到的input数据存储在ev结构体中,我们看看其结构体的定义:

struct input_event {

       struct timeval time;//时间

       __u16 type;  //事件的类型,可以确定出是按键消息还是触摸消息,或者其他.

       __u16 code;  //数据的类型,假如是触摸消息,那么code可以确定消息是x坐标还y坐标或者其他

       __s32 value; //数据的值

};

在input_callback()方法中,我们看到了调用ui.cpp的RecoveryUI::process_key()方法处理按键消息。

1.2.3 处理按键消息

在上一小节中,讲到了在ui.cpp的RecoveryUI::process_key()方法中处理按键消息,下面我们看看其代码:

void RecoveryUI::process_key(int key_code, int updown) {

    bool register_key = false;

    if (updown) {

} else {

      register_key = true;//表示是按键的up消息

    }

    if (register_key) {

        switch (CheckKey(key_code)) {

          case RecoveryUI::IGNORE:

            break;

          case RecoveryUI::TOGGLE:

            ShowText(!IsTextVisible());

            break;

 

          case RecoveryUI::REBOOT:

            android_reboot(ANDROID_RB_RESTART, 0, 0);

            break;

          case RecoveryUI::ENQUEUE:

            pthread_mutex_lock(&key_queue_mutex);//互斥锁

            const int queue_max = sizeof(key_queue) / sizeof(key_queue[0]);

            if (key_queue_len < queue_max) {

                key_queue[key_queue_len++] = key_code; //将按键号记录下来

                pthread_cond_signal(&key_queue_cond);//唤醒阻塞线程开始读数据

            }

            pthread_mutex_unlock(&key_queue_mutex);

            break;

        }

    }

}

在代码中,我们可以看到,这里指处理按键的up消息,所以我们只需要关注switch语句中的RecoveryUI::ENQUEUE:处理。

其实传进来的参数 key_code就是按键号,在这里的处理,就是把它存入key_queue[]数组中。但是我们知道,process_key()方法是在新开的线程中被调用的,当然还需要将消息传送到主线程中去,所以,这里就需要用到了互斥锁和线程阻塞和唤醒。

在主线程初始化的时候,就初始化了互斥锁和线程阻塞唤醒的条件变量,代码如下:

 pthread_mutex_t key_queue_mutex;

 pthread_cond_t key_queue_cond;

 pthread_mutex_init(&key_queue_mutex, NULL);//初始化互斥锁

 pthread_cond_init(&key_queue_cond, NULL);//初始化条件变量

我对互斥锁的理解是被锁的代码保证当前只有一个调用着,这样可以完成代码的同步操作。

我们在设计的时候,当没有输入消息的时候,主线程会进入阻塞状态,当有输入消息的时候,将会唤醒主线程。

所以,在上面代码中,将对数组key_queue[]的赋值放到互斥锁里面,然后调用pthread_cond_signal(&key_queue_cond)唤醒主线程开始读取按键消息。

1.2.4 主线程读取输入消息

     在这节介绍之前,先来学习两个知识点吧,互斥锁线程阻塞唤醒

     为了跟上一节的输入消息处理衔接上,我们这里先看ui.ccp的RecoveryUI::WaitKey()方法,因为该方法直接读取到了key_queue[]数组中的数据,其代码如下:

int RecoveryUI::WaitKey()

{

    pthread_mutex_lock(&key_queue_mutex);//互斥锁

    do {

        struct timeval now;

        struct timespec timeout;

        gettimeofday(&now, NULL);

        timeout.tv_sec = now.tv_sec;

        timeout.tv_nsec = now.tv_usec * 1000;

        timeout.tv_sec += UI_WAIT_KEY_TIMEOUT_SEC;

        int rc = 0;

        while (key_queue_len == 0 && rc != ETIMEDOUT) {

            rc = pthread_cond_timedwait(&key_queue_cond, &key_queue_mutex,

                                        &timeout);//主线程阻塞了

        }

    } while (usb_connected() && key_queue_len == 0);

 

    int key = -1;

    if (key_queue_len > 0) {

        key = key_queue[0];//获取数组里面第一个值

        memcpy(&key_queue[0], &key_queue[1], sizeof(int) * --key_queue_len);//数据移动

    }

    pthread_mutex_unlock(&key_queue_mutex);

    return key;

}

这里的整个函数代码都放在互斥锁里面,是在主线程中被调用的。我们知道当没有输入消息的时候,key_queue_len=0,所以将在中 pthread_cond_timedwait()阻塞。对于这个阻塞,解除阻塞有两种方法,第一种当然是在有输入事件的时候,调用pthread_cond_signal(...)解除阻塞,第二种的timeout时间到了,会解除阻塞唤醒主线程。

所以,当有输入事件的时候,主线程被唤醒,然后将key_queue[]数组中的第一个元素赋值给key变量,然后返回。这里还有一个对key_queue[]的操作,当取出数组第一个元素的时候,将后面的元素通过指针的方式,移动到前面一位。

好了,这时候应该知道了主线程如何获取到案件消息了。下面我们将从recovery.cpp的main函数开始,讲解主线程得得到按键消息是怎么处理的。在main方法中,进入人机交互界面调用的方法是prompt_and_wait(),其代码如下:

static void

prompt_and_wait(Device* device) {

    const char* const* headers = prepend_title(device->GetMenuHeaders());

    for (;;) {

        finish_recovery(NULL);

        //等待用户线程菜单

        int chosen_item = get_menu_selection(headers, device->GetMenuItems(), 0, 0, device);

        chosen_item = device->InvokeMenuItem(chosen_item);

        int status;

        int wipe_cache;

        switch (chosen_item) {

            case Device::REBOOT://重启

                return;

            case Device::WIPE_DATA://格式化data分区

                ......

                break;

            case Device::WIPE_CACHE://格式化cache分区

                ......

                break;

            case Device::APPLY_EXT://从SD卡中选择升级包升级

                .......

                break;

            case Device::APPLY_CACHE://从cache分区中选择升级包升级

                ......

                break;

 

            case Device::APPLY_ADB_SIDELOAD://通过adb进行升级

                ......

                break;

        }

    }

}

在该方法中,有一个for的无线循环,在该循环中,调用了get_menu_selection()方法,这个方法是核心,它会经过一步步调用,最终调用到RecoveryUI::WaitKey()方法获取到按键事件,然后做出相应的处理,并返回体现用户选择的chosen_item变量,再经过InvokeMenuItem()处理,得到用户真正的操作,下面的switch方法中,就是对用户选择做出相应的动作。

那么,在这里,我们就主要关注get_menu_selection()方法,里面有我们想要的东西,其代码如下:

static int

get_menu_selection(const char* const * headers, const char* const * items,

                   int menu_only, int initial_selection, Device* device) {

    ui->StartMenu(headers, items, initial_selection);//显示在屏幕上的菜单

    int selected = initial_selection;

    int chosen_item = -1;

    while (chosen_item < 0) {

        int key = ui->WaitKey();//看到了这个苦等的方法,获取按键号

        int visible = ui->IsTextVisible();

        int action = device->HandleMenuKey(key, visible);//处理按键消息

        if (action < 0) {

            switch (action) {

                case Device::kHighlightUp:;//光标上移

                    --selected;

                    selected = ui->SelectMenu(selected)

                    break;

                case Device::kHighlightDown://光标下移

                    ++selected;

                    selected = ui->SelectMenu(selected);

                    break;

                case Device::kInvokeItem://选择了当前的菜单

                                   chosen_item = selected;

                    break;

                case Device::kNoAction:

                    break;

            }

        } else if (!menu_only) {

            chosen_item = action;

        }

    }

    ui->EndMenu();

    return chosen_item;

}

这里首先需要调用 ui->StartMenu(),在屏幕上显示菜单对话框,接着在一个while()循环中等待用户按键消息。这里我们看到了调用ui->WaitKey()方法,其实就是我们前面介绍的,读取按键消息,返回的是按键号,然后根据按键号,调用HandleMenuKey()方法处理,得到用户的意图,有上下移动光标,选择当前菜单进入。

好了,有了上面的介绍,那么现在我们开始加入触摸的支持了。

2 添加touch支持

有了上面的介绍,加入触摸的支持应该就不是什么难事了的。说起来应该有三个步骤:

第一,应该添加触摸消息的输入。

第二,处理触摸消息,将触摸消息处理成上、下移动、点击三种事件。

第三,添加主线程读取触摸消息及处理。

当然,还必须保证在recovery模式中,触摸驱动的加载。按照上面定的三个步骤,我们一步步来实现。

2.1添加触摸消息的输入

     这个非常简单,只需要在events.c中的ev_init()方法中添加EV_ABS的支持即可,代码如下:

int ev_init(ev_callback input_cb, void *data)

{

       ......

           

            if (!test_bit(EV_KEY, ev_bits) && !test_bit(EV_REL, ev_bits) &&!test_bit(EV_ABS,ev_bits)) {

                close(fd);

                continue;

            }

          ......

}

添加了这么一句,就可以将触摸事件的设备节点添加到了ev_fds[]结构体的数组中了。

2.2 处理触摸消息

在处理触摸消息之前,我给大家一个打印信息,是对一次触摸消息的打印:

printf,type=3,code=57,value=0     //触摸屏幕的手指id号,0表示第一个手指

printf,type=3,code=48,value=200   //表示触摸的工具的接触面为200

printf,type=3,code=53,value=549   //x坐标

printf,type=3,code=54,value=596   //y坐标

printf,type=3,code=50,value=1     //可以不关注

 

printf,type=0,code=2,value=0      //上报了一次触摸消息完毕

printf,type=0,code=0,value=0

 

printf,type=3,code=57,value=0

printf,type=3,code=48,value=200

printf,type=3,code=53,value=549

printf,type=3,code=54,value=596

printf,type=3,code=50,value=1

 

printf,type=0,code=2,value=0

printf,type=0,code=0,value=0

 

printf,type=3,code=48,value=0      //手指离开了触摸面

printf,type=0,code=0,value=0

我触摸了屏幕立即抽开,就有了上面的打印。在上面我们可以看到,一条打印代表一条输入消息,需要5条消息才能描述一个触摸面,上面描述了两个触摸面,两次的坐标是一样的。input_event,type=3,说明是触摸消息。下面看看input_event.code代表的意思:

#define  ABS_MT_POSITION_X  0x35  //表示x坐标

#define  ABS_MT_POSITION_Y  0x36   //表示y坐标

#define  ABS_MT_TOUCH_MAJOR  0x30  //接触面的长轴。

#define  ABS_MT_WIDTH_MAJOR  0x32  //接触工具的长轴

#define  ABS_MT_TRACKING_ID   0x39  //表示当前多触摸手指分配的ID号

好了,有了上面的一点点介绍,就可以对其处理了。

需要在回到函数中添加处理触摸事件的方法,代码如下:

int RecoveryUI::input_callback(int fd, short revents, void* data)

{

    struct input_event ev;

    int ret;

    ret = ev_get_input(fd, revents, &ev);

    ......

       if(self->touch_handle_input(ev)) //处理触摸消息

              return 0;

    ......

    if (ev.type == EV_KEY && ev.code <= KEY_MAX)

        self->process_key(ev.code, ev.value);

 

    return 0;

}

在介绍touch_handle_input()方法方法之前,先看看我定义的几个变量及方法,在ui.h中添加如下:

struct TouchEvent{

          int x;

                int y;

       }mTouchEvent[5],lastEvent,firstEvent;

       int touch_id,move_pile;

定义了结构体TouchEvent用来存储触摸事件的x、y轴坐标,因为触摸屏支持5个手指触摸,所以mTouchEvent[5]数组分别存储5个手指的坐标,lastEvent表示最新的一个触摸坐标,firstEvent表示触摸按下的第一个坐标。

我自己定义了touch_handle_input()方法,处理触摸消息,其定义在ui.cpp中,代码如下:

int RecoveryUI::touch_handle_input(input_event ev){

       if(ev.type==EV_ABS){

              int touch_code = 0;

        switch(ev.code){

           case ABS_MT_TRACKING_ID:

                    touch_id = ev.value;

                     break;

                 case ABS_MT_TOUCH_MAJOR:

                     if(ev.value==0){//所有手指离开触摸面

                           if((firstEvent.y==lastEvent.y)){

                              int *sreenPara=self->GetScreenPara();//获取当前屏幕显示数据

                                   int select =mTouchEvent[0].y/sreenPara[2]-sreenPara[0];

                                   if(select>=0&&select<sreenPara[1]){

                                          menu_select = select;//记录点击的菜单列编号

                                          touch_code = Device::kInvokeItem;//点击动作

                                   }

                            }

                            for(int i=0;i<5;i++){

                                   mTouchEvent[i].x = 0;

                                   mTouchEvent[i].y = 0;

                            }

                            lastEvent.x=lastEvent.y=0;

                            firstEvent.x=firstEvent.y=0;

                           

                       }

                     break;

                 case ABS_MT_WIDTH_MAJOR:

                    break;

                 case ABS_MT_POSITION_X: //记录x坐标

                     lastEvent.x = ev.value;

                       if(mTouchEvent[touch_id].x == 0){

                            mTouchEvent[touch_id].x = ev.value;

                           }

                       if(firstEvent.x==0&&touch_id==0){

                 firstEvent.x = ev.value;

                       }

 

                    break;

                 case ABS_MT_POSITION_Y: //记录y坐标

              if((ev.value-lastEvent.y)*move_pile<0){

                    move_pile = 0;

                                   key_queue_len = 0;

                       }else if(lastEvent.y!=0){

                           move_pile += ev.value-lastEvent.y;

                       }

                     lastEvent.y = ev.value;

              if(firstEvent.y==0&&touch_id==0){

                 firstEvent.y = ev.value;

                       }

                     if(mTouchEvent[touch_id].y == 0){

                            mTouchEvent[touch_id].y = ev.value;

                           }else if((ev.value-mTouchEvent[touch_id].y)>20){

                               touch_code = Device::kHighlightDown;//向下移动

                               mTouchEvent[touch_id].y = ev.value;

                               mTouchEvent[touch_id].x = lastEvent.x;

                           }else if((ev.value-mTouchEvent[touch_id].y)<-20){

                               touch_code = Device::kHighlightUp;//向上移动

                               mTouchEvent[touch_id].y = ev.value;

                               mTouchEvent[touch_id].x = lastEvent.x;

                           }

                       break;

                 default :

                            break;

              }

              pthread_mutex_lock(&key_queue_mutex);

              const int queue_max = sizeof(key_queue) / sizeof(key_queue[0]);

        if (key_queue_len < queue_max&&touch_code!=0) {

            key_queue[key_queue_len++] = touch_code;//往数组中添加数据

                     pthread_cond_signal(&key_queue_cond);  //唤醒主线程猪肚数据    

        }

              pthread_mutex_unlock(&key_queue_mutex);

       return 1;   

       }else if(ev.type == EV_SYN){

         touch_id = -1;

         return 0;

       }

       return 0;

}

其实代码量也没多少的。触摸消息是一条条传过来的,而且需要5条消息才能描述一个触摸面,所以需要定义一些变量来存储消息。

对于滑动消息,我是根据一次滑动的距离为20个坐标点的时候,认为是一次up、或down动作,而根据第一次触摸的坐标和离开的时候的坐标相等的时候,认为is一次点击动作。

我们看下手指离开触摸屏的处理,当手指离开的时候,如果firstEvent.y==lastEvent.y,那么然为是一个点击的动作,则需要作出响应。GetScreenPara()方法是在screen_ui.cpp中定义的,代码如下:

int* ScreenRecoveryUI::GetScreenPara()

{

  int *ScreenPara = new int[3];

  ScreenPara[0] = menu_top;  //菜单的头信息的列数

  ScreenPara[1] = menu_items; //用户可以选择的菜单列数

  ScreenPara[2] = CHAR_HEIGHT; //每列菜单占的高度

  return ScreenPara;

}

其实这个方法就是获取了当前显示菜单的布局数据,通过这三个数据,我就可以计算出每列菜单所在的坐标范围,举个列子,用户可以选择的第2个菜单的坐标范围应该是从

(menu_top+1)*CHAR_HEIGHT到(menu_top+2)*CHAR_HEIGHT之间。有了这些数据,我就可以根据点击的y坐标,计算出用户点击的是哪一列菜单了。

好了,触摸处理的三个动作,我存储在一个变量touch_code中,触摸消息与按键消息公用一个数据数组key_queue[],所以为了避免与按键消息的冲突,我将触摸消息处理后定义了负数,存在touch_code变量中。描述三个动作的三个变量,是在Device中原来已经定义好了的:

Device::kInvokeItem = -4  //选中

Device::kHighlightDown = -3 //向下移动

Device::kHighlightUp = -2 //向上移动

    与按键消息类似,将消息存在key_queue[]数组中,主线程还是需要通过WaitKey()获取。下面看主线程的处理。

2.3 主线程处理触摸消息

     主线程触摸消息的处理,在get_menu_selection()方法中,该后的代码如下:

static int

get_menu_selection(const char* const * headers, const char* const * items,

                   int menu_only, int initial_selection, Device* device) {

    ui->StartMenu(headers, items, initial_selection);

    int selected = initial_selection;

    int chosen_item = -1;

    while (chosen_item < 0) {

        int key = ui->WaitKey();//读取输入消息

        int visible = ui->IsTextVisible();

        int action ;

        if (key == -1) {   // ui_wait_key() timed out

            if (ui->WasTextEverVisible()) {

                continue;

            } else {

                LOGI("timed out waiting for key input; rebooting.\n");

                ui->EndMenu();

                return 0; // XXX fixme

            }

        }else if(key<=Device::kHighlightUp&&key>=Device::kInvokeItem){//触摸消息

            action = key;

        }else {

            action = device->HandleMenuKey(key, visible);//按键消息

       

        if (action < 0) {

            switch (action) {

                case Device::kHighlightUp:

                    --selected;

                    selected = ui->SelectMenu(selected);

                    break;

                case Device::kHighlightDown:

                    ++selected;

                    selected = ui->SelectMenu(selected);

                    break;

                case Device::kInvokeItem:

                                   if(ui->menu_select!=-1){//触摸消息

                                          chosen_item = ui->menu_select;

                                          ui->menu_select = -1;

                                          }else{

                                          chosen_item = selected;

                                          }

                    break;

                case Device::kNoAction:

                    break;

            }

        } else if (!menu_only) {

            chosen_item = action;

        }

    }

    ui->EndMenu();

    return chosen_item;

}

    ui->WaitKey()读取的输入消息存在key变量中,key=-1,说明是主线程阻塞超时,不需要任何操作。当key是为-2、-3、-4的任意一个值时,说明是触摸事件。那么就可以做处理了。

 

<think>好的,我现在需要解决用户关于CiteSpace在可视化分析时出现错误并提示“recovery”的问题。首先,我需要回忆一下常见的CiteSpace报错情况和解决方案。根据用户提供的引用内容,特别是引用[1]和[2],可能涉及到软件安装或数据处理的问题。 用户提到错误提示是“recovery”,这可能与数据恢复或文件损坏有关。我应该先考虑数据文件的问题,比如输入的文献数据格式是否正确,是否有缺失或损坏的文件。另外,软件本身的配置也可能导致此类错误,比如Java环境的问题,因为CiteSpace是基于Java开发的。 接下来,我需要分步骤思考可能的解决方案: 1. **检查数据文件完整性**:用户可能在导入数据时使用了不兼容或损坏的文件,例如从知网导出的专利文献数据格式不正确。需要确认文件是否为正确的格式(如CSV或TXT),并且数据完整。 2. **清理缓存和临时文件**:CiteSpace在运行过程中会生成临时文件,这些文件如果损坏可能导致错误。清理缓存可能解决问题。 3. **更新Java环境**:CiteSpace依赖Java,如果Java版本过旧或配置错误,可能导致运行时错误。建议用户检查Java版本并更新到最新。 4. **调整内存分配**:处理大型数据集时,如果内存不足,可能导致程序崩溃并提示恢复错误。增加CiteSpace可用的内存参数可能有效。 5. **重新安装CiteSpace**:如果软件本身文件损坏,重新安装可能修复问题,特别是从可信来源如引用[1]中的链接下载最新版本。 6. **查看日志文件**:CiteSpace通常会生成日志文件,查看这些日志可以获取更具体的错误信息,帮助定位问题。 在整理这些步骤时,需要确保每个解决方案都清晰,并引用相关参考资料。例如,引用[1]提到安装和下载,可能涉及正确的安装步骤;引用[2]涉及数据处理,可能与数据格式有关。 此外,用户可能需要生成相关问题,比如关于数据预处理、软件配置或其他常见错误的解决方法。这些问题应该与当前问题相关,帮助用户进一步了解可能涉及的方面。 最后,要确保回答结构清晰,步骤详细,并且语言符合中文习惯,避免使用专业术语过多,必要时进行解释。同时,检查是否有遗漏的解决方案,确保覆盖常见的可能性。</think>针对CiteSpace在可视化分析时出现报错并提示“recovery”的问题,以下是分步解决方案: --- ### **1. 检查数据文件完整性** - **问题原因**:输入的数据文件(如知网导出的专利文献数据)可能格式错误、缺失关键字段或包含非法字符。 - **解决方案**: 1. 确保数据文件为CiteSpace支持的格式(如`CNKI_ref_*.txt`或`WoS_*.txt`),字段分隔符统一[^2]。 2. 用文本编辑器打开文件,检查是否有乱码或异常符号(如未闭合的引号)。 3. 重新从知网导出数据,选择“UTF-8”编码格式以避免兼容性问题。 --- ### **2. 清理缓存和临时文件** - **问题原因**:CiteSpace生成的临时文件(如`project`文件夹中的缓存)损坏可能导致崩溃。 - **解决方案**: 1. 关闭CiteSpace,删除项目文件夹中的`data`、`output`等子目录。 2 手动清理系统临时文件夹(Windows路径:`C:\Users\用户名\AppData\Local\Temp`)。 --- ### **3. 更新Java环境** - **问题原因**:CiteSpace基于Java开发,版本不匹配或配置错误可能引发“recovery”错误。 - **解决方案**: 1. 检查Java版本:命令行输入`java -version`,确保版本≥Java 8。 2. 若版本过低,前往[Oracle官网](https://www.oracle.com/java/)下载最新JDK。 3. 配置环境变量:将Java安装路径添加到系统变量`PATH`中。 --- ### **4. 调整内存分配** - **问题原因**:处理大规模数据时内存不足会导致进程崩溃。 - **解决方案**: 1. 编辑CiteSpace启动文件(如`CiteSpace.vmoptions`),增加内存参数: ```text -Xmx4G # 分配4GB内存(根据实际内存调整) ``` 2. 若使用Windows,右键快捷方式→属性→在目标路径末尾添加`-Xmx4G`。 --- ### **5. 重新安装CiteSpace** - **问题原因**:软件核心文件损坏或安装不完整。 - **解决方案**: 1. 从[官方推荐渠道](http://t.csdnimg.cn/Yd1I5)下载最新版本[^1]。 2. 彻底卸载旧版本(删除安装目录及用户目录下的`.citespace`文件夹)。 3. 重新安装时关闭杀毒软件,避免误删关键组件。 --- ### **6. 查看日志定位具体错误** - **操作步骤**: 1. 打开CiteSpace日志文件(路径:`安装目录/logs/citespace.log`)。 2. 搜索关键词`ERROR`或`Recovery`,根据日志提示的代码行或数据位置针对性修复。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值