1
\$\begingroup\$

I'm having trouble trying to figure out why my LibGDX game only runs successfully once on Android.

Assuming the game has not been installed on the device, when I run the application from eclipse the game runs fine. After closing the game, and attempting to run the game again by opening the game on my phone (Not through eclipse) it seems to just hang on this screen forever: http://i.stack.imgur.com/QcLCW.png

From my testing, I have discovered that none of my Android Launcher code is executed, .onCreate() is never called. The LogCat shows that no errors are thrown and the application only seems to load the LibGDX library and then nothing. This is the entire LogCat output:

Entire LogCat output.

Sometimes clearing the cache and data for the game on my device seems to temporarily fix the problem, but not always.

This is the AndroidLauncher class:

package com.ifs_studios.cheekychameleon.android;

import java.util.HashMap;

import android.content.Intent;
import android.content.IntentSender.SendIntentException;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.RelativeLayout;
import android.widget.RelativeLayout.LayoutParams;
import android.widget.Toast;

import com.badlogic.gdx.backends.android.AndroidApplication;
import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration;
import com.google.android.gms.ads.AdListener;
import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.AdSize;
import com.google.android.gms.ads.AdView;
import com.google.android.gms.ads.InterstitialAd;
import com.google.android.gms.analytics.GoogleAnalytics;
import com.google.android.gms.analytics.HitBuilders;
import com.google.android.gms.analytics.Logger.LogLevel;
import com.google.android.gms.analytics.Tracker;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
import com.google.android.gms.games.Games;
import com.google.android.gms.plus.Plus;
import com.ifs_studios.cheekychameleon.ActionResolver;
import com.ifs_studios.cheekychameleon.CheekyChameleon;

/**
 * MemoryBlox Android Launcher.
 * 
 * @author BleedObsidian (Jesse Prescott)
 */
public class AndroidLauncher extends AndroidApplication implements ActionResolver, ConnectionCallbacks, OnConnectionFailedListener {
    /**
     * Banner Advert AdMob key.
     */
    private static final String BANNER_ADMOB_UNIT_ID= "REMOVED-STACKOVERFLOW";

    /**
     * Fullscreen Advert AdMob key.
     */
    private static final String FULLSCREEN_ADMOB_UNIT_ID= "REMOVED-STACKOVERFLOW";

    /**
     * Google API Client.
     */
    private GoogleApiClient googleApiClient;

    /**
     * If currently resolving a sign in error.
     */
    private boolean isResolvingSignInError;

    /**
     * Global Leaderboard ID.
     */
    private static final String LEADERBOARD_ID = "REMOVED-STACKOVERFLOW";

    /**
     * Unique request resolve error ID.
     */
    private static final int REQUEST_RESOLVE_ERROR = 1267;

    /**
     * Unique request for achievements ID.
     */
    private static final int REQUEST_ACHIEVEMENTS = 1268;

    /**
     * Unique request for leaderboards ID.
     */
    private static final int REQUEST_LEADERBOARD = 1269;

    /**
     * Google Analytics Trackers.
     */
    private HashMap<TrackerName, Tracker> trackers = new HashMap<TrackerName, Tracker>();

    /**
     * Google Analytics App Tracker.
     */
    private Tracker appTracker;

    /**
     * Google Analytics Global Tracker.
     */
    private Tracker globalTracker;

    /**
     * Google Analytics Ecommerce Tracker.
     */
    private Tracker ecommerceTracker;

    /**
     * If the game has been started.
     */
    private boolean isStarted = false;

    /**
     * Advertisement View.
     */
    protected AdView adView;

    /**
     * Interstitial Advertisement.
     */
    protected InterstitialAd interstitialAd;

    /**
     * Game View.
     */
    protected View gameView;

