background

As there are many problems in resource release, most companies will adopt multi-process mode. When the game page is closed, they will directly kill the process to ensure the complete release of resources. However, this method has great limitations, such as embedding coCOS games in the existing live broadcast Activity, such scenes actually need to add coCOS games as a view way, this article is to solve this scene.

The effect is as follows:

Profile has done a memory test, basically no leakage, in addition to the V8 engine is relatively small, permanent does not affect the actual use.

Next, let’s get down to business. This article should be the most honest on the web, because it will solve this problem right away.

Environment to prepare

Cocos Creator: 2.1.3 and 2.4.2 are effectively measured, and other versions will be tried again when there is time

VS Code: the latest version is available for viewing and editing C++ engine Code

Android Studio: The latest version is just enough to encapsulate the core utility classes and sample code

MacBook Pro: Programming computer

Generating Android projects

Built using the Creator project, the configuration is as follows:

Configure the Android SDK and NDK versions as follows:

With luck, you should be able to run Android mini-games, but now it only exists as an Activity, and the entire process only has one mini-game. Follow me step by step to adjust ~

Engine adjustment

Engine directory: / Applications/CocosCreator/Creator / / CocosCreator 2.4.2. The app/Contents/Resources/cocos2d – x

cocos/platform/android/CCApplication-android.cpp

Application::~Application()
{
    #if USE_AUDIO
    AudioEngine::end(a);#endif

    // The following is the new code
    LOGD(">>>>>>>> ~Application");
    Configuration::destroyInstance(a); _scheduler->unscheduleAll(a); EventDispatcher::destroy(a); se::ScriptEngine::getInstance() - >cleanup(a);// Comment out this line, do not release the V8 engine, the actual memory footprint is very small
    // se::ScriptEngine::destroyInstance();
  
    delete _renderTexture;
    _renderTexture = nullptr;

    Application::_instance = nullptr;
}
Copy the code

cocos/platform/android/jni/JniImp.cpp

// Added the JNI method to actively shut down the game
JNIEXPORT void JNICALL JNI_RENDER(nativeExit)(JNIEnv* env)
{
  LOGD("nativeExitApp");
  exitApplication(a);restartJSVM(a); }Copy the code

cocos/scripting/js-bindings/jswrapper/v8/ScriptEngine.cpp

bool ScriptEngine::isDebuggerEnabled(a) const
{
  	// Turn off V8 debug mode
  	return false;
  	// return ! _debuggerServerAddr.empty() && _debuggerServerPort > 0;
}
Copy the code

The Java wrapper

Directory: / Applications/CocosCreator/Creator / / CocosCreator 2.4.2. The app/Contents/Resources/cocos2d – x/cocos/platform/android/Java/SRC / org/cocos2dx/lib

cocos/platform/android/java/src/org/cocos2dx/lib/CocosHelper.java

New class: CocosHelper to move logic from Cocos2dxActivity to get rid of Activity restrictions

package org.cocos2dx.lib;

import android.app.Activity;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.widget.RelativeLayout;

import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLDisplay;

public class CocosHelper implements Cocos2dxHelper.Cocos2dxHelperListener {
    private Activity context;
    private RelativeLayout container;
    private Cocos2dxGLSurfaceView mGLSurfaceView = null;
    private Cocos2dxRenderer renderer = null;
    private int[] mGLContextAttrs = {8.8.8.8.0.0.0};

    public Cocos2dxGLSurfaceView getGLSurfaceView(a){
        return  mGLSurfaceView;
    }

    public CocosHelper(Activity context, RelativeLayout container) {
        this.context = context;
        this.container = container;
        Utils.setActivity(this.context);
        Utils.hideVirtualButton();
        onLoadNativeLibraries();
        Cocos2dxHelper.init(this.context, this);
        CanvasRenderingContext2DImpl.init(this.context);
    }

