Android 12(S) 图形显示系统 - 示例应用(二)

2023-11-07

1 前言

为了更深刻的理解Android图形系统抽象的概念和BufferQueue的工作机制,这篇文章我们将从Native Level入手,基于Android图形系统API写作一个简单的图形处理小程序。透过这个小程序我们将学习如何使用Native API创建Surface,如何请求图形缓冲区,如何向图形缓冲区中写入数据等知识。Talk is cheap, show me the code。让我们马上开始吧!

注:本系列文章的分析及代码均基于Android 12(S) Source Code,可参考:AOSPXRef  或 http://aosp.opersys.com/

2 程序源码简介

  • 源码下载

地址:https://github.com/yrzroger/NativeSFDemo

注:可以git命令下载(比如git clone git@github.com:yrzroger/NativeSFDemo.git)或直接Download ZIP解压后使用

  • 源码编译

本demo程序是基于Android源码编译环境开发的,所以需要放到Android源码目录下编译。

将上一步中下载的的源码放到Android源码的合适目录下,然后执行mm进行编译,得到可执行档 NativeSFDemo

  • 源码运行

将可执行档NativeSFDemo放到目标测试平台/system/bin下(比如:adb push NativeSFDemo /system/bin/)

然后执行 adb shell NativeSFDemo

  • 效果展示

程序中去绘制单色背景: 红色->绿色->蓝色背景交替展示,如下图所示:

至此你已经收获一个可以供后续学习研究的demo小程序了 !!!


Tips:

Android源码是一个宝藏,即提供了丰富多彩的APIs供开发者使用,又可以在其中搜索到很多有价值的APIs使用实例。本文中提供的演示Demo亦是基于源码中的参考来完成的。

我把参考位置列于此:

参考1:/frameworks/av/media/libstagefright/SurfaceUtils.cpp 

参考2:/frameworks/native/libs/gui/tests/CpuConsumer_test.cpp


3 程序源码分析

在显示子系统中,Surface 是一个接口,供生产者与消费者交换缓冲区。通过Surface我们就能向BufferQueue请求Buffer,并和Android Native窗口系统建立连接。本文展示的demo就是基于Surface建立起来的。

  • 封装类NativeSurfaceWrapper

NativeSurfaceWrapper是对Surface的一层封装,用于获取屏幕参数并创建与配置Surface属性。

首先看到头文件中该类的定义:

class NativeSurfaceWrapper : public RefBase {
public:
    NativeSurfaceWrapper(const String8& name);
    virtual ~NativeSurfaceWrapper() {}

    virtual void onFirstRef();

    // Retrieves a handle to the window.
    sp<ANativeWindow>  getSurface() const;

    int width() { return mWidth; }
    int height() { return mHeight; }

private:
    DISALLOW_COPY_AND_ASSIGN(NativeSurfaceWrapper);
    ui::Size limitSurfaceSize(int width, int height) const;

    sp<SurfaceControl> mSurfaceControl;
    int mWidth;
    int mHeight;
    String8 mName;
};

NativeSurfaceWrapper继承自Refbase,这样就可以使用智能指针sp,wp来管理其对象,避免内存泄漏。

同时可以重写onFirstRef方法,在创建NativeSurfaceWrapper对象第一次被引用时调用onFirstRef做一些初始化操作。

下面是onFirstRef的定义:

void NativeSurfaceWrapper::onFirstRef() {
    sp<SurfaceComposerClient> surfaceComposerClient = new SurfaceComposerClient;
    status_t err = surfaceComposerClient->initCheck();
    if (err != NO_ERROR) {
        ALOGD("SurfaceComposerClient::initCheck error: %#x\n", err);
        return;
    }

    // Get main display parameters.
    sp<IBinder> displayToken = SurfaceComposerClient::getInternalDisplayToken();
    if (displayToken == nullptr)
        return;

    ui::DisplayMode displayMode;
    const status_t error =
            SurfaceComposerClient::getActiveDisplayMode(displayToken, &displayMode);
    if (error != NO_ERROR)
        return;

    ui::Size resolution = displayMode.resolution;
    resolution = limitSurfaceSize(resolution.width, resolution.height);
    // create the native surface
    sp<SurfaceControl> surfaceControl = surfaceComposerClient->createSurface(mName, resolution.getWidth(), 
                                                                             resolution.getHeight(), PIXEL_FORMAT_RGBA_8888,
                                                                             ISurfaceComposerClient::eFXSurfaceBufferState,
                                                                             /*parent*/ nullptr);

    SurfaceComposerClient::Transaction{}
            .setLayer(surfaceControl, std::numeric_limits<int32_t>::max())
            .show(surfaceControl)
            .apply();

    mSurfaceControl = surfaceControl;
    mWidth = resolution.getWidth();
    mHeight = resolution.getHeight();
}

