Android bad support

Hello, just a quick topic. Cocos2d-x android development is more and more harder nowadays.
If you, like me, are using prebuilt libraries, which makes the app (project, not the final apk) way smaller, compilation way faster and general management of multiple apps way better, you may become more and more frustrated about cocos2d-x android development.

Here comes the world of Android hacks.

  1. I have to use old NDK r13b, because it won’t work on newer ones (doesn’t matter if you’re using prebuilt or not). It’s now NDK r15 and still, nothing changed.
  2. I have to use old build tools to use gen-libs, because there’s no “android” command anymore. So I have to switch directories back and forth. For “normal” development it’s working fine with latest build tools because I’m running app through gradle (android studio) directly.
  3. While using gen-libs I have to add a parameter “–ap android-19”, because API > 19 broken compatibilities. I see, that’s not cocos team fault, but it’s now API level 26 and still, there’s nothing that can be done about it.

All this stuff is pretty old, have been reported at least few times each and we didn’t get any “real” fix.

As a developer, I’d like to use latest tools and SDKs, but these problem are getting more and more problematic.

I’d like to please cocos2d-x team to fix these problems. I’m experienced with cocos2d-x, so I’ll manage somehow, but new developers won’t be happy encountering the same problems again and again.

Have a good day.

3 Likes

@zhangxm what are your thoughts here. Can we modernize a bit after 3.16?

Hmm weird. I currently use Android Studio and It works great with the source.

I’ve heard that cocos has to recompile all the time from source, but if you don’t edit any of the cocos2d-x source, then it should only ever take a while to compile the first time. After that, only source that I change gets compiled. Not sure what other are experiencing.

I configured gradle so that I can build from there instead of the command line tool, which I think this is the best way actually.

Here’s what my gradle look like:


apply plugin: 'com.android.application'

android {
    compileSdkVersion 26
    buildToolsVersion "26.0.1"

    defaultConfig {
        applicationId "PACKAGE_NAME"
        minSdkVersion PROP_MIN_SDK_VERSION
        targetSdkVersion PROP_TARGET_SDK_VERSION
        versionCode 16
        versionName "0.6.1"

        externalNativeBuild {
            ndkBuild {
                targets 'LIB_TARGET'
                arguments 'NDK_TOOLCHAIN_VERSION=4.9'
                arguments 'APP_PLATFORM=android-' + PROP_APP_PLATFORM

                def module_paths = [project.file("../../cocos2d").absolutePath,
                                    project.file("../../cocos2d/cocos").absolutePath,
                                    project.file("../../cocos2d/external").absolutePath]
                if (Os.isFamily(Os.FAMILY_WINDOWS)) {
                    // should use '/'
                    module_paths = module_paths.collect {
                        it.replaceAll('\\\\', '/')
                    }
                    arguments 'NDK_MODULE_PATH=' + module_paths.join(";")
                } else {
                    arguments 'NDK_MODULE_PATH=' + module_paths.join(':')
                }

                arguments '-j' + Runtime.runtime.availableProcessors()
                abiFilters.addAll(PROP_APP_ABI.split(':').collect { it as String })
            }
        }

        lintOptions {
            checkReleaseBuilds false
            // Or, if you prefer, you can continue to check for errors in release builds,
            // but continue the build even when errors are found:
            abortOnError false
        }
    }

    sourceSets.main {
        java.srcDir "src/main/java" // May vary based on you config
        res.srcDir "src/main/res" // May vary based on you config
        manifest.srcFile "AndroidManifest.xml" // May vary based on you config
        assets.srcDir "../../Resources" // May vary based on you config
    }

    externalNativeBuild {
        ndkBuild {
            path "jni/Android.mk"
        }
    }

    signingConfigs {
        dev {
            if (project.hasProperty("STORE_FILE")) {
                storeFile file(STORE_FILE)
                storePassword STORE_PASSWORD
                keyAlias ALIAS_DEV
                keyPassword ALIAS_DEV_PASSWORD
                v2SigningEnabled true
            }
        }

        release {
            if (project.hasProperty("STORE_FILE")) {
                storeFile file(STORE_FILE)
                storePassword STORE_PASSWORD
                keyAlias ALIAS_RELEASE
                keyPassword ALIAS_RELEASE_PASSWORD
                v2SigningEnabled true
            }
        }
    }

    buildTypes {
        debug {
            debuggable true
            jniDebuggable true
            renderscriptDebuggable true
            minifyEnabled false
            shrinkResources false

            if (project.hasProperty("STORE_FILE")) {
                signingConfig signingConfigs.dev
            }

            externalNativeBuild {
                ndkBuild {
                    arguments 'NDK_DEBUG=1'
                }
            }
        }

        release {
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            if (project.hasProperty("STORE_FILE")) {
                signingConfig signingConfigs.release
            }

            externalNativeBuild {
                ndkBuild {
                    arguments 'NDK_DEBUG=0'
                }
            }
        }
    }
}

