c语言调用android surface,Android GUI SurfaceFlinger

本文涉及的源代码基于 Android-7.1.1r。

一、Android GUI 框架

SurfaceFlinger 是 Android GUI 的核心,但是从 OpenGL_ES 的角度来看,它也只是个“应用程序”。Android 的显示系统大致框架图下图所示:

bf3b0f1cf495

GUI_STRUCT.png

下面就“由下向上”来逐一分析该框架。

(1) 显示驱动

Linux 内核提供了统一的 framebuffer 显示驱动。设备节点是 /dev/graphics/fb* 或 /dev/fb*,而 fb0 表示第一个 Monitor,当前系统实现中只用到了一个显示屏。

(2) HAL 层

Android 的 HAL 层提供了 Gralloc,包括 fb 和 gralloc 两个设备。

fb 负责打开内核中的 framebuffer,初始化配置,并提供 post,setSwapIntervel 等操作接口;

gralloc 用于管理帧缓冲区的分配和释放。

HAL 层还包含另一个重要模块 —— “Composer”,它为厂商自定制“UI合成”提供了接口。Composer 的直接使用者是 SurfaceFlinger 中的 HWComposer,HWComposer 除了负责管理 Composer 的 HAL 模块外,还负责 VSync 信号(软件、硬件)的产生和控制。

(3) FramebufferNativeWindow

FramebufferNativeWindow 是负责 OpenGL ES(通用函数库) 在 Android 平台上本地化的中介之一,它将 Android 的窗口系统与 OpenGL ES 产生联系,为 OpenGL ES 配置本地窗口的是 EGL。

(4) EGL

EGL 负责为 OpenGL ES 配合本地窗口。OpenGL ES 更多的只是一个接口协议,具体实现即可以采用软件,也可以采用硬件实现,而 EGL 会去读取 egl.cfg,并根据用户的设置来动态加载 libagl(软件实现)或是 libhgl(硬件实现)。

(5) DisplayDevice

SurfaceFlinger 中持有一个成员数组 mDisplays 用来描述系统中支持的各种"显示设备",具体有那些 Display 是由 SurfaceFlinger 在 readyToRun 中进行判断并赋值的。DisplayDevice 在初始化的时候会调用 eglGetDisplay,eglCreateWindowSurface 等接口,并利用 EGL 来完成 OpenGL ES 环境的搭建。

(6) OpenGL ES 模块

很多模块都可以调用 OpenGL ES 提供的 API,其中就包括 SurfaceFLinger 和 DisplayerDevice。

与 OpenGL ES 的相关的模块分为以下几类:

配置类:帮助 OpenGL ES 完成配置,包括 EGL,DisplayHardware 都属这一类。

依赖类:OpenGL ES 要运行的起来所依赖的“本地化”的东西,在上图中指的就是 FramebufferNativeWindow。

使用类:使用 OpenGL ES 的用户,如 DisplayDevice 即扮演了使用者,又扮演了构建 OpenGL ES 的配置者。

二、HAL

HAL 是子系统(显示系统、音频系统)与 Linux 内核驱动之间的统一接口。

HAL 需要解决以下问题:

硬件的抽象;

接口的稳定;

灵活的使用。

(1) 硬件抽象

HAL 多数使用 C 语言编写,而 C 语言不是面向对象的,所以具体的“继承”关系就没有像 C++ 或是 Java 这类的面向对象的语言表现的那么直接。在 C 语言中要实现类似的“继承”关系,只需要让子类的第一个成员变量是父类结构即可。以 Gralloc 为例,它就是 hw_module_t 的子类,代码如下:

typedef struct gralloc_module_t {

struct hw_module_t common; // 父类

// 结构体中定义函数指针(结构体中不能定义函数)

int (*registerBuffer)(struct gralloc_module_t const* module,

buffer_handle_t handle);

int (*unregisterBuffer)(struct gralloc_module_t const* module,

buffer_handle_t handle);

int (*lock)(struct gralloc_module_t const* module,

buffer_handle_t handle, int usage,

int l, int t, int w, int h,

void** vaddr);

int (*unlock)(struct gralloc_module_t const* module,

buffer_handle_t handle);

int (*perform)(struct gralloc_module_t const* module,

int operation, ... );

int (*lock_ycbcr)(struct gralloc_module_t const* module,

buffer_handle_t handle, int usage,

int l, int t, int w, int h,

struct android_ycbcr *ycbcr);

int (*lockAsync)(struct gralloc_module_t const* module,

buffer_handle_t handle, int usage,

int l, int t, int w, int h,

void** vaddr, int fenceFd);

int (*unlockAsync)(struct gralloc_module_t const* module,

buffer_handle_t handle, int* fenceFd);

int (*lockAsync_ycbcr)(struct gralloc_module_t const* module,

buffer_handle_t handle, int usage,

int l, int t, int w, int h,

struct android_ycbcr *ycbcr, int fenceFd);

void* reserved_proc[3];

} gralloc_module_t;

