Android窗口系统

我们知道Android系统采用OpenGL来绘制3D图形,OpenGL ES提供了本地窗口(NativeWindow)的概念,无论是在Android平台中还是其他平台中,只要实现OpenGL ES中的本地窗口定义的接口,就可以利用OpenGL ES来绘制图形。由于Android系统所有服务都建立在C/S模式下,因此Android系统在实现OpenGL ES的本地窗口时仍然包括两种本地窗口,服务进程端的本地窗口定义为FramebufferNativeWindow,该本地窗口直接由 SurfaceFlinger管理。在应用程序进程端定义的本地创建为SurfaceTextureClient。在Android系统中,它们之间为一 对多的关系,如下图所示:

每个应用程序App可以有多个窗口,即多个Surface,每个Surface所需的图形缓冲区由SurfaceFlinger进程中的
BufferQueue对象负责管理,图形缓冲区个数最多可以有32个,每个图形缓冲区用GraphicBuffer来定义,应用程序在图形绘制前,请求
SurfaceFlinger进程中的BufferQueue对象在内存中分配一块图形缓冲区,应用程序完成图形绘制后,由SurfaceFlinger
将多个应用程序需要显示的Surface进行图形混合,混合后的图形窗口使用FramebufferNativeWindow来描述,同时将混合后的图形
数据拷贝到Framebuffer的后台缓冲区中,等待渲染到显示屏上。以下就是Android的窗口系统设计模型:

FramebufferNativeWindow本地窗口所需的图形缓冲区直接从Framebuffer中分配,而Surface本地窗口所需的图
形缓冲区则是从内存中分配,无论是从Framebuffer中分配还是从内存中分配,图形缓冲区的分配工作都是由Gralloc硬件抽象层完成,在Android图形显示之硬件抽象层Gralloc中详细分析了Gralloc模块,而Android图形缓冲区分配过程源码分析则分析了图形缓冲区的分配过程。SurfaceFlinger收集所有应用程序的显示需求,然后对应用程序所需显示的图形做图像混合操作,然后输出到自己的FramebufferNativeWindow本地窗口上。为了使用OpenGL
ES绘制图形窗口,必须实现OpenGL ES定义的本地窗口协议NativeWindow。

  1. EGLSurface eglCreateWindowSurface(EGLDisplay dpy, EGLConfig config,
  2. NativeWindowType window,
  3. const EGLint *attrib_list)

函数eglCreateWindowSurface是OpenGL ES提供用于创建窗口的函数接口,参数window的类型为NativeWindowType,定义如下:

frameworks\native\opengl\include\EGL\eglplatform.h

  1. typedef EGLNativeWindowType  NativeWindowType;
  2. #if defined(_WIN32) || defined(__VC32__) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__) /* Win32 and WinCE */
  3. typedef HWND    EGLNativeWindowType;
  4. #elif defined(__WINSCW__) || defined(__SYMBIAN32__)  /* Symbian */
  5. typedef void *EGLNativeWindowType;
  6. #elif defined(__ANDROID__) || defined(ANDROID)
  7. typedef struct ANativeWindow*           EGLNativeWindowType;
  8. #elif defined(__unix__)
  9. typedef Window   EGLNativeWindowType;
  10. #else
  11. #error "Platform not recognized"
  12. #endif