android.applicationVariants.all { variant ->
    // delete previous files first
    delete "${buildDir}/intermediates/assets/${variant.dirName}"

    variant.mergeAssets.doLast {
        copy {
            from "${buildDir}/../../Resources"
            into "${buildDir}/intermediates/assets/${variant.dirName}"
            exclude "**/*.gz"
        }
    }
}

dependencies {
    /// At the time of writing, I'm using android studio beta, which deprecated `complile` in favor of `implementation`
    implementation fileTree(include: ['*.jar'], dir: 'libs') 
    implementation project(':libcocos2dx')
    implementation project(':A_PLUGIN')
    implementation project(':B_PLUGIN')
    implementation 'com.android.support:appcompat-v7:25.3.1'
}

Very similar to what they already have, just some tweaks. It works great for me and I’m using the latest NDK 15c

When Android Studio 3 Comes out, I’ll try to do the same for a patch on Cocos2d-x Github. Hopefully this will help!

I do use currently Android Studio 3 (beta something) with cocos2d-x 3.15.1 from source.

Gradle is great, because I created two flavors and it all worked very well. I also deleted the “copy resources to assets folder” the way, that my resource folder is now the assets folder. The same you did, so you can remove the part

android.applicationVariants.all { variant ->
  // delete previous files first
  delete "${buildDir}/intermediates/assets/${variant.dirName}"

  variant.mergeAssets.doLast {
    copy {
        from "${buildDir}/../../Resources"
        into "${buildDir}/intermediates/assets/${variant.dirName}"
        exclude "**/*.gz"
    }
  }
}

My gradle file also create split apk depending on abi. It sets the versionCode correct etc.

I also need to recompile everything.

I don’t quite understand this issue, what did you mean can not work? Which did you do?

I think we should add prebuilt support for gradle, like using cocos gen-libs --android-studio

So what’s your suggestion?

And i still suggest not using prebuilt version. It will bring more works to us, we should add more resource to test it, and we are limited on human resource. Prebuilt is not easy to debug, currently i can use Android Studio to debug c++ codes.

  1. NDK problem. The app runs on NDK r13b, but on r15 I’m getting the following error:

Error:(688) Android NDK: Module MyGame_shared depends on undefined modules: log

Also, you’re probably aware of this topic: Pre-built libs for android not compiles with NDK 14+

  1. gen-libs --android-studio, yes - we need that! Is it that hard to fix? (avoid using android command).

  2. API level > 19 - well, fix errors, which are occurring. Mostly, as far as I know, it’s some missing functions like of or rand that can be replaced with something else, can they? Or it’s just a tip of an iceberg?

Why not prebuilt? It’s great. Fast, small, easy to be shared by all projects. I can debug c++ code as well (with android studio). The only downside is you can’t debug cocos2d-x core, but that’s rarely even needed and if it’s needed I can change config a bit to switch for “include source” version.

Just check out how many people love it:
http://discuss.cocos2d-x.org/t/how-to-speed-up-cocos2d-x-build-with-prebuilt-lib

Also: can we fix this? It’s a very annoying bug: [Android] Audio Decoding Issues Discussion

In android SoundPool implementation when there are all channels currently used and you want to play sound, it stops one of the old sounds (probably the oldest) to make room for the new one. But in AudioEngine it doesn’t play this new sound at all and prints log limited max instance of AudioEngine
In my game you have a piano, each key plays for few seconds (sound is fading, so technically in the first second it’s loudest). Playing 16 or 32 keys in few seconds is not that hard and then you’re clicking on the key, but it won’t play (until there’s room for that). Previously it worked great on SimpleAudioEngine (SoundPool), except for very long loading (and this is working great in AudioEngine).