onFirstRef中完成主要工作:

1. 创建一个SurfaceComposerClient对象,这是SurfaceFlinger的Client端,它将建立和SurfaceFlinger服务的通信;

2. 获取屏幕参数,SurfaceComposerClient::getActiveDisplayMode获取当前的DisplayMode,其中可以得到resolution信息;

3. 创建Surface & SurfaceControl,createSurface方法会通过Binder通信机制一直呼叫到SurfaceFlinger,SurfaceFlinger会进行创建Layer等操作;

4. createSurface时会设置width,height,format等信息;

5. setLayer,设置窗口的z-order,SurfaceFlinger根据z-Oder决定窗口的可见性及可见大小;

6. show,让当前窗口可见;

7. apply,使透过Transaction进行的设置生效,属性信息传给SurfaceFlinger;


Tips:

创建Surface的过程会涉及到与SurfaceFlinger的互动,SurfaceFlinger是一个系统级的服务,负责创建/管理/合成Surface对应的Layer,这部分我们本文暂不展开,之后文章中会陆续讲解。


limitSurfaceSize方法

该方法的作用是将width和height限制在设备GPU支持的范围内。

ui::Size NativeSurfaceWrapper::limitSurfaceSize(int width, int height) const {
    ui::Size limited(width, height);
    bool wasLimited = false;
    const float aspectRatio = float(width) / float(height);

    int maxWidth = android::base::GetIntProperty("ro.surface_flinger.max_graphics_width", 0);
    int maxHeight = android::base::GetIntProperty("ro.surface_flinger.max_graphics_height", 0);

    if (maxWidth != 0 && width > maxWidth) {
        limited.height = maxWidth / aspectRatio;
        limited.width = maxWidth;
        wasLimited = true;
    }
    if (maxHeight != 0 && limited.height > maxHeight) {
        limited.height = maxHeight;
        limited.width = maxHeight * aspectRatio;
        wasLimited = true;
    }
    SLOGV_IF(wasLimited, "Surface size has been limited to [%dx%d] from [%dx%d]",
             limited.width, limited.height, width, height);
    return limited;
}

该方法会将屏幕的width/height和max_graphics_width/max_graphics_height进行比较,取较小者作为创建Surface的参数。

这一点也是Android 12引入的一个新特性。getActiveDisplayMode获取到的是屏幕的真实分辨率(real display resolution),但GPU可能不支持高分辨率的UI合成,所以必须对framebuffer size做出限制。

比如设备可以4K分辨率进行video的解码和渲染,但由于硬件限制application UI只能以1080P进行合成。

  • NativeSFDemo的main方法

main方法比较简单

1. signal函数注册监听SIGINT信号的handler,也就是保证Ctrl+C退出程序的完整性;

2. 创建NativeSurfaceWrapper对象,并调用drawNativeSurface进行图片的绘制;

int main() {
    signal(SIGINT, sighandler);

    sp<NativeSurfaceWrapper> nativeSurface(new NativeSurfaceWrapper(String8("NativeSFDemo")));
    drawNativeSurface(nativeSurface);
    return 0;
}

按下Ctrl+C退出程序时,呼叫到sighandler将mQuit这个标志设为true,这样会使图片的while循环就可以正常流程退出了

void sighandler(int num) {
    if(num == SIGINT) {
        printf("\nSIGINT: Force to stop !\n");
        mQuit = true;
    }
}

  • drawNativeSurface方法