NativeWindowType
定义为EGLNativeWindowType类型,而该类型在不同的平台中有不同的定义,这是因为OpenGL
ES是一个跨平台的图形绘制库,对于Android系统来说,其定义为ANativeWindow指针类型,而ANativeWindow的定义如下:

  1. struct ANativeWindow
  2. {
  3. #ifdef __cplusplus
  4. ANativeWindow(): flags(0), minSwapInterval(0), maxSwapInterval(0), xdpi(0), ydpi(0)
  5. {
  6. common.magic = ANDROID_NATIVE_WINDOW_MAGIC;
  7. common.version = sizeof(ANativeWindow);
  8. memset(common.reserved, 0, sizeof(common.reserved));
  9. }
  10. void incStrong(const void* id) const {
  11. common.incRef(const_cast<android_native_base_t*>(&common));
  12. }
  13. void decStrong(const void* id) const {
  14. common.decRef(const_cast<android_native_base_t*>(&common));
  15. }
  16. #endif
  17. struct android_native_base_t common;
  18. const uint32_t flags;//用于描述该Surface的一些属性
  19. const int   minSwapInterval;//最小交换间隔时间
  20. const int   maxSwapInterval;//最大交换间隔时间
  21. const float xdpi;//水平方向的密度
  22. const float ydpi;//垂直方向的密度
  23. intptr_t    oem[4];//为OEM预留
  24. //设置交换间隔时间
  25. int     (*setSwapInterval)(struct ANativeWindow* window,int interval);
  26. //申请一个图形缓冲区buffer
  27. int     (*dequeueBuffer)(struct ANativeWindow* window,struct ANativeWindowBuffer** buffer);
  28. //锁定图形缓冲区
  29. int     (*lockBuffer)(struct ANativeWindow* window,struct ANativeWindowBuffer* buffer);
  30. //buffer渲染完成后,它调用这个接口来unlock和post buffer
  31. int     (*queueBuffer)(struct ANativeWindow* window,struct ANativeWindowBuffer* buffer);
  32. //向本地窗口查询相关信息
  33. int     (*query)(const struct ANativeWindow* window,int what, int* value);
  34. //执行本地窗口支持的各种操作
  35. int     (*perform)(struct ANativeWindow* window,int operation, ... );
  36. //取消一个已经dequeued的buffer
  37. int     (*cancelBuffer)(struct ANativeWindow* window,struct ANativeWindowBuffer* buffer);
  38. //预留
  39. void* reserved_proc[2];
  40. }android_native_window_t;

当使用C++编译器是,为ANativeWindow定义了相应的构造函数,在OpenGL ES下的Android本地窗口系统的类关系图如下:

从上图可以看出,Surface和FramebufferNativeWindow都继承于ANativeWindow,因此也就继承了
OpenGL ES下的本地窗口定义的相关协议:ANativeWindow中定义的相关接口。下面分别对这两种类型的本地窗口进行深入分析。

FramebufferNativeWindow

前面已经介绍了FramebufferNativeWindow是SurfaceFlinger服务进程维护的本地窗口,用于描述经过图形混合
后的,即将渲染显示的图形窗口。FramebufferNativeWindow不仅实现了从ANativeWindow中继承下来的接口,自己还定义了
一些额外属性:
  1. class FramebufferNativeWindow
  2. : public ANativeObjectBase<
  3. ANativeWindow,
  4. FramebufferNativeWindow,
  5. LightRefBase<FramebufferNativeWindow> >
  6. {
  7. framebuffer_device_t* fbDev; //描述Framebuffer设备
  8. alloc_device_t* grDev; //描述gpu设备
  9. sp<NativeBuffer> buffers[NUM_FRAME_BUFFERS];//定义2个图形缓冲区
  10. sp<NativeBuffer> front; //前台图形缓冲区,即正在渲染的图形缓冲区
  11. mutable Mutex mutex;
  12. Condition mCondition;
  13. int32_t mNumBuffers; //图形缓冲区个数
  14. int32_t mNumFreeBuffers; //可以使用的图形缓冲区个数
  15. int32_t mBufferHead; //
  16. int32_t mCurrentBufferIndex;//当前图形缓冲区的索引
  17. bool mUpdateOnDemand;
  18. };

