ubuntu 18.04 Android Studio运行模拟器时提示“/dev/kvm device: permission denied”

升级 `ubuntu` 系统, 从 `16.04.5` 升级到 `18.04.1` ,接着又开始配置各种软件环境。

当配置好 `Android` 开发环境,准备创建一个模拟器并运行程序环境看是否OK时,问题出现了。

创建和运行时都提示:`/dev/kvm device: permission denied` 或者 `/dev/kvm device: open failed`,而且模拟器跑不起来。

执行命令查看:

$ ls -al /dev/kvm
crw------- 1 root root 10, 232 11月 17 22:37 /dev/kvm

需要安装 `qemu-kvm` 并把当前用户加入到 `kvm` 用户组即可:

$ sudo apt install qemu-kvm

$ sudo adduser `whoami` kvm

$ ls -al /dev/kvm
crw-rw---- 1 root kvm 10, 232 11月 18 14:40 /dev/kvm

然后运行模拟器。

如果依旧报错,则需要修改 /dev/kvm 的所有者为当前用户,如下:

$ sudo chown `whoami` /dev/kvm

参考链接


Android Studio 3.5.2最小化接入weex 0.28.0实践

目前在尝试使用weex,但是在搭建基础的最小化项目的时候,官方文档描述的相当佛系,导致搭建出来的项目没办法正常运行。

下面我们探讨一下,使用`Android Studio 3.5.2`新建一个项目,实现最小化接入`weex 0.28.0`实践过程。

官方文档要求在项目的`build.gradle` 中增加如下内容:

implementation 'org.apache.weex:sdk:0.28.0@aar'
implementation 'com.alibaba:fastjson:1.2.62'

但是实际上,由于`weex 0.28.0`的调整,以前版本自动引入的`facebook`提供的`JS`引擎js-android,现在被修改为需要手工引入,但是文档没有清晰的指出这个问题,导致运行的时候,会由于找不到`libjsc.so`而导致`WXSDKEngine`初始化失败。

官方提供了一个`download_jsc.gradle`的脚本解决这个问题(这个脚本的功能仅仅是下载`libjsc.so` ),需要在项目的 `build.gradle` 的头部增加这个脚本文件的引用:

apply from: 'https://raw.githubusercontent.com/apache/incubator-weex/release/0.28/android/sdk/buildSrc/download_jsc.gradle'

如果下载不成功,也可从本站下载

apply from: 'https://www.mobibrw.com/wp-content/uploads/2019/11/download_jsc.gradle'

完成后的`build.gradle`中完整内容如下:

apply plugin: 'com.android.application'
apply from: 'https://raw.githubusercontent.com/apache/incubator-weex/release/0.28/android/sdk/buildSrc/download_jsc.gradle'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.mobibrw.weex"
        minSdkVersion 22
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        ndk {
            abiFilters "armeabi","armeabi-v7a", "arm64-v8a", "x86"
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'com.google.android.material:material:1.0.0'
    implementation 'org.apache.weex:sdk:0.28.0@aar'
    implementation 'com.alibaba:fastjson:1.2.62'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

接下来,就是具体的代码部分了,如下,需要自定义一个`Application`类,在`Application`的初始化部分初始化`WXSDKEngine`,代码如下:

package com.mobibrw.weex;

import android.app.Application;
import org.apache.weex.InitConfig;
import org.apache.weex.WXSDKEngine;

public class WeexApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        final InitConfig config = new InitConfig.Builder().build();
        /**
         * 底层的初始化是异步执行的,尤其是初始化JS引擎部分的代码(WXBridgeManager),是相当耗时的
         * 因此,在调用完初始化之后,Activity第一次调用的时候,一定要增加是否已经初始化完成的判断
         * 如果没有完成初始化,适当的增加延迟等待的代码
         **/
        WXSDKEngine.initialize(this, config);
    }

}