绘制图片的核心逻辑都在这个方法中,我们先看一下代码:

int drawNativeSurface(sp<NativeSurfaceWrapper> nativeSurface) {
    status_t err = NO_ERROR;
    int countFrame = 0;
    ANativeWindowBuffer *nativeBuffer = nullptr;
    ANativeWindow* nativeWindow = nativeSurface->getSurface().get();

    // 1. connect the ANativeWindow as a CPU client. Buffers will be queued after being filled using the CPU
    err = native_window_api_connect(nativeWindow, NATIVE_WINDOW_API_CPU);
    if (err != NO_ERROR) {
        ALOGE("ERROR: unable to native_window_api_connect\n");
        return EXIT_FAILURE;
    }

    // 2. set the ANativeWindow dimensions
    err = native_window_set_buffers_user_dimensions(nativeWindow, nativeSurface->width(), nativeSurface->height());
    if (err != NO_ERROR) {
        ALOGE("ERROR: unable to native_window_set_buffers_user_dimensions\n");
        return EXIT_FAILURE;
    }

    // 3. set the ANativeWindow format
    err = native_window_set_buffers_format(nativeWindow, PIXEL_FORMAT_RGBX_8888);
    if (err != NO_ERROR) {
        ALOGE("ERROR: unable to native_window_set_buffers_format\n");
        return EXIT_FAILURE;
    }

    // 4. set the ANativeWindow usage
    err = native_window_set_usage(nativeWindow, GRALLOC_USAGE_SW_WRITE_OFTEN);
    if (err != NO_ERROR) {
        ALOGE("native_window_set_usage failed: %s (%d)", strerror(-err), -err);
        return err;
    }

    // 5. set the ANativeWindow scale mode
    err = native_window_set_scaling_mode(nativeWindow, NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW);
    if (err != NO_ERROR) {
        ALOGE("native_window_set_scaling_mode failed: %s (%d)", strerror(-err), -err);
        return err;
    }

    // 6. set the ANativeWindow permission to allocte new buffer, default is true
    static_cast<Surface*>(nativeWindow)->getIGraphicBufferProducer()->allowAllocation(true);

    // 7. set the ANativeWindow buffer count
    int numBufs = 0;
    int minUndequeuedBufs = 0;

    err = nativeWindow->query(nativeWindow,
            NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &minUndequeuedBufs);
    if (err != NO_ERROR) {
        ALOGE("error: MIN_UNDEQUEUED_BUFFERS query "
                "failed: %s (%d)", strerror(-err), -err);
        goto handle_error;
    }

    numBufs = minUndequeuedBufs + 1;
    err = native_window_set_buffer_count(nativeWindow, numBufs);
    if (err != NO_ERROR) {
        ALOGE("error: set_buffer_count failed: %s (%d)", strerror(-err), -err);
        goto handle_error;
    }

    // 8. draw the ANativeWindow
    while(!mQuit) {
        // 9. dequeue a buffer
        int hwcFD = -1;
        err = nativeWindow->dequeueBuffer(nativeWindow, &nativeBuffer, &hwcFD);
        if (err != NO_ERROR) {
            ALOGE("error: dequeueBuffer failed: %s (%d)",
                    strerror(-err), -err);
            break;
        }

        // 10. make sure really control the dequeued buffer
        sp<Fence> hwcFence(new Fence(hwcFD));
        int waitResult = hwcFence->waitForever("dequeueBuffer_EmptyNative");
        if (waitResult != OK) {
            ALOGE("dequeueBuffer_EmptyNative: Fence::wait returned an error: %d", waitResult);
            break;
        }

        sp<GraphicBuffer> buf(GraphicBuffer::from(nativeBuffer));

        // 11. Fill the buffer with black
        uint8_t* img = nullptr;
        err = buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img));
        if (err != NO_ERROR) {
            ALOGE("error: lock failed: %s (%d)", strerror(-err), -err);
            break;
        }

        //12. Draw the window
        countFrame = (countFrame+1)%3;
        fillRGBA8Buffer(img, nativeSurface->width(), nativeSurface->height(), buf->getStride(),
                        countFrame == 0 ? 255 : 0,
                        countFrame == 1 ? 255 : 0,
                        countFrame == 2 ? 255 : 0);

        err = buf->unlock();
        if (err != NO_ERROR) {
            ALOGE("error: unlock failed: %s (%d)", strerror(-err), -err);
            break;
        }

        // 13. queue the buffer to display
        int gpuFD = -1;
        err = nativeWindow->queueBuffer(nativeWindow, buf->getNativeBuffer(), gpuFD);
        if (err != NO_ERROR) {
            ALOGE("error: queueBuffer failed: %s (%d)", strerror(-err), -err);
            break;
        }

        nativeBuffer = nullptr;
        sleep(1);
    }