接下来看看FramebufferNativeWindow对象的构造过程:

  1. FramebufferNativeWindow::FramebufferNativeWindow() : BASE(), fbDev(0), grDev(0), mUpdateOnDemand(false)
  2. {
  3. hw_module_t const* module;
  4. //加载gralloc模块
  5. if (hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module) == 0) {
  6. int stride;
  7. int err;
  8. int i;
  9. //打开fb设备
  10. err = framebuffer_open(module, &fbDev);
  11. ALOGE_IF(err, "couldn't open framebuffer HAL (%s)", strerror(-err));
  12. //打开gpu设备
  13. err = gralloc_open(module, &grDev);
  14. ALOGE_IF(err, "couldn't open gralloc HAL (%s)", strerror(-err));
  15. //设备打开失败,返回
  16. if (!fbDev || !grDev)
  17. return;
  18. mUpdateOnDemand = (fbDev->setUpdateRect != 0);
  19. // 初始化变量值
  20. mNumBuffers = NUM_FRAME_BUFFERS;//2
  21. mNumFreeBuffers = NUM_FRAME_BUFFERS;//2
  22. mBufferHead = mNumBuffers-1;//1
  23. #ifdef FRAMEBUFFER_FORCE_FORMAT
  24. *((uint32_t *)&fbDev->format) = FRAMEBUFFER_FORCE_FORMAT;
  25. #endif
  26. //创建2个NativeBuffer
  27. for (i = 0; i < mNumBuffers; i++)
  28. {
  29. buffers[i] = new NativeBuffer(fbDev->width, fbDev->height, fbDev->format, GRALLOC_USAGE_HW_FB);
  30. }
  31. //为NativeBuffer分配缓冲区
  32. for (i = 0; i < mNumBuffers; i++)
  33. {
  34. err = grDev->alloc(grDev,fbDev->width, fbDev->height, fbDev->format,GRALLOC_USAGE_HW_FB, &buffers[i]->handle, &buffers[i]->stride);
  35. ALOGE_IF(err, "fb buffer %d allocation failed w=%d, h=%d, err=%s",i, fbDev->width, fbDev->height, strerror(-err));
  36. if (err)
  37. {
  38. mNumBuffers = i;
  39. mNumFreeBuffers = i;
  40. mBufferHead = mNumBuffers-1;
  41. break;
  42. }
  43. }
  44. //使用Framebuffer的设备描述符来初始化本地窗口ANativeWindow的相关属性
  45. const_cast<uint32_t&>(ANativeWindow::flags) = fbDev->flags;
  46. const_cast<float&>(ANativeWindow::xdpi) = fbDev->xdpi;
  47. const_cast<float&>(ANativeWindow::ydpi) = fbDev->ydpi;
  48. const_cast<int&>(ANativeWindow::minSwapInterval) = fbDev->minSwapInterval;
  49. const_cast<int&>(ANativeWindow::maxSwapInterval) = fbDev->maxSwapInterval;
  50. } else {
  51. ALOGE("Couldn't get gralloc module");
  52. }
  53. //为本地窗口ANativeWindow设置回调接口函数
  54. ANativeWindow::setSwapInterval = setSwapInterval;
  55. ANativeWindow::dequeueBuffer = dequeueBuffer;
  56. ANativeWindow::lockBuffer = lockBuffer;
  57. ANativeWindow::queueBuffer = queueBuffer;
  58. ANativeWindow::query = query;
  59. ANativeWindow::perform = perform;
  60. }

函数首先加载Gralloc模块,关于硬件抽象层模块的加载过程,在Android硬件抽象Hardware库加载过程源码分析
经有详细的介绍了。当成功加载Gralloc模块后,依次打开Gralloc模块中定义的Framebuffer设备及gpu设备,我们知道
Gralloc模块中定义的Framebuffer设备用于将已经准备好了的图形缓冲区渲染到帧缓冲区中,而定义的gpu设备用于分配一块图形缓冲区,并
且将这块图形缓冲区映射到应用程序的地址空间。关于Framebuffer设备和gpu设备的打开过程请参阅Android图形显示之硬件抽象层Gralloc
打开fb和gpu设备后,将这两种设备描述符分别保存到FramebufferNativeWindow的成员变量fbDev和grDev中。接着创建了
两个NativeBuffer对象,并从Framebuffer帧缓冲区中分配了两块图形缓冲区。Android系统为定义的两种本地窗口分别定义了相应
的图形缓冲区buffer的描述符,对于FramebufferNativeWindow本地窗口来说,为其定义的图形缓冲区描述符为
NativeBuffer,而对于应用程序端的本地窗口Surface,为其定义的图形缓冲区描述符为GraphicBuffer,它们之间的类关系图如
下:

从上面的类继承图中可以看出,无论是NativeBuffer还是GraphicBuffer,它们都继承于
ANativeWindowBuffer,ANativeWindowBuffer用于描述一块图形缓冲区buffer的属性信息,比如图形的宽,高,图
像格式及该buffer的句柄等等信息。

  1. typedef struct ANativeWindowBuffer
  2. {
  3. //针对C++编译器定义构造函数
  4. #ifdef __cplusplus
  5. ANativeWindowBuffer() {
  6. common.magic = ANDROID_NATIVE_BUFFER_MAGIC;
  7. common.version = sizeof(ANativeWindowBuffer);
  8. memset(common.reserved, 0, sizeof(common.reserved));
  9. }
  10. void incStrong(const void* id) const {
  11. common.incRef(const_cast<android_native_base_t*>(&common));
  12. }
  13. void decStrong(const void* id) const {
  14. common.decRef(const_cast<android_native_base_t*>(&common));
  15. }
  16. #endif
  17. struct android_native_base_t common;//描述EGL版本信息
  18. int width; //图像宽度
  19. int height; //图像高度
  20. int stride; //
  21. int format; //图像格式
  22. int usage; //该buffer的用途
  23. void* reserved[2]; //保留
  24. buffer_handle_t handle; //该buffer的句柄信息
  25. void* reserved_proc[8];
  26. } android_native_buffer_t;

