I am new to RoboGuice and I am trying to set up my activity to use DI. However, nothing happens when I attempt to use it. I only get a blank black window with no content and no logging in my Activity.onCreate() method after I call "super.onCreate(savedInstanceState);"
See these 2 snippets of code:
public class ClikClokActivity extends RoboActivity{
#Inject
private TileAdapter tileAdapter;
#Inject
private GameLogicService gameLogicService;
#Inject
private GridOperationQueue gridOperationQueue;
private GridView gridView;
#Inject
private Handler handler;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
Log.v(this.getClass().toString(), "Entering onCreate");
super.onCreate(savedInstanceState);
Log.v(this.getClass().toString(), "Never logs this with RoboGuice");
setContentView(R.layout.main);
gridView = (GridView) findViewById(R.id.gridview);
gridView.setNumColumns(Constants.GRID_WIDTH);
gridView.setAdapter(tileAdapter);
Log.v(this.getClass().toString(), "GridView initialized");
gridOperationQueue.start();
Log.v(this.getClass().toString(), "Completed onCreate");
}
and
public class ClikClokApplication extends RoboApplication{
#Override
protected void addApplicationModules(List<Module> modules) {
modules.add(new ClikClokModule());
}
}
and
public class ClikClokModule extends AbstractAndroidModule {
#Override
protected void configure() {
}
}
and
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.clikclok"
android:versionCode="1"
android:versionName="1.0">
<application android:name="com.clikclok.ClikClokApplication" android:icon="#drawable/icon" android:label="#string/app_name">
<activity android:name=".ClikClokActivity"
android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
If you look at the above code, I never get the second logging. However, if I was to extend from Activity instead and remove the android:name="com.clikclok.ClikClokApplication" attribute from my manifest then I do get the second logging (albeit fails with NullPointers as there is no initialization performed).
So what may be happening in super.onCreate(savedInstanceState); that is causing my application not to work?
Thanks
Update from the above:
I've spent quite a bit of time investigating this and using Eclipse's debugger can now see where my code seems to hang within RoboGuice.
The following code is from the InjectorImpl class:
public void injectMembers(Object instance) {
// Reaches here but...
MembersInjector membersInjector = getMembersInjector(instance.getClass());
// ....this comment is never reached
membersInjector.injectMembers(instance);
}
So I dug into the Guice 3.0 code using my debugger and into the FailableCache class:
public V get(K key, Errors errors) throws ErrorsException {
// Reaches here....
Object resultOrError = delegate.get(key);
// ...but not here
if (resultOrError instanceof Errors) {
errors.merge((Errors) resultOrError);
throw errors.toException();
} else {
#SuppressWarnings("unchecked") // create returned a non-error result, so this is safe
V result = (V) resultOrError;
return result;
}
}
How could this be that it just hangs while retrieving a key from a map? I'm not familiar enough with the code and it is quite confusing to troubleshoot.
Any advice is appreciated.
Try to add some bindings in your configure method.
You can also try to inject your grid view like this
#InjectView(R.id.gridview)
GridView gridView;
I hope this will help you.
Regards.
This was caused by me attempting to inject my Activity class into a service class.
So I was attempting to #Inject an instance of "ClikClokActivity" instead of "Activity".
Once I removed this injection attempt, everything worked fine. Not sure if this exposes some other issue with RoboGuice or Guice itself.
Related
Problem: I need to run some code at every start before my app is ready to be used.
At first, I tried doing it in a dedicated activity.
AndroidManifest.xml
<activity android:name=".MainActivity" />
<activity android:name=".StarterActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
AppLoader.java
public class AppLoader {
private static Object someInstance;
public static void load(Runnable onCompleteCallback) {
try {
someInstance = new Object();
//potentially long operation to initialize the app
Thread.sleep(5000);
onCompleteCallback.run();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
public static void checkInitialized() {
if (someInstance == null) {
throw new RuntimeException("Not initialized");
}
}
}
StarterActivity.java
public class StarterActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AppLoader.load(() -> {
MainActivity.start(this);
finish();
});
}
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
public static void start(Context context) {
Intent starter = new Intent(context, MainActivity.class);
context.startActivity(starter);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AppLoader.checkInitialized();
}
}
This works fine if the app is cold started via the launcher icon but crashes in all other cases. Simple way to reproduce the issue:
Go to developer settings on your device and set "Background process limit" to "No background process"
Open the app
Open some other app
Open the app again. Result: it crashes.
Here's an article describing a similar problem: Android process death — and the (big) implications for your app
Possible solutions:
Lazy loading/reactive approach. I try to use it as much as possible but there is always some code I need to run in a blocking way before user can interact with the app so this is not enough.
Putting all of that code in App.onCreate(). This would probably work for small apps but I've seen large apps that take 5-10 seconds to initialize, and I doubt they use onCreate() for that. Possible downsides: ANR and/or excessive startup time in Android Vitals?
Checking if the app is initialized in a BaseActivity, but that would require either blocking onCreate or managing lifecycle callbacks manually which doesn't sound like a good idea.
So, what's the proper way to run some code every time the app is launched?
Note: Normally StarterActivity would be a splash screen, AppLoader would be injected, etc, but I left that out for simplicity.
AndroidManifest.xml
<application
android:name=".AppLoader"
AppLoader.java
public class AppLoader extends Application {
private static Object someInstance;
#Override
public void onCreate() {
super.onCreate();
// DO YOUR STUFF
}
}
Update
- Use Handler with splash screen.
public class StarterActivity extends AppCompatActivity {
private Handler handler;
private Runnable myStuffRunnable;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
handler = new Handler();
myStuffRunnable = new Runnable(){
public void run(){
// DO MY STUFF
MainActivity.start(this);
}
};
}
#Override
protected void onPause() {
handler.removeCallbacks(myStuffRunnable);
super.onPause();
}
#Override
protected void onResume() {
super.onResume();
handler.post(myStuffRunnable);
}
#Override
protected void onDestroy() {
handler.removeCallbacks(myStuffRunnable);
super.onDestroy();
}
}
Your app is throwing the RuntimeException you set in AppLoader.checkInitialized() method, because your someInstance object is losing it's state when the app goes to background and gets killed by the system ('cause you have set your device to hold zero background threads). So, when you try to reopen the app, the system launches MainActivity directly (and not StarterActivity) because it is trying to restore it's previous state. But variables are not restored, not even static variables.
So, if you need the Object someInstance on your MainActivity, you should integrate it's instantiation into MainActivitie's lifecycle, overriding methods like onSavedInstanceState, onRestoreInstanceState, etc, to properly handle and reaload this object if your app gets killed by the system.
Take a look on this https://developer.android.com/guide/components/activities/activity-lifecycle
If anyone's interested, I ended up just redirecting the user to StarterActivity if needed to make sure the necessary code is executed at every start.
public abstract class BaseActivity extends AppCompatActivity {
private boolean isCreated;
#Override
protected final void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (!appLoader.isLoaded()) {
StarterActivity.start(this);
finish();
return;
}
onCreateActivity(savedInstanceState);
isCreated = true;
}
protected void onCreateActivity(#Nullable Bundle savedInstanceState) {
}
#Override
protected final void onDestroy() {
super.onDestroy();
if (isCreated) {
onDestroyActivity();
}
}
protected void onDestroyActivity() {
}
}
All activities extend BaseActivity (except StarterActivity) and override onCreateActivity/onDestroyActivity instead of onCreate/onDestroy.
I have been looking for an solution on how to make my app not to loose data when orientation of phone changes. I have found one and it is adding this line of code
android:configChanges="orientation|keyboardHidden">
to android manifest. So i did it:
<?xml version="1.0" encoding="utf-8"?>
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
And my data still get lost when orientation changes, could anyone please help me ? Thank you very much.
My java code:
public class MainActivity extends AppCompatActivity {
int scoreofShooter1 = 0;
int scoreofShooter2 = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void addOneForShooter1(View view) {
scoreofShooter1 = scoreofShooter1 + 1;
displayForShooter1(scoreofShooter1);
}
public void addTwoForShooter1(View view) {
scoreofShooter1 = scoreofShooter1 + 2;
displayForShooter1(scoreofShooter1);
}
public void addThreeForShooter1(View view) {
scoreofShooter1 = scoreofShooter1 + 3;
displayForShooter1(scoreofShooter1);
}
public void addFourForShooter1(View view) {
scoreofShooter1 = scoreofShooter1 + 4;
displayForShooter1(scoreofShooter1);
}
public void addOneForShooter2(View view) {
scoreofShooter2 = scoreofShooter2 + 1;
displayForShooter2(scoreofShooter2);
}
public void addTwoForShooter2(View view) {
scoreofShooter2 = scoreofShooter2 + 2;
displayForShooter2(scoreofShooter2);
}
public void addThreeForShooter2(View view) {
scoreofShooter2 = scoreofShooter2 + 3;
displayForShooter2(scoreofShooter2);
}
public void addFourForShooter2(View view) {
scoreofShooter2 = scoreofShooter2 + 4;
displayForShooter2(scoreofShooter2);
}
public void resetScoreOfShooter1(View view) {
scoreofShooter1 = 0;
displayForShooter1(scoreofShooter1);
}
public void resetScoreOfShooter2(View view) {
scoreofShooter2 = 0;
displayForShooter2(scoreofShooter2);
}
public void displayForShooter1(int score) {
TextView scoreView = (TextView) findViewById(R.id.shooter_1_score);
scoreView.setText(String.valueOf(score));
}
public void displayForShooter2(int score) {
TextView scoreView = (TextView) findViewById(R.id.shooter_2_score);
scoreView.setText(String.valueOf(score));
}
}
Change this:
android:configChanges="orientation|keyboardHidden">
to:
android:configChanges="orientation|keyboardHidden|screenSize">
"orientation":
The screen orientation has changed — the user has rotated
the device.
Note: If your application targets API level 13 or higher (as declared
by the minSdkVersion and targetSdkVersionattributes), then you should
also declare the "screenSize" configuration, because it also changes
when a device switches between portrait and landscape orientations.
Your solution is bad practice as stated in this great article
Perhaps the hackiest and most widely abused workaround is to disable the default destroy-and-recreate behavior by setting the android:configChanges attribute in your Android manifest. The apparent simplicity of this approach makes it extremely attractive to developers; Google engineers, however, discourage its use. The primary concern is that it requires you to handle device configuration changes manually in code. Handling configuration changes require you to take many additional steps to ensure that each and every string, layout, drawable, dimension, etc. remains in sync with the device’s current configuration, and if you aren’t careful, your application can easily have a whole series of resource-specific bugs as a result.
Another reason why Google discourages its use is because many
developers incorrectly assume that setting
android:configChanges="orientation" (for example) will magically
protect their application from unpredictable scenarios in which the
underlying Activity will be destroyed and recreated. This is not the
case. Configuration changes can occur for a number of reasons—not just
screen orientation changes. Inserting your device into a display dock,
changing the default language, and modifying the device’s default font
scaling factor are just three examples of events that can trigger a
device configuration change, all of which signal the system to destroy
and recreate all currently running Activities the next time they are
resumed. As a result, setting the android:configChanges attribute is
generally not good practice.
there are two scenarios:
1- if you just need to save some attributes just use onSavedInstanceState and onRestoreInstanceState
2- If you have a more complicated scenario where you have running Threads, AsyncTasks, Sockets.
you could:
1- Manage the Object Inside a Retained Fragment as described here
2- the better solution is to use loaders which are created to solve this exact problem.
from android docs:
If you fetch the data directly in the activity or fragment, your users will suffer from lack of responsiveness due to performing potentially slow queries from the UI thread.
If you fetch the data from another thread, perhaps with AsyncTask, then you're responsible for managing both the thread and the UI thread
through various activity or fragment lifecycle events, such as
onDestroy() and configurations changes.
Loaders solve these problems and includes other benefits. For example:
Loaders run on separate threads to prevent janky or unresponsive UI.
Loaders simplify thread management by providing callback methods when
events occur. Loaders persist and cache results across configuration
changes to prevent duplicate queries. Loaders can implement an
observer to monitor for changes in the underlying data source. For
example, CursorLoader automatically registers a ContentObserver to
trigger a reload when data changes.
for your particular situation
use the following code:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
scoreofShooter1 = savedInstanceState.getInt("scoreofShooter1");
scoreofShooter2 = savedInstanceState.getInt("scoreofShooter2");
}
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("scoreofShooter1", scoreofShooter1);
outState.putInt("scoreofShooter2", scoreofShooter2);
}
I am working on an android app that needs to load a UnityPlayer instance in an activiy, using code from the following forum post as a guide:
http://forum.unity3d.com/threads/98315-Using-Unity-Android-In-a-Sub-View .
Initially the application is correctly displaying the UnityPlayer inside an activity called "UnityActivity.java".
The problem starts when the user navigates back to the MainActivity (by either pressing the hardware back button or clicking on the ActionBar back button) and then tries to re-open the UnityActivity - in which case a black screen is shown instead of the UnityPlayer. A user in the forums suggested forwarding the onPause and onResume lifecycle events to the UnityPlayer, as shown in the code bellow. When doing that, however, the following errors show up and the app crashes.
This is logged when navigating to the UnityActivity for the first time:
W/libc(21095): pthread_create sched_setscheduler call failed: Operation not permitted
This error is logged when clicking the back button:
W/Choreographer(20963): Already have a pending vsync event. There should only be one at a time.
This error is logged when navigating to the UnityActivity for the second time:
A/libc(21095): Fatal signal 11 (SIGSEGV) at 0x00000000 (code=1), thread 21176 (Thread-5073)
...at which point I get kicked out of the application.
Code
This is an excerpt of the main activity MainActivity.java :
public void startUnityActivity(View view) {
Intent intent = new Intent(this, UnityActivity.class);
startActivity(intent);
}
This is an excerpt of the Unity activity UnityActivity.java :
public class UnityActivity extends ActionBarActivity {
UnityPlayer m_UnityPlayer;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_unity);
m_UnityPlayer = new UnityPlayer(this);
int glesMode = m_UnityPlayer.getSettings().getInt("gles_mode", 1);
m_UnityPlayer.init(glesMode, false);
FrameLayout layout = (FrameLayout) findViewById(R.id.unityView);
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
layout.addView(m_UnityPlayer, 0, lp);
m_UnityPlayer.windowFocusChanged(true);
m_UnityPlayer.resume();
}
#Override
public void onWindowFocusChanged(boolean hasFocus)
{
super.onWindowFocusChanged(hasFocus);
m_UnityPlayer.windowFocusChanged(hasFocus);
}
#Override
public void onPause() {
super.onPause();
m_UnityPlayer.pause();
}
#Override
public void onResume() {
super.onResume();
m_UnityPlayer.resume();
}
This is how the activities are described in the manifest ../AndroidManifest.xml:
<application
android:allowBackup="true"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name"
android:theme="#style/AppTheme" >
<activity
android:name="com.package.example.MainActivity"
android:label="#string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.package.example.UnityActivity"
android:label="#string/title_activity_unity"
android:screenOrientation="portrait"
android:launchMode="singleTask"
android:parentActivityName="com.package.example.MainActivity"
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale">
<meta-data android:name="unityplayer.UnityActivity" android:value="true" />
<meta-data android:name="unityplayer.ForwardNativeEventsToDalvik" android:value="true" />
</activity>
</application>
This is how the layout of the UnityActivity is defined ../res/layout/activity_unity.xml:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.package.example.UnityActivity"
tools:ignore="MergeRootFrame" >
<FrameLayout
android:id="#+id/unityView"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
</FrameLayout>
</FrameLayout>
I'd be thankful for any tips and solutions pointing me in the right direction.
Ok, easy things first
W/libc(21095): pthread_create sched_setscheduler call failed: Operation not permitted
There is nothing you can do about it. You even get this when you compile directly from Unity for Android, so it's a problem inside the engine.
Basic Setup
The guide you linked is pretty outdated. You no longer need to copy files from various locations to create a simple Android project.
Create a Android project by setting Build Settings -> Android -> Google Android project
You now have a complete package ready to import into Eclipse or Android Studio
Compile and deploy
Using UnityPlayer in a subactivity
The class UnityPlayerNativeActivity in your new Android project shows you how to setup the UnityPlayer and what events you need to forward. Here is the version used by Unity 4.3.4
package de.leosori.NativeAndroid;
import com.unity3d.player.*;
import android.app.NativeActivity;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
public class UnityPlayerNativeActivity extends NativeActivity
{
protected UnityPlayer mUnityPlayer; // don't change the name of this variable; referenced from native code
// UnityPlayer.init() should be called before attaching the view to a layout - it will load the native code.
// UnityPlayer.quit() should be the last thing called - it will unload the native code.
protected void onCreate (Bundle savedInstanceState)
{
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
getWindow().takeSurface(null);
setTheme(android.R.style.Theme_NoTitleBar_Fullscreen);
getWindow().setFormat(PixelFormat.RGB_565);
mUnityPlayer = new UnityPlayer(this);
if (mUnityPlayer.getSettings ().getBoolean ("hide_status_bar", true))
getWindow ().setFlags (WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
int glesMode = mUnityPlayer.getSettings().getInt("gles_mode", 1);
boolean trueColor8888 = false;
mUnityPlayer.init(glesMode, trueColor8888);
View playerView = mUnityPlayer.getView();
setContentView(playerView);
playerView.requestFocus();
}
protected void onDestroy ()
{
mUnityPlayer.quit();
super.onDestroy();
}
// onPause()/onResume() must be sent to UnityPlayer to enable pause and resource recreation on resume.
protected void onPause()
{
super.onPause();
mUnityPlayer.pause();
}
protected void onResume()
{
super.onResume();
mUnityPlayer.resume();
}
public void onConfigurationChanged(Configuration newConfig)
{
super.onConfigurationChanged(newConfig);
mUnityPlayer.configurationChanged(newConfig);
}
public void onWindowFocusChanged(boolean hasFocus)
{
super.onWindowFocusChanged(hasFocus);
mUnityPlayer.windowFocusChanged(hasFocus);
}
public boolean dispatchKeyEvent(KeyEvent event)
{
if (event.getAction() == KeyEvent.ACTION_MULTIPLE)
return mUnityPlayer.onKeyMultiple(event.getKeyCode(), event.getRepeatCount(), event);
return super.dispatchKeyEvent(event);
}
}
Although UnityPlayerNativeActivity extends NativeActivity you can still extend from ActionBarActivity instead without any problems as far as I can tell. At least it worked during my experiments.
The most important part you are missing is the call to mUnityPlayer.quit() during onDestroy(). Trying to create a new instance of UnityPlayer while the old one is still running will lead to crashes, hanging activities and endless suffering.
Unexpected behavior of mUnityPlayer.quit()
Fixing that you may be surprised that now your whole App simply closes when you return from your UnityActivity. mUnityPlayer.quit() will kill the process it is running inside. Not a single method will execute after calling mUnityPlayer.quit(), not even the onDestroy() method will finish.
The path to victory is to start your UnityActivity as a new process by adding the parameter android:process=":UnityKillsMe to your activty inside your AndroidManifest.xml.
In your case it would look like this
<activity
android:name="com.package.example.UnityActivity"
android:label="#string/title_activity_unity"
android:screenOrientation="portrait"
android:launchMode="singleTask"
android:process=":UnityKillsMe"
android:parentActivityName="com.package.example.MainActivity"
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale">
<meta-data android:name="unityplayer.UnityActivity" android:value="true" />
<meta-data android:name="unityplayer.ForwardNativeEventsToDalvik" android:value="false" />
</activity>
I'm not sure about the parameter unityplayer.ForwardNativeEventsToDalvik... The project created in the beginning sets it to false and the official (outdated) documentation mentions
Since touch/motion events are processed in native code, Java views would normally not see those events. There is, however, a forwarding mechanism in Unity which allows events to be propagated to the DalvikVM.
In my small example project I could not see a difference
The road ahead
You need to find a workflow to integrate your development with Unity with the Android project or vice versa. Exporting again with Unity would conflict with the changes you made in your Android project, so you would need to export into a separate folder and link to the Unity part from your Android project.
According to the aforementioned documentation you may be able to integrate your compiled Android classes and AndroidManifest.xml as plugins into Unity.
The resulting .class file(s) should be compressed into a .jar file and placed in the Assets->Plugins->Android folder. Since the manifest dictates which activity to launch it is also necessary to create a new AndroidManifest.xml. The AndroidManifest.xml file should also be placed in the Assets->Plugins->Android folder.
Good luck!
Question is two year ago, but I still struggled to find a detailed guide about it.
So I wrote one
The main Idea is to take the Unity project and use it as a library in the native android app.
I hope this guide will help someone.
well, i dont have clear answer for this
but i have found couple of interesting psots
1-
pthread_create warning on android
talks about W/libc(21095): pthread_create sched_setscheduler call failed: Operation not permitted
the post starts with After calling pthread_create function I receive next message:
i quote from the post:
This error means, that the process trying to create the thread hasn't
the appropriate privileges to set the scheduling priorty as specified.
and the post marked as answer have some good info to. have a look there
but on your code you are NOT calling pthread_create() -- [1]
2-
Meaning of Choreographer messages in Logcat
W/Choreographer(20963): Already have a pending vsync event. There should only be one at a time
i quote
Choreographer lets apps to connect themselves to the vsync, and
properly time things to improve performance.
and
Yes, I am. I understand the Choreographer is probably the component
that handles animations and when it doesn't get enough cpu cycles, it
skips some frames and outputs this debug message
so this is related to animation on UI ---[2]
so?
based on [1] and [2]: its an issue with the animation generated by Unity,
and its not under your control,
my own conclusion and opinion is:
this is a Bug at the Unity code, that need to be fixed by Unity ? maybe?
sorry, if not helping but i have tried.
i have never worked with Unity, but i tried to get conclusion from some other posts.
good luck
You can see this link
Integrate Unity3d view into Android activity
public class UnityPlayerNativeActivity extends NativeActivity
{
protected UnityPlayer mUnityPlayer; // don't change the name of this variable; referenced from native code
// UnityPlayer.init() should be called before attaching the view to a layout - it will load the native code.
// UnityPlayer.quit() should be the last thing called - it will unload the native code.
protected void onCreate (Bundle savedInstanceState)
{
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
getWindow().takeSurface(null);
setTheme(android.R.style.Theme_NoTitleBar_Fullscreen);
getWindow().setFormat(PixelFormat.RGB_565);
mUnityPlayer = new UnityPlayer(this);
if (mUnityPlayer.getSettings ().getBoolean ("hide_status_bar", true))
getWindow ().setFlags (WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
int glesMode = mUnityPlayer.getSettings().getInt("gles_mode", 1);
boolean trueColor8888 = false;
mUnityPlayer.init(glesMode, trueColor8888);
View playerView = mUnityPlayer.getView();
setContentView(playerView);
playerView.requestFocus();
}
protected void onDestroy ()
{
mUnityPlayer.quit();
super.onDestroy();
}
// onPause()/onResume() must be sent to UnityPlayer to enable pause and resource recreation on resume.
protected void onPause()
{
super.onPause();
mUnityPlayer.pause();
}
protected void onResume()
{
super.onResume();
mUnityPlayer.resume();
}
public void onConfigurationChanged(Configuration newConfig)
{
super.onConfigurationChanged(newConfig);
mUnityPlayer.configurationChanged(newConfig);
}
public void onWindowFocusChanged(boolean hasFocus)
{
super.onWindowFocusChanged(hasFocus);
mUnityPlayer.windowFocusChanged(hasFocus);
}
public boolean dispatchKeyEvent(KeyEvent event)
{
if (event.getAction() == KeyEvent.ACTION_MULTIPLE)
return mUnityPlayer.onKeyMultiple(event.getKeyCode(), event.getRepeatCount(), event);
return super.dispatchKeyEvent(event);
}
}
I can't for the life of me figure out how to manage dialogs without using configChanges to specify that you want to manually handle orientation changes.
So lets say you have this AndroidManifest:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.testandroid"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="15" />
<application
android:icon="#drawable/ic_launcher"
android:label="#string/app_name"
android:theme="#style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="#string/title_activity_main" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Take this MainActivity.java:
package com.example.testandroid;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.app.Dialog;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.View;
public class MainActivity extends Activity {
private final static String TAG = "MainActivity";
Dialog mDialog = null;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate");
setContentView(R.layout.activity_main);
}
public void doShowDialog(View b) {
Log.d(TAG, "doShowDialog");
showDialog(1);
}
private void tryDismiss() {
Log.d(TAG, "tryDismiss");
try {
dismissDialog(1);
removeDialog(1);
mDialog.dismiss();
} catch(IllegalArgumentException ex) {
Log.e(TAG, ex.getMessage());
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
#Override
protected void onPause() {
tryDismiss();
super.onPause();
Log.d(TAG, "onPause");
}
#Override
protected Dialog onCreateDialog(int dialog) {
Builder b = new AlertDialog.Builder(this);
b.setTitle("Hello").setMessage("Waiting..");
mDialog = b.create();
return mDialog;
}
}
and this layout (main.xml)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Open Dialog"
android:onClick="doShowDialog"
/>
</LinearLayout>
It doesn't seem to matter if you call from onDestroy or onPause, the dialog shows back up after the orientation switches. But why? I told it to go away. If call removeDialog/dismissDialog it does nothing when called before the orientation changes. I can't figure out for the life of me why this is. The only way to get rid of this that I know of is to handle the orientation change yourself by using
android:configChanges="keyboardHidden|orientation"
I know the new way of working is to use the FragmentDialog stuff which I have not upgraded to yet and am not ready to rewrite my whole app for that. Just seems strange that this doesn't work.
This is just an example of a real world problem I'm having in my app where the user can request some data be pulled from a remote server (to update a spinner's data), and if they switch orientation the loading dialog will never go away and there seems to be no fix for this besides handling the orientation change with the android:configChanges option. Which I can do but it seems ridiculous to me to have to do that.
-- Update --
Removed the button to dismiss the dialog as it's not necessary and you can't click it anyways since the dialog is on top.
To reproduce just start the app, click the button that opens the dialog, and then rotate your phone.
Your dialog is saved in onSaveInstanceState, so you might try dismissing it before it's launched:
#Override
protected void onSaveInstanceState(Bundle state)
{
tryDismiss();
super.onSaveInstanceState(state);
}
Also I don't really understand why do you use Activity's onCreateDialog to manage dialogs. The reason it was designed was to handle orientation changes automatically. If you want to handle it manually, why don't you just use dialog's functions? Instead of using showDialog(id) and onCreateDialog(id) just launch it directly, it won't reappear after rotating the screen.
Builder b = new AlertDialog.Builder(this);
b.setTitle("Hello").setMessage("Waiting..");
Dialog mDialog = b.create();
mDialog.show(); // <-----
Sebastian got it but I'd like to explain a bit more about the situation and my findings on dialogs since it can be quite confusing:
As sebastian put it, you must call dismissDialog/removeDialog from
onSaveInstanceState if you want the dialog gone before the rotation.
While you can create a dialog from onCreate, if you don't dismiss it before the orientation
change you won't be able to dismiss in the onCreate method when the activity restarts. You must call dismissDialog from onPostCreate
Calling dismissDialog didn't work in onRestoreInstanceState after orientation change as well. I tried both before and after calling super.onRestoreInstanceState and neither worked (thought it would since dimissing happens in onSaveInstanceState)
Even more important than this is that I learned if you are doing some Asynchronous task, such as an HTTP call and you have an inner class which contains a callback function which will run when the task is complete, you need to be aware that that inner class method will contain a reference to the original instance of the outer Activity class if the screen is rotated. This was not at all obvious to me as and I wasn't actually using AsyncTask as many others have had issues with (I was using an asynchronous http library).
Taking a small example similar to my real code:
public class MyActivity extends Activity {
private static MyActivity sThis;
#Override
public void onCreate(Bundle state) {
super.onCreate(state);
sThis = this;
doAsyncWork();
}
#Override
public void onDestroy() {
super.onDestroy();
sThis = null;
}
private void doAsyncWork() {
showDialog(LOADING_DIALOG);
ExampleAsyncWorker.work(new AsyncWorkerCallback() {
#Override
public void onWorkComplete() {
dismissDialog(LOADING_DIALOG); //doesn't work if orientation change happened.
}
});
}
}
The above code, through the CategoryManager, connects to an external server, downloads a list of categories, and once it is complete calls onCategoriesObtained and then onFetchComplete (also there are some error handling callback function removed for brevity). If an orientation change happens between the fetchCategories call and onFetchComplete then the call to dismissDialog in onFetchComplete will never work. The reason is that this inner class has an implicit reference to the original instance of the Activity class which was created before the orientation change. Thus, when you call dismissDialog you are calling it on the original instance not the new one, which will cause the dismissDialog to fail with the message: "no dialog with id 1 was ever shown via Activity#showDialog". I did figure out a way around this but its a bit of hack:
In your Activity class, include a static reference to the this reference and set it in onCreate, and null it in onDestroy, and use that reference from your inner class like so:
public class MyActivity extends Activity {
private static MyActivity sThis;
#Override
public void onCreate(Bundle state) {
super.onCreate(state);
sThis = this;
doAsyncWork();
}
#Override
public void onDestroy() {
super.onDestroy();
sThis = null;
}
private void doAsyncWork() {
showDialog(LOADING_DIALOG);
ExampleAsyncWorker.work(new AsyncWorkerCallback() {
#Override
public void onWorkComplete() {
sThis.dismissDialog(LOADING_DIALOG);
}
});
}
}
Note that I'm not sure this is a great practice but it worked for me. I know there can be problems with inner classes in activities that refer to the outer class (leaking the context) so there may be better ways of solving this problem.
AFAIK , This window leaked can be handled in two ways.
#Override
public void onDestroy(){
super.onDestroy();
if ( Dialog!=null && Dialog.isShowing() ){
Dialog.dismiss();
}
}
Or
if(getActivity()!= null && !getActivity().isFinishing()){
Dialog.show();
}
Here Dailog is your progress /alert dailog
for creating your app without the savedinstace you can use super.onCreate(null);
I have an activity using an xml layout where a WebView is embedded. I am not using the WebView in my activity code at all, all it does is sitting there in my xml layout and being visible.
Now, when I finish the activity, I find that my activity is not being cleared from memory. (I check via hprof dump). The activity is entirely cleared though if I remove the WebView from the xml layout.
I already tried a
webView.destroy();
webView = null;
in onDestroy() of my activity, but that doesn't help much.
In my hprof dump, my activity (named 'Browser') has the following remaining GC roots (after having called destroy() on it):
com.myapp.android.activity.browser.Browser
- mContext of android.webkit.JWebCoreJavaBridge
- sJavaBridge of android.webkit.BrowserFrame [Class]
- mContext of android.webkit.PluginManager
- mInstance of android.webkit.PluginManager [Class]
I found that another developer has experienced similar thing, see the reply of Filipe Abrantes on:
http://www.curious-creature.org/2008/12/18/avoid-memory-leaks-on-android/
Indeed a very interesting post.
Recently I had a very hard time
troubleshooting a memory leak on my
Android app. In the end it turned out
that my xml layout included a WebView
component that, even if not used, was
preventing the memory from being
g-collected after screen rotations/app
restart… is this a bug of the current
implementation, or is there something
specific that one needs to do when
using WebViews
Now, unfortunately there has been no reply on the blog or the mailing list about this question yet. Therefore I am wondering, is that a bug in the SDK (maybe similar to the MapView bug as reported http://code.google.com/p/android/issues/detail?id=2181) or how to get the activity entirely off the memory with a webview embedded?
I conclude from above comments and further tests, that the problem is a bug in the SDK: when creating a WebView via XML layout, the activity is passed as the context for the WebView, not the application context. When finishing the activity, the WebView still keeps references to the activity, therefore the activity doesn't get removed from the memory.
I filed a bug report for that , see the link in the comment above.
webView = new WebView(getApplicationContext());
Note that this workaround only works for certain use cases, i.e. if you just need to display html in a webview, without any href-links nor links to dialogs, etc. See the comments below.
I have had some luck with this method:
Put a FrameLayout in your xml as a container, lets call it web_container. Then programmatically ad the WebView as mentioned above. onDestroy, remove it from the FrameLayout.
Say this is somewhere in your xml layout file e.g. layout/your_layout.xml
<FrameLayout
android:id="#+id/web_container"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
Then after you inflate the view, add the WebView instantiated with the application context to your FrameLayout. onDestroy, call the webview's destroy method and remove it from the view hierarchy or you will leak.
public class TestActivity extends Activity {
private FrameLayout mWebContainer;
private WebView mWebView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.your_layout);
mWebContainer = (FrameLayout) findViewById(R.id.web_container);
mWebView = new WebView(getApplicationContext());
mWebContainer.addView(mWebView);
}
#Override
protected void onDestroy() {
super.onDestroy();
mWebContainer.removeAllViews();
mWebView.destroy();
}
}
Also FrameLayout as well as the layout_width and layout_height were arbitrarily copied from an existing project where it works. I assume another ViewGroup would work and I am certain other layout dimensions will work.
This solution also works with RelativeLayout in place of FrameLayout.
Here's a subclass of WebView that uses the above hack to seamlessly avoid memory leaks:
package com.mycompany.view;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.util.AttributeSet;
import android.webkit.WebView;
import android.webkit.WebViewClient;
/**
* see http://stackoverflow.com/questions/3130654/memory-leak-in-webview and http://code.google.com/p/android/issues/detail?id=9375
* Note that the bug does NOT appear to be fixed in android 2.2 as romain claims
*
* Also, you must call {#link #destroy()} from your activity's onDestroy method.
*/
public class NonLeakingWebView extends WebView {
private static Field sConfigCallback;
static {
try {
sConfigCallback = Class.forName("android.webkit.BrowserFrame").getDeclaredField("sConfigCallback");
sConfigCallback.setAccessible(true);
} catch (Exception e) {
// ignored
}
}
public NonLeakingWebView(Context context) {
super(context.getApplicationContext());
setWebViewClient( new MyWebViewClient((Activity)context) );
}
public NonLeakingWebView(Context context, AttributeSet attrs) {
super(context.getApplicationContext(), attrs);
setWebViewClient(new MyWebViewClient((Activity)context));
}
public NonLeakingWebView(Context context, AttributeSet attrs, int defStyle) {
super(context.getApplicationContext(), attrs, defStyle);
setWebViewClient(new MyWebViewClient((Activity)context));
}
#Override
public void destroy() {
super.destroy();
try {
if( sConfigCallback!=null )
sConfigCallback.set(null, null);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
protected static class MyWebViewClient extends WebViewClient {
protected WeakReference<Activity> activityRef;
public MyWebViewClient( Activity activity ) {
this.activityRef = new WeakReference<Activity>(activity);
}
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
try {
final Activity activity = activityRef.get();
if( activity!=null )
activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
}catch( RuntimeException ignored ) {
// ignore any url parsing exceptions
}
return true;
}
}
}
To use it, just replace WebView with NonLeakingWebView in your layouts
<com.mycompany.view.NonLeakingWebView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
...
/>
Then make sure to call NonLeakingWebView.destroy() from your activity's onDestroy method.
Note that this webclient should handle the common cases, but it may not be as full-featured as a regular webclient. I haven't tested it for things like flash, for example.
Based on user1668939's answer on this post (https://stackoverflow.com/a/12408703/1369016), this is how I fixed my WebView leak inside a fragment:
#Override
public void onDetach(){
super.onDetach();
webView.removeAllViews();
webView.destroy();
}
The difference from user1668939's answer is that I have not used any placeholders. Just calling removeAllViews() on the WebvView reference itself did the trick.
## UPDATE ##
If you are like me and have WebViews inside several fragments (and you do not want to repeat the above code across all of your fragments), you can use reflection to solve it. Just make your Fragments extend this one:
public class FragmentWebViewLeakFree extends Fragment{
#Override
public void onDetach(){
super.onDetach();
try {
Field fieldWebView = this.getClass().getDeclaredField("webView");
fieldWebView.setAccessible(true);
WebView webView = (WebView) fieldWebView.get(this);
webView.removeAllViews();
webView.destroy();
}catch (NoSuchFieldException e) {
e.printStackTrace();
}catch (IllegalArgumentException e) {
e.printStackTrace();
}catch (IllegalAccessException e) {
e.printStackTrace();
}catch(Exception e){
e.printStackTrace();
}
}
}
I am assuming you are calling your WebView field "webView" (and yes, your WebView reference must be a field unfortunately). I have not found another way to do it that would be independent from the name of the field (unless I loop through all the fields and check if each one is from a WebView class, which I do not want to do for performance issues).
After reading http://code.google.com/p/android/issues/detail?id=9375, maybe we could use reflection to set ConfigCallback.mWindowManager to null on Activity.onDestroy and restore it on Activity.onCreate. I'm unsure though if it requires some permissions or violates any policy. This is dependent on android.webkit implementation and it may fail on later versions of Android.
public void setConfigCallback(WindowManager windowManager) {
try {
Field field = WebView.class.getDeclaredField("mWebViewCore");
field = field.getType().getDeclaredField("mBrowserFrame");
field = field.getType().getDeclaredField("sConfigCallback");
field.setAccessible(true);
Object configCallback = field.get(null);
if (null == configCallback) {
return;
}
field = field.getType().getDeclaredField("mWindowManager");
field.setAccessible(true);
field.set(configCallback, windowManager);
} catch(Exception e) {
}
}
Calling the above method in Activity
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setConfigCallback((WindowManager)getApplicationContext().getSystemService(Context.WINDOW_SERVICE));
}
public void onDestroy() {
setConfigCallback(null);
super.onDestroy();
}
I fixed memory leak issue of frustrating Webview like this:
(I hope this may help many)
Basics:
To create a webview, a reference (say an activity) is needed.
To kill a process:
android.os.Process.killProcess(android.os.Process.myPid()); can be called.
Turning point:
By default, all activities run in same process in one application. (the process is defined by package name). But:
Different processes can be created within same application.
Solution:
If a different process is created for an activity, its context can be used to create a webview. And when this process is killed, all components having references to this activity (webview in this case) are killed and the main desirable part is :
GC is called forcefully to collect this garbage (webview).
Code for help: (one simple case)
Total two activities: say A & B
Manifest file:
<application
android:allowBackup="true"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name"
android:process="com.processkill.p1" // can be given any name
android:theme="#style/AppTheme" >
<activity
android:name="com.processkill.A"
android:process="com.processkill.p2"
android:label="#string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.processkill.B"
android:process="com.processkill.p3"
android:label="#string/app_name" >
</activity>
</application>
Start A then B
A > B
B is created with webview embedded.
When backKey is pressed on activity B, onDestroy is called:
#Override
public void onDestroy() {
android.os.Process.killProcess(android.os.Process.myPid());
super.onDestroy();
}
and this kills the current process i.e. com.processkill.p3
and takes away the webview referenced to it
NOTE: Take extra care while using this kill command. (not recommended due to obvious reasons). Don't implement any static method in the activity (activity B in this case). Don't use any reference to this activity from any other (as it will be killed and no longer available).
You need to remove the WebView from the parent view before calling WebView.destroy().
WebView's destroy() comment - "This method should be called after this WebView has been removed from the view system."
You can try putting the web activity in a seperate process and exit when the activity is destroyed, if multiprocess handling is not a big effort to you.
There is an issue with "app context" workaround: crash when WebView tries to show any dialog. For example "remember the password" dialog on login/pass forms submition (any other cases?).
It could be fixed with WebView settings' setSavePassword(false) for the "remember the password" case.