接下来,就是具体的`Activity`内容展现代码部分了,代码如下:

package com.mobibrw.weex;

import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import org.apache.weex.IWXRenderListener;
import org.apache.weex.WXSDKEngine;
import org.apache.weex.WXSDKInstance;
import org.apache.weex.common.WXRenderStrategy;

public class MainActivity extends AppCompatActivity implements IWXRenderListener {

    private WXSDKInstance wXSDKInstance;

    private static final String TAG = "WXSampleTAG";

    private void loadPages() {
        /**
         * bundleUrl source http://dotwe.org/vue/38e202c16bdfefbdb88a8754f975454c
         */
        String pageName = "WXSample";
        String bundleUrl = "http://dotwe.org/raw/dist/38e202c16bdfefbdb88a8754f975454c.bundle.wx";
        wXSDKInstance.renderByUrl(pageName, bundleUrl, null, null, WXRenderStrategy.APPEND_ASYNC);
    }

    private void asyncLoadPages(final View view) {
        /**
         * 此处一定要判断WXSDKEngine是否已经成功初始化了,由于WXSDKEngine底层初始化的库非常多
         * 导致整个的初始化非常的耗时,并且这个初始化是异步执行的,尤其是初始化JS引擎部分的代码(WXBridgeManager)。
         * 因此有非常大的概率导致当第一次使用Week的API的时候,底层还没有完成初始化
         * 导致出现错信息 "degradeToH5|createInstance fail|wx_create_instance_error isJSFrameworkInit==false reInitCount == 1"
         * 这段耗时可以通过在程序启动的时候增加启动等待页面来人性化的忽略这部分耗时。
         **/
        if(!WXSDKEngine.isInitialized()) {
            view.postDelayed(new Runnable() {
                @Override
                public void run() {
                    asyncLoadPages(view);
                }
            }, 1);
        } else {
            loadPages();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final View view = findViewById(R.id.activity_main);

        wXSDKInstance = new WXSDKInstance(this);
        wXSDKInstance.registerRenderListener(this);
        asyncLoadPages(view);
    }

    @Override
    public void onPointerCaptureChanged(boolean hasCapture) {

    }

    @Override
    public void onViewCreated(WXSDKInstance instance, View view) {
        setContentView(view);
    }

    @Override
    public void onRenderSuccess(WXSDKInstance instance, int width, int height) {
        Log.i(TAG, "onRenderSuccess");
    }

    @Override
    public void onRefreshSuccess(WXSDKInstance instance, int width, int height) {
        Log.i(TAG, "onRefreshSuccess");
    }

    @Override
    public void onException(WXSDKInstance instance, String errCode, String msg) {
        Log.e(TAG, "onException:" + msg);
    }

    @Override
    protected void onResume() {
        super.onResume();
        if(wXSDKInstance!=null){
            wXSDKInstance.onActivityResume();
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        if(wXSDKInstance!=null){
            wXSDKInstance.onActivityPause();
        }
    }
    @Override
    protected void onStop() {
        super.onStop();
        if(wXSDKInstance!=null){
            wXSDKInstance.onActivityStop();
        }
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(wXSDKInstance!=null){
            wXSDKInstance.onActivityDestroy();
        }
    }
}

需要注意的是`WXSDKEngine`是异步初始化的,导致在首次调用的时候,可能会因为没有正常初始化而出现异常,因此需要等待初始化完成。

具体的例子项目在这里下载 Weex

鉴于开源项目经常性找不到文件,因此记录下来 http://dotwe.org/raw/dist/38e202c16bdfefbdb88a8754f975454c.bundle.wx 这个文件里面的内容:

// { "framework": "Vue" }

/******/
(function(modules) { // webpackBootstrap
    /******/ // The module cache
    /******/
    var installedModules = {};

    /******/ // The require function
    /******/
    function __webpack_require__(moduleId) {

        /******/ // Check if module is in cache
        /******/
        if (installedModules[moduleId])
        /******/
            return installedModules[moduleId].exports;

        /******/ // Create a new module (and put it into the cache)
        /******/
        var module = installedModules[moduleId] = {
            /******/
            exports: {},
            /******/
            id: moduleId,
            /******/
            loaded: false
                /******/
        };

        /******/ // Execute the module function
        /******/
        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

        /******/ // Flag the module as loaded
        /******/
        module.loaded = true;

        /******/ // Return the exports of the module
        /******/
        return module.exports;
        /******/
    }


    /******/ // expose the modules object (__webpack_modules__)
    /******/
    __webpack_require__.m = modules;

    /******/ // expose the module cache
    /******/
    __webpack_require__.c = installedModules;

    /******/ // __webpack_public_path__
    /******/
    __webpack_require__.p = "";

    /******/ // Load entry module and return exports
    /******/
    return __webpack_require__(0);
    /******/
})
/************************************************************************/
/******/
([
    /* 0 */
    /***/
    function(module, exports, __webpack_require__) {

        'use strict';

        var _e202c16bdfefbdb88a8754f975454c = __webpack_require__(1);

        var _e202c16bdfefbdb88a8754f975454c2 = _interopRequireDefault(_e202c16bdfefbdb88a8754f975454c);

        function _interopRequireDefault(obj) {
            return obj && obj.__esModule ? obj : {
                default: obj
            };
        }

        _e202c16bdfefbdb88a8754f975454c2.default.el = '#root';
        new Vue(_e202c16bdfefbdb88a8754f975454c2.default);

        /***/
    },
    /* 1 */
    /***/
    function(module, exports, __webpack_require__) {

        var __vue_exports__, __vue_options__
        var __vue_styles__ = []

        /* styles */
        __vue_styles__.push(__webpack_require__(2))

        /* script */
        __vue_exports__ = __webpack_require__(3)

        /* template */
        var __vue_template__ = __webpack_require__(4)
        __vue_options__ = __vue_exports__ = __vue_exports__ || {}
        if (
            typeof __vue_exports__.default === "object" ||
            typeof __vue_exports__.default === "function"
        ) {
            if (Object.keys(__vue_exports__).some(function(key) {
                    return key !== "default" && key !== "__esModule"
                })) {
                console.error("named exports are not supported in *.vue files.")
            }
            __vue_options__ = __vue_exports__ = __vue_exports__.default
        }
        if (typeof __vue_options__ === "function") {
            __vue_options__ = __vue_options__.options
        }
        __vue_options__.__file = "/usr/src/app/raw/38e202c16bdfefbdb88a8754f975454c.vue"
        __vue_options__.render = __vue_template__.render
        __vue_options__.staticRenderFns = __vue_template__.staticRenderFns
        __vue_options__._scopeId = "data-v-02e1e786"
        __vue_options__.style = __vue_options__.style || {}
        __vue_styles__.forEach(function(module) {
            for (var name in module) {
                __vue_options__.style[name] = module[name]
            }
        })
        if (typeof __register_static_styles__ === "function") {
            __register_static_styles__(__vue_options__._scopeId, __vue_styles__)
        }

        module.exports = __vue_exports__


        /***/
    },
    /* 2 */
    /***/
    function(module, exports) {

        module.exports = {
            "text": {
                "fontSize": 50
            }
        }

        /***/
    },
    /* 3 */
    /***/
    function(module, exports) {

        'use strict';

        Object.defineProperty(exports, "__esModule", {
            value: true
        });
        //
        //
        //
        //
        //
        //
        //
        //
        //
        //

        exports.default = {
            data: function data() {
                return {
                    text: 'Hello World.'
                };
            }
        };

        /***/
    },
    /* 4 */
    /***/
    function(module, exports) {

        module.exports = {
            render: function() {
                var _vm = this;
                var _h = _vm.$createElement;
                var _c = _vm._self._c || _h;
                return _c('div', [_c('text', {
                    staticClass: ["text"]
                }, [_vm._v(_vm._s(_vm.text))])])
            },
            staticRenderFns: []
        }
        module.exports.render._withStripped = true

        /***/
    }
    /******/
]);

参考链接


Android Studio 3.5.1配置NDK路径

早期版本的Android Studio在全局配置NDK的路径信息,但是从Android Studio 3.4版本开始,NDK的路径信息被转移到Project Structure部分去配置了,这变成了一个工程相关的配置,每个工程可以单独配置独立的NDKSDK版本。

具体操作如下图:

继续阅读Android Studio 3.5.1配置NDK路径

Android Gradle Plugin源码解析之externalNativeBuild

在Android Studio 2.2开始的Android Gradle Plugin版本中,Google集成了对cmake的完美支持,而原先的ndkBuild的方式支持也变得更加良好。这篇文章就来说说Android Gradle Plugin与交叉编译之间的一些事,即externalNativeBuild相关的task,主要是解读一下gradle构建系统相关的源码。

继续阅读Android Gradle Plugin源码解析之externalNativeBuild

Overriding a default option(…) value in CMake from a parent CMakeLists.txt

CMakeLists.txt

option(BUILD_FOR_ANDROID "Build For Android" OFF)

if(SYSTEM.Android AND NOT BUILD_FOR_ANDROID)
    set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${NATIVE_LIBRARY_OUTPUT}/${ANDROID_ABI})
endif()

CMakeLists.txt

set(BUILD_FOR_ANDROID ON)
add_subdirectory(${CHILD_ROOT_DIR}/ ${CMAKE_CURRENT_SOURCE_DIR}/build)

执行如下命令的时候:

/Users/xxxx/Library/Android/sdk/cmake/3.6.4111459/bin/cmake --trace-expand \
-H/Users/xxxx/Source/example/demo/android/app \
-B/Users/xxxx/Source/example/demo/android/app/.externalNativeBuild/cmake/debug/arm64-v8a \
-DANDROID_ABI=arm64-v8a \
-DANDROID_PLATFORM=android-21 \
-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=/Users/xxxx/Source/example/demo/android/app/build/intermediates/cmake/debug/obj/arm64-v8a \
-DCMAKE_BUILD_TYPE=Debug \
-DANDROID_NDK=/Users/xxxx/Library/Android/android-ndk-r16b \
-DCMAKE_TOOLCHAIN_FILE=/Users/xxxx/Library/Android/android-ndk-r16b/build/cmake/android.toolchain.cmake \
-DCMAKE_MAKE_PROGRAM=/Users/xxxx/Library/Android/sdk/cmake/3.6.4111459/bin/ninja \
-G"Android Gradle - Ninja" \
-DANDROID_ARM_NEON=TRUE \
-DANDROID_TOOLCHAIN=gcc \
-DANDROID_PLATFORM=android-21 \
-DANDROID_STL=gnustl_shared

会观察到生成的配置文件中 BUILD_FOR_ANDROID 不一定能生效。

需要如下配置才行:
CMakeLists.txt

set(BUILD_FOR_ANDROID ON CACHE BOOL "" FORCE)
add_subdirectory(${CHILD_ROOT_DIR}/ ${CMAKE_CURRENT_SOURCE_DIR}/build)

参考链接


Use ccache with CMake for faster compilation

C and C++ compilers aren’t the fastest pieces of software out there and there’s no lack of programmer jokes based on tedium of waiting for their work to complete.

There are ways to fix the pain though - one of them is ccache. CCache improves compilation times by caching previously built object files in private cache and reusing them when you’re recompiling same objects with same parameters. Obviously it will not help if you’re compiling the code for the first time and it also won’t help if you often change compilation flags. Most C/C++ development however involves recompiling same object files with the same parameters and ccache helps alot.

For illustration, here’s the comparison of first and subsequent compilation times of a largish C++ project:

Original run with empty cache:

$ make -j9
...
real    0m56.684s
user    5m31.996s
sys     0m41.638s

Recompilation with warm cache:

$ make -j9
...
real    0m5.929s
user    0m11.896s
sys     0m8.722s

Installation

CCache is available in repositories on pretty much all distributions. On OS X use homebrew:

$ brew install ccache

and on Debian-based distros use apt:

$ apt-get install ccache

CMake configuration

After ccache is installed, you need to tell CMake to use it as a wrapper for the compiler. Add these lines to your CMakeLists.txt:

# Configure CCache if available
find_program(CCACHE_FOUND ccache)
if(CCACHE_FOUND)
        set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
        set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache)
