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:
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: https://i.stack.imgur.com/msMOz.png
The only thing that raises an eyebrow for me in LogCat is:
Launch timeout has expired, giving up wake lock! Activity idle timeout for ActivityRecord
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= "XXXXX";
/**
* Fullscreen Advert AdMob key.
*/
private static final String FULLSCREEN_ADMOB_UNIT_ID= "XXXXX";
/**
* 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 = "XXXXX";
/**
* 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) {
super.onCreate(savedInstanceState);
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
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;
RelativeLayout layout = new RelativeLayout(this);
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
this.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
this.createInterstitialAd();
this.createAdView();
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);
layout.addView(this.adView, params);
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);
this.adView.setBackgroundColor(Color.WHITE);
}
/**
* Create fullscreen AdView.
*/
private void createInterstitialAd() {
this.interstitialAd = new InterstitialAd(AndroidLauncher.this);
this.interstitialAd.setAdUnitId(AndroidLauncher.FULLSCREEN_ADMOB_UNIT_ID);
AdRequest adRequest = new AdRequest.Builder().build();
this.interstitialAd.loadAd(adRequest);
}
@Override
public void loadInterstitialAdvert() {
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() {
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() {
if(interstitialAd.isLoaded()) {
this.interstitialAd.show();
} else {
AdRequest adRequest = new AdRequest.Builder().build();
this.interstitialAd.loadAd(adRequest);
this.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.
Edit - Extra Info:
I am using the lateset version of LibGDX (1.5.4)