    public void start(final String path) {
        this.renderer = this.createRenderer();
        this.renderer.setScreenWidthAndHeight(this.container.getWidth(), this.container.getHeight());
        this.renderer.setDefaultResourcePath(path);
        this.container.addView(this.mGLSurfaceView);
    }

    public void pause(a) {
        Cocos2dxHelper.onPause();
        mGLSurfaceView.onPause();
    }

    public void resume(a) {
        Utils.hideVirtualButton();
        Cocos2dxHelper.onResume();
        mGLSurfaceView.onResume();
    }

    public void destroy(a) {
        if (this.renderer ! =null) {
            this.renderer.exit();
            new Handler().postDelayed(new Runnable() {
                @Override
                public void run(a) {
                    CocosHelper.this.renderer = null;
                    CocosHelper.this.container.removeView(CocosHelper.this.mGLSurfaceView);
                    CocosHelper.this.mGLSurfaceView = null; }},500); }}private void onLoadNativeLibraries(a) {
        try {
            ApplicationInfo ai = this.context.getPackageManager().getApplicationInfo(this.context.getPackageName(), PackageManager.GET_META_DATA);
            Bundle bundle = ai.metaData;
            String libName = bundle.getString("android.app.lib_name");
            System.loadLibrary(libName);
        } catch(Exception e) { e.printStackTrace(); }}private Cocos2dxRenderer createRenderer(a)  {
        this.mGLSurfaceView = this.createSurfaceView();
        this.mGLSurfaceView.setPreserveEGLContextOnPause(true);
        mGLSurfaceView.setBackgroundColor(Color.TRANSPARENT);
        Cocos2dxRenderer renderer = new Cocos2dxRenderer();
        this.mGLSurfaceView.setCocos2dxRenderer(renderer);

        return renderer;
    }

    private Cocos2dxGLSurfaceView createSurfaceView(a) {
        Cocos2dxGLSurfaceView glSurfaceView = new Cocos2dxGLSurfaceView(this.context);
        //this line is need on some device if we specify an alpha bits
        if(this.mGLContextAttrs[3] > 0) glSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT);

