There is an ImageView and a GLSurfaceView setup with 'match_parent' parameters on the screen. The ImageView gets populated quickly and shows up on the screen. Then, the GLSurfaceView gets populated on top of it with modifications to the image. So far so good.
However, these views also live inside of a ViewPager with images to the left and right of them. Before introducing the OpenGL Surface View on top of the ImageView, the views would transition out to the left and right as expected. But after introducing the GLSurfaceView on top of it, as soon as the user starts to swipe their fingers to move to the next image, the SurfaceView becomes transparent causing the image to disappear. It even forces the image below it to disappear in the ImageView. Showing the background of the parent view.
I am unclear on how to approach this issue. It would be nice if the texture slid out to the side just like the ImageView or even be transparent but leave the ImageView behind it visible would be fine.
Even GLSurfaceView.RENDERMODE_CONTINUOSLY doesn't keep the texture around during the transition.
It would seem that the reason that I cannot get the effect that I am trying to achieve is because the GLSurfaceView is always placed behind other views causing transparency on the foreground views. Thus, the GLSurfaceView would have priority over being drawn. In order to achieve the effect that I would want, I probably need to be sure to use View.setVisibility( ) for invisible when swiping and set it back to visible after the ViewPager settles. That is the current conclusion that I have come across after seeing a couple other links as a by-product of different research.
z-order for GLSurfaceViews
scrolling in GLSurfaceViews
It doesn't seem as though the GLSurfaceView class really performs as similar to a view as I had been originally expecting. So a little extra care will need to be taken in order to progress further to keep the ImageView visible transitioning between images in the ViewPager.
Within a viewPager I managed to accomplish something along the lines of the following code. This is still in a rough state of course since it was in the middle of testing, but it accomplishes what I am looking for. Which is for a texture to start loading when we begin to settle on an image, and then disappear revealing the original image below it once the user begins to leave the image. And this seems to work without tearing or flickers which is how I would expect for it to behave.
#Override
public void onPageScrollStateChanged(int state)
{
switch(state)
{
case ViewPager.SCROLL_STATE_DRAGGING:
Log.i("photoViewer:","dragging ViewPager");
if(photoSurfaceView_ != null && photoViewFrameLayout != null)
{
photoSurfaceView_.setAlpha(0);
photoSurfaceView_.invalidate();
}
break;
case ViewPager.SCROLL_STATE_IDLE:
photoSurfaceView_.setVisibility(View.VISIBLE);
photoSurfaceView_.setAlpha(1);
Log.i("photoViewer:","idle ViewPager");
break;
case ViewPager.SCROLL_STATE_SETTLING:
int childCount = photoViewFrameLayout.getChildCount();
if(childCount >= 2)
{
photoViewFrameLayout.removeViews(1, childCount-1);
}
new Thread(new Runnable() {
#Override
public void run()
{
Looper.prepare();
final Bitmap openGlBitmap = BitmapFactoryUtils.resizeAsNecessaryForOpenGLtexture(photoPaths[photoViewPager_.getCurrentItem()], new BitmapFactory.Options());
Rect bounds = BitmapFactoryUtils.calculateBoundsForBitmap(activityContext, openGlBitmap);
int scaledHeight = bounds.height();
int scaledWidth = bounds.width();
photoSurfaceView_ = new PhotoViewSurfaceView(activityContext, openGlBitmap);
photoSurfaceView_.setLayoutParams(new LayoutParams(scaledWidth, scaledHeight, Gravity.CENTER));
photoSurfaceView_.setVisibility(View.VISIBLE);
photoSurfaceView_.setAlpha(0);
runOnUiThread(new Runnable() {
#Override
public void run()
{
photoViewFrameLayout.addView(photoSurfaceView_);
}
});
}
}).start();
Log.i("photoViewer:","settling ViewPager");
break;
}
Edit: As requested I wanted to provide some additional input as to how to resize the texture for an OpenGL SurfaceView that will use a Bitmap for input.
The following is a quick snippet of some of the code available for the first step in the process in checking against whether or not the texture size of the current Bitmap will fit within a single texture. If you want to tile the texture to keep the image larger, then this gets more complicated and this was not what was needed for my resolution.
The texture size can be retrieved with the following code:
private void assignTextureSize()
{
/* The maximum texture size can be found within the OpenGL context and then shared amongst other applications. */
int[] maxTextureSize = new int[1];
GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, maxTextureSize, 0);
maxTextureSize = maxTextureSize[0];
}
but only from within the Renderer class and for my case I waited until within the onDrawFrame of the Texture to do the assignment; before a Bitmap has been assigned to it.
Then, the goal of the Bitmap being loaded for me was to simply load what I could without running out of memory. For this, I simply modified the Options of the Bitmap that I was about to load and try and load it within a try catch block. If it ran out of memory, I would try it again, but with a smaller footprint.
try
{
final int sizeFactors[] = { 1, 2, 4, 8, 16, 32 };
if (new File(bitmapFilePath).exists())
{
BitmapFactory.Options options = new BitmapFactory.Options();
for (int i = 0; i < sizeFactors.length; ++i)
{
try
{
options.inSampleSize = sizeFactors[i];
Bitmap bmp = BitmapFactory.decodeFile(bitmapFilePath, options);
if(bmp.getHeight() > maximumTextureSize ||
bmp.getWidth() > maximumTextureSize)
{
continue;
}
/*
* #category Check against EXIF data if the image needs to be rotated or not for viewing.
*/
Matrix matrix = new Matrix ();
ExifInterface exif = new ExifInterface(bitmapFilePath);
int orientation = exif.getAttributeInt (ExifInterface.TAG_ORIENTATION, 1);
switch(orientation)
{
case ExifInterface.ORIENTATION_NORMAL:
break;
case ExifInterface.ORIENTATION_ROTATE_90:
matrix.postRotate (90);
break;
case ExifInterface.ORIENTATION_ROTATE_180:
matrix.postRotate (180);
break;
case ExifInterface.ORIENTATION_ROTATE_270:
matrix.postRotate (270);
break;
default:
}
bmp = Bitmap.createBitmap (bmp, 0, 0, bmp.getWidth (), bmp.getHeight (), matrix, true);
return bmp;
} catch (OutOfMemoryError outOfMemory)
{
//Do nothing, it will return when one that doesn't run out of memory is loaded. Or you could do additional checking to bail if needed, etc.
}
}
throw new Exception("Not enough memory for loading image");
}
} catch (Exception exception)
{
Log.e("CustomLogTag", "Exception for loading the image that was selected for extraction.");
}
There are many ways to accomplish this, but this is one approach to take and I wanted to add clarity and additional information to this previous post as requested since it did not contain as much information on my accepted answer as I would have like either.
Related
I have searched for hours on the subject of memory leaks and I cant seem to solve my problem. After exactly 9 rotations my app crashes with an OutOfMemory error. I have both Bitmaps and Drawables with Bitmaps in my app. I have tried putting in code that removes callbacks to drawables, I have tried setting all Bitmaps to null as well as manually calling the garbage collector. I had all of this code in the onSaveInstanceState() method since I assume that is called whenever the screen changes and before destruction of the view. None of these solutions worked.
I got some results with the Bitmaps turned to null, however that only added about another 9 screen rotations before another memory leak. Where is my leak? What am I doing wrong? I was under the impression that when a screen is rotated, everything is destroyed and recreated, that must obviously be false.
I dont want to post my code because A. there is a lot of it and B. its close to finishing and so becomes a company secret per se. If there is something you must absolutely see to solve this then I will post it. I think I just need a really good explanation of what is actually going on when the screen is rotated and how to correctly handle bitmaps and drawables. Note: This error does not pop up if I leave the app and then go back in, it only happens when the view is resized upon screen rotation.
I have about 7 bitmaps and 7 drawables, all created at launch and resized when the screen rotates. DOing that several times stops the app.
Some simplified code:
How I set up a Bitmap to a drawable. This one sets to a ClipDrawable as well:
//Full Bar
colorbarMap = BitmapFactory.decodeResource(res, R.drawable.colorbar);
colorbarDraw = new BitmapDrawable(res, colorbarMap);
colorbarDraw.setBounds(barX, barY, barX2, barY2);
colorbarClip = new ClipDrawable(colorbarDraw, Gravity.BOTTOM, ClipDrawable.VERTICAL);
colorbarClip.setBounds(barX, barY, barX2, barY2);
colorbarClip.setLevel(currentLevel);
//Empty Bar
colorbaremptyMap = BitmapFactory.decodeResource(res, R.drawable.colorempty);
colorbaremptyDraw = new BitmapDrawable(res, colorbaremptyMap);
colorbaremptyDraw.setBounds(barX, barY, barX2, barY2);
The above code runs once at the start of the view initializing based on this code:
private void init(){
//System
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.HONEYCOMB) {
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
res = getResources();
options = new BitmapFactory.Options();
viewTreeObserver = getViewTreeObserver();
if (viewTreeObserver.isAlive()) {
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
getViewTreeObserver().removeOnGlobalLayoutListener(this);
view_width = getWidth();
view_height = getHeight();
afterLayout();
}
});
}
}
The first code snippet runs under the method: afterLayout()
Nothing is done with the bitmaps after this. Each bitmap is initialized with an x and y location based on the view width and height. That location is edited using a rectangle to set its bounds for example a moving object.
If the Bitmap that to be displayed is small enough than use this or you don't need a high res image
bitmapOption = new BitmapFactory.Options();
bitmapOption.inScaled = true;
bitmapOption.inSampleSize = 2;
imageBitmap = BitmapFactory.decodeFile(imgFile.getAbsolutePath(), bitmapOption);
this will reduce the amount of size of bitmap see the bald guys explanation
optimizing bitmap
put your bitmap loading code in try finally block and use this at the end
try {
...
}finally {
if (imageBitmap != null) {
//recycle the bitmap to improve performance and avoid OO Exception
imageBitmap.recycle();
imageBitmap = null;
}
}
This will recycler the bitmap space taken by the image whenever GC is called
I'm using androidplot to display a fixed-sized graph, which I've setted up a BitmapShader to match each Range interval and I need to set this shader to be always displayed on the graph, since it's beginning.
My problem is:
I can't intialize the graph with this shader (I've tested it using as base the DemoApp and the shader is working properly). Every time I try to get the GridRect using the method getGridRect() it returns null, no matter where I call this function during the activity creation. I can only set the shadder after the activity is created, but only with a button or something like it.
I searched through the entire source code but could not find where this measure occurs during the activity's lifecycle.
Unfortunately, I was unable the proper way of getting the gridrect mesured during it's creatiion, but I've found a way around.
I was able to define the graph height by getting the XYPlot view-height and subtract the graph top and bottom padding.
A snippet from my code:
#Override
protected void onCreate(Bundle savedInstanceState) {
...
mPlot.getGraphWidget().setPaddingBottom(20);
mPlot.getGraphWidget().setPaddingTop(20);
//Added a ViewTreeObserver to be sure when the Plot was measured.
mPlot.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
mPlot.getViewTreeObserver().removeOnGlobalLayoutListener(this);
//Avoid the factory trying to scale bitmap according to Display density
Options options = new BitmapFactory.Options();
options.inScaled = false;
Bitmap bm = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.chartbackground, options), 1, (int) mPlot.getHeight() - 40, false);
BitmapShader backgroundShader = new BitmapShader(bm, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
Matrix shaderMatrix = new Matrix();
//This matrix references always stayed the same even if the plot changed its size.
shaderMatrix.setTranslate(49.6875f, 30.75f);
backgroundShader.setLocalMatrix(shaderMatrix);
mPlot.getGraphWidget().getGridBackgroundPaint().setShader(backgroundShader);
//Setting the background to be a little bit translucid.
mPlot.getGraphWidget().getGridBackgroundPaint().setAlpha(90);
}
});
...
}
I am rendering some very simple HTML (just some text and a small image) in a WebView off screen (not set as content view of an activity) so I can create a Bitmap from the content.
The way to know when the content is fully rendered I have based on this answer:
final AtomicBoolean rendered = new AtomicBoolean(false);
final WebView view = new WebView(this) {
#Override
public void invalidate() {
if (getProgress() == 100 && getContentHeight() > 0) {
if (! rendered.get()) {
rendered.set(true);
// Content should be fully rendered
}
}
super.invalidate();
}
};
// Load and lay out content
view.loadUrl(url);
view.setInitialScale(100);
view.layout(0, 0, 240, 420);
I've tested this successfully on 4.1.2, 4.2.2, 4.4.2 and 5.0. But with 4.0.3, it seems invalidate() is never called.
While trying all kinds of things I found out that showing a Toast and doing a delayed (1 sec.) call to invalidate() solves the problem:
#Override
public void onPageFinished(final WebView view, final String url) {
Toast.makeText(MainActivity.this, "Page loaded", Toast.LENGTH_SHORT).show();
handler.postDelayed(new Runnable() {
#Override
public void run() {
if (! rendered.get()) {
view.invalidate();
}
}
}, 1000);
}
Of course I don't consider this a valid solution, but while the delayed call to invalidate() somehow makes sense to me, I really wonder what side effect of the Toast does the trick here. Does it do some damage causing a redraw or something like that?
Sorry can not help you very much since I would like to make same operation of you (get screenshot from offline webview control) but in my case I'm not able to receive invalidate() event until the webview is offline. If I set as visible by inserting into the main window the invalidate() start to be generated but in case of offline (invisible) no invalidate() call is generated. The code you developed for get such result is only the snippet you posted here or there is some additional function to call for have the webview "active" in offline mode also?
About your problem what I can suggest is to override the other invalidate() definitions like:
public void invalidate(Rect dirty)
public void invalidate(int l, int t, int r, int b)
since in my tests I noted also these last was called.
Thank you
After a lot more testing on different (virtual) devices, this is the way that works reliable for me so far. Note that the "hack" for 4.0.3 mentioned in my original question still applies but is not included here.
Overriding invalidate() did not prove to be always reliable on all tested versions, so I am using PictureListener.onNewPicture(WebView view, Picture picture) even though it is deprecated.
In my activity:
final WebView view = new WebView(this);
// Zooming in can improve font quality
final float scale = 2.0;
// Unfortunately, there is no method view.getContentWidth()
final int contentWidth = 240;
view.setPictureListener(new PictureListener() {
#Override
public void onNewPicture(final WebView view, final Picture picture) {
if (view.getProgress() == 100 && view.getContentHeight() > 0) {
view.setPictureListener(null);
// Content is now fully rendered
final int width = Math.round(contentWidth * scale);
final int height = Math.round(view.getContentHeight() * scale);
final Bitmap bitmap = getBitmap(view, width, height);
// Display or print bitmap...
}
}
});
view.loadUrl(url);
view.setInitialScale(Math.round(scale * 100));
// Width and height must be at least 1
view.layout(0, 0, 1, 1);
And the getBitmap() method:
private Bitmap getBitmap(
final WebView view, final int width, final int height) {
final Bitmap bitmap = Bitmap.createBitmap(
width, height, Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
view.draw(canvas);
return bitmap;
}
This is the best I can offer. The app has not been tested extensively yet, but so far it never failed to correctly render a small document including a small GIF image.
I have got a Image (Bitmap) on a ImageView, without flickering. When I change something with setPixel(x, y, COLOR_VALUE), so some Pixels are changed on the ImageView, it begins to flicker, where I changed the Pixels.
public class Drawer extends ImageView {
private Bitmap someBitmap;
public void doSomeDrawing() {
for (int i = 0; i < 100; i = i + 2) {
someBitmap.setPixel(x, y, COLOR_VALUE);
}
setOnDraw();
}
public void setOnDraw() {
this.setImageBitmap(someBitmap);
}
Try getting a copy of your bitmap and draw on it. Then recycle your old bitmap.
The problem here might also be that setting the pixel takes time and if you do this on the UI Thread, it will slow down your app and may cause flickering too. How much time does doSomething take ?
my live wallpaper doesn't slide smoothly most of the time on home screen change.
there is not a complicate coding for the position of background. it just use the xpixles that located on the onoffsetschanged method to adjust the x position of the wallpaper.
sometime it slide well same as in the sun rise live wallpaper app. but most of the other time it slide.. dunno how to describe it but its like a static sharp move. is there any idea about that guys.
best regards.
private void drawS() {
final SurfaceHolder holder = getSurfaceHolder();
canvas = null;
try {
canvas = holder.lockCanvas();
if (canvas != null) {
background = BitmapFactory.decodeResource(getResources(),
R.drawable.background);
canvas.drawBitmap(background, mXPixels, mYPixels, null);
}
}
finally {
if (canvas != null) {
holder.unlockCanvasAndPost(canvas);
}
}
handler.removeCallbacks(runnableS);
if (visible) {
handler.postDelayed(runnableS, 25);
}
}
try to avoid resource creation in draw routine; remove this from drawS() function:
background = BitmapFactory.decodeResource(getResources(),
R.drawable.background);
and put it wherever you initialize your stuff and just use reference to background Bitmap later on.
If you take a look at logcat you'll see that there's a lot or resource shuffling and memory management going on (allocating memory for your resource, decoding bitmap image in it etc) every time you call your drawS(); ie every time you draw your background...
Cheers,
Ivica
I encountered the same problem and solved by following Ivica suggestion.
I moved the bitmap creation line inside the surface creation sub:
public void onSurfaceCreated(SurfaceHolder holder) {
super.onSurfaceCreated(holder);
mybackground = BitmapFactory.decodeResource(getResources(), R.drawable.mybg);
}
In this way the sliding is very fluent