(2) 接口的稳定

HAL 中的接口必须是稳定不变的,Android 系统中已经预定好了这些接口,如下图所示(源码位置:hardware/libhardware/include/hardware):

bf3b0f1cf495

HAL接口.png

(3) 灵活的使用

硬件生产商只要按照 Android 提供的硬件要求来实现 HAL 接口,手机开发商只需要移植硬件生产商提供的 HAL 库就可以了。

三、Android 终端显示设备 ———— Gralloc 和 Framebuffer

Framebuffer 是 Linux 内核提供的图形硬件的抽象描述,它占用了系统内存的一部分,是一块包含屏幕显示信息的缓冲区。在 Android 中,Framebuffer 提供的设备文件节点是 /dev/graphics/fb*。这里以 sony Xperia 为例,它的 fb 节点如下图所示:

bf3b0f1cf495

Sony_Xperia_fb.png

Android 的子系统不会直接使用内核驱动,而是由 HAL 层来间接引用底层框架。显示系统也是一样,它通过 HAL 层来做操作帧缓冲区,而完成这一中介任务的就是 Gralloc。

3.1、Gralloc 模块的加载

Gralloc 对应的模块是在 FramebufferNativeWindow(GUI 结构图中位于 Grlloc 上方) 的构造函数中加载的(在 Androdi-7.1.1 中是通过 Gralloc1.cpp 进行加载的),即:

int err = hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module); //

hw_get_module 是上层使用(FramebufferNativeWindow)者加载 HAL 库的入口。lib 库有以下几种形式;

gralloc.[ro.hardware].so

gralloc.[ro.product.board].so

gralloc.[ro.board.platform].so

gralloc.[ro.arch].so

当以上文件都不存在时,就使用默认的:

gralloc.default.so

源码位置:hardware/libhardware/modules/gralloc/,由 gralloc.cpp,framebuffer.cpp 和 mapper.cpp 三个主要文件编译而成。

3.2、Gralloc 提供的接口

Gralloc 是 hw_module_t 的子类,hw_module_t 代码如下:

typedef struct hw_module_t {

uint32_t tag;

uint16_t module_api_version;

#define version_major module_api_version

uint16_t hal_api_version;

#define version_minor hal_api_version

const char *id;

const char *name;

const char *author;

struct hw_module_methods_t* methods; // hw_module_t 中必须提供

void* dso;

#ifdef __LP64__

uint64_t reserved[32-7];

#else

uint32_t reserved[32-7];

#endif

} hw_module_t;

typedef struct hw_module_methods_t {

// 函数指针,用于打开设备

int (*open)(const struct hw_module_t* module, const char* id,

struct hw_device_t** device);

} hw_module_methods_t;

任何硬件设备的 HAL 库都必须实现 hw_module_methods_t,该结构体中只有一个函数指针变量,也就是 open,由于函数体内不能定义函数,所以这里使用了函数指针。当上层使用者调用 hw_get_module 时,系统首先会在指定目录下加载正确的 HAL 库,然后通过 open 函数打开指定的设备。这里 open 方法对应的实现是 gralloc_device_open()@gralloc.cpp。open接口可以可以帮助上层使用者打开两种设备:

define GRALLOC_HARDWARE_FB0:主屏

define GRALLOC_HARDWARE_GPU0:负责图形缓冲区的分配和释放

define 前有“#”,由于格式问题,这里没有打出。

下面看 gralloc_device_open() 的实现:

// gralloc.cpp

int gralloc_device_open(const hw_module_t* module, const char* name,

hw_device_t** device)