endif(CCACHE_FOUND)

Rerun cmake and next make should use ccache for wrapper.

Usage with Android NDK

CCache can even be used on Android NDK - you just need to export NDK_CCACHE environment variable with path to ccache binary. ndk-build script will automatically use it. E.g.

$ export NDK_CCACHE=/usr/local/bin/ccache

$ ndk-build -j9

(Note that on Debian/Ubuntu the path will probably be /usr/bin/ccache)

CCache statistics

To see if ccache is really working, you can use ccache -s command, which will display ccache statistics:

cache directory                     /Users/jernej/.ccache
primary config                      /Users/jernej/.ccache/ccache.conf
secondary config      (readonly)    /usr/local/Cellar/ccache/3.2.2/etc/ccache.conf
cache hit (direct)                 77826
cache hit (preprocessed)           17603
cache miss                         46999
called for link                       18
compile failed                        45
ccache internal error                  1
preprocessor error                    62
unsupported source language          204
files in cache                     48189
cache size                           1.2 GB
max cache size                      20.0 GB

On second and all subsequent compilations the “cache hit” values should increase and thus show that ccache is working.

参考链接


Use ccache with CMake for faster compilation

Toast问题深度剖析

题记

Toast 作为 Android 系统中最常用的类之一,由于其方便的api设计和简洁的交互体验,被我们所广泛采用。但是,伴随着我们开发的深入,Toast 的问题也逐渐暴露出来。本文章就将解释 Toast 这些问题产生的具体原因。 本系列文章将分成两篇:

  • 第一篇,我们将分析 Toast 所带来的问题
  • 第二篇,将提供解决 Toast 问题的解决方案

