OpenGL ES 2.0 - How can multiple views use the same renderer? - java

Right now I have my ApplicationActivity, this activity is responsible for managing multiple views (GLSurfaceViews). Can / Should I have all the views set the renderer to a "global" renderer?
Code:
public class ApplicationActivity extends Activity
{
private static final String TAG = ApplicationActivity.class.getSimpleName();
private final Stack<Screen> screens = new Stack<Screen>();
private GlRenderer glRenderer;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
Log.d(TAG, "Main Activity Created");
setupGraphics();
ChangeScreen(new MainMenu(this, glRenderer)); //Creating a new Screen sets the renderer
}
private void setupGraphics()
{
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
glRenderer = new GlRenderer(this);
}
public void Draw() //Is called by the glRenderer onDrawFrame() { mainActivity.Draw() }
{
}
}
Its the same activity switching between GLSurfaceViews and by my knowledge I believe that the method setRenderer sets the view renderer and then starts the rendering thread (creating a new thread) but I don't want to recreate the thread every time I switch between views - may create potential problems.
So in the end I want a Renderer class just to keep graphics sepreate from business logic and such but, I don't know if using one Renderer is even possible, without setting the thread again?

You can only use Multiple Views with the same Renderer only if you properly switch out between them with GLSurfaceView.onPause() / .onResume();
My specific case:
#Override
protected void onPause() //Overrides onPause from Activity
{
surfaceViews.peek().onPause();
super.onPause();
}
So everytime the activity pauses I would have to pause the current View. And if the Activity resumes then resume the View also.
I also have a method called SetView which will either (pause and remove then change to another View) or (pause and then change to another View) this is accomplished using a Stack
public void SetView(View screen)
{
if (!screens.empty())
{
screens.peek().onPause();
screens.pop();
}
screens.push(screen);
setContentView(screens.peek());
}
Of course though because we are using Views instead of Activities now we must Override the onBackPressed() to go back to previous Views.
#Override
public void onBackPressed()
{
if (screens.size() == 1)
super.onBackPressed();
else
{
screens.pop();
setContentView(screens.peek());
screens.peek().onResume();
}
}

By doing new GLRenderer() you create new instance of your class. So there is no problem to have the same renderer used in different activities.
EDIT: I seem to misunderstand your question - if you want many GL surfaces visible at once, then no, it is not possible. But it got nothing to do with reusing renderer code.

Related

How to hide the preview of the app when it's in the task manager

I make a game with Java and LibGdx
It's a game of skill with a time limit, and the problem is the player can push home button to pause app, and go to task manager, and can see preview of app to seek what he has to find without counting time
So, I would like to hide the preview of the app when the player push home button to pause
What I tried to do
I override pause and resume handler
#Override
public void pause() {
Gdx.app.log("LibGDX", "pause");
state = State.PAUSE;
Gdx.graphics.setContinuousRendering(false);
Gdx.graphics.requestRendering();
}
#Override
public void resume() {
Gdx.app.log("LibGDX", "resume");
state = State.RUN;
Gdx.graphics.setContinuousRendering(true);
}
and main part of my public void render(float) function
switch (state)
{
case RUN:
// game
break;
case PAUSE:
// a black rectangle on screen
shapeRenderer.setProjectionMatrix(camera.combined);
shapeRenderer.begin(ShapeRenderer.ShapeType.Filled);
shapeRenderer.setColor(Color.BLACK);
shapeRenderer.rect(0, 0, GameScreen.WIDTH, GameScreen.HEIGHT);
shapeRenderer.end();
break;
default:
break;
}
log tell me that pause and resume are indeed invoked
but in task manager, I still see the game and not the black rectangle
Update 1
to Ridcully answer :
I tried what you suggested
package com.mygdx.game;
import android.os.Bundle;
import android.view.WindowManager;
import com.badlogic.gdx.backends.android.AndroidApplication;
import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration;
public class AndroidLauncher extends AndroidApplication {
#Override
protected void onCreate (Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AndroidApplicationConfiguration config = new AndroidApplicationConfiguration();
// --
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
// --
initialize(new MyGame(), config);
}
}
But same thing, I still see the screenshot
I'm with Android Studio 2.1
My smartphone: Oneplus One
and Android 6.0.1
Do you have an idea ? thanks
There is a flag for that. You can use this little method, e.g. in onCreate() to prevent Android from creating a 'screenshot' for the 'recent activities' list. You can activate/deactivate as you want, e.g. only activate it when the actual game board is visible etc.
/**
* Sets/clears FLAG_SECURE which prevents system from creating screenshots for 'recent activities' list.
* Flag should be set while user is in vault.
*
* #param set
*/
protected void setWindowFlagSecure(boolean set) {
if (set) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
} else {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
}
EDIT
I just found this SO answer, regarding Libgdx applications: Seems you have to set the flag AFTER invoking the initialize() method.
You can do that using
public class SampleActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE);
setContentView(R.layout.main);
}
}
Make sure you do that before you call setContentView.