{

int status = -EINVAL;

// 打开 gralloc 设备或是打开 fb 设备。

if (!strcmp(name, GRALLOC_HARDWARE_GPU0)) {

gralloc_context_t *dev;

dev = (gralloc_context_t*)malloc(sizeof(*dev));

/* initialize our state here */

memset(dev, 0, sizeof(*dev));

/* initialize the procs */

dev->device.common.tag = HARDWARE_DEVICE_TAG;

dev->device.common.version = 0;

dev->device.common.module = const_cast(module);

dev->device.common.close = gralloc_close;

dev->device.alloc = gralloc_alloc;

dev->device.free = gralloc_free;

*device = &dev->device.common;

status = 0;

} else {

status = fb_device_open(module, name, device); // 打开 Framebuffer

}

return status;

}

下面看 framebuffer 设备的打开过程:

int fb_device_open(hw_module_t const* module, const char* name,

hw_device_t** device)

{

int status = -EINVAL;

if (!strcmp(name, GRALLOC_HARDWARE_FB0)) {

/* initialize our state here */

fb_context_t *dev = (fb_context_t*)malloc(sizeof(*dev)); // 分配 hw_device_t 空间,这只是一个“壳”

memset(dev, 0, sizeof(*dev)); // 初始化

/* initialize the procs */

dev->device.common.tag = HARDWARE_DEVICE_TAG;

dev->device.common.version = 0;

dev->device.common.module = const_cast(module);

// 核心接口

dev->device.common.close = fb_close;

dev->device.setSwapInterval = fb_setSwapInterval;

dev->device.post = fb_post;

dev->device.setUpdateRect = 0;

private_module_t* m = (private_module_t*)module;

status = mapFrameBuffer(m); // 内存映射 mmap

if (status >= 0) {

int stride = m->finfo.line_length / (m->info.bits_per_pixel >> 3);

int format = (m->info.bits_per_pixel == 32)

? (m->info.red.offset ? HAL_PIXEL_FORMAT_BGRA_8888 : HAL_PIXEL_FORMAT_RGBX_8888)

: HAL_PIXEL_FORMAT_RGB_565;

const_cast(dev->device.flags) = 0;

const_cast(dev->device.width) = m->info.xres;

const_cast(dev->device.height) = m->info.yres;

const_cast(dev->device.stride) = stride;

const_cast(dev->device.format) = format;

const_cast(dev->device.xdpi) = m->xdpi;

const_cast(dev->device.ydpi) = m->ydpi;

const_cast(dev->device.fps) = m->fps;

const_cast(dev->device.minSwapInterval) = 1;

const_cast(dev->device.maxSwapInterval) = 1;

*device = &dev->device.common; // 核心

}

}

return status;

}

其中 fb_context_t 是 framebuffer 内部使用的一个类,它包含了众多信息,而最终返回的 device 只是其内部的 device.common。这种“通用和差异”并存的编码风格在 HAL 层非常常见。

fb_context_t 唯一的成员就是 framebuffer_device_t,这是对 frambuffer 设备的统一描述。

struct fb_context_t {

framebuffer_device_t device;

};

一个标准的 fb 设备通常要提供如下的函数实现:

int(post)(struct framebuffer_device_t dev, buffer_handle_t buffer);

将 buffer 数据 post 到显示屏上。要求 buffer 必须与屏幕尺寸一致,并且没有被 locked。这样的话

buffer 内容将在下一次 VSYNC 中被显示出来。

int(setSwapInterval)(struct framebuffer_device_t window, int interval);

设置两个缓冲区交换的时间间隔

int(setUpdateRect)(struct framebuffer_device_t window, int left, int top, int width, int height);

设置刷新区域,需要 framebuffer 驱动支持“update-on-demand”。也就是说在这个区域外的数据很可能

被认为无效。

framebuffer_device_t 中的重要成员变量:

typedef struct framebuffer_device_t {

struct hw_device_t common;

const uint32_t flags; // 用来记录系统帧缓冲区的标志

const uint32_t width; // 用来描述设备显示屏的宽度

const uint32_t height; // 用来描述设备显示屏的高度

const int stride; // 用来描述设备显示屏的一行有多少个像素点

const int format; // 用来描述系统帧缓冲区的像素格式

const float xdpi; // 用来描述设备显示屏在宽度上的密度

const float ydpi; // 用来描述设备显示屏在高度上的密度

const float fps; // 用来描述设备显示屏的刷新频率

const int minSwapInterval; // 用来描述帧缓冲区交换前后两个图形缓冲区的最小时间间隔

const int maxSwapInterval; // 用来描述帧缓冲区交换前后两个图形缓冲区的最大时间间隔

int reserved[8];//保留

// 用来设置帧缓冲区交换前后两个图形缓冲区的最小和最大时间间隔

int (*setSwapInterval)(struct framebuffer_device_t* window,int interval);

// 用来设置帧缓冲区的更新区域

int (*setUpdateRect)(struct framebuffer_device_t* window,int left, int top, int width, int height);

// 用来将图形缓冲区buffer的内容渲染到帧缓冲区中去

int (*post)(struct framebuffer_device_t* dev, buffer_handle_t buffer);

// 用来通知 fb 设备,图形缓冲区的组合工作已经完成

int (*compositionComplete)(struct framebuffer_device_t* dev);

void (*dump)(struct framebuffer_device_t* dev, char *buff, int buff_len);

int (*enableScreen)(struct framebuffer_device_t* dev, int enable);

// 保留

void* reserved_proc[6];

} framebuffer_device_t;