接着为创建的2个NativeBuffer分配空间,使用Gralloc模块中的gpu来完成空间的分配过程,同时指定标志位为GRALLOC_USAGE_HW_FB,表示从系统帧缓冲区Framebuffer中分配。

  1. err = grDev->alloc(grDev,fbDev->width, fbDev->height, fbDev->format,GRALLOC_USAGE_HW_FB, &buffers[i]->handle, &buffers[i]->stride);

关于图形缓冲区的完整分配过程请阅读Android图形缓冲区分配过程源码分析
FramebufferNativeWindow完成图形缓冲区的分配后,还需初始化从ANativeWindow中继承而来的本地窗口定义的相关接口,
即FramebufferNativeWindow实现ANativeWindow本地窗口协议。从FramebufferNativeWindow的构
造函数中,我们知道,FramebufferNativeWindow从Framebuffer中分配了2个缓冲区,说明
FramebufferNativeWindow使用了双缓冲技术,使用双缓冲技术的优点是什么呢?假设我们需要绘制这样一个画面,包括两个三角形和三个
圆形,最终结果如下图所示:

在只有一个buffer的情况下,我们是直接以屏幕为画板来实时做画的,假设图中的每一个三角形或圆形都需要0.5秒为例,那么总计耗时应该是0.5*5=2.5秒,图形绘制过程如下:

对于用户来说,他将看到一个不断刷新的画面。对于图像刷新很频繁的情况,用户的体验就会更差。出现这种现象的原因就是程序直接以屏幕为绘图板,把还
没有准备就绪的图像直接呈现给了用户。换句话说,如果将整幅图绘制完成以后再刷新到屏幕上,那么对于用户来说,他在任何时候看到的都是完整的图像。采用两
个缓冲区绘制图形的情况如下:

既然FramebufferNativeWindow创建了两块图形缓冲区,那它是如何维护这两块图形缓冲区的呢?接下来就介绍图形缓冲区的申请过程:

  1. int FramebufferNativeWindow::dequeueBuffer(ANativeWindow* window,
  2. ANativeWindowBuffer** buffer)
  3. {
  4. FramebufferNativeWindow* self = getSelf(window);
  5. Mutex::Autolock _l(self->mutex);
  6. //从FramebufferNativeWindow对象中取出fb设备描述符,在构造FramebufferNativeWindow对象时,已经打开了fb设备
  7. framebuffer_device_t* fb = self->fbDev;
  8. //计算当前申请的图形缓冲区在buffers数组中的索引,同时将下一个申请的buffer的索引保存到mBufferHead中
  9. int index = self->mBufferHead++;
  10. //如果申请的下一个buffer的索引大于或等于buffer总数,则将下一个申请的buffer索引设置为0,这样就实现了对buffer数组的循环管理
  11. if (self->mBufferHead >= self->mNumBuffers)
  12. self->mBufferHead = 0;
  13. //如果当前没有空闲的buffer,即mNumFreeBuffers= 0,则线程睡眠等待buffer的释放
  14. while (!self->mNumFreeBuffers) {
  15. self->mCondition.wait(self->mutex);
  16. }
  17. //存在了空闲buffer,线程被唤醒继续执行,由于此时要申请一块buffer,因此空闲buffer的个数又需要减1
  18. self->mNumFreeBuffers--;
  19. //保存当前申请的buffer在缓冲区数组中的索引位置
  20. self->mCurrentBufferIndex = index;
  21. //得到buffer数组中的NativeBuffer对象指针
  22. *buffer = self->buffers[index].get();
  23. return 0;
  24. }

dequeueBuffer
函数就是从FramebufferNativeWindow创建的包含2个图形缓冲区的缓冲区队列buffers中取出一块空闲可用的图形buffer,
如果当前缓冲区队列中没有空闲的buffer,则当前申请buffer线程阻塞等待,等待其他线程释放图形缓冲区。mNumFreeBuffers用来描
述可用的空闲图形buffer个数,index记录当前申请buffer在图形缓冲区队列中的索引位置,mBufferHead指向下一次申请的图形
buffer的位置,由于我们是循环利用两个缓冲区的,所以如果这个变量的值超过mNumBuffers,就需要置0。也就是说mBufferHead的
值永远只能是0或者1。

