I am having an issue where I am refreshing a LineDataSet graph real time by updating the data and calling mMyChart.invalidate().
One thread is requesting the data from a server, processing it, and sending it via a message to the second thread, which is the main activity thread and is responsible for drawing the graph.
The issue is this call to invalidate seems to complete before the chart has actually redrawn, so my other thread requesting the data goes off and requests more data and sends it to the thread to re draw the chart. The thread drawing the charts gets very behind after a while and starts taking a long time to respond to anything, the longer it has been running, the longer it takes.
My question is straight forward in that I want to make it so I don't continue requesting data until the plot has finished drawing. How do I receive a callback when drawing completes? Alternately, is there a way to detect when it is drawing?
Thanks,
Eric
You could try a ViewTreeObserver (related SO question here)
A view tree observer is used to register listeners that can be notified of global changes in the view tree. Such global events include, but are not limited to, layout of the whole tree, beginning of the drawing pass, touch mode change.... A ViewTreeObserver should never be instantiated by applications as it is provided by the views hierarchy. Refer to getViewTreeObserver() for more information.
Or you could try extending the particular Chart type you are using and override onDraw(Canvas canvas) to call a listener when the draw pass completes:
public class ExtendedBarChart extends BarChart {
public ExtendedBarChart(Context context) {
super(context);
}
public ExtendedBarChart(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ExtendedBarChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// TODO: Drawing completed, execute callback...
}
}
Related
I am creating a custom View and I would like to listen for the transformation changes. For example, the ones triggered by View#setScaleX. One way to do it is overriding all the methods:
setTranslationX
setTranslationY
setTranslationZ
setElevation
setRotation
setRotationX
setRotationY
setScaleX
setScaleY
setPivotX
setPivotY
setCameraDistance
setAnimationMatrix
Am I missing anything? I don't care for the top/left/bottom/right properties so they are left out intentionally. However this is cumbersome. It would be better if I can just get a callback and listen for it. Is that possible?
//Make some kind of callback
public interface TransformationCallback{
//String whatWho is an example it can be anything.
void onTransform(String whatWho);
}
public class YourView extends View{
private TransformationCallback callback;
//Pass an interface into the View constructor
public YourView(Context context, TransformationCallback callback){
super(context);
this.callback = callback;
}
}
#Override
public void setTranslationX(float x){
//call onTransform from the callback
callback.onTransform("setTranslationX was called");
super.setTranslationX(x);
}
The only problem with this, is it will not detect internal changes to the underlining values that these functions "set".
For example there is a variable inside View called protected int mLeft; Which is modified multiple times, internally not using functions.
The variable is also protected meaning abstractions of View can also modify it without function calls.
For the most part only external classes that mess with Views will use those functions which may or may not effect you.
I've have a custom view which overrides onDraw and I've noticed that Graphics memory keeps increasing overtime, until my app crashes with OOM (it ranges anywhere from 4h to 12h, based on device).
I'm doing a bit complex drawing but for reproducing purposes, this code does the trick:
package com.example.testdrawing;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.Nullable;
import java.util.Random;
public class CustomView extends View {
private Random rand;
private Paint paint;
public CustomView(Context context) {
super(context);
init(context);
}
public CustomView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
public CustomView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
public CustomView(Context context, #Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context);
}
private void init(Context context) {
rand = new Random();
paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(10);
// Simulate invalidation loop
final Thread thread = new Thread(new Runnable() {
#Override
public void run() {
while (true) {
// I invoke postInvalidate() when the rendering data change.
postInvalidate();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
thread.start();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 1) This one leaks memory
canvas.drawOval(0, 0, 500 + (rand.nextInt(100)), 900 + (rand.nextInt(100)), paint);
// 2) This one keeps graphic memory at constant
//canvas.drawOval(0, 0, 500, 900, paint);
}
}
Basically, the memory is retained whenever a drawing location is dynamic. If location is static, the memory remains at constant. In both cases, the graphic memory doesn't go down. Here is the profiler output after ~12 minutes for the CustomView:
Full sample here
EDIT(#PerracoLabs): I don't believe that Random is the culprit. This reproduces by just drawing to dynamic coordinates. I.e:
canvas.drawOval(x++ % 500, y++ % 500, w++ % 1080, h++ % 1000, paint);
Also, if these are just allocations stats, why are they accounted into total memory? If not released, it's a leak, right?
It is also strange that memory increase rate is ~100kb regardless of what's drawn.
EDIT 2:
I've attached full sample app that produces this (On Pixel 4, Android 10):
I've stopped profiling since the profiler slowed down to the point it became unusable.
Note that occasional drops where some memory is indeed freed.
Again, to me it doesn't make sense that for few draw calls, the overhead is ~200MB of allocated graphic memory.
I'd really like to understand what's going on here. Obviously, there is a difference when drawing on dynamic locations on the canvas compared when the location is fixed, at which time the memory consumption stabilises.
After testing your code, instead of a memory leak, seems to be allocated memory space which hasn't been reclaimed yet.
In Android the total used memory is the sum of everything including unused resources, and not necessarily resources allocated directly by yourself, these can be allocated by other methods.
Android uses a Garbage Collector to manage memory. The goal of the garbage collector is to ensure that there is enough free memory when it is needed, reclaiming it with minimal CPU overhead, rather than freeing as much memory as possible in one go.
In the test the draw method is being called nonstop, which keeps on allocating memory, but after a threshold it stops allocating anymore. In such test as it never stops calling the draw method, to be a leak, memory should keep growing always without stopping at any threshold, and eventually the system would kill the app.
Next a 37 minute screenshot. I took a few captures and overlapped the allocation info panels for better understanding. As you can see the memory growth is the native one, yet after 20 minutes there is no more growth staying at around 60Mb.
Note that calling explicitly the garbage collector will not free (reclaim) such memory, as System.gc() only triggers a suggestion and is at the discretion of the system to collect resources, which is basically when the JVM needs memory.
I know this is an old post, but I recently encountered the same problem.
The issue is with the paint object. I haven't tested it with all of the draw functions, but when you use a paint object with style STROKE and using canvas.drawCircle(x, y, radius, paint) where the x and y are changed at random - the graphics memory will be increased and never released until an inevitable OOM. This happens on Android 11, but not on Android 7 - haven't tested other versions.
You can easily reproduce it by creating a custom view like so:
class OOMLayout: FrameLayout {
val paint = Paint().apply {
color = Color.RED
style = Paint.Style.STROKE
strokeWidth = 4f
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val x = (100..400).random().toFloat()
val y = (100..400).random().toFloat()
canvas.drawCircle(x, y, 10f, paint)
invalidate()
return
}
}
This will take some time to reach the OOM but the memory leak is clearly visible in the android profiler.
To speed up the memory consumption put the drawCircle call inside a loop f.e. a 1000 iterations per onDraw - this way the app will OOM in few seconds.
When using paint with style FILL - there are no such problems on Android 11.
I've got a fragment, which I construct and insert a GraphicalView, and I need to know when the GraphicalView (chart) is done being drawn. the api isChartDrawn always returns false, including within onResume of the containing fragment.
public void onResume()
{
Log.d(TAG, "onResume, the mChart isDrawn: " + mChart.isChartDrawn());
super.onResume();
mListener.didNotificyChartDrawn();
}
Is there a notification I'm not seeing, or strategy for knowing when the chart is done being rendered? I'm asking because I need to access the series points from within one of the series of the XYChart used to construct the graphical view, like this:
mChart = new GraphicalView(getActivity(), mXYChart);
where mXYChart is an instance of the LineChart.
The graph renders fine, and I'm able to access the points I need later on via touch handling, just need to get to them a little earlier now and am hitting this issue. Any work arounds, etc, appreciated.
you're getting that because during onResume still was not draw yet. That's whole Android, not just aChartEngine. I'm not sure it's the best design decision, but that's how it is.
But good news is: there's a nice trick.
getView().getViewTreeObserver().addOnDrawListener(new OnDrawListener(){
void onDraw(){
getView().getViewTreeObserver().removeOnDrawListener(this);
// do your stuff here
}
});
this trick is used A LOT for animation, so you can measure stuff on screen and do the proper animations.
If you look at the onDraw method in GraphicalView, it sets the boolean mDrawn to true at the very end. So what must be happening is you are calling
public boolean isChartDrawn() {
return mDrawn;
}
Before it has completed the onDraw method. I would either create a interval handler to keep checking if mDrawn has been changed to true, or modify the library file GraphicalView so that it has an optional listener that you can attach to be fired off when the thing is drawn:
DrawnInterface mCallback;
public interface DrawnInterface{
public void onDrawn();
}
.....
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.getClipBounds(mRect);
......
canvas.drawBitmap(fitZoomImage, left + width - zoomSize * 0.75f, buttonY, null);
}
mDrawn = true;
mCallback.onDrawn();
}
Then make your calling activity implement the DrawnInterface you defined, and initialize the interface inside the constructor of GraphicalView
I was looking for a while for answer on my question but I didn`t get what I need. I have an application with a ListView, and form where I can add new record to DB. So there is not much queries to do.
How to handle connections to db ? Should I close it after getting what I want or should I keep it open whole time until app is closed ? I want to know what is the best way while thinking about performence and battery life.
According to this post by a Google engineer (Dianne Hackborn), there's nothing wrong with leaving the database connection open:
Android made a deliberate design decision that is can seem surprising,
to just give up on the whole idea of applications cleanly exiting and
instead let the kernel clean up their resources. After all, the
kernel needs to be able to do this anyway. Given that design, keeping
anything open for the entire duration of a process's life and never closing it is simply not a leak. It will be cleaned up when the
process is cleaned up.
So, for simplicity, I would extend the Application class to provide a single well-defined entry point for your code, and open the database connection in its onCreate(). Store the DB connection as a field in your Application, and provide an accessor method to make the connection available to rest of your code.
Then, don't worry about closing it.
In general I'd close the connection in the onDestroy() function of the Activity which opened the connection. I'd close() a cursor from a database in the function which uses the cursor.
public MyActivity extends Activity{
private myDatabase mDatabase; // myDatabase extends SQLiteOpenHelper
private Cursor mCursor;
public MyActivity(Context context){
super(context);
initMemberVariables();
}
public ElementButton(Context context, AttributeSet attrS){
super(context, attrS);
initMemberVariables();
}
public ElementButton(Context context, AttributeSet attrS, int defStyle){
super(context, attrS, defStyle);
initMemberVariables();
}
private void initMemberVariables(){
mDatabase = new PSEdb(this.getContext());
}
private void getData(){
mCursor = mDatabase.MyGetterFunction();
while(mCursor.moveToNext()){
try{
// populate your data
}catch(CursorIndexOutOfBoundsException ex){
// handle the exception
}
}
mCursor.close();
}
#Override
public void onDestroy(){
super.onDestroy();
mDatabase.close();
}
}
Establishing the connection to the database is expensive. If connections are not in short supply, and the database is local, I'd keep the connection open rather than establishing it for each write operation to the database, as you'd typically do in a client-server application that needs to scale to accommodate a large number of concurrent users.
Please explain how does the drawing cache work in Android. I'm implementing a custom View subclass. I want my drawing to be cached by the system. In the View constructor, I call
setDrawingCacheEnabled(true);
Then in the draw(Canvas c), I do:
Bitmap cac = getDrawingCache();
if(cac != null)
{
c.drawBitmap(cac, 0, 0, new Paint());
return;
}
Yet the getDrawingCache() returns null to me. My draw() is not called neither from setDrawingCacheEnabled(), nor from getDrawingCache(). Please, what am I doing wrong?
There's a hard limit on drawing cache size, available via the ViewConfiguration class.. My view is larger than allowed for caching.
FYI, the sources of the View class are available via the SDK Manager for some (not all) Android versions.
Hopefully this explains it.
public class YourCustomView extends View {
private String mSomeProperty;
public YourCustomView(Context context) {
super(context);
}
public YourCustomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public YourCustomView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setSomeProperty(String value) {
mSomeProperty = value;
setDrawingCacheEnabled(false); // clear the cache here
invalidate();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// specific draw logic here
setDrawingCacheEnabled(true); // cache
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
...
}
}
Example code explained.
In the setSomeProperty() method call setDrawingCacheEnabled(false) to clear the cache and force a redraw by calling invalidate().
Call setDrawingCacheEnabled(true) in the onDraw method after drawing to the canvas.
Optionally place a log statement in the onDraw method to confirm it is only called once each time you call the setSomeProperty() method. Be sure to remove the log call once confirmed as this will become a performance issue.