In my opinion, there are several ways to “fix” this problem:
a) kill the oldest sound
b) kill the oldest sound with the same filename

Approach b is better because it won’t kill a background music for example.

It’s actually ideal setup. I’m doing exactly the same, specially with quick switch for source code if needed.

Don’t understand why it’s not like pushed as main idea and workflow for cocos2d-x, because it’s too heavy to just even archive an project in Xcode each time with source code.

I have one folder with prebuilt cocos2d-x libs and I can use it for any my other games.
If I update cocos2d-x git folder, then I just recompile it once, built prebuilt libs and again for all games I’m now with latest cocos.

Opposite of this if source code for each game located in each game folder - you should update cocos2d-x in each game and recompile each game with massive cocos2d-x source code, I can’t even imagine development in such way…

Also, overall project size with prebuilt libs is dramatically reduced, because it doesn’t contains all source code of the engine and with it git files also. It’s average 1.0Gb-1.5Gb + for each project.

@anon98020523 my setup is made exactly the same way :slight_smile:

This issue is fixed in https://github.com/cocos2d/cocos2d-x/issues/17443

I will try to fix it.

About the audio engine issue, @dumganhar please take a look.

Yep, the logic of killing the oldest sound is better than just printing limited max instance of AudioEngine..
I like a) much more. since b) will make also trigger the room problem if you’re playing more than max different audios.

To be more specific, we should not kill background music in which case we need to add AudioEngine::playBackgroundMusic back rather than using AudioEngine::play2d for BG music and effects.

But I have to say sorry for that I don’t have enough time to do the refactor(I’m busy in CocosCreator v1.7 work).
Contribution is welcome and we need your help.
Otherwise, please wait for sometime (I know this issue isn’t fixed for some months ) when I come back.

I hope. It’s overall bug described in my topic here Pre-built libs for android not compiles with NDK 14+(don’t know why form glithced and all images are gone, not beed fixed by your guys, anyways without images you can try to build). And bug created - https://github.com/cocos2d/cocos2d-console/issues/420

We need just a pre-build to work with all latest updates. Cocos console repot should be updated with fixes(to be able to compile like: “cocos gen-libs -p android --android-studio --app-abi armeabi-v7a --ap android-26” ) and probably cocos2d-x too to fix errors like “Cocos2d-x/cocos/./2d/CCActionGrid3D.cpp:509: error: undefined reference to 'rand’” when compiling with NDK 14+

@dumganhar I’ve added the following code in AudioEngine:

AudioEngine.h - new field in AudioInfo struct:

double timestamp;

In AudioEngine.cpp changed if function when number of instances is exceeded:

if (_audioIDInfoMap.size() >= _maxInstances) {
    double oldestTimestamp = std::numeric_limits<double>::max();
    int oldestId = INVALID_AUDIO_ID;
    std::string ofilePath; //not needed, just for debugging
    for(auto it = _audioIDInfoMap.begin(); it != _audioIDInfoMap.end(); ++it){
        if(it->second.timestamp < oldestTimestamp){
            oldestId = it->first;
            oldestTimestamp = it->second.timestamp;
            ofilePath = *it->second.filePath;
        }
    }

    log("Max instance limit of AudioEngine exceeded. Stopping the oldest sound with id: %d and path: %s", oldestId, ofilePath.c_str());
    AudioEngine::stop(oldestId);

}

and after calling _audioEngineImpl->play2d also we have to remember the current timestamp:

audioRef.timestamp = utils::gettime();

I’m not sure if it’s correct :slight_smile: It may be even just getting the smallest sound id, but probably it won’t work in all situations correctly.

Also, I don’t know what’s the purpose of profileHelper. In my case, it’s null so the code is not executed. Can you explain what’s the purpose of profile helper?

I’ve created a test case for this. I’m lazy so I’ve replaced the first case with my code :smiley:

bool AudioIssue11143Test::init()
{
    if (AudioEngineTestDemo::init())
    {
        auto& layerSize = this->getContentSize();

        auto playItem = TextButton::create("play", [](TextButton* button){
            float diff = 0.05f;

            for(int i = 0; i < 200; i++){
                float delay = diff * (i + 1);
                int j = i % 10 + 81;

                char buff1[255];
                snprintf(buff1, sizeof(buff1), "k%d", i);
                std::string key = buff1;

                char buff2[255];
                snprintf(buff2, sizeof(buff2), "audio/SoundEffectsFX009/FX0%d.mp3", j);
                std::string path = buff2;

                button->scheduleOnce([path](float dt){
                    AudioEngine::play2d(path);
                }, delay, key);
            }
        });
        playItem->setPosition(layerSize.width * 0.5f, layerSize.height * 0.5f);
        addChild(playItem);

        return true;
    }

    return false;
}