上图描述了图形绘制的整个过程,SurfaceFlinger首先从FramebufferNativeWindow中申请出列一块图形
buffer,然后将系统中的各个Surface的GraphicBuffer进行图形混合,将混合后的图形保存到申请所得的图形buffer中,接着将
该buffer放回FramebufferNativeWindow的图形缓冲区队列中,最后将该buffer渲染到显示屏幕上。接下来介绍图形
buffer如列过程:
  1. int FramebufferNativeWindow::queueBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer)
  2. {
  3. FramebufferNativeWindow* self = getSelf(window);
  4. Mutex::Autolock _l(self->mutex);
  5. //从FramebufferNativeWindow对象中取出fb设备描述符,在构造FramebufferNativeWindow对象时,已经打开了fb设备
  6. framebuffer_device_t* fb = self->fbDev;
  7. //从NativeBuffer对象中取出buffer_handle_t
  8. buffer_handle_t handle = static_cast<NativeBuffer*>(buffer)->handle;
  9. const int index = self->mCurrentBufferIndex;
  10. //调用framebuffer_device_t中注册的post函数将已绘制好的buffer渲染到Framebuffer中。
  11. int res = fb->post(fb, handle);
  12. //将当前NativeBuffer保存为前台buffer
  13. self->front = static_cast<NativeBuffer*>(buffer);
  14. //由于当前NativeBuffer已经渲染完成,因此将当前buffer入列,从而可以被申请
  15. self->mNumFreeBuffers++;
  16. //唤醒图形buffer申请出列线程,表示已有空闲buffer可以被申请
  17. self->mCondition.broadcast();
  18. return res;
  19. }

这里将调用fb设备的post方法将buffer渲染到屏幕上,然后修改空闲buffer个数,最后唤醒正在申请图形buffer出列,却因无空闲buffer而睡眠的线程。

  1. static int fb_post(struct framebuffer_device_t* dev, buffer_handle_t buffer)
  2. {
  3. //校验buffer_handle_t
  4. if (private_handle_t::validate(buffer) < 0)
  5. return -EINVAL;
  6. //将framebuffer_device_t强制转换为fb_context_t指针
  7. fb_context_t* ctx = (fb_context_t*)dev;
  8. //将buffer_handle_t强制转换为private_handle_t指针
  9. private_handle_t const* hnd = reinterpret_cast<private_handle_t const*>(buffer);
  10. //通过fb_context_t设备描述符找到对应的硬件抽象设备hw_device_t,在根据hw_device_t找到对应的硬件抽象模块hw_moudle_t,最后强制转换为private_module_t指针
  11. private_module_t* m = reinterpret_cast<private_module_t*>(dev->common.module);
  12. //如果当前buffer是从Framebuffer中分配的缓冲区
  13. if (hnd->flags & private_handle_t::PRIV_FLAGS_FRAMEBUFFER) {
  14. const size_t offset = hnd->base - m->framebuffer->base;
  15. m->info.activate = FB_ACTIVATE_VBL;
  16. m->info.yoffset = offset / m->finfo.line_length;
  17. if (ioctl(m->framebuffer->fd, FBIOPUT_VSCREENINFO, &m->info) == -1) {
  18. ALOGE("FBIOPUT_VSCREENINFO failed");
  19. m->base.unlock(&m->base, buffer);
  20. return -errno;
  21. }
  22. m->currentBuffer = buffer;
  23. } else {
  24. // If we can't do the page_flip, just copy the buffer to the front
  25. // FIXME: use copybit HAL instead of memcpy
  26. void* fb_vaddr;
  27. void* buffer_vaddr;
  28. m->base.lock(&m->base, m->framebuffer, GRALLOC_USAGE_SW_WRITE_RARELY, 0, 0, m->info.xres, m->info.yres,&fb_vaddr);
  29. m->base.lock(&m->base, buffer, GRALLOC_USAGE_SW_READ_RARELY, 0, 0, m->info.xres, m->info.yres,&buffer_vaddr);
  30. memcpy(fb_vaddr, buffer_vaddr, m->finfo.line_length * m->info.yres);
  31. m->base.unlock(&m->base, buffer);
  32. m->base.unlock(&m->base, m->framebuffer);
  33. }
  34. return 0;
  35. }

最后通过FBIOPUT_VSCREENINFO命令进入Framebuffer驱动,将图形渲染显示。