变 量

描 述

uint32_t flags

标志位,指示framebuffer 的属性配置

uint32_t width; uint32_t height;

framebuffer 的宽和高,以像素为单位

int format

framebuffer 的像素格式,比如:HAL_PIXEL_FORMAT_RGBA_8888,HAL_PIXEL_FORMAT_RGBX_8888,HAL_PIXEL_FORMAT_RGB_888,HAL_PIXEL_FORMAT_RGB_565 等等

float xdpi;float ydpi;

x和y轴的密度(pixel per inch)

float fps

屏幕的每秒刷新频率,假如无法正常从设备获取的话,默认设置为 60Hz

int minSwapInterval;int maxSwapInterval;

该 framebuffer 支持的最小和最大缓冲交换时间

我们以下面简图来小结对 Gralloc 的分析:

[图片上传失败...(image-f944da-1542183518291)]

四、Android 本地窗口

Native Window为OpenGL与本地窗口系统之间搭建了桥梁。整个GGUI系统至少需要两种本地窗口:

面向管理者(SurfaceFlinger)

SurfaceFlinger 是系统中所有 UI 界面的管理者,需要直接或间接的持有“本地窗口”,此本地窗口是

FramebufferNativeWindow(4.2+ 后被废弃)。

面向应用程序

这类本地窗口是 Surface。

正常情况按照 SDK 向导生成 APK 应用程序,是采用 Skia 等第三方图形库,而对于希望使用 OpenGL ES 来完成复杂界面渲染的应用开发者来说,Android 也提供封装的 GLSurfaceView(或其他方式)来实现图形显示。

4.1、FramebufferNativeWindow

EGL 需要根据本地窗口来为 OpenGL/OpenGL ES 创造环境,但是不论哪一类本地窗口都需要和“本地窗口类型”保持一致。

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

...

typedef HWND EGLNativeWindowType;

#elif defined(__WINSCW__) || defined(__SYMBIAN32__) /* Symbian */

typedef int EGLNativeDisplayType;

typedef void *EGLNativeWindowType;

typedef void *EGLNativePixmapType;

#elif defined(__ANDROID__) || defined(ANDROID) // Android 系统

struct ANativeWindow;

struct egl_native_pixmap_t;

typedef struct ANativeWindow* EGLNativeWindowType;

...

#elif defined(__unix__) // unix 系统

...

typedef Window EGLNativeWindowType;

#else

#error "Platform not recognized"

#endif

...

EGLNativeWindowType 在不同系统中对应不同的数据类型,而在 Android 中对应的是 ANativeWindow 指针。

// /system/core/include/system/window.h

struct ANativeWindow

{

...

const uint32_t flags; // 与 Surface 或 update 有关的属性

const int minSwapInterval; // 最小交换时间间隔

const int maxSwapInterval; // 最大交换时间间隔

const float xdpi; // 水平方向密度 dpi

const float ydpi; // 垂直方向密度 dpi

intptr_t oem[4];

...

// 设置交换时间

int (*setSwapInterval)(struct ANativeWindow* window,

int interval);

// 向本地窗口查询相关信息

int (*query)(const struct ANativeWindow* window,

int what, int* value);

// 用于执行本地窗口的相关操作

int (*perform)(struct ANativeWindow* window,

int operation, ... );

int (*cancelBuffer_DEPRECATED)(struct ANativeWindow* window,

struct ANativeWindowBuffer* buffer);

// EGL 通过该接口来申请 buffer

int (*dequeueBuffer)(struct ANativeWindow* window,

struct ANativeWindowBuffer** buffer, int* fenceFd);

// EGL 对 buffer 渲染完成后就调用该接口,来 unlock 和 post buffer

int (*queueBuffer)(struct ANativeWindow* window,

struct ANativeWindowBuffer* buffer, int fenceFd);

// 取消一个已经 dequeue 的 buffer

int (*cancelBuffer)(struct ANativeWindow* window,

struct ANativeWindowBuffer* buffer, int fenceFd);

};