    @Override
    protected void onCreate (Bundle savedInstanceState) {
        if(this.isStarted) {
            return;
        } else {
            this.isStarted = true;
        }

        this.requestWindowFeature(Window.FEATURE_NO_TITLE);

        super.onCreate(savedInstanceState);

        this.setTitle("Cheeky Chameleon");

        this.appTracker = this.getTracker(TrackerName.APP_TRACKER);
        this.globalTracker = this.getTracker(TrackerName.GLOBAL_TRACKER);
        this.ecommerceTracker = this.getTracker(TrackerName.ECOMMERCE_TRACKER);

        this.appTracker.enableAdvertisingIdCollection(true);
        this.globalTracker.enableAdvertisingIdCollection(true);
        this.ecommerceTracker.enableAdvertisingIdCollection(true);

        AndroidApplicationConfiguration config = new AndroidApplicationConfiguration();
        config.useAccelerometer = false;
        config.useCompass = false;
        config.useWakelock = true;
        config.hideStatusBar = true;
        config.useImmersiveMode = true;

        this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        this.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);

        RelativeLayout layout = new RelativeLayout(this);
        RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);
        layout.setLayoutParams(params);

        this.createInterstitialAd();

        this.createAdView();
        layout.addView(this.adView);

        this.createGameView(config);
        layout.addView(this.gameView);

        this.setContentView(layout);
        this.startAdvertising();
    }

    @Override
    public void onStart() {
        super.onStart();
        GoogleAnalytics.getInstance(this).reportActivityStart(this);
        GoogleAnalytics.getInstance(this).getLogger()
        .setLogLevel(LogLevel.INFO);

        this.googleApiClient = new GoogleApiClient.Builder(this)
        .addApi(Plus.API).addScope(Plus.SCOPE_PLUS_LOGIN)
        .addApi(Games.API).addScope(Games.SCOPE_GAMES)
        .addConnectionCallbacks(this)
        .addOnConnectionFailedListener(this)
        .build();
    }

    @Override
    public void onStop() {
        super.onStop();
        System.out.println("Stop");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        GoogleAnalytics.getInstance(this).reportActivityStop(this);
        this.googleApiClient.disconnect();
        System.out.println("Destroy");
    }

    /**
     * Create AdView.
     * 
     * @return AdView.
     */
    private void createAdView() {
        this.adView = new AdView(this);
        this.adView.setAdSize(AdSize.SMART_BANNER);
        this.adView.setAdUnitId(AndroidLauncher.BANNER_ADMOB_UNIT_ID);
        this.adView.setId(12398);

        RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, RelativeLayout.TRUE);
        params.addRule(RelativeLayout.CENTER_HORIZONTAL, RelativeLayout.TRUE);
        this.adView.setLayoutParams(params);
        this.adView.setBackgroundColor(Color.WHITE);
    }

    /**
     *  Create fullscreen AdView.
     */
    private void createInterstitialAd() {
        this.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                interstitialAd = new InterstitialAd(AndroidLauncher.this);
                interstitialAd.setAdUnitId(AndroidLauncher.FULLSCREEN_ADMOB_UNIT_ID);

                AdRequest adRequest = new AdRequest.Builder().build();
                interstitialAd.loadAd(adRequest);
            }
        });
    }

    @Override
    public void loadInterstitialAdvert() {
        this.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                AdRequest adRequest = new AdRequest.Builder().build();
                interstitialAd.loadAd(adRequest);
            }
        });
    }

    /**
     * Create GameView.
     * 
     * @param config AndroidApplicationConfiguration.
     * @return View.
     */
    private void createGameView(AndroidApplicationConfiguration config) {
        this.gameView = this.initializeForView(new CheekyChameleon(this), config);

        this.gameView.setSystemUiVisibility(
                View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                | View.SYSTEM_UI_FLAG_FULLSCREEN
                | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);

        RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        params.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE);
        params.addRule(RelativeLayout.CENTER_HORIZONTAL, RelativeLayout.TRUE);
        params.addRule(RelativeLayout.ABOVE, adView.getId());
        this.gameView.setLayoutParams(params);
    }

    /**
     * Start Advertising.
     * 
     * @param adView AdView.
     */
    private void startAdvertising() {
        this.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                AdRequest.Builder adRequestBuilder = new AdRequest.Builder();
                   AdRequest adRequest = adRequestBuilder.build();
                   adView.setAdListener(new AdListener() {
                       @Override
                       public void onAdFailedToLoad(int error) {
                            System.out.println("Error: " + error);
                       }
                   });
                   adView.loadAd(adRequest);
            }
        });
    }

    @Override
    public void setTrackerScreenName(String name) {
        this.globalTracker.setScreenName(name);
        this.globalTracker.send(new HitBuilders.AppViewBuilder().build());
        System.out.println("Tracking screen: " + name);
    }

    /**
     * Get google analytics tracker.
     * 
     * @param trackerName TrackerName.
     * @return Tracker.
     */
    public synchronized Tracker getTracker(TrackerName trackerName) {
        if (!this.trackers.containsKey(trackerName)) {
            GoogleAnalytics analytics = GoogleAnalytics.getInstance(this);

            Tracker tracker = (trackerName == TrackerName.APP_TRACKER) ? analytics
                    .newTracker(R.xml.app_tracker)
                    : (trackerName == TrackerName.GLOBAL_TRACKER) ? analytics
                            .newTracker(R.xml.global_tracker) : analytics
                            .newTracker(R.xml.ecommerce_tracker);
            tracker.enableAdvertisingIdCollection(true);
            this.trackers.put(trackerName, tracker);
        }

        return this.trackers.get(trackerName);
    }


    /**
     * Enum used to identify the tracker that needs to be used for tracking.
     * 
     * A single tracker is usually enough for most purposes. In case you do need
     * multiple trackers, storing them all in Application object helps ensure
     * that they are created only once per application instance.
     */
    public enum TrackerName {
        APP_TRACKER, // Tracker used only in this app.
        GLOBAL_TRACKER, // Tracker used by all the apps from a company. eg: roll-up tracking.
        ECOMMERCE_TRACKER, // Tracker used by all ecommerce transactions from a company.
    }


    @Override
    public void displayInterstitialAdvert() {
        this.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if(interstitialAd.isLoaded()) {
                    interstitialAd.show();
                } else {
                    AdRequest adRequest = new AdRequest.Builder().build();
                    interstitialAd.loadAd(adRequest);
                    interstitialAd.show();
                }
            }
        });
    }

    @Override
    public void signInGooglePlayServices() {
        if(!this.isResolvingSignInError) {
            this.googleApiClient.connect();
        }
    }

    @Override
    public void signOutGooglePlayServices() {
        this.googleApiClient.disconnect();
    }

    @Override
    public boolean isSignedInGooglePlayServices() {
        return this.googleApiClient.isConnected();
    }

    @Override
    public void displayAchievements() {
        if(this.googleApiClient.isConnected()) {
            this.startActivityForResult(Games.Achievements.getAchievementsIntent(this.googleApiClient), AndroidLauncher.REQUEST_ACHIEVEMENTS);
        } else {
            this.googleApiClient.connect();
        }
    }

    @Override
    public void displayLeaderboards() {
        if(this.googleApiClient.isConnected()) {
            this.startActivityForResult(Games.Leaderboards.getLeaderboardIntent(this.googleApiClient,
                    AndroidLauncher.LEADERBOARD_ID), AndroidLauncher.REQUEST_LEADERBOARD);
        } else {
            this.googleApiClient.connect();
        }
    }

    @Override
    public void submitScore(int score) {
        if(this.googleApiClient.isConnected()) {
            System.out.println("Submiting score: " + score);
            Games.Leaderboards.submitScore(this.googleApiClient, AndroidLauncher.LEADERBOARD_ID, score);
        }
    }

    @Override
    public void onConnectionFailed(ConnectionResult result) {
        if(this.isResolvingSignInError) {
            return;
        } else if (result.hasResolution()){
            try {
                this.isResolvingSignInError = true;
                result.startResolutionForResult(this, AndroidLauncher.REQUEST_RESOLVE_ERROR);
                System.out.println("Failed to connect to Google Play Services, Resolving");
            } catch(SendIntentException exception) {
                this.googleApiClient.connect();
            }
        } else {
            GooglePlayServicesUtil.showErrorDialogFragment(result.getErrorCode(), this, AndroidLauncher.REQUEST_RESOLVE_ERROR);
            this.isResolvingSignInError = false;
            System.out.println("Failed to connect to Google Play Services: " + result.getErrorCode());
        }
    }

    @Override
    public void onConnected(Bundle bundle) {
        System.out.println("Connected to Google Play Services");
    }

    @Override
    public void onConnectionSuspended(int number) {
        System.out.println("Google Play Services connection suspended");
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == AndroidLauncher.REQUEST_RESOLVE_ERROR) {
            this.isResolvingSignInError = false;

            if (resultCode == RESULT_OK) {
                if (!this.googleApiClient.isConnecting() &&
                        !this.googleApiClient.isConnected()) {
                    this.googleApiClient.connect();
                }
            } else {
                System.out.println("Sign in failed: " + resultCode);
            }
        } else if(requestCode == AndroidLauncher.REQUEST_ACHIEVEMENTS) {
            if (resultCode != RESULT_OK && resultCode != RESULT_CANCELED) {
                Toast.makeText(this, "Failed to show achivements", Toast.LENGTH_SHORT).show();
                System.out.println("Failed to display achievements.");
            }
        }
    }
}