(注:本文源码基于Android 7.0)

1. 异常和偶尔不显示的问题

当你在程序中调用了 ToastAPI,你可能会在后台看到类似这样的 Toast 执行异常:

android.view.WindowManager$BadTokenException
    Unable to add window -- token android.os.BinderProxy@7f652b2 is not valid; is your activity running?
    android.view.ViewRootImpl.setView(ViewRootImpl.java:826)
    android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:369)
    android.view.WindowManagerImpl.addView(WindowManagerImpl.java:94)
    android.widget.Toast$TN.handleShow(Toast.java:459)

另外,在某些系统上,你没有看到什么异常,却会出现 Toast 无法正常展示的问题。为了解释上面这些问题产生的原因,我们需要先读一遍 Toast 的源码。

继续阅读Toast问题深度剖析

macOS Mojave (10.14.3) Android Studio 3.3.1 NDK 19.1.5304403 导入并构建Vuh项目

以前在 Android Studio 3.2.1上vuh库使用的例子 中实现了一个使用 vuh 库的例子。 那个例子中的 vuh 库是我们编译好 libvuh.so 之后直接引用的,我们下面实现通过直接编译代码实现整合。

尝试过使用 ExternalProject_addinclude 的方式包含 vuh 库,但是都不是很成功。

其中 ExternalProject_add 导入的项目只能编译一次,即使指定 BUILD_ALWAYS 1 也没用,这个应该是 Ninja 导致的问题,导致当出现多个 ABI 或者 vuh 库代码变动之后,不能重新编译,出现各种编译错误。