handle_error:
    // 14. cancel buffer
    if (nativeBuffer != nullptr) {
        nativeWindow->cancelBuffer(nativeWindow, nativeBuffer, -1);
        nativeBuffer = nullptr;
    }

    // 15. Clean up after success or error.
    err = native_window_api_disconnect(nativeWindow, NATIVE_WINDOW_API_CPU);
    if (err != NO_ERROR) {
        ALOGE("error: api_disconnect failed: %s (%d)", strerror(-err), -err);
    }

    return err;
}

处理的大概过程

1. 获取我们已经创建Surface的窗口ANativeWindow,作为CPU客户端来连接ANativeWindow,CPU填充buffer数据后入队列进行后续处理;

2. 设置Buffer的大小尺寸native_window_set_buffers_user_dimensions;

3. 设置Buffer格式,可选,之前创建Layer的时候已经设置了;

4. 设置Buffer的usage,可能涉及protected的内容,这里我们简单设为GRALLOC_USAGE_SW_WRITE_OFTEN;

5. 设置scale模式,如果上层给的数据,比如Video,超出Buffer的大小后,怎么处理,是截取一部分还是,缩小;

6. 设置permission允许分配新buffer,默认true;

7. 设置Buffer数量,即BufferQueue中有多少个buffer可以用;

8. 下面的流程就是请求buffer并进行绘制图像的过程

9. dequeueBuffer先请求一块可用的Buffer,也就是FREE的Buffer;

10. Buffer虽然是Free的,但是在异步模式下,Buffer可能还在使用中,需要等到Fence才能确保buffer没有在被使用;

11. lock方法可以获取这块GraphicBuffer的数据地址;

12. 绘制图像,即把图像颜色数据写入Buffer里面,我们这里使用fillRGBA8Buffer来填充纯色图片;

13. 将绘制好的Buffer,queue到Buffer队列中,入队列后的buffer就可以被消费者处理或显示了;

14. 错误处理,取消掉Buffer,cancelBuffer;

15. 断开BufferQueue和窗口的连接,native_window_api_disconnect。

  • fillRGBA8Buffer
void fillRGBA8Buffer(uint8_t* img, int width, int height, int stride, int r, int g, int b) {
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            uint8_t* pixel = img + (4 * (y*stride + x));
            pixel[0] = r;
            pixel[1] = g;
            pixel[2] = b;
            pixel[3] = 0;
        }
    }
}

fillRGBA8Buffer用指定的RGBA填充buffer数据,我们设置的颜色格式为PIXEL_FORMAT_RGBX_8888,所以每个像素点均由4个字节组成,前3个字节分别为R/G/B颜色分量。


我们可以通过执行 dumpsys SurfaceFlinger 来查看图层的信息

Display 4629995328241972480 HWC layers:
---------------------------------------------------------------------------------------------------------------------------------------------------------------
 Layer name
           Z |  Window Type |  Comp Type |  Transform |   Disp Frame (LTRB) |          Source Crop (LTRB) |     Frame Rate (Explicit) (Seamlessness) [Focused]
---------------------------------------------------------------------------------------------------------------------------------------------------------------
 bbq-wrapper#0
  rel      0 |            0 |     CLIENT |          0 |    0    0 1920 1080 |    0.0    0.0 1920.0 1080.0 |                                              [ ]