Android View onResume method?

When returning to an activity using the back button and
startActivity(new Intent(this, ActivityMainMenu.class));
is called, are there any methods that automatically go to a custom view?
I've noticed that when going back to the view, it's no longer invalidated.
Basically without using the activity's onResume I want to be able to resume my custom view.
For anyone else who wants to know you can use:
protected void onAttachedToWindow()
It's called every time a view is attached to the Window.
In the Android Source for TextView, it posts a Runnable.
if (ss.error != null) {
final CharSequence error = ss.error;
// Display the error later, after the first layout pass
post(new Runnable() {
public void run() {
setError(error);
}
});
}

Custom view isn't being restored after adding it to a layout from AsyncTask's onPostExecute()

I've written a pretty large custom view which overrides onSaveInstanceState() and onRestoreInstanceState(Parcelable state).
I wanted to populate a LinearLayout with my custom view, so I wrote the following code:
public class MainActivity extends Activity {
private LinearLayout mRootLayout;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRootLayout = (LinearLayout) findViewById(R.id.root_layout);
int i;
// Test: adding 10 instances of MyCustomView.
for (i = 0; i < 10; ++i) {
MyCustomView cv = new MyCustomView(this);
// I set an ID for this view so that onSaveInstanceState() and
// onRestoreInstanceState(Parcelable state) will be called
// automatically.
cv.setId(++i);
mRootLayout.addView(cv);
}
}
// ...
}
It works fine - mRootLayout is indeed being populated with 10 instances of MyCustomView, and each instance of MyCustomView is being properly restored after, for example, screen rotation.
I've noticed that due to the fact that MyCustomView is pretty large, my code is being heavy on the UI thread.
To solve the issue and take some effort off of the UI thread, I decided to use a custom AsyncTask, which will create an instance of MyCustomView in doInBackground() and add it to the the main layout ( mRootLayout ) in onPostExecute().
The following code is my custom AsyncTask:
private class LoadMyCustomViewTask extends AsyncTask<Void, Void, MyCustomView> {
private Context mContext;
private LinearLayout mLayoutToPopulate;
private int mId;
public LoadMyCustomViewTask(Context context, LinearLayout layout, int id) {
mContext = context;
mLayoutToPopulate = layout;
mId = id;
}
#Override
protected MyCustomView doInBackground(Void... params) {
MyCustomView cv = new MyCustomView(mContext);
return cv;
}
#Override
protected void onPostExecute(MyCustomView result) {
result.setId(mId);
mLayoutToPopulate.addView(result);
}
}
In MainActivity I use it as follows:
public class MainActivity extends Activity {
private LinearLayout mRootLayout;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRootLayout = (LinearLayout) findViewById(R.id.root_layout);
int i;
for (i = 0; i < 10; ++i) {
new LoadMyCustomViewTask(this, mRootLayout, ++i).execute();
}
}
// ...
}
This code works too, but there is only one problem - MyCustomView is not being restored at all.
For debug purposes I put a Log.d(...) in MyCustomView's onSaveInstanceState() and in onRestoreInstanceState(Parcelable state), and I've noticed that onRestoreInstanceState(Parcelable state) isn't being called.
Do you have any idea why onRestoreInstanceState(Parcelable state) isn't being called when I use an AsyncTask to populate mRootLayout, but it is indeed being called when I create MyCustomView completely on the UI thread?
Thank you.
Edit: I'm posting the methods onSaveInstanceState() and onRestoreInstanceState() of MyCustomView
#Override
protected Parcelable onSaveInstanceState() {
debug("onSaveInstanceState()");
Bundle state = new Bundle();
state.putParcelable(_BUNDLE_KEY_PARENT_STATE, super.onSaveInstanceState());
state.putBooleanArray(_BUNDLE_KEY_CLICKED_VIEWS, mClickedViews);
return state;
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
debug("onRestoreInstanceState(Parcelable state)");
if (state instanceof Bundle) {
Bundle bundle = (Bundle) state;
mClickedViews = bundle.getBooleanArray(_BUNDLE_KEY_CLICKED_VIEWS);
state = bundle.getParcelable(_BUNDLE_KEY_PARENT_STATE);
}
super.onRestoreInstanceState(state);
}
View state restoration begins at the root view and moves down to all of the child views attached at that time. This can be seen in the ViewGroup.dispatchRestoreInstanceState method. This means that Android can only restore your views if they are part of the view hierarchy at the time Activity.onRestoreInstanceState is called.
Using the AsyncTask, you are creating your views asynchronously and then scheduling them to be added some time later when the main looper is idle. Considering the lifecycle, Android only lets your AsyncTask.onPostExecute run after Activity.onStart, Activity.onRestoreInstanceState, Activity.onResume, etc. are called. Your views are being added to the layout too late for automatic restoration to take place.
If you add log statements to those methods mentioned above, as well as to your AsyncTask.onPostExecute, you will be able to see how the ordering/timing plays out in reality. The following code runs after Activity.onRestoreInstanceState even though it all happens on the main thread, simply because of the scheduling:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
new Handler().post(new Runnable() {
#Override
public void run() {
Log.i("TAG", "when does this run?");
}
});
...
}
Smells like a false observation... creating a view on a background thread should not affect your activity lifecycle.
That said, doing anything at all with View objects on background threads is a no-no and I'm surprised you got this far with such an approach. All View code should be quick and avoid blocking. If you have long-running work to do then separate that work into the background thread, post the results of that complex computation to the main thread, and keep all the actual View/presentation stuff on the main thread where it belongs.
I remember having read about how onSaveInstanceState()/onRestoreInstanceState() work a time ago and it was not a "fixed-situation-rules" thing. As I can't find the references to it at this moment, I'll try to explain it with my own words:
Basically, a factor on which depends the calling of those methods is the resources left that the device has. Both methods will get in action when a second Activity gets focused and the first one gets killed due to lack of resources. Concretely, onRestoreInstanceState should be triggered when that Activity was killed and restarted, so it gets the previous state.
Although you've not posted your MyCustomView implementation, my guess is that when you do that entirely on the main UI Thread, you're involving some action that makes the MainActivity lose its focus and once the MyCustomView is created, it needs to restore its state. Doing this in a separate thread (as AsyncTask does) makes creating those Views in paralell, so your main Activity doesn't lose its focus and thus it doesn't get killed, so those methods are not called.
Concluding this, don't worry if those methods are not always called as they don't have to be called everytime, just when needed, and that doesn't mean there's something going wrong.
I recommend you to connect SDK sources to your IDE and walk there with debugger through the whole process related with the View class onRestoreInstanceState(). Since I don't have your code, looking at sources I can only guess what might have gone wrong, but from what I see, that might be related to problem:
1) try to set an Id to every view you generate
2) try to use Fragment as a host to your views (instead of MainActivity).

