When I was debugging my app with AndroidDeviceMonitor, I found that my app's heap was odd. After starting the app (SplashActivity -> MainActivity), the "1-byte array" has been allocated 42MB. I was sure the SplashActivity has been destroyed and I'm using LeakCanary to discover if there is any memory leakage. But I did not find anything.
Then I try to create a new SplashActiviy, just setContentView in onCreate() method without any other code.
The layout xml is like this:
<?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"
android:layout_gravity="center"
android:gravity="center">
<ImageView
android:id="#+id/splash"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY"
android:src="#drawable/splash2"/>
<ImageView
android:id="#+id/logo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:scaleType="fitCenter"
android:src="#drawable/splashlogo"/>
</RelativeLayout>
I debug again and the "1-byte array" is still 42MB. Then I try to remove the src tag from my layout. The "1-byte array" was reduced to 35MB. So, I guess the problem is that image resources have not been recycled. Can anyone tell me more about the detail of the first launched Activity. Why doesn't it release those resources?
What you can do to make sure that imge image is freed up for garbage collection is set the image to null in onStop to make sure that nothing has reference to it.
#Override
public void onStop() {
super.onStop();
mImageView.setImageDrawable(null);
}
You can then use AndroidDeviceMonitor to force a garbage collection to make sure it is freed from the heap.
Related
I have a simple activity named Test1.
This is the layout code.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/test"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<ImageView
android:id="#+id/imageview1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:src="#drawable/load"
android:scaleType="fitXY" />
</RelativeLayout>
In my onDestory method, I release the mImageView resource and in Android profile, the memory of mImageView has really be recycled.
#Override
protected void onDestroy() {
super.onDestroy();
releaseImageViewResource(mImageView);
layout.removeView(mImageView);
mImageView.setVisibility(View.GONE);
mImageView.setImageDrawable(null);
mImageView = null;
}
But when I start other simple activity, the memory of mImageView cannot be recycled. Why and how to solve the problem?
To handle images, I suggest for you to use libraries like Glide or Picasso, they will handle everything for you. (memory leaks, caching, etc)
Not sure what you mean by "start other simple activity", but if you are going to another activity within the application, then your current activity (with the imageview) should be paused instead of destroyed. Either call finish() when you go to the other activity or just put that onDestroy() code in the onPause() method
I have a two variants of activity_main.xml (for portrait/landscape orientation). In this activity, user can choose items and browse detailed information about selected item.
In portraint orientation, fragments added to flFragmentContainer dynamically, after item choosing, details fragment replaces list fragment:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="#+id/flFragmentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"></FrameLayout>
</LinearLayout>
In landscape orientation, fragments described in XML file statically:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<fragment
android:id="#+id/frgTaskList"
android:name="com.exprod.xchecklist.fragments.TaskListFragment"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
tools:layout="#layout/fragment_task_list" />
<fragment
android:id="#+id/frgTaskDetails"
android:name="com.exprod.xchecklist.fragments.TaskDetailsFragment"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
tools:layout="#layout/fragment_task_details">
</fragment>
</LinearLayout>
</LinearLayout>
Here is the code of onCreate() method (of MainActivity).
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager fragmentManager = getSupportFragmentManager();
TaskDetailsFragment taskDetailsFragment = (TaskDetailsFragment)fragmentManager.findFragmentById(R.id.frgTaskDetails);
isDynamicMode = taskDetailsFragment == null || !taskDetailsFragment.isInLayout();
if(isDynamicMode){
FragmentTransaction transaction = fragmentManager.beginTransaction();
Fragment taskListFragment = new TaskListFragment();
if (savedInstanceState == null) {
transaction.add(R.id.flFragmentContainer, taskListFragment, TaskListFragment.FRAGMENT_TAG);
}else{
transaction.replace(R.id.flFragmentContainer, taskListFragment, TaskListFragment.FRAGMENT_TAG);
}
transaction.commit();
}
}
PROBLEM: When I rotate device to landscape orientation, I get twice calls of creation methods in first fragment (TaskListFragment) (onCreateView(), onActivityCreated(), ... ). This indicates that the old fragment remains in activity and recreated on orientation change.
How I can finally destroy old fragment?? I did not find the answer on the Internet.
P.S: Sorry for my bad English...
I see at least two approaches to do what you want.
Handle orientation change yourself:
As described in this document, you can prevent automatic Activity destroy and re-creation and handle orientation change yourself. In this case, you would remove the old Fragment before inflating a new layout into Activity.
Remove the old Fragment after Activity re-creation:
You could also add some logic that determines the orientation as described here (or use your current heuristic that sets isDynamicMode), and manually remove the unnecessary Fragments during onCreate().
The better way:
Although it is possible to do what you want, I can't undertand why would you like to do it this way - you use TaskListFragment in both portrait and landscape configurations, therefore the most logical thing to do is to reuse it. Except for simplifying things, it will also let you keep the state of that Fragment on orientation change (which, I believe, is desirable in your case).
Thanks for Vasiliy answer. I follow this link
and add
android:configChanges="oritentation"
to my manifest file:
<activity android:name=".MyActivity"
android:configChanges="orientation"
android:label="#string/app_name">
and the annoying behavior (fragment not attached to activity) when screen is rotated disappears
Just add to your activity
#Override
public void onSaveInstanceState(Bundle outState) {}
because implementation of this method in Android's Activity that is a parent of all Activities, saved Fragment.
I am using webview to create a basic web browser,
Currently I have one activity and multiple fragments :
-- Browser
-- something else
-- Something else
in Browser Fragment I have the webview :
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
tools:context=".fragments.BrowserFragment">
<android.support.v4.widget.SwipeRefreshLayout
android:id="#+id/swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:id="#+id/webview_holder"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true">
<include
android:id="#+id/button_layout"
layout="#layout/bottom_btn_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"/>
<LinearLayout
android:id="#+id/web_page_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="#id/button_layout"
android:orientation="vertical">
</LinearLayout>
<WebView
android:id="#+id/web_page"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="#id/button_layout"/>
</RelativeLayout>
</android.support.v4.widget.SwipeRefreshLayout>
</LinearLayout>
I am able to save the state of the webview on screen rotation , by using
#Override
public void onSaveInstanceState(Bundle outState) {
if(mWebView != null){
mWebView.saveState(outState);
}
super.onSaveInstanceState(outState);
}
and calling this OnActivityCreated :
mWebView = (WebView)getActivity().findViewById(R.id.web_page);
if (savedInstanceState != null){
mWebView.restoreState(savedInstanceState);
}else{
mWebView.loadUrl(makeUrl(url));
}
This works fine when screen rotates and activity is recreated.
But when I use navigation drawer and replace fragment in the same container and then try to come back to same fragment . The state of webview is lost.
I get the point that onSaveInstanceState() is not called when there is Fragment transaction. So I tried saving the webView state by calling onSaveInstanceState() from OnPause of the fragment. But in that case I get savedInstanceState as null in OnCreateView so I am not able to restore state.
I need to maintain state of multiple webviews and comeBack to them on click of a list.
I also tried saving webview, when onSave() is called in fragment, in a static HashMap in activity and try to assign it to webview of newFragment. But I get Blank screen in that case.
Could someone give me pointers how I can approach this.
I do have a very simple application with just a single CheckBox. I thought that when android does destroy my activity the state of this checkbox will be list. What actually happens is, that the checkbox keeps checked.
The onDestroy() is actually called (on rotation or when I switch to another app -> my emulator has this dev configuration).
So why does this work without using onRestoreInstanceState() and onSaveInstanceState()?
Android Target is 4.2 Jelly Bean.
Here my Activity:
public class CoffeeMixerActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_coffee_mixer);
}
protected void onDestroy() {
super.onDestroy();
Log.d("MyMessage", "Destroy my app");
}
}
Here my Layout file:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context=".CoffeeMixerActivity" >
<CheckBox
android:id="#+id/checkBoxSprinkles"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Sprinkles" />
</RelativeLayout>
Ok.. found the solution by myself:
Android CheckBox -- Restoring State After Screen Rotation
Description:
Every View does have the android:saveEnabled flag on true.. which means that when the View does have an ID the state is automatically saved when the view is freezed...
Here there attribute documentation:
http://developer.android.com/reference/android/R.attr.html#saveEnabled
Hello I'm writing a little Android app (Version 2.3.3). Now i get this strange NullPointer Exception in this very basic code:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mainmenu);
newDeck = (Button) findViewById(R.id.newDeckB);
loadDeck = (Button) findViewById(R.id.loadDeckB);
viewEdition = (Button) findViewById(R.id.viewEditionB);
newDeck.setOnClickListener(this);
loadDeck.setOnClickListener(this);
viewEdition.setOnClickListener(this);
}
Im using this simple layout at the moment in main menu.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<Button android:id="#+id/newDeckB"
android:text="New Deck"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<Button android:id="#+id/loadDeckB"
android:text="Load Deck"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<Button android:id="#+id/viewEditionB"
android:text="View Edition"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<TextView android:id="#+id/currentDeckTextView"
android:text="Default Deck"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
Now my problem is a nullpointexception in line 25, which is the line where i set the first clickListener
newDeck.setOnClickListener(this);
Using the debugger i figured out that the button newDeck is null. I searched a lot in the web but the only answer to such kind of problem was to check that the setContentView is set before the findViewById. This is obviously the case here.
I would be very glad for any kind of advice.
Thx in Before!
Get your views and set the listeners in onPostCreate() method.
There are two events that the App expects, onCreate(), and onStart()
Which one you put this function in, matters.
I had to move "findViewByID" from onCreate() to onStart()
#Override
protected void onStart() {
// use findViewById() here instead of in onCreate()
}