I have searched a lot for a solution for my up and down buttons and can't seem to get anything working. When using the ImageButtons in the program, I am able to press three ImageButtons before I get OutOfMemoryError. Please any help will be great.
for(int i = 0; i < keys.length; i++)
{
keys[i].setOnTouchListener(new View.OnTouchListener()
{
#Override
public boolean onTouch(View v, MotionEvent event)
{
if(event.getAction() == MotionEvent.ACTION_DOWN)
{
for(int down = 0; down < keys.lenght; down++)
{
if(v == keys[down])
{
keys[down].keys[down].setImageResource(drawIDDOWN[down]);
keys[down].setAdjustViewBounds(true);
}
}
}
if(event.getAction() == MotionEvent.ACTION_UP)
{
for(int up = 0; up < keys.length; up++)
{
if(v == keys[up])
{
keys[up].setImageResource(drawIDUP[up]);
keys[up].setAdjustViewBounds(true);
}
}
}
return false;
}
});
}
Here is the LogCat:
threadid=1: thread exiting with uncaught exception
FATAL EXCEPTION: main
java.lang.OutOfMemoryError: bitmap size exceeds VM budget
android.graphics.Bitmap.nativeCreate(Native Method)
android.graphics.Bitmap.createBitmap(Bitmap.java:477)
android.graphics.Bitmap.createBitmap(Bitmap.java:444)
android.graphics.Bitmap.createScaledBitmap(Bitmap.java:349)
android.graphics.BitmapFactory.finishDecode(BitmapFactory.java:498)
android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:473)
android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:336)
android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:697)
android.content.res.Resources.loadDrawable(Resources.java:1709)
android.content.res.Resources.getDrawable(Resources.java:581)
android.widget.ImageView.resolveUri(ImageView.java:501)
android.widget.ImageView.setImageResource(ImageView.java:280)
f7kidzCalc.com.KidzCalcActivity$1.onTouch(KidzCalcActivity.java:253)
android.view.View.dispatchTouchEvent(View.java:3881)
android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:942)
android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:942)
android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:942)
android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:942)
android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:942)
android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:942)
android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:942)
com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchTouchEvent
(PhoneWindow.java:1691)
com.android.internal.policy.impl.PhoneWindow.superDispatchTouchEvent
(PhoneWindow.java:1125)
android.app.Activity.dispatchTouchEvent(Activity.java:2096)
com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchTouchEvent
(PhoneWindow.java:1675)
android.view.ViewRoot.deliverPointerEvent(ViewRoot.java:2194)
android.view.ViewRoot.handleMessage(ViewRoot.java:1878)
android.os.Handler.dispatchMessage(Handler.java:99)
android.os.Looper.loop(Looper.java:123)
android.app.ActivityThread.main(ActivityThread.java:3683)
java.lang.reflect.Method.invokeNative(Native Method)
java.lang.reflect.Method.invoke(Method.java:507)
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
dalvik.system.NativeStart.main(Native Method)
Is there an easy way to garbage collect, recycle images or just anything? I'm stuck..
Make sure you are not storing the activities or context in any other part of your program. This really messes with your memory management and causes lots of leaks.
You can send pointers to it using methods such as:
Good Form
public void doSomethingtoContext(Context c){
c.getApplicationContext()....//Variable 'c' only lasts for the length of the method
}
Bad Form
public static Context cOnText;
public void doSomethingtoContext(Context c){
cOnText = c; //DO NOT STORE THIS VALUE HERE
}
Basically, what I am trying to say is that, most of the times the image size aren't the problem, but instead something easy to overlook like this case.
Hope this helps.
Good luck!
Reduce the size of the image you are using. I had the exact problem. Image i was using was >500kB. Reduced size is around 50-60kB.
Related
I am new in android.i am loading data from json. While on orientation change destroy and recreate the activity so the json loading everytime orientation changing..
I am getting the following error:
{
E/AndroidRuntime: FATAL EXCEPTION: main
java.lang.OutOfMemoryError at java.lang.AbstractStringBuilder.enlargeBuffer(AbstractStringBuilder.java:94)
at java.lang.AbstractStringBuilder.append0(AbstractStringBuilder.java:145)
at java.lang.StringBuilder.append(StringBuilder.java:202)
at org.json.JSONTokener.syntaxError(JSONTokener.java:450)
at org.json.JSONTokener.nextValue(JSONTokener.java:97)
at org.json.JSONTokener.readObject(JSONTokener.java:362)
at org.json.JSONTokener.nextValue(JSONTokener.java:100)
at org.json.JSONTokener.readObject(JSONTokener.java:385)
at org.json.JSONTokener.nextValue(JSONTokener.java:100)
at org.json.JSONTokener.readArray(JSONTokener.java:430)
at org.json.JSONTokener.nextValue(JSONTokener.java:103)
at org.json.JSONTokener.readObject(JSONTokener.java:385)
at org.json.JSONTokener.nextValue(JSONTokener.java:100)
at org.json.JSONObject.<init>(JSONObject.java:154)
at org.json.JSONObject.<init>(JSONObject.java:171)
at com.ProjectName.activities.MainActivity.get_Project_Json(MainActivity.java:238)
at com.ProjectName.activities.MainActivity.onCreate(MainActivity.java:115)
at android.app.Activity.performCreate(Activity.java:5104)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1080)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2144)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2230)
at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:3692)
at android.app.ActivityThread.access$700(ActivityThread.java:141)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1240)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:5041)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
at dalvik.system.NativeStart.main(Native Method)
}
especially in JellyBean and KitKat (Device:3.2 to 4.0inches)
{
public void get_Json_Assets() {
StringBuilder sb = new StringBuilder();
BufferedReader br = null;
try {
br = new BufferedReader(new InputStreamReader(getAssets().open("Somejson.json")));
String temp;
while ((temp = br.readLine()) != null)
sb.append(temp);
} catch (OutOfMemoryError e) {
e.fillInStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
br.close();
} catch (Exception e) {
e.fillInStackTrace();
}
}
all_Point_Json = sb.toString();
}
}
{`public void get_Project_Json() {
get_Json_Assets();
try {
JSONObject point_Json= new JSONObject(all_Point_Json);
JSONArray point_Chapter =point_Json.getJSONArray("chapter");
for (int i = 0; i < point_Chapter.length(); i++) {
HashMap<String, String> mPoint_Details = new HashMap<>();
mPoint_Details.put("mPoint", (String) point_Chapter.get(i));
mPoint_Details.put("mPoint_No", String.valueOf(i + 1));
All_Point.add(mPoint_Details);
}
} catch (JSONException e) {
e.fillInStackTrace();
}
}`}
First of all, your code is very messy. You should clean it up. Moreover, I don't know in which part of Activity life cycle are you calling this code. I suppose, you're doing it in onResume() or onCreate() method. Probably this *.json file contains a lot of data and you are allocating memory for it. During screen rotation, you're doing it again, after next rotation, memory is allocated again and so on. You should clean memory in onPause() method or load this data in a different way (e.g. in the Service and then pass it to Activity). In addition, you should avoid loading large files at once. You can consider loading parts of that file successively and for sure load this data in a separate thread (e.g. with AsyncTask or RxJava if you're familiar with it), because it's non-deterministic and probably a long operation.
I got the LogCat :
java.lang.RuntimeException: takePicture failed
at android.hardware.Camera.native_takePicture(Native Method)
at android.hardware.Camera.takePicture(Camera.java:1061)
at android.hardware.Camera.takePicture(Camera.java:1006)
at com.cidtech.portraitcatch.service.MyService.takePic(MyService.java:168)
at com.cidtech.portraitcatch.service.MyService$1.onClick(MyService.java:78)
at android.view.View.performClick(View.java:4088)
at android.view.View$PerformClick.run(View.java:16984)
at android.os.Handler.handleCallback(Handler.java:615)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4745)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
at dalvik.system.NativeStart.main(Native Method)
the curial code is below:
public void takePic(){
home_takePic.setEnabled(false);
camera = Camera.open();
if(bitmapList == null){
bitmapList = new ArrayList<Bitmap>();
}
for(int i=0; i<5; i++){
camera.takePicture(null, null, new MyPictureCallback());
SystemClock.sleep(1000);
}
saveBitmapToSdcard();
releaseResource();
home_takePic.setEnabled(true);
}
I also do this:
public void takePic(){
home_takePic.setEnabled(false);
camera = Camera.open();
***camera.startPreview();***
if(bitmapList == null){
bitmapList = new ArrayList<Bitmap>();
}
for(int i=0; i<5; i++){
camera.takePicture(null, null, new MyPictureCallback());
SystemClock.sleep(1000);
}
// saveBitmapToSdcard();
releaseResource();
home_takePic.setEnabled(true);
}
but still get the same Exception
the method camera.takePicture have been performed in version 4.0.4 but faild in version 4.1.1, who can tell me how to solve it ,please help me ,thanks
You need to be making a call to startPreview() before you attempt the call to takePicture(). Do this after you call Camera.open().
As per Android documentation:
Important: Call startPreview() to start updating the preview surface.
Preview must be started before you can take a picture.
startPreview documentation
I have a working code now with my thesis but I decided to clean it up using functions/methods/objects (not really sure what to call them) but after organizing them, my app crashes everytime i start it. I dont really know what the problem is.
Main Screen shows Start and Exit Button. When I press START, the app says "Unfortunately thesis has stopped".
My code goes like this:
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.items, menu);
View v = (View) menu.findItem(R.id.search).getActionView();
final EditText txtSearch = ( EditText ) v.findViewById(R.id.txt_search);
txtSearch.setOnEditorActionListener(new OnEditorActionListener() {
#Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
String curtextArray = txtSearch.getText().toString();
char[] curletters = curtextArray.toCharArray();
char[] curenhancedLetters = curtextArray.toCharArray();
//probably, problem here on Stemming stem
Stemming stem = new Stemming(curtextArray, curletters, curenhancedLetters);
AsyncTaskRunner newTask = new AsyncTaskRunner(enhancedStem);
// and probably, problem is here on the stem.<x process>;
stem.removeApostrophe();
stem.vowelMarking();
stem.shortSyllable();
if (continueStem == 1){
stem.region1();
stem.region2();
stem.shortWord();
stem.step0();
stem.step1a();
stem.step1b();
newTask.execute();
}
return false;
};
});
return super.onCreateOptionsMenu(menu);
}
Here's my Stemming Class
public class Stemming {
String textArray;
char[] letters;
char[] enhancedLetters;
public Stemming (String curtextArray, char[] curletters, char[] curenhancedLetters){
this.textArray = curtextArray;
this.letters = curletters;
this.enhancedLetters = curenhancedLetters;
}
public Stemming(){
}
public void removeApostrophe(){
...processes here
}
public void vowelMarking(){
...processes here
}
public void shortSyllable(){
...processes here
}
public void region1(){
...processes here
}
public void region2(){
...processes here
}
public void shortWord(){
...processes here
}
public void step0(){
...processes here
}
public void step1a(){
...processes here
}
public void step1b(){
...processes here
}
}
}
I have a theory on why it crashes. Is this method possible? (pseudocode):
public class Stemming {
String result;
String sample = "A A A A A";
public void changeAtoB{
//do process to convert all As to Bs making String sample = "B B B B B"
result = sample;
}
public void changeBtoC{
//do process to convert all Bs to Cs making String result = "C C C C C"
result = result;
}
... so on {
}
}
What I did was process the string in a straight manner without doing any variable declarations (my variables are declared globally) or initializations. and I also did not put any return statements.
My code used to work when It was still without those functions/methods/objects.
Sorry about my long post. Don't know how to explain it better. I hope you help me. Thank you in Advance!
LOGCAT:
>E/AndroidRuntime(11007): FATAL EXCEPTION: main
E/AndroidRuntime(11007): java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{com.atienzaerni.thesis/com.atienzaerni.thesis.secondactivity}: java.lang.NullPointerException
E/AndroidRuntime(11007): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1891)
E/AndroidRuntime(11007): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1992)
E/AndroidRuntime(11007): at android.app.ActivityThread.access$600(ActivityThread.java:127)
E/AndroidRuntime(11007): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1158)
E/AndroidRuntime(11007): at android.os.Handler.dispatchMessage(Handler.java:99)
E/AndroidRuntime(11007): at android.os.Looper.loop(Looper.java:137)
E/AndroidRuntime(11007): at android.app.ActivityThread.main(ActivityThread.java:4441)
E/AndroidRuntime(11007): at java.lang.reflect.Method.invokeNative(Native Method)
E/AndroidRuntime(11007): at java.lang.reflect.Method.invoke(Method.java:511)
E/AndroidRuntime(11007): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
E/AndroidRuntime(11007): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
E/AndroidRuntime(11007): at dalvik.system.NativeStart.main(Native Method)
E/AndroidRuntime(11007): Caused by: java.lang.NullPointerException
E/AndroidRuntime(11007): at android.app.Activity.findViewById(Activity.java:1794)
E/AndroidRuntime(11007): at com.atienzaerni.thesis.secondactivity.<init>(secondactivity.java:54)
E/AndroidRuntime(11007): at java.lang.Class.newInstanceImpl(Native Method)
E/AndroidRuntime(11007): at java.lang.Class.newInstance(Class.java:1319)
E/AndroidRuntime(11007): at android.app.Instrumentation.newActivity(Instrumentation.java:1023)
E/AndroidRuntime(11007): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1882)
E/AndroidRuntime(11007): ... 11 more
I/Process(11007): Sending signal. PID: 11007 SIG: 9
(I'm using my phone by the way. Not an emulator. If this makes a difference)
It looks like you are getting a NullPointerException because v is null at this line:
final EditText txtSearch = ( EditText ) v.findViewById(R.id.txt_search);
This is probably caused by null being returned from the menu items action view here:
View v = (View) menu.findItem(R.id.search).getActionView();
You should either set the action view in code and not call getActionView or make sure you have a proper action view set in the menu XML. Without seeing your menu XML it's hard to tell if the problem lies in the XML.
I am rather new to Android/Java programming and looking for some help with a very, very basic app that I am working with. I am purely building this just to get used to Java coding and building applications for Droids, so bear with me.
I have 10 radio buttons grouped together and only one of them is the right answer (button #10). If the user clicks on any of them 1-9 I have it displaying a message that says "sorry try again", but if they click on #10, I want it to take them to a new activity, namely a page where I will put up some graphics and text that says congraulations and things of that nature. The buttons work fine for 1-9, but when I click on #10 I am gettinga force close error.
Here's what I have so far:
public class MainQuiz extends Activity implements OnCheckedChangeListener, android.widget.RadioGroup.OnCheckedChangeListener {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_quiz);
((RadioGroup)findViewById(R.id.radio_group)).setOnCheckedChangeListener(this);}
public void onCheckedChanged(RadioGroup radioGroup, int checkedId) {
String numeral = null;
if (checkedId == R.id.button1) {
numeral = "Nope, try again!";
} else if (checkedId == R.id.button2) {
numeral = "Nope, try again!";
} else if (checkedId == R.id.button3) {
numeral = "Nope, try again!";
}
else if (checkedId == R.id.button4) {
numeral = "Nope, try again!";
}
else if (checkedId == R.id.button5) {
numeral = "Nope, try again!";
}
else if (checkedId == R.id.button6) {
numeral = "Nope, try again!";
}
else if (checkedId == R.id.button7) {
numeral = "Nope, try again!";
}
else if (checkedId == R.id.button8) {
numeral = "Nope, try again!";
}
else if (checkedId == R.id.button9) {
numeral = "Nope, try again!";
}
else if (checkedId == R.id.button10) {
Intent myIntent = new Intent(MainQuiz.this, CorrectAnswer.class);
MainQuiz.this.startActivity(myIntent);
}
Toast.makeText(getApplicationContext(), ""+numeral+"",
Toast.LENGTH_SHORT).show(); }
Any help would be apprecitaed, I am sure this is a simple fix and I am sure it is something glaring in the syntax and code that I have messed up, but again, I am new :)
Thanks!
Here is the log after the Force Close:
11-14 13:56:48.982: D/AndroidRuntime(862): Shutting down VM
11-14 13:56:48.982: W/dalvikvm(862): threadid=1: thread exiting with uncaught exception (group=0x4001d800)
11-14 13:56:48.992: E/AndroidRuntime(862): FATAL EXCEPTION: main
11-14 13:56:48.992: E/AndroidRuntime(862): java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{com.test.quiz/com.test.quiz.CorrectAnswer}: java.lang.ClassCastException: com.test.quiz.CorrectAnswer
11-14 13:56:48.992: E/AndroidRuntime(862): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2585)
11-14 13:56:48.992: E/AndroidRuntime(862): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2679)
11-14 13:56:48.992: E/AndroidRuntime(862): at android.app.ActivityThread.access$2300(ActivityThread.java:125)
11-14 13:56:48.992: E/AndroidRuntime(862): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2033)
11-14 13:56:48.992: E/AndroidRuntime(862): at android.os.Handler.dispatchMessage(Handler.java:99)
11-14 13:56:48.992: E/AndroidRuntime(862): at android.os.Looper.loop(Looper.java:123)
11-14 13:56:48.992: E/AndroidRuntime(862): at android.app.ActivityThread.main(ActivityThread.java:4627)
11-14 13:56:48.992: E/AndroidRuntime(862): at java.lang.reflect.Method.invokeNative(Native Method)
11-14 13:56:48.992: E/AndroidRuntime(862): at java.lang.reflect.Method.invoke(Method.java:521)
11-14 13:56:48.992: E/AndroidRuntime(862): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)
11-14 13:56:48.992: E/AndroidRuntime(862): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)
11-14 13:56:48.992: E/AndroidRuntime(862): at dalvik.system.NativeStart.main(Native Method)
11-14 13:56:48.992: E/AndroidRuntime(862): Caused by: java.lang.ClassCastException: com.test.quiz.CorrectAnswer
11-14 13:56:48.992: E/AndroidRuntime(862): at android.app.Instrumentation.newActivity(Instrumentation.java:1021)
11-14 13:56:48.992: E/AndroidRuntime(862): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2577)
11-14 13:56:48.992: E/AndroidRuntime(862): ... 11 more
The force close will always put an exception stack trace in your logcat file. Please put this in future posts, it tells us what line crashed and why.
My guess from your code and newness to Android- you probably didn't add the CorrectAnswer activity to your manifest.
I don't know what is your problem, but you can improve your code:
use "switch - case" construction.
switch (chekedId) {
case R.id.button10:
Intent myIntent = new Intent(MainQuiz.this, CorrectAnswer.class);
startActivity(myIntent);
break;
default:
Toast.makeText(getApplicationContext(), "Nope, try again!",
Toast.LENGTH_SHORT).show();
}
It seems like this line is probably what is causing your problems.
MainQuiz.this.startActivity(myIntent);
Remove the MainQuiz, and it should work
this.startActivity(myIntent);
MainQuiz.this.startActivity(myIntent);
replace by
startActivity(myIntent);
I've been looking for some time now for detailed design documents describing the architecture of the Dalvik VM's garbage collector, but haven't turned up much. Given the performance implications of GC runs I'd really like to have a better understanding of 5 specific issues:
1. What exactly triggers GC in Android? Other VM implementations I've seen usually allow for a certain percentage of system memory to be allocated to an application before their GC receives a signal to run. Scanning the following LogCat however seems to show Dalvik GC running at least in part quite often--
12-14 11:34:57.753: D/dalvikvm(279): GC_EXTERNAL_ALLOC freed 735 objects / 54272 bytes
in 90ms
12-14 11:34:57.893: D/dalvikvm(279): GC_EXTERNAL_ALLOC freed 256 objects / 12240 bytes
in 61ms
12-14 11:34:57.943: I/jPCT-AE(279): Loading Texture...
12-14 11:34:57.993: D/dalvikvm(279): GC_FOR_MALLOC freed 65 objects / 2840 bytes in
52ms
12-14 11:34:58.013: I/dalvikvm-heap(279): Grow heap (frag case) to 5.039MB for
1048592-byte allocation
12-14 11:34:58.073: D/dalvikvm(279): GC_FOR_MALLOC freed 1 objects / 40 bytes in 59ms
12-14 11:34:58.243: D/dalvikvm(279): GC_EXTERNAL_ALLOC freed 11 objects / 432 bytes in
55ms
12-14 11:34:58.283: I/jPCT-AE(279): Loading Texture...
12-14 11:34:58.333: D/dalvikvm(279): GC_FOR_MALLOC freed 10 objects / 416 bytes in 46ms
12-14 11:34:58.344: I/dalvikvm-heap(279): Grow heap (frag case) to 6.040MB for
1048592-byte allocation
12-14 11:34:58.423: D/dalvikvm(279): GC_FOR_MALLOC freed 2 objects / 80 bytes in 75ms
12-14 11:34:58.563: D/dalvikvm(279): GC_EXTERNAL_ALLOC freed 10 objects / 384 bytes in
47ms
12-14 11:34:58.603: I/jPCT-AE(279): Loading Texture...
12-14 11:34:58.653: D/dalvikvm(279): GC_FOR_MALLOC freed 11 objects / 464 bytes in 44ms
12-14 11:34:58.663: I/dalvikvm-heap(279): Grow heap (frag case) to 7.040MB for
1048592-byte allocation
12-14 11:34:58.743: D/dalvikvm(279): GC_FOR_MALLOC freed 2 objects / 80 bytes in 75ms
12-14 11:34:58.973: I/System.out(279): started document!
...
12-14 11:43:05.393: I/jPCT-AE(279): Memory usage before compacting: 5867 KB used out
of 6215 KB
12-14 11:43:05.453: D/dalvikvm(279): GC_EXPLICIT freed 2560 objects / 145712 bytes in
61ms
12-14 11:43:05.503: D/dalvikvm(279): GC_EXPLICIT freed 295 objects / 21448 bytes in
51ms
12-14 11:43:05.717: I/jPCT-AE(279): Memory usage after compacting: 5705 KB used out of
6215 KB
...
12-14 11:43:05.792: D/dalvikvm(279): GC_EXTERNAL_ALLOC freed 105 objects / 6152 bytes
in 56ms
12-14 11:43:05.855: D/dalvikvm(279): GC_FOR_MALLOC freed 3 objects / 80 bytes in 51ms
...
12-14 11:43:12.863: D/dalvikvm(279): GC_EXTERNAL_ALLOC freed 864 objects / 1099072
bytes in 70ms
12-14 11:43:13.053: D/dalvikvm(279): GC_EXTERNAL_ALLOC freed 45 objects / 1760 bytes
in 55ms
12-14 11:43:14.533: D/dalvikvm(279): GC_EXTERNAL_ALLOC freed 49 objects / 2376 bytes
in 58ms
12-14 11:43:14.933: D/dalvikvm(279): GC_EXTERNAL_ALLOC freed 34 objects / 1408 bytes
in 55ms
12-14 11:43:15.423: D/dalvikvm(279): GC_EXTERNAL_ALLOC freed 13 objects / 504 bytes in
58ms
12-14 11:43:15.953: D/dalvikvm(279): GC_EXTERNAL_ALLOC freed 13 objects / 520 bytes in
56ms
...
12-14 11:43:31.203: I/jPCT-AE(279): Visibility lists disposed!
12-14 11:43:31.203: I/jPCT-AE(279): All texture data unloaded from gpu!
12-14 11:43:31.203: I/jPCT-AE(279): Renderer disposed!
12-14 11:43:31.203: I/jPCT-AE(279): Static references cleared...
...
12-14 11:43:36.943: E/dalvikvm-heap(279): 2964320-byte external allocation too large
for this process.
12-14 11:43:36.953: E/GraphicsJNI(279): VM won't let us allocate 2964320 bytes
12-14 11:43:36.953: D/AndroidRuntime(279): Shutting down VM
12-14 11:43:36.953: W/dalvikvm(279): threadid=1: thread exiting with uncaught
exception (group=0x4001d800)
12-14 11:43:36.973: E/AndroidRuntime(279): FATAL EXCEPTION: main
12-14 11:43:36.973: E/AndroidRuntime(279): android.view.InflateException: Binary XML
file line #33: Error inflating class <unknown>
12-14 11:43:36.973: E/AndroidRuntime(279): at
android.view.LayoutInflater.createView(LayoutInflater.java:513)
12-14 11:43:36.973: E/AndroidRuntime(279): at
com.android.internal.policy.impl.PhoneLayoutInflater.
onCreateView(PhoneLayoutInflater.java:56)
12-14 11:43:36.973: E/AndroidRuntime(279): at
android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:563)
12-14 11:43:36.973: E/AndroidRuntime(279): at
android.view.LayoutInflater.rInflate(LayoutInflater.java:618)
12-14 11:43:36.973: E/AndroidRuntime(279): at
android.view.LayoutInflater.rInflate(LayoutInflater.java:621)
12-14 11:43:36.973: E/AndroidRuntime(279): at
android.view.LayoutInflater.inflate(LayoutInflater.java:407)
12-14 11:43:36.973: E/AndroidRuntime(279): at
android.view.LayoutInflater.inflate(LayoutInflater.java:320)
12-14 11:43:36.973: E/AndroidRuntime(279): at
com.ai.ultimap.views.Manual.onItemClick(Manual.java:467)
12-14 11:43:36.973: E/AndroidRuntime(279): at
android.widget.AdapterView.performItemClick(AdapterView.java:284)
12-14 11:43:36.973: E/AndroidRuntime(279): at
android.widget.AbsListView$PerformClick.run(AbsListView.java:1696)
12-14 11:43:36.973: E/AndroidRuntime(279): at
android.os.Handler.handleCallback(Handler.java:587)
12-14 11:43:36.973: E/AndroidRuntime(279): at
android.os.Handler.dispatchMessage(Handler.java:92)
12-14 11:43:36.973: E/AndroidRuntime(279): at android.os.Looper.loop(Looper.java:123)
12-14 11:43:36.973: E/AndroidRuntime(279): at
android.app.ActivityThread.main(ActivityThread.java:4627)
12-14 11:43:36.973: E/AndroidRuntime(279): at
java.lang.reflect.Method.invokeNative(Native Method)
12-14 11:43:36.973: E/AndroidRuntime(279): at
java.lang.reflect.Method.invoke(Method.java:521)
12-14 11:43:36.973: E/AndroidRuntime(279): at
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)
12-14 11:43:36.973: E/AndroidRuntime(279): at
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)
12-14 11:43:36.973: E/AndroidRuntime(279): at dalvik.system.NativeStart.main(Native
Method)
12-14 11:43:36.973: E/AndroidRuntime(279): Caused by:
java.lang.reflect.InvocationTargetException
12-14 11:43:36.973: E/AndroidRuntime(279): at android.widget.ImageView.<init>
(ImageView.java:108)
12-14 11:43:36.973: E/AndroidRuntime(279): at
java.lang.reflect.Constructor.constructNative(Native Method)
12-14 11:43:36.973: E/AndroidRuntime(279): at
java.lang.reflect.Constructor.newInstance(Constructor.java:446)
12-14 11:43:36.973: E/AndroidRuntime(279): at
android.view.LayoutInflater.createView(LayoutInflater.java:500)
12-14 11:43:36.973: E/AndroidRuntime(279): ... 18 more
12-14 11:43:36.973: E/AndroidRuntime(279): Caused by: java.lang.OutOfMemoryError:
bitmap size exceeds VM budget
12-14 11:43:36.973: E/AndroidRuntime(279): at
android.graphics.Bitmap.nativeCreate(Native Method)
12-14 11:43:36.973: E/AndroidRuntime(279): at
android.graphics.Bitmap.createBitmap(Bitmap.java:468)
12-14 11:43:36.973: E/AndroidRuntime(279): at
android.graphics.Bitmap.createBitmap(Bitmap.java:435)
12-14 11:43:36.973: E/AndroidRuntime(279): at
android.graphics.Bitmap.createScaledBitmap(Bitmap.java:340)
12-14 11:43:36.973: E/AndroidRuntime(279): at
android.graphics.BitmapFactory.finishDecode(BitmapFactory.java:488)
12-14 11:43:36.973: E/AndroidRuntime(279): at
android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:462)
12-14 11:43:36.973: E/AndroidRuntime(279): at
android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:323)
12-14 11:43:36.973: E/AndroidRuntime(279): at
android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:697)
12-14 11:43:36.973: E/AndroidRuntime(279): at
android.content.res.Resources.loadDrawable(Resources.java:1709)
12-14 11:43:36.973: E/AndroidRuntime(279): at
android.content.res.TypedArray.getDrawable(TypedArray.java:601)
12-14 11:43:36.973: E/AndroidRuntime(279): at android.widget.ImageView.<init>
(ImageView.java:118)
12-14 11:43:36.973: E/AndroidRuntime(279): ... 22 more
12-14 11:43:38.763: I/Process(279): Sending signal. PID: 279 SIG: 9
As you can see I'm specifically running into an outofmemory error during an ~3 MB bitmap load... This doesn't make sense to me since GC recently ran and nothing allocated since should have brought the VM within 3MB of capacity (256 MB). Is there only a small percentage of that 256 MB system RAM which is actually given to the VM before it crashes? Could it be that the Bitmap loading process has its own memory allocation cap?
I know object pooling is a good way to try to avoid GC during game loops, but without knowing EXACTLY what triggers Dalvik GC we're still placing an awful lot of faith in the OS and Google's vague discussions of performance best-practices.
Can the GC state (e.g. 'about to run', 'running', 'finished running') be tracked from code so that large resource allocations might be planned strategically around available memory? I have read this post on the matter: Determine when the Android GC runs which offers an interesting potential solution, but still relies on a 'trick'. I'd like to know if there is a supported API call somewhere which can be relied upon in production code (not just debug) to track the precise state of the garbage collector. System.gc() might be useful in some cases IF GC state can be checked; otherwise, since it can't promise an immediate GC run, its usefulness drops quite a bit.
Is GC always system-wide, or can separate threads (such as a dedicated rendering thread for a game) escape the potential performance lag issues caused by GC?
Given the following hypothetical scenario:
'I have an object which costs (VM RAM budget)/2 bytes to instantiate, and I instantiate it immediately with a single reference. I then null out that reference, making the object eligible for GC but of course not actually releasing its memory yet. I then immediately instantiate the object again.'
Would this crash the VM or is there some way the OS handles such extreme situations automatically to avoid crashing the VM? If the OS doesn't handle it, I would cite this as a good example of why my question #2 above is valid; if GC state could be tracked, logic could be included in the source to handle huge object allocation issues (in reality more likely to be large resources than badly designed classes) by checking to see if memory from a GC eligible object had been freed before loading the new huge object instance, and showing a small loading animation while polling GC in the background. This should avoid application not responding errors as well as legitimate out of memory errors... Some sort of onGC() listener would be ideal; could a GC listener be implemented in native code without re-building the OS kernel?
5.Finally, some source code... do I have the right idea for performance-efficient Android programming?
Activity Class:
package com.ai.ultimap;
//imports omitted...
public class UltiMapActivity extends Activity {
//Housekeeping
private String viewDriverID = "";
private static final int TUTORIAL = 7;
//visuals
private HomeView hv; //home view
private ConfigView cv; //config view
private MapView mv; //map view
private Manual man; //manual view
private int manCount = 0; //tracks the number of times the manual has been called
//with menu button, ignoring button presses unless value is zero
private PathCreator pcv; //path creator view
private MasterGL mgl; //the gl center
private String pending = "Coming soon...";
private PathCreator draw;
private Surfacer morlock;
// Used to handle pause and resume...
private static UltiMapActivity master;
//XML I/O considerations
private String fXML = "mypaths.xml";
private String sXML = "data was not saved properly...?";
private FileOutputStream fos;
private FileInputStream fis;
private FileWriter fw;
private FileReader fr;
private Date theDate = new Date();
private char[] buf = new char[1];
//Feedback stuffs
private FeedbackController feed;
//tracking you... :)
private WifiStalk stalk;
private long lat;
private long longitude;
//Testing
private DrawView dv;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("me","ultimap created!");
master = null;
mgl = new MasterGL(this); //revisit this later for versatility
man = new Manual(this);
feed = new FeedbackController(this);
stalk = new WifiStalk(this);
draw = new PathCreator(this);
hv = new HomeView(this,draw);
try {
BeanCounter bean = new BeanCounter(this);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (XmlPullParserException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
showDialog(TUTORIAL);
}
#Override
public boolean onKeyDown(int keyCode,KeyEvent e){
if (keyCode == 82){
if (viewDriverID.equals("hv")){
hv.removeHV();
}
else if (viewDriverID.equals("cv")){
cv.removeCV();
}
else if (viewDriverID.equals("mv")){
return true;
}
else if (viewDriverID.equals("pcv")){
return true;
}
if(man.getAddedState() == 0){
//Show the manual code...
System.out.println("View we're coming from: " + this.getVDID());
Log.e("me", "man.getaddedstate does equal 0, should be about to makeMan");
man.makeMan();
}
else if(man.getAddedState() == 2){
man.removeMan();
man.removeMan2();
man.setAddedState(1);
}
else if(man.getAddedState() == 1){
System.out.println("View we're coming from: " + this.getVDID());
man.addMan();
}
}
return true;
}
#Override
protected Dialog onCreateDialog(int id) {
//alerts ommitted for space
}
//Used to track the semantic context of what the Activity is displaying
//Getters/setters for external access ommitted
#Override
protected void onStart(){
super.onStart();
Log.d("me","ultimap started!");
}
#Override
protected void onPause() {
super.onPause();
Log.d("me","ultimap paused!");
if (mgl.getGLview() != null){
mgl.getGLview().onPause();
}
if (draw.getGLV() != null){
draw.getGLV().onPause();
}
}
#Override
protected void onResume() {
super.onResume();
Log.d("me","ultimap resumed!");
stalk.killListener();
if (mgl.getGLview() != null){
mgl.getGLview().onResume();
Log.d("me", "mgl.getGLview is NOT null on resume");
}
else if (mgl.getGLview() == null){
mgl.initGL();
mgl.getGLview().onResume();
Log.d("me", "mgl.getGLview is null on resume");
}
if (draw.getGLV() != null){
draw.getGLV().onResume();
Log.d("me", "draw.getGLV is NOT null on resume");
}
else if (draw.getGLV() == null && draw.getHGL() != null){
draw.pcvInit();
Log.d("me", "draw.getGLV is null on resume");
}
if (hv.getMV() != null && hv.getMV().getGLV() != null){
hv.getMV().getGLV().onResume();
Log.d("me", "map.getGLV is NOT null on resume");
}
else if (hv.getMV() != null && hv.getMV().getGLV() == null &&
hv.getMV().getHGL() != null){
hv.getMV().mvInit();
Log.d("me", "map.getGLV is null on resume");
}
}
#Override
protected void onStop() {
super.onStop();
//feed.getSP().release();
Log.d("me","ultimap stopped!");
}
#Override
protected void onRestart(){
super.onRestart();
Log.d("me","ultimap restarted!");
if (mgl != null){
mgl.initGL();
}
}
#Override
protected void onDestroy(){
super.onDestroy();
Log.d("me","ultimap destroyed!");
mgl.disposeTextures();
if (feed.getSP() != null && feed.getSID() != 0 && feed.getLoaded() ==
true){
feed.getSP().unload(feed.getSID());
feed.getSP().release();
}
}
}
Tutorial View Manager Class:
/*
* This class defines an in-app manual which is callable/dismissable
* in a non-invasive way...
*
* http://www.codeproject.com/KB/android/ViewFlipper_Animation.aspx
*http://developer.android.com/reference/android/widget/
*ViewFlipper.html#ViewFlipper%28android.content.Context%29
* http://developer.android.com/resources/articles/avoiding-memory-leaks.html
*/
package com.ai.ultimap.views;
//imports ommitted
public class Manual extends View implements OnItemClickListener{
private UltiMapActivity hUMA;
private ListView lv1;
private ListAdapter la;
private LayoutInflater mInflater;
private Vector<RowData> data;
private TextView tv;
private RelativeLayout holderRL;
private View v;
private View v2;
private int addedState = 0; //tracks whether or not a view has been instantiated,
//and if so whether or not it is the currently visible view
private int addedState2 = 0;
//Grid View stuff
private GridView helpGrid;
//ViewFlipper stuff
private ViewFlipper vf;
private TextView tutTV;
private String mapTutString = "Map View Tutorial Part: ";
private String pcTutString = "Path Creator Tutorial Part: ";
private String tutType;
private TextView counterTV;
private int partCounter = 1;
private float oldTouchValue = 0.0f;
private boolean searchOk = true;
private ImageView floatingImage;
public Manual(UltiMapActivity hAct){
super(hAct);
hUMA = hAct;
holderRL = new RelativeLayout(hUMA);
v = new View(hUMA);
floatingImage = new ImageView(hUMA);
}
//Here we summon and populate the grid view
public void makeMan(){
if (addedState == 0){
Log.e("me", "in makeMan");
mInflater = (LayoutInflater)
hUMA.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
hUMA.addContentView(holderRL, new
LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT));
v = mInflater.inflate(R.layout.helpgrid, holderRL, false);
helpGrid = (GridView) v.findViewById(R.id.manGV);
helpGrid.setAdapter(new ImageAdapter(hUMA));
hUMA.addContentView(v, new
LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT));
helpGrid.setOnItemClickListener(this);
addedState = 2;
}
}
public void addMan(){
if (v != null && addedState == 1){
v.setVisibility(VISIBLE);
v.bringToFront();
addedState = 2;
}
}
public void addMan2(){
if (v2 != null && addedState2 == 1){
v2.setVisibility(VISIBLE);
v2.bringToFront();
addedState2 = 2;
}
}
public void removeMan(){
if (v != null && addedState == 2){
v.setVisibility(GONE);
addedState = 1;
String s = hUMA.getVDID();
if (s.equals("hv")){
hUMA.getHome().addHV();
Log.d("me", "add hjomeview called from anual");
Log.d("me", "hv addedstate : " +
hUMA.getHome().getAddedState());
}
else if (s.equals("cv")){
hUMA.getConfig().addCV();
}
else if (s.equals("mv")){
hUMA.getHome().getMV().mvInit();
}
else if (s.equals("pcv")){
hUMA.getDraw().pcvInit();
}
}
}
public void removeMan2(){
if (v2 != null && addedState2 == 2){
v2.setVisibility(GONE);
addedState2 = 1;
String s = hUMA.getVDID();
if (s.equals("hv")){
hUMA.getHome().addHV();
Log.d("me", "add hjomeview called from manual");
Log.d("me", "hv addedstate : " +
hUMA.getHome().getAddedState());
}
else if (s.equals("cv")){
hUMA.getConfig().addCV();
}
else if (s.equals("mv")){
hUMA.getHome().getMV().mvInit();
}
else if (s.equals("pcv")){
hUMA.getDraw().pcvInit();
}
}
}
//addedstate getters and setters ommitted for space
#Override
public boolean onTouchEvent(MotionEvent touchevent) {
switch (touchevent.getAction())
{
case MotionEvent.ACTION_DOWN:
{
System.out.println("received a touch down at " + touchevent.getX()
+ "," + touchevent.getY());
oldTouchValue = touchevent.getX();
if(this.searchOk==false) return false;
float currentX = touchevent.getX();
if (currentX > (vf.getWidth()/2))
{
vf.setInAnimation(AnimationHelper.inFromRightAnimation());
vf.setOutAnimation(AnimationHelper.outToLeftAnimation());
vf.showNext();
if (partCounter <= 3 && partCounter >= 1){
partCounter++;
}
else if (partCounter == 4){
partCounter = 1;
}
else{
Log.e("me", "partCounter got past 4...");
}
if(tutType.equals("map")){
counterTV.setText(mapTutString + partCounter);
}
else if(tutType.equals("pc")){
counterTV.setText(pcTutString + partCounter);
}
else{
Log.e("me","not getting valid tutType string");
}
}
if (currentX <= (vf.getWidth()/2))
{
vf.setInAnimation(AnimationHelper.inFromLeftAnimation());
vf.setOutAnimation(AnimationHelper.outToRightAnimation());
vf.showPrevious();
if (partCounter >= 2 && partCounter <= 4){
partCounter--;
}
else if (partCounter == 1){
partCounter = 4;
}
else{
Log.e("me", "partCounter got below 1...");
}
if(tutType.equals("map")){
counterTV.setText(mapTutString + partCounter);
}
else if(tutType.equals("pc")){
counterTV.setText(pcTutString + partCounter);
}
else{
Log.e("me","not getting valid tutType string");
}
}
break;
}
case MotionEvent.ACTION_UP:
{
//nothing to do here
}
}
return false;
}
public void setUserText(String str){
tv.setText(str);
}
private class CustomTV extends TextView{
private String content = "";
public CustomTV(Context c, String str){
super(c);
content = str;
this.setText(content);
}
}
/**
* Data type used for custom adapter. Single item of the adapter.
*/
private class RowData {
protected String mItem;
protected String mDescription;
RowData(String item, String description){
mItem = item;
mDescription = description;
}
#Override
public String toString() {
return mItem + " " + mDescription;
}
}
private class CustomAdapter extends ArrayAdapter<RowData> {
public CustomAdapter(Context context, int resource,
int textViewResourceId, List<RowData> objects) {
super(context, resource, textViewResourceId, objects);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
//widgets displayed by each item in your list
TextView item = null;
TextView description = null;
//data from your adapter
RowData rowData= getItem(position);
//we want to reuse already constructed row views...
if(null == convertView){
convertView = mInflater.inflate(R.layout.custom_row, null);
holder = new ViewHolder(convertView);
convertView.setTag(holder);
}
holder = (ViewHolder) convertView.getTag();
item = holder.getItem();
item.setText(rowData.mItem);
description = holder.getDescription();
description.setText(rowData.mDescription);
return convertView;
}
}
/**
* Wrapper for row data.
*
*/
private class ViewHolder {
private View mRow;
private TextView description = null;
private TextView item = null;
public ViewHolder(View row) {
mRow = row;
}
public TextView getDescription() {
if(null == description){
description = (TextView) mRow.findViewById(R.id.cbox);
}
return description;
}
public TextView getItem() {
if(null == item){
item = (TextView) mRow.findViewById(R.id.cbox2);
}
return item;
}
}
#Override
public void onItemClick(AdapterView<?> arg0, View arg1, int position, long id) {
v.setVisibility(GONE);
if (addedState2 == 0){
hUMA.addContentView(this,DefineLayoutParams.getParams(DefineLayoutParams.getMM()));
//this is why the onTouch only starts lsitening at this point
if (position == 0){
v2 = mInflater.inflate(R.layout.flipper, holderRL, false);
vf = (ViewFlipper) v2.findViewById(R.id.manFlipperVF);
tutTV = (TextView) v2.findViewById(R.id.manDescriptionTV);
counterTV = (TextView) v2.findViewById(R.id.mapviewtutCounterTV);
tutTV.setText("Map View Instructions: ...");
counterTV.setText(mapTutString + partCounter);
tutType = "map";
}
else if (position == 1){
v2 = mInflater.inflate(R.layout.flipperpc, holderRL, false);
vf = (ViewFlipper) v2.findViewById(R.id.manFlipperpcVF);
tutTV = (TextView) v2.findViewById(R.id.manDescriptionpcTV);
counterTV = (TextView) v2.findViewById(R.id.manFlipperCounterpcTV);
tutTV.setText("Path Creator Tutorial:...");
counterTV.setText(pcTutString + partCounter);
tutType = "pc";
}
addedState2 = 2;
hUMA.addContentView(v2, DefineLayoutParams.getParams(DefineLayoutParams.getWW()));
}
else if(addedState2 == 1){
v2.setVisibility(VISIBLE);
addedState2 = 2;
}
}
public String getTutType(){
return tutType;
}
}
Tutorial View Flipper XML:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<ScrollView
android:id="#+id/manDerscriptionSV"
android:layout_width="match_parent"
android:layout_height="200px"
>
<TextView
android:id="#+id/manDescriptionTV"
android:text="Coming Soon..."
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</ScrollView>
<TextView
android:id="#+id/mapviewtutCounterTV"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Map View Tutorial Part: "
android:gravity="center"
android:layout_below="#id/manDerscriptionSV"
/>
<ViewFlipper
android:id="#+id/manFlipperVF"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#id/mapviewtutCounterTV"
>
<ImageView
android:id="#+id/mapviewtut1"
android:src="#drawable/mapviewtutflipper1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<ImageView
android:id="#+id/mapviewtut2"
android:src="#drawable/mapviewtutflipper2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<ImageView
android:id="#+id/mapviewtut3"
android:src="#drawable/mapviewtutflipper3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<ImageView
android:id="#+id/mapviewtut4"
android:src="#drawable/mapviewtutflipper4"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</ViewFlipper>
</RelativeLayout>
thanks,
CCJ
What exactly triggers GC in Android?
That is an internal implementation detail that SDK developers should not worry about.
Other VM implementations I've seen usually allow for a certain percentage of system memory to be allocated to an application before their GC receives a signal to run.
I'll take your word for that. Java does not behave this way. The JVM does not care how much system memory exists -- it only cares about its potential heap size (e.g., -Xmx) for its own VM, at most.
Scanning the following LogCat however seems to show Dalvik GC running at least in part quite often
Correct. Particularly on newer versions of Android, the GC runs concurrently in its own thread, rather than the stop-the-world approach taken earlier on.
This doesn't make sense to me since GC recently ran and nothing allocated since should have brought the VM within 3MB of capacity (256 MB).
It is highly unlikely that you have 256MB of heap space for your VM. Depending on your device, it may be as low as 16MB.
Moreover, Android does not have a compacting GC algorithm, and so even though you may have more than 3MB available, you may not have a contiguous 3MB block.
This is why it is important to either recycle() your Bitmap objects or try to reuse them (e.g., inBitmap of BitmapOptions, added in API Level 11).
Also, you can use DDMS to create a heap dump and MAT to inspect it, to determine more precisely where your memory is going and who is holding onto what. This works better on Android 3.0+, as MAT will be able to report on Bitmap memory more accurately in those versions.
Is there only a small percentage of that 256 MB system RAM which is actually given to the VM before it crashes?
Yes. It's called the heap. Android devices have a heap size limit. Typically, it is in the 16-48MB range, depending on Android OS version and screen resolution.
Could it be that the Bitmap loading process has its own memory allocation cap?
No, it works off of the same heap size budget. Starting with Android 3.0, it really loads the memory out of the same heap as the rest of the Dalvik objects use -- previously, it used blocks of system RAM outside of the heap, but the space was counted against the heap's size budget.
but without knowing EXACTLY what triggers Dalvik GC we're still placing an awful lot of faith in the OS and Google's vague discussions of performance best-practices
Life, as they say, goes on.
Can the GC state (e.g. 'about to run', 'running', 'finished running') be tracked from code so that large resource allocations might be planned strategically around available memory? ... I'd like to know if there is a supported API call somewhere which can be relied upon in production code (not just debug) to track the precise state of the garbage collector.
No.
Is GC always system-wide, or can separate threads (such as a dedicated rendering thread for a game) escape the potential performance lag issues caused by GC?
GC is never "system-wide" for any VM. GC is always within a VM.
On newer versions of Android, GC is concurrent and therefore will not materially block any thread in normal circumstances. On older versions of Android, GC is stop-the-world and will affect all threads. The change definitely was in place for Android 3.0 -- my memory is fuzzy on whether the concurrent GC was already in place for Android 2.3 or not. There is a 2011 Google I|O presentation on memory management in Android that you may wish to watch.
Would this crash the VM or is there some way the OS handles such extreme situations automatically to avoid crashing the VM?
Android should force an immediate GC before raising the OutOfMemoryException. This scenario qualifies as not "normal circumstances" per my previous paragaraph.