Handler().postDelayed() sending multiple intents when orientation changes

Ok I'm working on a splash screen that pauses for 1.5 seconds and works great, except for one thing. Once the timer is started in onCreate if the configuration (orientation) changes then the timer gets reset and then end result is it starts my ParchmentActivity.java twice.
How can I prevent the handler from sending the intent twice?
Thanks in advance!
Full code can be found #: https://github.com/n00bware/android_apps_parchment
Here is my code (from example http://www.anddev.org/novice-tutorials-f8/splash-fade-activity-animations-overridependingtransition-t9464.html):
public class SplashScreen extends Activity {
private static final int SPLASH_DISPLAY_TIME = 1500; /* 1.5 seconds */
private static final String TAG = "Parchment";
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/* Create a new handler with which to start the main activity
and close this splash activity after SPLASH_DISPLAY_TIME has
elapsed. */
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
Intent parchment = new Intent(SplashScreen.this, ParchmentActivity.class);
SplashScreen.this.startActivity(parchment);
SplashScreen.this.finish();
overridePendingTransition(R.anim.fade_main_in, R.anim.fade_splash_out);
}
}, SPLASH_DISPLAY_TIME);
}
/* I found a suggestion to try overriding onConfigurationChanged()
but it doesn't stop a new timer from being started */
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}
/* I also tried overriding onStart() but this also doesn't stop a
new timer. What exactly is called when an orientation configuration
changes happens? */
#Override
public void onStart() {
super.onStart();
setContentView(R.layout.splash);
}
you can create a new static boolean, set it false and in on create do the Handler action only if the flag is false...
PS: inside the if statement, you must set the boolean flag to true :)
Good luck!
Create the handler in onCreate, release it in onDestroy, send a message / post a runnable in onStart, remove message / runnable in onStop.
This will reset the timer with each rotate, so you could potentially keep the splash screen up if you rotated the device every second.
In Android it can take a second or so to switch rotations, you probably want this behaviour because it would be possible to start app, rotate and not see the splash.