This is my AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.ifs_studios.cheekychameleon.android"
    android:versionCode="29"
    android:versionName="0.4.1" >

    <uses-sdk android:minSdkVersion="11" android:targetSdkVersion="21" />

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="com.android.vending.BILLING" />
    <uses-permission android:name="android.permission.VIBRATE"/>
    <uses-permission android:name="android.permission.VIBRATE"/>
    <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/>
    <uses-permission android:name="com.google.android.providers.gsf.permission.WRITE_GSERVICES"/>


    <application
        android:allowBackup="false"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name">
        <activity
            android:name="com.ifs_studios.cheekychameleon.android.AndroidLauncher"
            android:label="@string/app_name" 
            android:screenOrientation="portrait"
            android:configChanges="keyboard|keyboardHidden|orientation|screenSize">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity android:name="com.google.android.gms.ads.AdActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"/>

        <meta-data android:name="com.google.android.gms.version"
        android:value="@integer/google_play_services_version" />
        <meta-data
        android:name="com.google.android.gms.analytics.globalConfigResource"
        android:resource="@xml/global_tracker" />
        <meta-data android:name="com.google.android.gms.games.APP_ID"
        android:value="@string/app_id" />
    </application>
</manifest>

From the output of the LogCat I have no clue what the problem could be. I have tested this on an Xperia U and Xperia Z1 and the problem still persists. Thanks for your help.