ANativeWindow 更像一份“协议”,规定了本地窗口的形态和功能。下面来分析 FramebufferNativeWindow 是如何履行“协议”的。

(1) FramebufferNativeWindow 构造函数

FramebufferNativeWindow 构造函数的功能包括:

加载 Gralloc 模块(GRALLOC_HARDWARE_MODULE_ID)。

打开 fb 和 gralloc(gpu0) 设备,打开后由 fbDev 和 grDev 管理。

根据设备属性为 FramebufferNativeWindow 赋初值。

根据 FramebufferNativeWindow 的实现来填充 ANativeWindow 中的“协议”。

其他必要的初始化。

所有申请到的缓冲区都由 FramebufferNativeWindow 中的 buffers[] 来记录,每个元素是一个 NativeBuffer,该类继承了 ANativeWindowBuffer, 该类的声明如下:

// /system/core/include/system/window.h

typedef struct ANativeWindowBuffer

{

...

int width;

int height;

int stride;

int format;

int usage;

void* reserved[2];

buffer_handle_t handle; // 代表内存块的句柄

void* reserved_proc[8];

} ANativeWindowBuffer_t;

(2) dequeueBuffer

int FramebufferNativeWindow::dequeueBuffer(ANativeWindow* window,

ANativeWindowBuffer** buffer)

{

FramebufferNativeWindow* self = getSelf(window);

Mutex::Autolock _l(self->mutex);

// 从 FramebufferNativeWindow 对象中取出 fb 设备描述符,在构造 FramebufferNativeWindow 对象时,已经打开了 fb 设备

framebuffer_device_t* fb = self->fbDev;

// 计算当前申请的图形缓冲区在 buffers 数组中的索引,同时将下一个申请的 buffe r的索引保存到 mBufferHead 中

int index = self->mBufferHead++;

// 如果申请的下一个 buffer 的索引大于或等于 buffer 总数,则将下一个申请的 buffer 索引设置为 0,这样就实现了对 buffer 数组的循环管理

if (self->mBufferHead >= self->mNumBuffers)

self->mBufferHead = 0;

// 如果当前没有空闲的 buffer,即 mNumFreeBuffers = 0,则线程睡眠等待 buffer 的释放

while (!self->mNumFreeBuffers) {

self->mCondition.wait(self->mutex);

}

// 存在了空闲 buffer,线程被唤醒继续执行,由于此时要申请一块 buffer,因此空闲 buffer 的个数又需要减 1

self->mNumFreeBuffers--;

// 保存当前申请的 buffer 在缓冲区数组中的索引位置

self->mCurrentBufferIndex = index;

// 得到 buffer 数组中的 NativeBuffer 对象指针

*buffer = self->buffers[index].get();

return 0;

}

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

4.2、SurfaceView

Surface 也继承了 ANativeWindow:

class Surface: public ANativeObjectBase{ ... }

Surface 是面向 Android 系统中所有 UI 应用程序的,即它承担着应用进程中的 UI 显示需求。

Surface 需要面向上层实现(主要是 Java 层)提供绘制图像的画板。SurfaceFlinger 需要收集系统中所有应用程序绘制的图像数据,然后集中显示到物理屏幕上。Surface 需要扮演相应角色,本质上还是由 SurfaceFlinger 服务统一管理的,涉及到很多跨进程的通信细节。

下面来看 Surface 中的关键成员变量:

成员变量

说明

sp mGraphicsBufferProducer

Surface 核心变量

BufferSlot mSlots[32]

Surface 内部存储 buffer 的地方,BufferSlot 内不包括:GraphicsBuffer 和 dirtyRegion,当用户 dequeue 时将申请内存

Surface 将通过 mGraphicBufferProducer 来获取 buffer,这些缓冲区会被记录在 mSlots 中数据中。mGraphicBufferProducer 这一核心成员的初始化流程如下:

ViewRootImpl 持有一个 Java 层的 Surface 对象(mSurface)。

ViewRootImpl 向 WindowManagerService 发起 relayout 请求,此时 mSurface 被赋予真正的有效值,

