android_app c++框架
找遍了全网,没有一个完整的可用的框架。ndk自带的android_native_app_glue确实不太好用,闭关几天,写出了一个框架。完全的消息队列调用,目前测试的主体框架是没有什么问题了,程序入口还是android_main。调用过程:
void android_main(android_app* app)
{
//APP_LOG("main : 程序启动");
//设置消息回调函数,就一个,整体和win32编程差不多了。
app->onEvent = on_app_event; //循环等待事情以进行处理。
while (app->running){//app处于运行状态
app->process_message();//处理消息主函数
if(!app->pause()){
//draw_frame();//绘制我们的游戏,相当于MainLoop事件
}
else{
//APP_LOG("PAUSE");
}
}
//APP_LOG("main : 程序结束");
}
消息事件结构:
#pragma pack(push, 1)
struct APP_EVENT
{
int id;
union{
void* data;
struct{//input motion
int16_t x, y, z, w;
};
struct{//input_keyboard
int32_t key;
int32_t flag;
};
struct{//ARect
int16_t left, top, right, bottom;
};
};
};
#pragma pack(pop)
主消息处理函数,完全防win32编程设计,当然,事件处理做的还不够全面。现在只是初期框架,但这个框架已经比glue那个清晰多了。
主消息回调函数:
void on_app_event(android_app* app, APP_EVENT& event)
{
switch(event.id)
{
case APP_START://activity启动
break;
case APP_STOP://停止
break;
case APP_RESUME://返回
break;
case APP_PAUSE://暂停
break;
case APP_DESTROY://销毁退出
break;
case APP_SAVE://保存状态
break;
case APP_WINDOW_CREATE://窗口创建,app->window有效
break;
case APP_WINDOW_CLOSE://窗口销毁
break;
case APP_WINDOW_ACTIVATE://窗口获得焦点
break;
case APP_WINDOW_DEACTIVATE://窗口失去焦点
break;
case APP_WINDOW_RESIZE://窗口大小改变
break; case APP_TOUCH_DOWN://触摸按下
//这三个事件,都使用event.x, event.y作为坐标
//event.z代表pointerid
break;
case APP_TOUCH_MOVE://触摸滑动
break;
case APP_TOUCH_UP:
break; default:
//APP_LOG("unknow msg:%d", cmd);
break;
}
}
ndk的activity入口函数其实是:
void ANativeActivity_onCreate(ANativeActivity* activity, void* savedState, size_t savedStateSize);
这个函数由主进程调用,主进程创建一个activity,通知native库创建环境并处理,如果这个函数不返回,app就会卡死无响应。所以我们native库的过程要在一个新的线程里面处理。直接使用主线程的回调也不是不可以,但无法处理MainLoop这个事件,这个事件主线程没给。如果新建一个线程,如何处理主线程回调的事件,就是一个根本的问题。glue库用了pipe,并大把大把的mutex lock处理。下面这个库,把lock基本都去掉了,不再使用ALooper进行消息处理。主线程和线程之间的通信还是用的pipe,因为简单易用。
方式很简单:(执行标准)
一:所有ANativeActivity_onCreate里面指定的回调函数,执行过程都在主线程里面,这个我们要打包。用APP_EVENT把事件封装,通过pipe发送到我们线程的事件队列,不对android_app内部数据进行操作。
二:android_main()过程里面,循环执行process_message(),进行事件处理。process_message()的执行过程就是,从pipe里面读取打包的APP_EVENT,并进行预处理、回调执行、后续处理。
三:所有事件方法,过程尽量保证清晰,可读性高。
目前库封装了部分输入交互操作,后续会继续封装。也欢迎大家评论,有用的着的尽可拿去用,public domain协议!
#ifndef ANDROID_APP_HPP
#define ANDROID_APP_HPP #include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <pthread.h>
#include <sched.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/resource.h> #include<sys/ioctl.h> #include <jni.h> #include <android/configuration.h>
#include <android/looper.h>
#include <android/native_activity.h>
#include <android/log.h> #include "pipefile.hpp"//封装的pipe操作,之后会发表 #include <cgl/public.h>//#define null NULL
#include <cgl/thread/mutex.hpp>//封装的锁操作,之后发表 using namespace cgl; #define ANDROID_APP_LOG(...) ((void)__android_log_print(ANDROID_LOG_INFO, "android_app", __VA_ARGS__))
#define ANDROID_APP_ERROR(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "android_app", __VA_ARGS__)) #define ANDROID_APP_DEBUG//debug 模式 #ifdef ANDROID_APP_DEBUG
#define ANDROID_APP_LOGV(...) ((void)__android_log_print(ANDROID_LOG_VERBOSE, "android_app", __VA_ARGS__))
#else
#define ANDROID_APP_LOGV(...) ((void)0)
#endif //android app events
enum ANDROID_APP_EVENT
{
APP_START,
APP_STOP,
APP_RESUME,
APP_PAUSE,
APP_DESTROY,
APP_SAVE,
APP_CONFIG,
APP_LOW_MEMORY, APP_WINDOW_CREATE, //set app.window
APP_WINDOW_CLOSE, //reset app.window
APP_WINDOW_ACTIVATE,
APP_WINDOW_DEACTIVATE,
APP_WINDOW_RESIZE,
APP_WINDOW_RECT_CHANGED,
APP_WINDOW_PAINT, APP_INPUT, //set app.inputQueue
APP_TOUCH_DOWN,
APP_TOUCH_UP,
APP_TOUCH_MOVE, APP_KEY_UP,
APP_KEY_DOWN,
APP_KEY_PRESS
}; #pragma pack(push, 1)
struct APP_EVENT
{
int id;
union{
void* data;
struct{//input motion
int16_t x, y, z, w;
};
struct{//input_keyboard
int32_t key;
int32_t flag;
};
struct{//ARect
int16_t left, top, right, bottom;
};
};
};
#pragma pack(pop) class android_app; extern void android_main(android_app* app); void android_app_post_event(android_app* app, const APP_EVENT& event);
void android_app_post_event(android_app* app, int id); class android_app
{
public:
ANativeActivity* activity;
AConfiguration* config;
ALooper* looper;
ANativeWindow* window; void* savedState;
size_t savedStateSize;
int stateSaved; void* userData; void (*onEvent)(android_app* app, APP_EVENT& event); int activityState;
bool running;
public:
pthread_t thread;
mutex_t mutex;
cond_t cond;
public:
io::pipefile message;
AInputQueue* inputQueue;
int destroyed;
public:
android_app() : activity(null), config(null), looper(null), window(null),
savedState(null), savedStateSize(), stateSaved(),
userData(null),
onEvent(null),
activityState(),
thread(), mutex(), cond(),
message(),
inputQueue(null),
destroyed()
{
running = false;
} void dispose(); bool pause()const
{
return activityState == APP_PAUSE;// || activityState == APP_STOP;
} int process_message()
{
APP_EVENT event;
int bytes;
ioctl(message.in(), FIONREAD, &bytes);
while(bytes){
if(this->read_event(event)){
//ANDROID_APP_LOGV("process message %d - %p", event.id, event.data);
this->process_begin(event);
if(this->onEvent){
this->onEvent(this, event);
}
this->process_end(event);
}
ioctl(message.in(), FIONREAD, &bytes);
}
ALooper_pollAll(, NULL, NULL, NULL);
//如果传感器有数据,立即处理。ident == USER
//ANDROID_APP_LOG("pollAll %d", ident);
return ;
} public://log
void print_config();
public:
void cond_wait()
{
cond.wait(mutex);
} public:
static int on_input(int fd, int events, void* data);
int on_input_event(AInputEvent* event);
private:
bool read_event(APP_EVENT& event);
void process_begin(APP_EVENT& event);
void process_end(APP_EVENT& event); int on_input_motion(AInputEvent* event);
int on_input_keyboard(AInputEvent* event);
}; void free_saved_state(android_app* app)
{
//auto_lock lock(app->mutex);
if (app->savedState != NULL) {
free(app->savedState);
app->savedState = NULL;
app->savedStateSize = ;
}
} void android_app::print_config()
{
char lang[], country[];
AConfiguration_getLanguage(config, lang);
AConfiguration_getCountry(config, country); ANDROID_APP_LOGV("Config: mcc=%d mnc=%d lang=%c%c cnt=%c%c orien=%d touch=%d dens=%d "
"keys=%d nav=%d keysHid=%d navHid=%d sdk=%d size=%d long=%d "
"modetype=%d modenight=%d",
AConfiguration_getMcc(config),
AConfiguration_getMnc(config),
lang[], lang[], country[], country[],
AConfiguration_getOrientation(config),
AConfiguration_getTouchscreen(config),
AConfiguration_getDensity(config),
AConfiguration_getKeyboard(config),
AConfiguration_getNavigation(config),
AConfiguration_getKeysHidden(config),
AConfiguration_getNavHidden(config),
AConfiguration_getSdkVersion(config),
AConfiguration_getScreenSize(config),
AConfiguration_getScreenLong(config),
AConfiguration_getUiModeType(config),
AConfiguration_getUiModeNight(config));
} #ifdef ANDROID_APP_DEBUG
const char* input_motion_name(int action)
{
switch(action){
case AMOTION_EVENT_ACTION_DOWN://触摸按下
return "MOTION_DOWN";
case AMOTION_EVENT_ACTION_UP://触摸弹起
return "MOTION_UP";
case AMOTION_EVENT_ACTION_MOVE://触摸移动
return "MOTION_MOVE";
case AMOTION_EVENT_ACTION_CANCEL:
return "MOTION_CACEL";
case AMOTION_EVENT_ACTION_OUTSIDE:
return "MOTION_OUTSIDE";
default:
break;
}
return null;
}
#endif int android_app::on_input_motion(AInputEvent* inputEvent)
{
APP_EVENT event; //motion source
switch(AInputEvent_getSource(inputEvent))
{
case AINPUT_SOURCE_TOUCHSCREEN:
//消息来源于触摸屏
break;
case AINPUT_SOURCE_TRACKBALL:
//消息来源于trackball,轨迹球 or 鼠标?
break;
default:
break;
} switch(AMotionEvent_getAction(inputEvent))
{
case AMOTION_EVENT_ACTION_DOWN://触摸按下
event.id = APP_TOUCH_DOWN;
break;
case AMOTION_EVENT_ACTION_UP://触摸弹起
event.id = APP_TOUCH_UP;
break;
case AMOTION_EVENT_ACTION_MOVE://触摸移动
event.id = APP_TOUCH_MOVE;
break;
case AMOTION_EVENT_ACTION_CANCEL:
break;
case AMOTION_EVENT_ACTION_OUTSIDE:
break;
default:
break;
} //getX() 是表示view相对于自身左上角的x坐标,
//getRawX() 是表示相对于物理屏幕左上角的x坐标值
//AMotionEvent_getXPrecision size_t count = AMotionEvent_getPointerCount(inputEvent);
for(int i=; i < count; ++i){
event.x = AMotionEvent_getX(inputEvent, i);
event.y = AMotionEvent_getY(inputEvent, i);
event.z = i; if(this->onEvent){
this->onEvent(this, event);
} #ifdef ANDROID_APP_DEBUG
ANDROID_APP_LOGV("%s : index=%d, pointer=%d, flag=%d state=%d x=%d, y=%d\n",
input_motion_name(AMotionEvent_getAction(inputEvent)),
i,
AMotionEvent_getPointerId(inputEvent, i),
AMotionEvent_getFlags(inputEvent),
AMotionEvent_getMetaState(inputEvent),
event.x, event.y);
#endif
}
return this->onEvent ? : ;
} int android_app::on_input_keyboard(AInputEvent* inputEvent)
{
//键盘控制键管用,字符键不管用
ANDROID_APP_LOGV("keyinput : action=%d flag=%d keycode=%d",
AKeyEvent_getAction(inputEvent),
AKeyEvent_getFlags(inputEvent),
AKeyEvent_getKeyCode(inputEvent)); APP_EVENT event;
switch(AKeyEvent_getAction(inputEvent)){
case AKEY_STATE_UNKNOWN:
break;
case AKEY_STATE_UP:
event.id = APP_KEY_UP;
break;
case AKEY_STATE_DOWN:
event.id = APP_KEY_DOWN;
break;
case AKEY_STATE_VIRTUAL:
event.id = APP_KEY_PRESS;
break;
default:
break;
} event.key = AKeyEvent_getKeyCode(inputEvent);
event.flag = AKeyEvent_getFlags(inputEvent);
if(this->onEvent){//if processed, reutrn 1
this->onEvent(this, event);
return ;
} return ;
} // input event callback
int android_app::on_input_event(AInputEvent* inputEvent)
{
if(AInputEvent_getType(inputEvent) == AINPUT_EVENT_TYPE_MOTION){
return this->on_input_motion(inputEvent);
}
else if(AInputEvent_getType(inputEvent) == AINPUT_EVENT_TYPE_KEY){
return this->on_input_keyboard(inputEvent);
}
return ;
} // inputQueue callback
int android_app::on_input(int fd, int events, void* data)
{
ANDROID_APP_LOGV("on_input %p", data);
android_app* app = (android_app*)data;
AInputEvent* event = NULL;
while (AInputQueue_getEvent(app->inputQueue, &event) >= ) {
//ANDROID_APP_LOGV("New input event: type=%d\n", AInputEvent_getType(event));
if (AInputQueue_preDispatchEvent(app->inputQueue, event)) {
continue;
}
AInputQueue_finishEvent(app->inputQueue, event, app->on_input_event(event));
}
return ;
} //读取事件
bool android_app::read_event(APP_EVENT& event)
{
return message.read(&event, sizeof(event)) == sizeof(event);
} #ifdef ANDROID_APP_DEBUG
const char* app_event_name(int id)
{
switch (id)
{
case APP_START: return "APP_START";
case APP_STOP: return "APP_STOP";
case APP_RESUME: return "APP_RESUME";
case APP_PAUSE: return "APP_PAUSE";
case APP_DESTROY: return "APP_DESTROY";
case APP_SAVE: return "APP_SAVE";
case APP_CONFIG: return "APP_CONFIG";
case APP_WINDOW_CREATE: return "APP_WINDOW_CREATE";
case APP_WINDOW_CLOSE: return "APP_WINDOW_CLOSE";
case APP_WINDOW_ACTIVATE: return "APP_WINDOW_ACTIVATE";
case APP_WINDOW_DEACTIVATE: return "APP_WINDOW_DEACTIVATE";
case APP_WINDOW_RESIZE: return "APP_WINDOW_RESIZE";
case APP_INPUT: return "APP_INPUT";
default:
break;
}
return null;
}
#endif //预处理事件
void android_app::process_begin(APP_EVENT& event)
{
switch (event.id){
case APP_START:
case APP_STOP:
case APP_RESUME:
case APP_PAUSE:
ANDROID_APP_LOGV("APP_STATE = %s\n", app_event_name(event.id));
this->activityState = event.id;
break;
case APP_DESTROY:
ANDROID_APP_LOGV("APP_DESTROY\n");
this->running = false;
break;
case APP_SAVE:
//free_saved_state(this);
break;
case APP_CONFIG:
ANDROID_APP_LOGV("APP_CONFIG\n");
AConfiguration_fromAssetManager(this->config, this->activity->assetManager);
this->print_config();
break; case APP_WINDOW_CREATE:
ANDROID_APP_LOGV("APP_WINDOW_CREATE : %p\n", event.data);
this->window = (ANativeWindow*)event.data;
break;
case APP_INPUT:{
ANDROID_APP_LOGV("APP_INPUT : %p\n", event.data);
if(this->inputQueue){
AInputQueue_detachLooper(this->inputQueue);
}
this->inputQueue = (AInputQueue*)event.data;
if(this->inputQueue){
AInputQueue_attachLooper(this->inputQueue, this->looper, , android_app::on_input, this);
}
break;
}
default:
ANDROID_APP_LOGV("APP_EVENT : %s", app_event_name(event.id));
break;
}
} //后续处理事件
void android_app::process_end(APP_EVENT& event)
{
switch (event.id) {
case APP_WINDOW_CLOSE:
ANDROID_APP_LOGV("APP_WINDOW_CLOSE\n");
this->window = NULL;
break;
case APP_SAVE:
ANDROID_APP_LOGV("APP_SAVE\n");
this->stateSaved = ;
break;
case APP_RESUME:
//free_saved_state(this);
break;
default:
break;
}
} void android_app::dispose()
{
ANDROID_APP_LOGV("android_app::dispose!");
free_saved_state(this);
//auto_lock lock(app->mutex);
if(this->inputQueue){
AInputQueue_detachLooper(this->inputQueue);
}
AConfiguration_delete(this->config);
this->destroyed = ;
this->cond.broadcast();
// Can't touch android_app object after this.
} // app main thread
void* android_app_main(void* param)
{
android_app* app = (android_app*)param; app->config = AConfiguration_new();
AConfiguration_fromAssetManager(app->config, app->activity->assetManager);
app->print_config(); app->looper = ALooper_prepare();
app->running = true; android_main(app); //android_main app->dispose(); return NULL;
} // --------------------------------------------------------------------
//
// Native activity interaction (主线程调用的函数)
//
// -------------------------------------------------------------------- android_app* android_app_create(ANativeActivity* activity, void* savedState, size_t savedStateSize)
{
android_app* app = new android_app;
app->activity = activity; if (savedState != NULL) {
/*
app->savedState = malloc(savedStateSize);
app->savedStateSize = savedStateSize;
memcpy(app->savedState, savedState, savedStateSize);
*/
} if(app->message.create()){
ANDROID_APP_ERROR("could not create pipe: %s", strerror(errno));
return NULL;
} //int flag = fcntl(app->message.in(), F_GETFL, 0 );
//fcntl(app->message.in(), F_SETFL, flag | O_NONBLOCK); pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&app->thread, &attr, android_app_main, app); return app;
} void android_app_post_event(android_app* app, const APP_EVENT& event)
{
//ANDROID_APP_LOGV("write event %d", event.id);
if(app->message.write(&event, sizeof(event)) != sizeof(event)) {
ANDROID_APP_ERROR("Failure writing android_app event: %s\n", strerror(errno));
}
} void android_app_post_event(android_app* app, int id)
{
APP_EVENT event = {id};
android_app_post_event(app, event);
} //
// 主线程回调函数
// //
// app
// void onStart(ANativeActivity* activity)
{
ANDROID_APP_LOGV("Activity Start: %p\n", activity);
android_app_post_event((android_app*)activity->instance, APP_START);
} void onStop(ANativeActivity* activity)
{
ANDROID_APP_LOGV("Activity Stop: %p\n", activity);
android_app_post_event((android_app*)activity->instance, APP_STOP);
} void onResume(ANativeActivity* activity)
{
ANDROID_APP_LOGV("Activity Resume: %p\n", activity);
android_app_post_event((android_app*)activity->instance, APP_RESUME);
} void onPause(ANativeActivity* activity)
{
ANDROID_APP_LOGV("Activity Pause: %p\n", activity);
android_app_post_event((android_app*)activity->instance, APP_PAUSE);
} void onDestroy(ANativeActivity* activity)
{
ANDROID_APP_LOGV("Activity Destroy: %p\n", activity);
android_app* app = (android_app*)activity->instance;
//auto_lock lock(app->mutex);
android_app_post_event(app, APP_DESTROY);
while (!app->destroyed) {
//app->cond_wait();
} app->message.close();
delete app;
} void* onSaveInstanceState(ANativeActivity* activity, size_t* outLen)
{
android_app* app = (android_app*)activity->instance;
void* savedState = NULL; ANDROID_APP_LOGV("Activity SaveInstanceState: %p\n", activity);
/*
auto_lock lock(app->mutex);
app->stateSaved = 0;
android_app_post_event(app, APP_SAVE);
while (!app->stateSaved) {
app->cond_wait();
} if (app->savedState != NULL) {
savedState = app->savedState;
*outLen = app->savedStateSize;
app->savedState = NULL;
app->savedStateSize = 0;
}
*/
return savedState;
} void onConfigurationChanged(ANativeActivity* activity) {
ANDROID_APP_LOGV("Activity ConfigurationChanged: %p\n", activity);
android_app_post_event((android_app*)activity->instance, APP_CONFIG);
} void onLowMemory(ANativeActivity* activity)
{
ANDROID_APP_LOGV("Activity LowMemory: %p\n", activity);
android_app_post_event((android_app*)activity->instance, APP_LOW_MEMORY);
} //
// window
// void onNativeWindowCreated(ANativeActivity* activity, ANativeWindow* window)
{
ANDROID_APP_LOGV("NativeWindowCreated: %p -- %p\n", activity, window);
APP_EVENT event;
event.id = APP_WINDOW_CREATE;
event.data = window;
android_app_post_event((android_app*)activity->instance, event);
} void onNativeWindowDestroyed(ANativeActivity* activity, ANativeWindow* window)
{
ANDROID_APP_LOGV("NativeWindowDestroyed: %p -- %p\n", activity, window);
android_app_post_event((android_app*)activity->instance, APP_WINDOW_CLOSE);
} void onWindowFocusChanged(ANativeActivity* activity, int focused)
{
ANDROID_APP_LOGV("WindowFocusChanged: %p -- %d\n", activity, focused);
android_app_post_event((android_app*)activity->instance, focused ? APP_WINDOW_ACTIVATE : APP_WINDOW_DEACTIVATE);
} void onNativeWindowResized(ANativeActivity* activity, ANativeWindow* window)
{
ANDROID_APP_LOGV("NativeWindowResized: %p -- %p\n", activity, window);
android_app_post_event((android_app*)activity->instance, APP_WINDOW_RESIZE);
} void onContentRectChanged(ANativeActivity* activity, const ARect* rect)
{
ANDROID_APP_LOGV("ContentRectChanged: [%d, %d - %d, %d]\n", rect->left, rect->top, rect->right, rect->bottom);
/*
APP_EVENT event;
event.id = APP_WINDOW_RECT_CHANGED;
event.left = rect->left;
event.top = rect->top;
event.right = rect->right;
event.bottom = rect->bottom;
android_app_post_event((android_app*)activity->instance, event);
*/
} void onNativeWindowRedrawNeeded(ANativeActivity* activity, ANativeWindow* window)
{
ANDROID_APP_LOGV("NativeWindowRedrawNeeded: %p -- %p\n", activity, window);
android_app_post_event((android_app*)activity->instance, APP_WINDOW_PAINT);
} //
// input
// void android_app_set_input(android_app* app, AInputQueue* inputQueue)
{
APP_EVENT event;
event.id = APP_INPUT;
event.data = inputQueue;
android_app_post_event(app, event);
} void onInputQueueCreated(ANativeActivity* activity, AInputQueue* queue)
{
ANDROID_APP_LOGV("InputQueueCreated: %p -- %p\n", activity, queue);
android_app_set_input((android_app*)activity->instance, queue);
} void onInputQueueDestroyed(ANativeActivity* activity, AInputQueue* queue)
{
ANDROID_APP_LOGV("InputQueueDestroyed: %p -- %p\n", activity, queue);
android_app_set_input((android_app*)activity->instance, NULL);
} //
// native activity entry
// JNIEXPORT
void ANativeActivity_onCreate(ANativeActivity* activity, void* savedState, size_t savedStateSize)
{
ANDROID_APP_LOGV("NativeActivity create: %p\n", activity); //app
activity->callbacks->onStart = onStart;
activity->callbacks->onStop = onStop;
activity->callbacks->onResume = onResume;
activity->callbacks->onPause = onPause;
activity->callbacks->onDestroy = onDestroy;
activity->callbacks->onSaveInstanceState = onSaveInstanceState;
activity->callbacks->onConfigurationChanged = onConfigurationChanged;
activity->callbacks->onLowMemory = onLowMemory; //window
activity->callbacks->onNativeWindowCreated = onNativeWindowCreated;
activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed;
activity->callbacks->onWindowFocusChanged = onWindowFocusChanged;
activity->callbacks->onNativeWindowResized = onNativeWindowResized;
activity->callbacks->onContentRectChanged = onContentRectChanged;
activity->callbacks->onNativeWindowRedrawNeeded = onNativeWindowRedrawNeeded; //input
activity->callbacks->onInputQueueCreated = onInputQueueCreated;
activity->callbacks->onInputQueueDestroyed = onInputQueueDestroyed; //create android app
activity->instance = android_app_create(activity, savedState, savedStateSize); //ANDROID_APP_LOGV("NativeActivity create successed.");
} #endif /* ANDROID_APP_HPP */
android_app c++框架的更多相关文章
- 避免重复造轮子的UI自动化测试框架开发
一懒起来就好久没更新文章了,其实懒也还是因为忙,今年上半年的加班赶上了去年一年的加班,加班不息啊,好了吐槽完就写写一直打算继续的自动化开发 目前各种UI测试框架层出不穷,但是万变不离其宗,驱动PC浏览 ...
- ABP入门系列(1)——学习Abp框架之实操演练
作为.Net工地搬砖长工一名,一直致力于挖坑(Bug)填坑(Debug),但技术却不见长进.也曾热情于新技术的学习,憧憬过成为技术大拿.从前端到后端,从bootstrap到javascript,从py ...
- 旺财速啃H5框架之Bootstrap(五)
在上一篇<<旺财速啃H5框架之Bootstrap(四)>>做了基本的框架,<<旺财速啃H5框架之Bootstrap(二)>>篇里也大体认识了bootst ...
- Angular企业级开发(5)-项目框架搭建
1.AngularJS Seed项目目录结构 AngularJS官方网站提供了一个angular-phonecat项目,另外一个就是Angular-Seed项目.所以大多数团队会基于Angular-S ...
- Scrapy框架爬虫初探——中关村在线手机参数数据爬取
关于Scrapy如何安装部署的文章已经相当多了,但是网上实战的例子还不是很多,近来正好在学习该爬虫框架,就简单写了个Spider Demo来实践.作为硬件数码控,我选择了经常光顾的中关村在线的手机页面 ...
- 制作类似ThinkPHP框架中的PATHINFO模式功能
一.PATHINFO功能简述 搞PHP的都知道ThinkPHP是一个免费开源的轻量级PHP框架,虽说轻量但它的功能却很强大.这也是我接触学习的第一个框架.TP框架中的URL默认模式即是PathInfo ...
- 旺财速啃H5框架之Bootstrap(四)
上一篇<<旺财速啃H5框架之Bootstrap(三)>>已经把导航做了,接下来搭建内容框架.... 对于不规整的网页,要做成自适应就有点玩大了.... 例如下面这种版式的页面. ...
- 一起学 Java(三) 集合框架、数据结构、泛型
一.Java 集合框架 集合框架是一个用来代表和操纵集合的统一架构.所有的集合框架都包含如下内容: 接口:是代表集合的抽象数据类型.接口允许集合独立操纵其代表的细节.在面向对象的语言,接口通常形成一个 ...
- Hibernatel框架关联映射
Hibernatel框架关联映射 Hibernate程序执行流程: 1.集合映射 需求:网络购物时,用户购买商品,填写地址 每个用户会有不确定的地址数目,或者只有一个或者有很多.这个时候不能把每条地址 ...
随机推荐
- 九度oj 题目1072:有多少不同的面值组合?(set集合)
题目1072:有多少不同的面值组合? 时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:3627 解决:1852 题目描述: 某人有8角的邮票5张,1元的邮票4张,1元8角的邮票6张,用这些邮 ...
- Python模块基础
概念: 在Python中,一个.py文件就称之为一个模块(Module) 好处: 1. 提高可维护性 2. 可重用 3. 避免函数名.变量名冲突. 每个模块有独立的命名空间,因此相同名字的函数和变量完 ...
- 利用express启一个server服务
安装express $ npm install express --save 在node.js中,我们最常用的框架就是express Express 是一个基于 Node.js 平台的极简.灵活的 w ...
- android开发里跳过的坑——button不响应点击事件
昨天遇到一个头疼的问题,在手机上按钮事件都很正常,但是在平板上(横屏显示的状态),button点击事件不响应,代码简化如下: public class Test extends Activity im ...
- Codeforces908G. New Year and Original Order
给n<=10^700,问1到n中每个数在各数位排序后得到的数的和.答案膜1e9+7. 一看就是数位DP啦..然而并没有什么思路.. 可以尝试统计n(i,j)表示数j在第i位的出现次数,知道了这个 ...
- Mycat环境搭建教程收集(待实践)
先收集,后续再实践. http://blog.csdn.net/dreamcode/article/details/44307377 http://blog.csdn.net/lanonola/art ...
- C#如何把写好的类编译成dll文件
1 新建一个类库项目 2 直接改写这个Class1.cs文件 3 记得要添加Windows.Forms引用 4 我直接把在别的项目中做好的cs文件搞到这里来,连文件名也改了(FilesDi ...
- UVa 1531 - Problem Bee
题目:如图所看到的的蜂巢型的图中.蜜蜂想从A点飞到B点,假设A与B不在同一个正六边形中, 则它先飞到A的中心.每次飞到相邻格子的中心,最后飞到B的中心,再飞到B点: 假设在一个格子中.直接飞过去就可以 ...
- 改动select默认样式,兼容IE9
前面有篇文章已经提供了怎样改动select标签的默认样式,可是仅仅能兼容到ie10,要兼容ie9仅仅能模拟一个类似的 html结构: <div class="select_diy&qu ...
- js制造运动的假象-------Day63
这里用的这个名称可能不合适,只是临时我也没有想起比較相应的名字来,那就这样先写吧. 首先,我先表达下我想实现的是什么效果: 想必大家都玩过,至少见过非常多小游戏,例如说超级玛丽.例如说flappy b ...