\$\endgroup\$
  • \$\begingroup\$ What's the last state your application goes through when you stop it? Does it go all the way to onDestroy()? Override onRestart() and write something to logcat in there, if the app is stopped but the process isn't killed it won't go through onCreate again. \$\endgroup\$ – bornander Mar 9 '15 at 10:30
  • \$\begingroup\$ That was a very useful comment thank you. I managed to figure out what the problem actually was. Thanks for your help anyway. \$\endgroup\$ – BleedObsidian Mar 10 '15 at 0:07
2
\$\begingroup\$

After days of stress, I've finally fixed the problem. After performing a backtrace on the main thread I discovered the program came to a halt when trying to load the Google Analytics trackers. This is the thread backtrace:

at com.google.android.gms.analytics.ae.getLogger(Unknown Source)
at com.google.android.gms.analytics.ae.W(Unknown Source)
at com.google.android.gms.analytics.z$a.f(Unknown Source) at com.google.android.gms.analytics.n.a(Unknown Source)
at com.google.android.gms.analytics.n.x(Unknown Source)
at com.google.android.gms.analytics.GoogleAnalytics.eZ(Unknown Source)
at com.google.android.gms.analytics.GoogleAnalytics.(Unknown Source)
at com.google.android.gms.analytics.GoogleAnalytics.(Unknown Source)
at com.google.android.gms.analytics.GoogleAnalytics.getInstance(Unknown Source)

I then did some research on this result. It appears to be a bug caused when trying to load the trackers from an xml file. This bug is discussed in more detail here:

To prevent this bug, I no longer load the trackers from their corrosponding xml files by removing this line in the AndroidManifest.xml:

<meta-data 
        android:name="com.google.android.gms.analytics.globalConfigResource"
        android:resource="@xml/analytics_global_config" />

Problem fixed, thanks guys.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.