使用 include 包含的项目会导致路径信息不正确,无法找到源代码文件。

最后使用 add_subdirectory实现。

修改之后的几个关键文件如下:

注意: VUH_ROOT_DIR 这个变量中指定 vuh 库代码的位置

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.8)

# for Vulkan
SET(Vulkan_INCLUDE_DIR ${ANDROID_NDK}/sources/third_party/vulkan/src/include/)
SET(Vulkan_LIBRARIES ${ANDROID_NDK}/platforms/${ANDROID_PLATFORM}/arch-${ANDROID_ARCH_NAME}/usr/lib)
if(X86_64)
    SET(Vulkan_LIBRARIES ${Vulkan_LIBRARIES}64)
endif()
SET(Vulkan_LIBRARIES ${Vulkan_LIBRARIES}/libvulkan.so)
add_library(vulkan SHARED IMPORTED)
set_target_properties(vulkan PROPERTIES IMPORTED_LOCATION ${Vulkan_LIBRARIES})

# for vuh
add_definitions(-DVK_USE_PLATFORM_ANDROID_KHR=1 -DVULKAN_HPP_TYPESAFE_CONVERSION=1)
SET(VUH_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../vuh/)
SET(VUH_BUILD_TESTS OFF)
SET(VUH_BUILD_DOCS OFF)
SET(VUH_BUILD_EXAMPLES OFF)