---------------------------------------------------------------------------------------------------------------------------------------------------------------

看到了没,一个名字为“bbq-wrapper#0”的Layer显示在最上层,也就是我们应用显示的图层,看到这里你一定有个疑问,我们设置的Surface Name不是“NativeSFDemo”吗 ?

dumpsys SurfaceFlinger信息中,我们还可以看到如下内容:

+ BufferStateLayer (NativeSFDemo#0) uid=0
  Region TransparentRegion (this=0 count=0)
  Region VisibleRegion (this=0 count=0)
  Region SurfaceDamageRegion (this=0 count=0)
      layerStack=   0, z=2147483647, pos=(0,0), size=(  -1,  -1), crop=[  0,   0,  -1,  -1], cornerRadius=0.000000, isProtected=0, isTrustedOverlay=0, isOpaque=0, invalidate=0, dataspace=Default, defaultPixelFormat=Unknown/None, backgroundBlurRadius=0, color=(0.000,0.000,0.000,1.000), flags=0x00000000, tr=[0.00, 0.00][0.00, 0.00]
      parent=none
      zOrderRelativeOf=none
      activeBuffer=[   0x   0:   0,Unknown/None], tr=[0.00, 0.00][0.00, 0.00] queued-frames=0, mRefreshPending=0, metadata={}, cornerRadiusCrop=[0.00, 0.00, 0.00, 0.00],  shadowRadius=0.000, 
+ BufferStateLayer (bbq-wrapper#0) uid=0
  Region TransparentRegion (this=0 count=0)
  Region VisibleRegion (this=0 count=1)
    [  0,   0, 1920, 1080]
  Region SurfaceDamageRegion (this=0 count=0)
      layerStack=   0, z=        0, pos=(0,0), size=(1920,1080), crop=[  0,   0,  -1,  -1], cornerRadius=0.000000, isProtected=0, isTrustedOverlay=0, isOpaque=1, invalidate=0, dataspace=Default, defaultPixelFormat=RGBx_8888, backgroundBlurRadius=0, color=(0.000,0.000,0.000,1.000), flags=0x00000100, tr=[0.00, 0.00][0.00, 0.00]
      parent=NativeSFDemo#0
      zOrderRelativeOf=none
      activeBuffer=[1920x1080:1920,RGBx_8888], tr=[0.00, 0.00][0.00, 0.00] queued-frames=0, mRefreshPending=0, metadata={dequeueTime:700243748286}, cornerRadiusCrop=[0.00, 0.00, 0.00, 0.00],  shadowRadius=0.000, 

两个BufferStateLayer:BufferStateLayer (NativeSFDemo#0)  和  BufferStateLayer (bbq-wrapper#0),其中bbq-wrapper#0的parent就是NativeSFDemo#0,这其中的关系我们之后的文章中会陆续分析。

4 小结

至此,我们已经建立起来了一个简单的图形图像处理的简单Demo,当让我们目前还是只从应用的较多介绍了基本图形APIs的使用逻辑,接下来的我们就基于此demo,深入底层逻辑探究其中的奥秘。



必读:

Android 12(S) 图形显示系统 - 开篇


作者:二的次方

出处:Android 12(S) 图形显示系统 - 示例应用(二) - 二的次方 - 博客园

本文版权归作者和博客园共有,转载必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android 12(S) 图形显示系统 - 示例应用(二) 的相关文章

  • 使用新语法应用 Android Gradle 插件

    如何使用新的 Gradle 插件语法应用 Android 插件 plugins id version 代替 buildscript dependencies classpath com android tools build gradle
  • Flutter 中的 AndroidManifest 中缺少默认通知通道元数据

    我在用firebase messaging 5 0 1软件包来实现推送通知 在 IOS 中一切正常 而在 Android 中 当我的移动应用程序在后台运行时 我收到通知 但它没有导航到相应的屏幕 它只是打开默认屏幕 如何实现到该特定屏幕的导
  • 使用 dpi 与 dp 缩放图像之间的差异

    我拥有所有由九个补丁位图组成的 dpi 可绘制目录 xxhdpi 和 xxxhdpi 是否必要 可绘制目录中的可绘制资源文件可检索所有缩放的位图 并且我使用可绘制资源文件 现在 我的问题是我还根据大小 小 正常等 创建了 缩放 布局目录 其
  • 如何在 Android TextView 中使用土耳其语字符,如“ş ç ı ö”?

    我想在 android TextView 中写入 ile 但它没有正确绘制 怎样才能使用这样的字符呢 例如 我将文本视图设置为 ile 它显示为 ile 我怎样才能解决这个问题 尝试以下方法 看看是否有帮助 source http grou
  • Android:我可以创建一个不是矩形的视图/画布吗?圆形的?

    我有一个圆形视图 悬停在主要内容上方 gt 从屏幕出来的 z 轴方向 当有人点击屏幕时 我希望选择主要内容或悬停在上方的视图 当它覆盖主视图时 到目前为止效果很好 我在透明画布上有一个圆形物品 这意味着您可以看到该圆圈之外的背景的所有内容
  • 如何在活动中的必填字段中显示 * 符号

    我需要在活动中的必填字段中显示 符号 你能建议我怎样才能做到这一点吗 任何帮助 将不胜感激 我想说 作为必填字段的标记不遵循本机 Android 主题 的组合setHint and setError对于 Android 应用程序来说看起来更
  • 不变违规:requireNativeComponent:在 UIManager 中找不到“RNSVGSvgViewAndroid”

    我对标题中提到的错误感到头疼 我正在使用react native gifted charts https www npmjs com package react native gifted charts v 1 0 3 https www
  • Android:如何使用后台线程?

    我开发了一个应用程序 它从互联网获取内容并相应地在设备的屏幕上显示它 该程序运行得很好 就是有点慢 加载并显示内容大约需要 3 4 秒 我想将获取内容并将其显示在后台线程中的所有代码放在一起 当程序执行这些功能时 我想显示一个进度对话框 你
  • 使用 Android Firebase 堆栈推送通知

    我开发了使用 Firebase 接收推送通知的 Android 应用程序 我的代码基于 Firebase Google 官方文档 https firebase google com docs cloud messaging android
  • Emma 不生成coverage.ec

    我设置了艾玛 它曾经对我有用 然后我们更改了源代码 现在它没有生成coverage ec根本不 它确实生成coverage em 测试临近结束时 出现错误消息 exec INSTRUMENTATION CODE 0 echo Downloa
  • 如何制作在手机和平​​板电脑上使用的响应式Android应用程序?

    我创建了一个 Android 应用程序 当我运行我的应用程序时Mobile Phone它工作得很好 但是当我跑进去时Tablet应用程序的布局已更改 那么 如何制作响应式Android应用程序用于Mobile并且也在Tablet 在Andr
  • 如何使用应用程序接口将蓝牙套接字传递给另一个活动

    因此 根据我收集的信息 套接字连接既不可序列化 也不可分割 但我需要将蓝牙连接传递给另一个活动 我不想作为中间人编写服务 所以请不要将此作为解决方案发布 我听说有一种方法可以使用自定义应用程序接口来传递这些类型的对象 但我一生都找不到这样的
  • 有多少种方法可以将位图转换为字符串,反之亦然?

    在我的应用程序中 我想以字符串的形式将位图图像发送到服务器 我想知道有多少种方法可以将位图转换为字符串 现在我使用 Base64 格式进行编码和解码 它需要更多的内存 是否有其他可能性以不同的方式做同样的事情 从而消耗更少的内存 现在我正在
  • 未解决的包含:“cocos2d.h” - Cocos2dx

    当我在 Eclipse 中导入 cocos2dx android 项目时 我的头文件上收到此警告 Unresolved inclusion cocos2d h 为什么是这样 它实际上困扰着我 该项目可以正确编译并运行 但我希望这种情况消失
  • 屏幕开/关检测

    在这里 我试图确定屏幕是否打开 但按下电源锁定 解锁按钮时它似乎不起作用 应用程序运行没有错误 但 if else 中的代码似乎没有效果 Edited现在代码可以工作了 谢谢Olgun 但媒体播放器播放不会停止 并且每次在屏幕上 离屏时都会
  • 我在 PopupMenu 中使用 ShareActionProvider,但显示两个 PopupMenu?

    我在 PopupMenu 中使用 ShareActionProvider 但是当我单击共享菜单项时 它会在屏幕上显示两个 PopupMenus 一个被另一个覆盖 一个显示应用程序图标和名称 另一个仅显示应用程序名称 除了这个问题之外 它工作
  • 如何在 Android 上将动态 alpha 遮罩应用于文本

    I want to make a dynamic alpha mask with drawable shapes as circles or whatever and apply it to a drawed text on Android
  • 在 Android 手机中通过耳机插孔发送数据

    我目前正在处理一个新项目 我必须通过具有特定电压的耳机插孔发送数据 然后我可以在该电压上工作 所以这里我需要根据我的数据来编程具体电压 我是否可以在android中访问耳机的输出电压 然后创建一个应用程序来控制该电压 这是一篇讨论此问题的
  • R.java是手动修改的!恢复到生成的版本

    我在布局中添加了一个 xml 文件 之后这个错误就来了 但问题是我还没有接触过 R java 文件 现在 在我的新活动中 我要将其内容视图设置为我新创建的 xml 文件 但是当我执行 R layout 时 新创建的 xml 不会出现在建议中
  • 将焦距(以毫米为单位)转换为像素 - Android

    在 Android 中 我当前正在访问camera s焦距通过使用getFocalLength in Camera1 Camera2不是一个选择 我正在尝试完全填充当前的计算 focal length pix focal length m

随机推荐

  • Java增强for循环(学习笔记)

    Java增强for循环 主要用于数组或者集合的增强型for循环 格式 for 声明语句 表达式 代码句子 声明语句 声明新的局部变量 该变量的类型必须和数组元素的类型匹配 其作用域限定在循环语句块 其值与此时数组元素的值相等 表达式 表达式
  • pycharm配置python路径_pycharm如何配置python环境

    pycharm配置python环境的方法是 1 依次点击 File Project Interpreter 2 点击 Show All 选择 Existing Environment 3 选择python的安装路径 点击OK即可 配置方法
  • 安装secureCRT提示sorry的解决办法

    摘自 你的secureCRT还在sorry吗 作者 丶PURSUING 发布时间 2021 03 12 08 21 37 网址 https blog csdn net weixin 44742824 article details 1146
  • WinCE5.0中文模拟器SDK(VS2005)的配置

    WinCE5 0中文模拟器SDK的安装过程不细说了 一路默认即可 下面主要介绍如何配置 使其能在VS2005中正常使用 安装完成后 打开VS2005 点击菜单 工具 选项 设备工具 设备 选择 Windows CE 5 0 ARMV4I E
  • Keil报错:Libraries\CMSIS\stm32f10x.h(298): error: #67: expected a "}"

    原因主要有三点 启动文件 头文件定义 驱动选择不一致 各项如下 1 启动文件 2 头文件定义 3 驱动选择 会导致报错的情况案例 1 启动文件为startup stm32f10x md s C C 的Define为 STM32F10X HD
  • moose安装过程中遇到问题及解决方案

    问题 curl 56 OpenSSL SSL read error 0A000126 SSL routines unexpected eof while reading errno 104 解决方案 未使用vpn 下载速度慢 可多次执行命令
  • [Unity3d]3D项目转换为VR项目(暴风魔镜SDK)

    使用暴风魔镜SDK来操作 将魔镜的摄像头拖放到项目中 将MoJingVrHead的Script剪切到CamRoot中 这个时候能看到显示2个物体了 不过使用的Canvas还是显示一个 调整Canvas的属性 使其显示2份 步骤一 将Rend
  • Linux下杀死指定命令进程

    ps grep cat awk F print 1 xargs kill 9 执行如下 在网上搜到其他不一样的方式 也在此贴一下 https www jianshu com p 80b141746fae
  • 深入理解JS闭包

    关于JS中闭包的理解 相信很多人都和笔者一样刚开始很是困惑 笔者也是在看了很多前辈的文章后 总结出一点自己的理解 记录与此 囿于笔者水平有限 若有错误之处 恳请不啬赐教 你可以在一个函数里面嵌套另外一个函数 嵌套 内部 函数对其容器 外部
  • Windows电脑怎么设置局域网内共享磁盘?

    前言 我有一台主机硬盘容量比较大 想做为一个共享硬盘 方便我其他笔记本能够往这台硬盘传输文件 想到的最好最快的方法就是通过局域网内部进行文件传输 通过局域网共享磁盘 这种方法也是非常便捷的 那如何设置操作呢 请详细看下文 局域网共享磁盘 共
  • 用QEMU虚拟国产飞腾+麒麟环境

    1 简述 由于调试 测试需要飞腾主机及麒麟的环境 但是飞腾主机资源有限 于是便尝试了下在Qemu下虚拟出来一个ARM主机用来作为测试环境 本文介绍如何在Qemu虚拟的ARM环境下安装麒麟操作系统 2 安装过程 2 1 准备 本次安装需要准备
  • 什么是分布式系统?

    分布式系统是由多个独立的计算机或计算节点组成的系统 这些节点通过消息传递或共享数据的方式进行协调和通信 以实现共同的目标 分布式系统的设计目标是提高系统的可靠性 可扩展性 性能和容错性 在一个分布式系统中 各个计算机节点之间相互合作 共同完
  • .NET Framework简介

    1 什么是 NET Framework NET Framework 是支持生成 运行下一代应用程序和XML Web Services的内部Windows组件 它简化了在高度分布式Internet环境中的应用程序开发 NET Framewor
  • python之数值类型数据及运算

    数据类型 数据类型分为 字符串 str 整型 int 浮点型 float 负数 complex 布尔型 bool 一 字符串 1双引号 单引号括起来的 2双引号开头 结尾 xxx 3单引号开通 结尾 xxx 4不能一边单一边双 5多行字符串
  • iOS 为app生成下载链接,并生成二维码

    1 打开这个网址 http aso100 com 在此处输入app名称 点搜索 2 看 第一个就是我们的app 下一步点击图标 3 点击app id 4 看连接出来了 5 最后到这个网站生成二维码 http 2bai com cn hao2
  • Vue2中使用高德地图(Loader )

    1 需求 根据输入的地址 地图显示地址的位置 2 准备工作 2 1 注册高德开放平台账户 并完成认证 根据具体实际情况 完成个人开发或者企业开发认证 高德开放平台https console amap com 2 2在应用管理 我的应用中添加
  • 斗地主老输?只能领低保?看我用Python写一个AI出牌器!现在一亿欢乐豆了!

    前言 最近在网上看到一个有意思的开源项目 快手团队开发的开源AI斗地主 DouZero 今天我们就一起来学习制作一个基于DouZero的欢乐斗地主出牌器 看看AI是如何来帮助我们斗地主 赢欢乐豆 实现财富自由的吧 首先一起来看看AI斗地主出
  • View那些事儿(1) -- View绘制的整体流程

    写在开头 Android的知识体系十分庞大 在Android的学习道路上难免会遇到学习了新东西就忘了旧东西的情况 本系列文章主要是对自己对View的学习过程进行一个深入的理解与总结 当然还结合自己在实际项目中的一些体会写了一些东西 当用户打
  • LINUX 下 用C语言编写 TCP/IP通信的 sqlite3数据库服务器

    一 功能需求 我们首先明确一下 我们要制作的这个小服务器 需要具备什么功能 1 1 用户的注册和登录 使用sqlite3数据库 插入新的用户和查询用户的名字和密码是否匹配 1 2 查询单词 单词及其解释中 保存在一个文本文件当中 需要打开文
  • Android 12(S) 图形显示系统 - 示例应用(二)

    1 前言 为了更深刻的理解Android图形系统抽象的概念和BufferQueue的工作机制 这篇文章我们将从Native Level入手 基于Android图形系统API写作一个简单的图形处理小程序 透过这个小程序我们将学习如何使用Nat