        Cocos2dxEGLConfigChooser chooser = new Cocos2dxEGLConfigChooser(this.mGLContextAttrs);
        glSurfaceView.setEGLConfigChooser(chooser);
        return glSurfaceView;
    }

    @Override
    public void showDialog(final String pTitle, final String pMessage) {}@Override
    public void runOnGLThread(final Runnable runnable) {
        this.mGLSurfaceView.queueEvent(runnable);
    }


    public class Cocos2dxEGLConfigChooser implements GLSurfaceView.EGLConfigChooser {
        protected int[] configAttribs;
        public Cocos2dxEGLConfigChooser(int redSize, int greenSize, int blueSize, int alphaSize, int depthSize, int stencilSize)
        {
            configAttribs = new int[] {redSize, greenSize, blueSize, alphaSize, depthSize, stencilSize};
        }
        public Cocos2dxEGLConfigChooser(int[] attribs)
        {
            configAttribs = attribs;
        }

        private int findConfigAttrib(EGL10 egl, EGLDisplay display,
                                     EGLConfig config, int attribute, int defaultValue) {
            int[] value = new int[1];
            if (egl.eglGetConfigAttrib(display, config, attribute, value)) {
                return value[0];
            }
            return defaultValue;
        }

        class ConfigValue implements Comparable<ConfigValue{
            public EGLConfig config = null;
            public int[] configAttribs = null;
            public int value = 0;
            private void calcValue(a) {
                // depth factor 29bit and [6,12)bit
                if (configAttribs[4] > 0) {
                    value = value + (1 << 29) + ((configAttribs[4] %64) < <6);
                }
                // stencil factor 28bit and [0, 6)bit
                if (configAttribs[5] > 0) {
                    value = value + (1 << 28) + ((configAttribs[5] %64));
                }
                // alpha factor 30bit and [24, 28)bit
                if (configAttribs[3] > 0) {
                    value = value + (1 << 30) + ((configAttribs[3] %16) < <24);
                }
                // green factor [20, 24)bit
                if (configAttribs[1] > 0) {
                    value = value + ((configAttribs[1] %16) < <20);
                }
                // blue factor [16, 20)bit
                if (configAttribs[2] > 0) {
                    value = value + ((configAttribs[2] %16) < <16);
                }
                // red factor [12, 16)bit
                if (configAttribs[0] > 0) {
                    value = value + ((configAttribs[0] %16) < <12); }}public ConfigValue(int[] attribs) {
                configAttribs = attribs;
                calcValue();
            }

            public ConfigValue(EGL10 egl, EGLDisplay display, EGLConfig config) {
                this.config = config;
                configAttribs = new int[6];
                configAttribs[0] = findConfigAttrib(egl, display, config, EGL10.EGL_RED_SIZE, 0);
                configAttribs[1] = findConfigAttrib(egl, display, config, EGL10.EGL_GREEN_SIZE, 0);
                configAttribs[2] = findConfigAttrib(egl, display, config, EGL10.EGL_BLUE_SIZE, 0);
                configAttribs[3] = findConfigAttrib(egl, display, config, EGL10.EGL_ALPHA_SIZE, 0);
                configAttribs[4] = findConfigAttrib(egl, display, config, EGL10.EGL_DEPTH_SIZE, 0);
                configAttribs[5] = findConfigAttrib(egl, display, config, EGL10.EGL_STENCIL_SIZE, 0);
                calcValue();
            }

            @Override
            public int compareTo(ConfigValue another) {
                if (value < another.value) {
                    return -1;
                } else if (value > another.value) {
                    return 1;
                } else {
                    return 0; }}@Override
            public String toString(a) {
                return "{ color: " + configAttribs[3] + configAttribs[2] + configAttribs[1] + configAttribs[0] +
                        "; depth: " + configAttribs[4] + "; stencil: " + configAttribs[5] + "; }"; }}@Override
        public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display)
        {
            int[] EGLattribs = {
                    EGL10.EGL_RED_SIZE, configAttribs[0],
                    EGL10.EGL_GREEN_SIZE, configAttribs[1],
                    EGL10.EGL_BLUE_SIZE, configAttribs[2],
                    EGL10.EGL_ALPHA_SIZE, configAttribs[3],
                    EGL10.EGL_DEPTH_SIZE, configAttribs[4],
                    EGL10.EGL_STENCIL_SIZE,configAttribs[5],
                    EGL10.EGL_RENDERABLE_TYPE, 4.//EGL_OPENGL_ES2_BIT
                    EGL10.EGL_NONE
            };
            EGLConfig[] configs = new EGLConfig[1];
            int[] numConfigs = new int[1];
            boolean eglChooseResult = egl.eglChooseConfig(display, EGLattribs, configs, 1, numConfigs);
            if (eglChooseResult && numConfigs[0] > 0)
            {
                return configs[0];
            }

            // there's no config match the specific configAttribs, we should choose a closest one
            int[] EGLV2attribs = {
                    EGL10.EGL_RENDERABLE_TYPE, 4.//EGL_OPENGL_ES2_BIT
                    EGL10.EGL_NONE
            };
            eglChooseResult = egl.eglChooseConfig(display, EGLV2attribs, null.0, numConfigs);
            if(eglChooseResult && numConfigs[0] > 0) {
                int num = numConfigs[0];
                ConfigValue[] cfgVals = new ConfigValue[num];

                // convert all config to ConfigValue
                configs = new EGLConfig[num];
                egl.eglChooseConfig(display, EGLV2attribs, configs, num, numConfigs);
                for (int i = 0; i < num; ++i) {
                    cfgVals[i] = new ConfigValue(egl, display, configs[i]);
                }

                ConfigValue e = new ConfigValue(configAttribs);
                // bin search
                int lo = 0;
                int hi = num;
                int mi;
                while (lo < hi - 1) {
                    mi = (lo + hi) / 2;
                    if (e.compareTo(cfgVals[mi]) < 0) {
                        hi = mi;
                    } else{ lo = mi; }}if(lo ! = num -1) {
                    lo = lo + 1;
                }
                Log.w("cocos2d"."Can't find EGLConfig match: " + e + ", instead of closest one:" + cfgVals[lo]);
                return cfgVals[lo].config;
            }

            return null; }}}Copy the code

cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxHelper.java

public static void runOnGLThread(final Runnable r) {
  	// ((Cocos2dxActivity)sActivity).runOnGLThread(r);
		Cocos2dxHelper.sCocos2dxHelperListener.runOnGLThread(r);
}

// Adjust the init method to add the listener parameter
public static void init(final Activity activity) {
  	Cocos2dxHelper.init(activity, (Cocos2dxHelperListener)activity);
}

public static void init(final Activity activity, Cocos2dxHelperListener listener) { sActivity = activity; Cocos2dxHelper.sCocos2dxHelperListener = (Cocos2dxHelperListener)listener; . . }public static void endApplication(a) {
  // Avoid exiting the Activity directly
  // if (sActivity ! = null)
  // sActivity.finish();
}

//////////////// code replaces /////////////////////
Cocos2dxActivity.getContext() = >getActivity()
Cocos2dxHelper.sActivity => getActivity(a)Copy the code

cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxRenderer.java

/ / way to increase the exit public void exit () {Cocos2dxRenderer. NativeExit (); } private static native void nativeExit();Copy the code

Write sample code

Java project, create a New MainAcivity and set it to start the Activity.

package org.cocos2d.examplecases;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.RelativeLayout;
import org.cocos2dx.lib.CocosHelper;

public class MainActivity extends AppCompatActivity {

    private RelativeLayout gameContainer;
    private CocosHelper helper;
    private int index = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if(! isTaskRoot()) { finish();return;
        }
        setContentView(R.layout.activity_main);

        this.findViewById(R.id.switchBtn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MainActivity.this.start(); }});this.findViewById(R.id.closeBtn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MainActivity.this.stop(); }});this.gameContainer = this.findViewById(R.id.gameContianer);
        this.helper = new CocosHelper(this.this.gameContainer);
    }

    private void start(a) {
        String resPath = "";
        if (index == 0) {
            index = -1;
            resPath = "@assets/game/bubble";
        } else {
            index = 0;
            resPath = "@assets/game/game1";
        }

        this.helper.start(resPath);
    }

    private void stop(a) {
        this.helper.destroy(); }}Copy the code

      
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="# 009688"
        android:orientation="vertical">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <Button
                android:id="@+id/switchBtn"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Switch games"
                android:textColor="#00695C"
                tools:ignore="TextContrastCheck" />

            <Button
                android:id="@+id/closeBtn"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Close the game" />
        </LinearLayout>
        <RelativeLayout
            android:id="@+id/gameContianer"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#bbbbbb"
            >
        </RelativeLayout>
    </LinearLayout>

</android.support.constraint.ConstraintLayout>
Copy the code

Configure the mini game pack:

In build.gradle the configuration is as follows:

Ok, this is all the operation, try ~

Afterword.

Next formal commercial use, but also need more detailed analysis of memory usage. In addition, the small game is part of the main process, and abnormal game may cause the App to flash back, which still needs to be optimized.

The resources

  • www.233tw.com/cocos/5613
  • Forum.cocos.org/t/topic/534…
  • Forum.cocos.org/t/cocos-cre…
  • Forum.cocos.org/t/cocos-cre…
  • Blog.csdn.net/xing4655787…
  • Forum.cocos.org/t/cocos2-x/…
  • Github.com/cocos-creat…