What it does is playing multiple sounds with a small delay. With the old code, you’ll hear multiple sounds, a short break (with multiple Fail to play logs) and so on. With the new code, you won’t hear any breaks :slight_smile:

Could you share your gradle file? I am especially interested in split apk.
Is it possible to configure gradle in that way that there is common cocos2dx folder for many games and compilation is made only once?

Regards
Chp

You can find it on github. It’s just an example. Feel to to adapt it to your needs or give feedback, if I have something wrong in it.

I think, if you change line 40 and 177 to a shared folder, everything would be fine. I never checked it, because I do currently only have one game in development.

PS: The dependency section is with implementation instead of compile. This is because of Android Studio 3 and the latest build tools.

I also don’t know what the profile helper is.
there is a default argument in play2d named AudioProfile. And AudioProfile could control name, maxInstances, delay

It’s not me design AudioEngine.

For a glance, I guess it’s for controlling whether to play an audio.

Look at this logic:

int AudioEngine::play2d(const std::string& filePath, bool loop, float volume, const AudioProfile *profile)
{
    int ret = AudioEngine::INVALID_AUDIO_ID;

    do {
        if (!isEnabled())
        {
            break;
        }
        
        if ( !lazyInit() ){
            break;
        }

        if ( !FileUtils::getInstance()->isFileExist(filePath)){
            break;
        }

        auto profileHelper = _defaultProfileHelper;
        if (profile && profile != &profileHelper->profile){
            CC_ASSERT(!profile->name.empty());
            profileHelper = &_audioPathProfileHelperMap[profile->name];
            profileHelper->profile = *profile;
        }
        
        if (_audioIDInfoMap.size() >= _maxInstances) {
            log("Fail to play %s cause by limited max instance of AudioEngine",filePath.c_str());
            break;
        }
        if (profileHelper)
        {
             if(profileHelper->profile.maxInstances != 0 && profileHelper->audioIDs.size() >= profileHelper->profile.maxInstances){
                 log("Fail to play %s cause by limited max instance of AudioProfile",filePath.c_str());
                 break;
             }
             if (profileHelper->profile.minDelay > TIME_DELAY_PRECISION) {
                 auto currTime = utils::gettime();
                 if (profileHelper->lastPlayTime > TIME_DELAY_PRECISION && currTime - profileHelper->lastPlayTime <= profileHelper->profile.minDelay) {
                     log("Fail to play %s cause by limited minimum delay",filePath.c_str());
                     break;
                 }
             }
        }

In your approach, Is background music which is played at first cleared while the room is full?

In my case, as I said, profile helper is null so the code is unreachable. That’s why I don’t know if it’s used in some situations or not. So I didn’t touch this piece of code.

In your approach, Is background music which is played at first cleared while the room is full?

Not yet, but I have 2 approaches:

  1. Create a separate function like in SimpleAudioEngine, add a flag in AudioInfo whatever it’s background music or not. Background music won’t be stopped when there’s no room.
  2. Don’t stop sounds, which are looped.

I’d like to send a pull request, but I’ve also changed spine runtimes, however it broke jsb_cocos2dx_spine_manual.cpp and I don’t know how to fix it :smiley:

You could create a another git branch locally, and pick only audio changes.Then push to your remote and send a Pull Request.

I’ve finally found time to fix spine issues, check out my pull request: https://github.com/cocos2d/cocos2d-x/pull/18378

It’s my first pull request ever so don’t be mad if I did something wrong :smiley:

Let me join this topic, I hope this is the right place, as I experience problems with android rebuild on Windows.

Thanks to help of @cocos99d I managed to get my helloworld compiling and it runs nicely on real device. It works best when done from Android Studio.

Now I have a problem that when I change anything on the original Scene (adding a label, changing script etc…) it does not seem to be picked up when I do recompile.

Sorry if this question is trivial for you guys, but do I really need to perform Build from Cocos Creator, then clean and package again from within Android Studio (all that with full cocos2d-x libs)?