将辗转生成的 SurfaceControl 通过S urface.copyFrom() 函数复制到 mSurface 中。

由此,Surface 由 SurfaceControl 管理,SurfaceControl 由 SurfaceComposerClient 创建。SurfaceComposerClient 获得的匿名 Binder 是 ISurfaceComposer,其服务端实现是 SurfaceFlinger。而 Surface 依赖的 IGraphicBufferProducer 对象在 Service 端的实现是 BufferQueue。

class SurfaceFlinger :

public BinderService, // 在 ServiceManager 中注册为 SurfaceFlinger

public BnSurfaceComposer, // 实现的接口却叫 ISurfaceComposer

Buffer,Consumer,Producer 是“生产者-消费者”模型中的 3 个参与对象,如何协调好它们的工作是应用程序能否正常显示UI的关键。Buffer 是 BufferQueue,Producer 是应用程序,Consumer 是 SurfaceFlinger。

五、BufferQueue

To be continued ....

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/559616.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

bat 取得服务列表_解读浩泽净水2018业绩:稳定增长背后的服务深化和科技跃进...

北京时间3月26日凌晨,苹果以一场没有任何硬件亮相的“软”发布会,宣告公司将向服务转型,欲以可持续的服务收入抵消iPhone遭遇的增长瓶颈。此举在大洋彼岸的中国引发了热烈反响。分析师们普遍认为苹果在“偷师”中国的BAT等互联网企业&#xf…

c语言实现多目标优化,MOPSO 多目标例子群优化算法

近年来,基于启发式的多目标优化技术得到了很大的发展,研究表明该技术比经典方法更实用和高效。有代表性的多目标优化算法主要有NSGA、NSGA-II、SPEA、SPEA2、PAES和PESA等。粒子群优化(PSO)算法是一种模拟社会行为的、基于群体智能的进化技术&#xff0c…

软件工程结构化建模的方法和工具_软件工程导论知识点梳理之概念题

1. 软件的定义:软件是程序、数据及开发、使用和维护程序需要的所有文档的完整集合。例:在信息处理和计算机领域内,一般认为软件是程序、文档和数据。2.软件的分类:按照应用场景:系统/应用软件按照功能:服务…

xcode w情ndows版,xcode Windows版安装使用介绍

在Windows安装xcode进行开发iOS应用可以省去昂贵的苹果机、体验虽然没那么好,但也能用。现在也有很多APP跨平台开发工具,在Windows就能完成开发测试,但上架iOS APP还是得要Mac苹果电脑去申请iOS证书和上传IPA到App Store。苹果电脑价格昂贵&a…

esp mounter pro_对比 | 以大欺小?剑指宋Pro和哈弗H6,欧尚X7的黑马潜质从何而来?...

随着近几年国内SUV市场的火爆,各个品牌也都先后推出并持续更新着自家的紧凑型SUV产品,目前在该细分领域里,哈弗H6一直都是稳居榜首的存在,作为后起之秀的比亚迪宋Pro则紧随其后。但在长安发力战略布局后,长安欧尚X7便以…

randomized algorithms 有哪些_毛毯分类有哪些 毛毯的种类

毛毯分类有哪些 毛毯的种类毛毯分类有哪些 毛毯的种类 a)毛毯分为双人毛毯、单人毛毯、童毯等多种规格。 b)按用途还可以床毯、膝毯、沙滩毯等。 c)毛毯还可以进行特殊整理:阻燃毛毯、防菌整理、负离子整理等等。 d)下面我按织造方式进行介绍: 一.机织毛毯 1.按原料分 羊毛毛毯…

floodlight ovs 更改拓扑_淘宝更改类目降权多久?被降权了怎么办?_推广运营(淘宝天猫)...

现在淘宝 (淘宝论坛)店主在经营店铺的过程中,会考虑将自己特别店铺进行类目的更改,那么也有一点店主会担心到如果淘宝更改类目会被降权吗?如果被降权了怎么办呢?那就一起了解具体的内容吧!跟大家分享一些淘宝宝贝被降权的处理方式:1、虚假销…

android 事务管理软件,安卓 Android基于安卓移动终端的个人事务管理系统

摘 要随着移动平台的崛起,越来越多的传统PC软件被移植到移动平台,比如ipad,iphone,Android等智能终端设备,在这些平台中,Android占领着最大的市场份额,所以为Android用户开发满足日常使用的软件…