add_subdirectory(${VUH_ROOT_DIR}src/ ${CMAKE_CURRENT_SOURCE_DIR}/build/)

# for example
SET(Vuh_INCLUDE_PATH ${VUH_ROOT_DIR}src/include)
include_directories(${Vulkan_INCLUDE_DIR})
include_directories(${Vuh_INCLUDE_PATH})

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
        native-lib

        # Sets the library as a shared library.
        SHARED

        # Provides a relative path to your source file(s).
        src/main/cpp/native-lib.cpp)


add_dependencies(native-lib vuh)

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
        log-lib

        # Specifies the name of the NDK library that
        # you want CMake to locate.
        log

        android)

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
        native-lib

        vulkan

        vuh

        ${log-lib})

注意:由于 vuh 库需要 CMake 3.8 。因此,我们需要手工指定CMake版本为3.10.2 。

如下:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.mobibrw.vuhandroid"
        minSdkVersion 24
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                version "3.10.2"
                cppFlags "-std=c++14 -v -g"
            }
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    externalNativeBuild {
        cmake {
            version "3.10.2"
            path "CMakeLists.txt"
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

如果出现如下错误:

CMake Error: CMake was unable to find a build program corresponding to "Ninja".  CMAKE_MAKE_PROGRAM is not set.  You probably need to select a different build tool.
-- Configuring incomplete, errors occurred!

则执行如下操作:

$ brew install ninja

如果出现如下错误:

* What went wrong:
Execution failed for task ':app:transformNativeLibsWithMergeJniLibsForDebug'.
> More than one file was found with OS independent path 'lib/armeabi-v7a/libvuh.so'

则删除代码中的 jniLibs/armeabi-v7a/libvuh.so 即可解决问题。

完整的例子点击此处下载 vuhAndroid

参考链接


macOS Mojave (10.14.3) Android Studio 3.3.1 指定使用CMake 3.10.2版本

目前最新版本的 Android Studio 3.3.1默认使用CMake 3.6版本,但是已经支持 CMake 3.10.2 版本了。

新版本的 CMake 3.10.2 新增了 FindVulkan.cmake 等一系列的改进,对于很多项目来说,会更友好。

目前默认依旧使用 CMake 3.6 版本,但是可以手工指定使用 CMake 3.10.2

继续阅读macOS Mojave (10.14.3) Android Studio 3.3.1 指定使用CMake 3.10.2版本