Update View at runtime in Android

The example is pretty straightforward: i want to let the user know about what the app is doing by just showing a text (canvas.drawText()). Then, my first message appears, but not the other ones. I mean, i have a "setText" method but it doesn't updates.
onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(splash); // splash is the view class
loadResources();
splash.setText("this");
boundWebService();
splash.setText("that"):
etc();
splash.setText("so on");
}
The view's text drawing works by doing just a drawText in onDraw();, so setText changes the text but doesn't show it.
Someone recommended me replacing the view with a SurfaceView, but it would be alot of trouble for just a couple of updates, SO... how the heck can i update the view dinamically at runtime?
It should be quite simple, just showing a text for say 2 seconds and then the main thread doing his stuff and then updating the text...
Thanks!
Update:
I tried implementing handler.onPost(), but is the same story all over again. Let me put you the code:
public class ThreadViewTestActivity extends Activity {
Thread t;
Splash splash;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
splash = new Splash(this);
t = new Thread(splash);
t.start();
splash.setTextow("OA");
try { Thread.sleep(4000); } catch (InterruptedException e) { }
splash.setTextow("LALA");
}
}
And:
public class Splash implements Runnable {
Activity activity;
final Handler myHandler = new Handler();
public Splash(Activity activity) {
this.activity=activity;
}
#Override
public void run() {
// TODO Auto-generated method stub
}
public synchronized void setTextow(final String textow) {
// Wrap DownloadTask into another Runnable to track the statistics
myHandler.post(new Runnable() {
#Override
public void run() {
TextView t = (TextView)activity.findViewById(R.id.testo);
t.setText(textow);
t.invalidate();
}
});
}
}
Although splash is in other thread, i put a sleep on the main thread, i use the handler to manage UI and everything, it doesn't changes a thing, it only shows the last update.
I haven't hit this yet, but I think the usual pattern is to do lengthy initialization in a background thread, and use Handler.post() to update the UI. See http://developer.android.com/reference/android/widget/ProgressBar.html for a different, but possibly related, example.
Also see this answer, especially the first paragraph:
The problem is most likely that you
are running the splash screen (some
sort of Dialog such as ProgressDialog
I assume) in the same thread as all
the work being done. This will keep
the view of the splash screen from
being updated, which can keep it from
even getting displayed to the screen.
You need to display the splash screen,
kick off an instance of AsyncTask to
go download all your data, then hide
the splash screen once the task is
complete.
Update (based on your update and your comment): You are not supposed to update the UI in any thread except the one where your Activity is created. Why is it impossible for you to load your resources in a background thread?
First: onCreate is executed on main UI thread of application so no UI updates until you leave it. Basically you need one thread to execute long running tasks and some mechanism to push updates into the UI.
Most usual approach is to extend AsyncTask see this link for further info
i suppose that your view is an extended view and you call onDraw for drawing the view, so, maybe the view isnĀ“t 'refresh' their state, so try this
onCreate(Bundle bundle) {
setContentView(splash); // splash is the view class
loadResources();
splash.setText("this");
splash.invalidate();
boundWebService();
splash.setText("that"):
splash.invalidate();
etc();
splash.setText("so on");
splash.invalidate();
}

Categories