Android服务端本地窗口FramebufferNativeWindow的更多相关文章

  1. Android 服务端开发之开发环境配置

    Android 服务端开发之开发环境配置 这里是在Eclipse的基础上安装PhpEclipse插件方法,PHPEclipse是Eclipse的 一个用于开发PHP的插件.当然也可以采用Java开发a ...

  2. erlang-百度云推送Android服务端功能实现-erlang

    百度云推送官方地址http://developer.baidu.com/wiki/index.php?title=docs/cplat/push 简单的介绍下原理: 百度云推送支持IOS和Androi ...

  3. delphi xe5 android 服务端和手机端的源码下载

    xe5 android的服务端和手机客户端的源代码下载地址 http://files.cnblogs.com/nywh2008/AndroidTest.rar

  4. 使用Zabbix服务端本地邮箱账号发送报警邮件及指定报警邮件操作记录

    邮件报警有两种情况:1)Zabbix服务端只是单纯的发送报警邮件到指定邮箱,发送报警邮件的这个邮箱账号是Zabbix服务端的本地邮箱账号(例如:root@localhost.localdomain), ...

  5. Android客户端与PC服务端、android服务端通过WiFi通信

    前期准备:我的是Linux Mint操作系统(总之折腾的过程中怀疑过是不是系统的问题),首先是要创建wifi热点给android手机使用,这个时候笔记本作为通信的服务器端,android手机作为客户端 ...

  6. Android服务端的设计

    1.创建自己的MyServletContextListener.java: package yybwb; import java.net.ServerSocket; import javax.serv ...

  7. [PHP]AES加密----PHP服务端和Android客户端

    本文采取128位AES-CBC模式加密和解密 1.首先对服务端安装mcrypt: sudo apt-get install php5-mcrypt php5-dev sudo php5enmod mc ...

  8. 从服务端下载文件到本地windows

    之前常使用本地ubuntu和远程的centos服务器或者是本地mac和远程centos服务器通过命令scp或者nc来进行文件的传输. 现在用的是windows系统,欲将服务器的某文件load到本地. ...

  9. linux epoll机制对TCP 客户端和服务端的监听C代码通用框架实现

    1 TCP简介 tcp是一种基于流的应用层协议,其“可靠的数据传输”实现的原理就是,“拥塞控制”的滑动窗口机制,该机制包含的算法主要有“慢启动”,“拥塞避免”,“快速重传”. 2 TCP socket ...

随机推荐

  1. FileSystemWatcher使用方法具体解释

    FileSystemWatcher控件主要功能: 监控指定文件或文件夹的文件的创建.删除.修改.重命名等活动.能够动态地定义须要监控的文件类型及文件属性修改的类型. 1.经常使用的几个基本属性: (1 ...

  2. 关于VS2008中的targetver.h文件

    targerver.h文件的作用: 定义程序运行的环境,如限制程序只能在XP下运行,限制程序在只能在Vin7下运行 或限制程序只能在XP以上系统运行,或限制程序只能在Server2003以上系统运行. ...

  3. maven报错cannot change version of project facet

    用Eclipse创建Maven结构的web项目的时候选择了默认的catalog,由于这个catalog比较老,用的servlet还是2.3,而现在最少也是2.5,所以经常会出现问题,在Projecdt ...

  4. jquery 根据网站url给导航nav添加active效果

    后台的同事因为把nav公用了,所以无法单页添加active,一下方法通过判断url的后缀给当前页添加active $(function(){ var _nava= $('.nav .nav-wrapp ...

  5. GDI+创建Graphics对象的2种方式

      1.this.CreateGraphics()     // 调用控件的CreateGraphics()方法 2.在OnPaint事件中,PaintEventArgs类型的参数e对象的Graphi ...

  6. 检测浏览器是否支持AJAX

    <script type="text/javascript"> function ajaxFunction() { var xmlHttp; try { // Fire ...

  7. Android-----------国际化多国语言文件夹命名汇总

    *如果不区分地区,则不加后面的-rxx内容 Arabic, Egypt (ar_rEG) —————————–阿拉伯语,埃及      Arabic, Israel (ar_rIL) ———————— ...

  8. Intellij Idea 配置database 连接SQL Server 2012

    首先确认通过TCP IP来连接连接SQLServer 2012     确保 Server Authentication选择了SQL Server and Windows Authentication ...

  9. oracle 语句汇总

    Oracle数据库常用sql语句 ORACLE 常用的SQL语法和数据对象 一.数据控制语句 (DML) 部分 1.INSERT  (往数据表里插入记录的语句) INSERT INTO 表名(字段名1 ...

  10. 0122——UITabBarController

    UITabBarController是IOS中很常用的一个viewController.UITabBarController通常作为整个程序的rootViewController,而且不能添加到别的c ...