word无法打开请去应用商店_word软件是什么?word文档是什么?可以用来干什么?...

1、为什么需要Microsoft Word?仅在计算机上安装操作系统不足以提高生产力。无论是创建电子表格,演示文稿,电子邮件还是文档,您都需要软件来执行所需的任何活动。Microsoft Word用于创建文档或您需要存储文本的任何内容。如果您购买…

android四个按钮平分,android 水平平分两个按钮

项目中需要显示水平两个按钮,且都要有间距,如下图所示:首先我想到的是使用权重,然后利用水平布局,这样应该可以实现,但真实的情况是这样的,代码如下:android:layout_width"fill…

iPhone清理喇叭灰尘_iphone正确清理扬声器灰尘的方法

夏天来临各种各样的手机问题就都出现了,手机发热已经成为了热议话题,网上的妙招也是层出不穷甚至有销售水冷装置风扇手柄的,或者diy给手机加铜钹,又或者使用石墨烯等等的方法,但还是避免不了发热,除手机发热…

Android搭建web,Android手机搭建WEB环境

原文:http://yangshare.com/Y-BLOG/?p246#more-246准备1、下载Android处理器对应的jdk包,类似树莓派archandroidlocalhost:~$ archarmv7l下载地址:jdk-8u152-linux-arm32-vfp-hflt.tar.gz2、安装一个xftp或者宝塔面板这样的文件操作工具xftp…

qq浏览器网页版_QQ邮箱回应部分用户登录异常:系后台服务波动,问题已解决...

5月6日消息,针对用户反映QQ邮箱登录异常情况,腾讯QQ邮箱官方回应称,因后台服务波动,部分用户出现登录异常情况,目前问题已解决。5月6日上午,有网友反映QQ邮箱崩溃,换浏览器依然无法登录&#xf…

android nfc ndef mifareclassic,Android NFC开发-实践篇

Android NFC开发-实践篇https://blog..net/_GYG/article/details/72899417在Android NFC开发-理论篇中,我们了解了在Android中开发NFC的一些理论知识,这篇我们继续应用我们上一篇学到的知识,实现对NDEF格式标签和MifareClassic格式标签的读写…

苹果6换屏多钱_苹果手机屏幕碎了怎么办?维修更换要多少钱

手机在使用过程中最容易发生的意外就是手滑摔碎屏幕了,那么苹果手机屏幕碎了碎了,维修更换要多少钱?大家都知道,苹果手机摔坏,进水均属于人为损坏,人为损坏不属保修范围,接下来针对苹果手机屏幕…

harmonyos sdk,HarmonyOS SDK对应的API版本跃迁引发的历史工程适配问题解决方案

历史工程自动适配由于最新版本的HarmonyOS SDK对应的API Version发生了跃迁,原有的API Version 3变成了当前的API Version 4,原有的API Version 4变成了当前的API Version 5。因此,使用最新版本的DevEco Studio打开历史工程,需要对…

dataframe 拼接_拼接关系图在石材生产过程中的重要性

石材生产加工中应用许多图,石材纹理图、平面面置图、平面图、立面图、剖面图,这些图对石材生产加工都有很大的帮助,发挥着各自的作用,担负着各自的角色。除了这些图外,石材生产加工中还有一种图—拼接关系图&#xff0…

app名字变为android+api,一起来做个app吧 wanandroid开放API

由于早期开放的一些API页码为0开始,后期接口修改为从1开始,为了兼顾之前的开放API,故无法统一。对于POST接口建议使用postman模拟在编写过程中如果遇到一些问题,也有一些参考项目,这里针对Java和Kotlin各自选择了一款&…

php 武汉海关对接_“双11”临近 海口海关全力备战跨境电商监管高峰

中新网海南新闻11月6日电(李佳臣)海口海关6日发布消息称,面对“双11”这一中国电商行业的年度盛事,海口马村港海关已做好准备,确保“双11”期间跨境电商业务24小时即时通关,包裹通关、出区“零等待”。为迎接即将到来的“双11”网…

嵌入式全栈工程师_我花了半个月,整理出了这篇嵌入式开发学习指南(学习路线+知识点梳理)...

不好意思久等了这篇文章让小伙伴们久等了。一年多以来,关于嵌入式开发学习路线、规划、看什么书等问题,被问得没有一百,也有大几十次了。但是无奈自己对这方面了解有限,所以每次都没法交代,搞得实在不好意思。但是办法…