I have a chronometer in my list view. The problem is that sometimes it gets leaked i.e. i can see the OnChronometerTickListener executing every second even after i have navigated to another tab or scrolled the list view item out of view or even pressed the Home button. It seems a waste of resources, since i need it to run only when it is actually visible.
Is there a way to avoid this behaviour? Note that it happens only sometimes.
public class TimerLayout extends LinearLayout {
private static final String LOG_TAG = "TimerLayout";
Button btn_endTimer;
Button btn_cancelTimer;
Chronometer cmt_timer;
Runnable updateTimerThread;
Handler handler;
public TimerLayout(Context context, AttributeSet attrs) {
super(context,attrs);
setOrientation(LinearLayout.VERTICAL);
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.timer, this, true);
cmt_timer = (Chronometer) getChildAt(0);
btn_endTimer = (Button) ((ViewGroup) getChildAt(1)).getChildAt(0);
btn_cancelTimer = (Button) ((ViewGroup) getChildAt(1)).getChildAt(1);
btn_endTimer.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View view){
cmt_timer.stop();
}
});
btn_cancelTimer.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View view){
cmt_timer.stop();
}
});
cmt_timer.setOnChronometerTickListener(new OnChronometerTickListener() {
#Override
public void onChronometerTick(Chronometer arg0) {
if(BuildConfig.DEBUG){Log.d(LOG_TAG,"onChronometerTick.objectid=" + System.identityHashCode(TimerLayout.this));}
}
});
}
public void init(Date startTime){
cmt_timer.stop();
if(startTime!=null){
Date now = new Date();
long elapsedTime = now.getTime() - startTime.getTime();
cmt_timer.setBase(SystemClock.elapsedRealtime() - elapsedTime);
cmt_timer.start();
}
}
}
I call the init method of this class in the bindView() method of my cursorAdaptor to start it.
The trick is to stop the chronometers in the onPause() of the fragment/activity.
So i create a class to hold the chronometers :
public class ChronometerHolder {
private WeakHashMap<Date, Chronometer> chronometerMap;
private static final String LOG_TAG = "ChronometerHolder";
public ChronometerHolder() {
chronometerMap = new WeakHashMap<Date, Chronometer>();
}
public void add(Date dt_startTime, Chronometer chronometer){
chronometerMap.put(dt_startTime, chronometer);
}
public void remove(Date dt_startTime){
chronometerMap.remove(dt_startTime);
}
public int getCount(){
return chronometerMap.size();
}
public void startAll() {
// start any chronometers that were paused
if (chronometerMap.size() > 0) {
Set<Entry<Date, Chronometer>> set = chronometerMap.entrySet();
Iterator<Entry<Date, Chronometer>> iterator = set.iterator();
Entry<Date, Chronometer> entry;
while (iterator.hasNext()) {
entry = (Entry<Date, Chronometer>) iterator.next();
entry.getValue().start();
}
}
}
}
public void stopAll() {
// stop any chronometers that might be running
if (chronometerMap.size() > 0) {
Set<Entry<Date, Chronometer>> set = chronometerMap.entrySet();
Iterator<Entry<Date, Chronometer>> iterator = set.iterator();
Entry<Date, Chronometer> entry;
while (iterator.hasNext()) {
entry = (Entry<Date, Chronometer>) iterator.next();
entry.getValue().stop();
}
}
}
}
Then i make the below changes :
Return the chronometer object from init() :
public Chronometer init(Date startTime){
Chronometer obj = null;
cmt_timer.stop();
if(startTime!=null){
Date now = new Date();
long elapsedTime = now.getTime() - startTime.getTime();
cmt_timer.setBase(SystemClock.elapsedRealtime() - elapsedTime);
cmt_timer.start();
obj = cmt_timer;
}
return obj;
}
In the fragment, instantiate the holder class :
ChronometerHolder chronometerHolder = new ChronometerHolder();
Every time you initialize the chrononmeter( in the bindView() of the CursorAdapter), add it to the holder :
Chronometer tmpChronometer = viewHolder.myTimer.init(dt_hitSessionStartTime);
if(tmpChronometer != null){
chronometerHolder.add(dt_hitSessionStartTime, tmpChronometer);
}
In onPause(), stop all the chronometers :
chronometerHolder.stopAll();
In onResume(), start all the chronometers :
chronometerHolder.startAll();
When the you press the Home button instead of exiting the app, and then open the app again, the bindView() calls are not executed. That means the chronometers are in a stopped state. So it has to be started in onResume() as done in #5.
Related
I have an adapter used to display messages on the list view alike messages in chat application . I am able to display the content flawlessly once the activity is created , but when I go back and create activity again , adapter don't work as usual .
What I found in debugging is follows:
function receives() is called when message is received and update the
register , as I mentioned above there is no problem to display the
data in list view once the activity is created , but once I go back
and relauch the activity I am not able to display received messages .
Is there something I am missing in onResume() onPause or onStart() method with respect to custom adapter such as registering or decalring the custom adapter again? Thanks for help.
Following is the code of my activity class which uses custom adapter to display sent and received messages:
public class hotListener extends ListActivity {
private XMPPConnection connection;
private IBinder binder;
private Handler mHandler = new Handler();
private ArrayList<String> messages = new ArrayList<String>();
ArrayList<ChatMessage> messagex= new ArrayList<ChatMessage>();;
ChattingAdapter adaptex;
Intent mIntent ;
private ListView listview;
EditText sender_message ;
String msg;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.listener);
//messagex.add(new ChatMessage("Hello", false));
adaptex = new ChattingAdapter(getApplicationContext(),messagex);
setListAdapter(adaptex);
Button send_button = (Button) findViewById(R.id.chat_send_message);
sender_message = (EditText) findViewById(R.id.chat_input);
send_button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
msg = sender_message.getText().toString();
sender_message.setText("");
if(!(msg.length()==0)){
messagex.add(new ChatMessage(msg, true));
//addNewMessage(new ChatMessage(msg, true));
adaptex.notifyDataSetChanged();
getListView().setSelection(messagex.size()-1);
}
}
});
if(!isMyServiceRunning()){
System.out.println("seems like service not running");
startService(new Intent(this,xService.class));
System.out.print(" now started ");
}
}
#Override
protected void onStart(){
super.onStart();
Boolean kuch = bindService(new Intent(this,xService.class), mConnection,Context.BIND_AUTO_CREATE);
//System.out.println(kuch);
System.out.println("bind done");
}
private void receives(XMPPConnection connection2) {
//ChatManager chatmanager = connection.getChatManager();
connection2.getChatManager().addChatListener(new ChatManagerListener() {
#Override
public void chatCreated(Chat arg0, boolean arg1) {
arg0.addMessageListener(new MessageListener() {
#Override
public void processMessage(Chat chat, Message message) {
final String from = message.getFrom();
final String body = message.getBody();
mHandler.post(new Runnable() {
ChatMessage kudi = new ChatMessage(body, false);
#Override
public void run() {
messagex.add(kudi);
adaptex.notifyDataSetChanged();
getListView().setSelection(messagex.size()-1);
Toast.makeText(hotListener.this,body,Toast.LENGTH_SHORT).show(); }
});
}
});
}
});
}
private boolean isMyServiceRunning() {
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
for(RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)){
if(xService.class.getName().equals(service.service.getClassName())){
return true;
}
}
//System.out.print("false");
return false;
}
#Override
protected void onResume() {
bindService(new Intent(this, xService.class), mConnection, Context.BIND_AUTO_CREATE);
super.onResume();
}
#Override
protected void onPause() {
unbindService(mConnection);
super.onPause();
}
private ServiceConnection mConnection = new ServiceConnection() {
#Override
public void onServiceDisconnected(ComponentName name) {
connection = null;
service = null;
}
#Override
public void onServiceConnected(ComponentName name, IBinder binder) {
//System.out.println("binding in hot listener");
service = ((xService.MyBinder)binder).getService();
connection = service.getConnection();
receives(connection);
Log.wtf("Service","connected");
}
};
void addNewMessage(ChatMessage m)
{
System.out.println("1");
messagex.add(m);
System.out.println("2");
adaptex.notifyDataSetChanged();
System.out.println("3");
getListView().setSelection(messagex.size()-1);
}
}
Here is my custom adapter (there is no problem in custom adapter but adding to make things clear) :
public class ChattingAdapter extends BaseAdapter{
private Context mContext;
private ArrayList<ChatMessage> mMessages;
public ChattingAdapter(Context context, ArrayList<ChatMessage> messages) {
super();
this.mContext = context;
this.mMessages = messages;
}
#Override
public int getCount() {
return mMessages.size();
}
#Override
public Object getItem(int position) {
return mMessages.get(position);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ChatMessage message = (ChatMessage) this.getItem(position);
ViewHolder holder;
if(convertView == null)
{
holder = new ViewHolder();
convertView = LayoutInflater.from(mContext).inflate(R.layout.listitem, parent, false);
holder.message = (TextView) convertView.findViewById(R.id.text1);
convertView.setTag(holder);
}
else
holder = (ViewHolder) convertView.getTag();
holder.message.setText(message.getMessage());
LayoutParams lp = (LayoutParams) holder.message.getLayoutParams();
//Check whether message is mine to show green background and align to right
if(message.isMine())
{ holder.message.setBackgroundResource(R.drawable.msgbox_new_selected_go_up);
lp.gravity = Gravity.RIGHT;
}
//If not mine then it is from sender to show orange background and align to left
else
{
holder.message.setBackgroundResource(R.drawable.msgbox_other_go_up);
lp.gravity = Gravity.LEFT;
}
holder.message.setLayoutParams(lp);
//holder.message.setTextColor(R.color.textColor);
return convertView;
}
private static class ViewHolder
{
TextView message;
}
#Override
public long getItemId(int position) {
//Unimplemented, because we aren't using Sqlite.
return position;
}
}
p.s: I am not storing any messages in sqlite as I dont want to restore messages for now, but I want new messages to be displayed at least onresume of activty. I can display sent messages after pressing send button but no received messages which works fine for the first time activity is created.
EDIT: I did more debugging , it turns out problem is not in resume activity , if I dont use receives() function for first time , and resume activity after going back , then receives() will work , that means , function inside receives() : getListView().setSelection(messagex.size()-1); works only once .
Either first time on receiving message or next time if and only if its not called first time on activity .
I think problem lies when you try to resume activity , you are still running the previous mHandler running and thus your instance of message is not destroyed and when you resume your activity it creates a problem . Make sure your mhandler destroys all instance of objects when unstop is called.
There's no place in your code where you save your messagex ArrayList. When you quit your activity by hitting back, your array get's distroyed (Garbage Collection takes care of it).
When you relaunch your activity your messagex ArrayList is created again, it's a brand new variable.
In fact, you're not relaunching your activity, you're creating a new instance.
EDIT:
I've never worked with the XMPPConnection objects before, but something else worth trying is the following:
When binding to the service, you're calling connection2.getChatManager().addChatListener and also arg0.addMessageListener but when unbinding you're not calling any removeXXX methods. I could be that since you're not removing your listeners, the whole XMPPConnection object still have references to the listeners that live in a dead Activity, and they are not being garbage collected.
I created an AlertDialog. And I need to put a timer there somehow.
Timer must show time from 90 seconds to 0 seconds.
Does someone know how to make that inscription in textView("90 sseconds to acceptance...") change every second with different text? ("90 sseconds to acceptance..." -> "89 sseconds to acceptance..." -> etc..)
Does this work for you?
/*
new CountDownUpdate((TextView)findViewById(R.id.accept_text), 90,
new CountDownUpdate.Callback(){
#Override
public void onCountDownComplete(TextView textView)
{
Toast.makeText(getApplicationContext(), "BOOM!", Toast.LENGTH_LONG).show();
}
});
*/
private static class CountDownUpdate implements Runnable
{
private Callback mCallback;
private int mFrom;
private TextView mView;
public CountDownUpdate(TextView view, int from, Callback callback)
{
mCallback = callback;
mFrom = from;
mView = view;
mView.post(this);
}
#Override
public void run()
{
mView.setText(mFrom + " seconds to acceptance...");
if(mFrom-- == 0){
if(mCallback != null){
mCallback.onCountDownComplete(mView);
}
}
else{
mView.postDelayed(this, 1000);
}
}
public static interface Callback
{
public void onCountDownComplete(TextView textView);
}
}
EDIT: I found out that the Activity is saving the instance, but the Fragments saved data is not making it up to the Activity savedInstanceState. I'm saving the current time in the outState, but its not making its way all the way up, as the activity has nothing in its SavedInstanceState for the time and returns 'null' for the time if I print it to the logcat....
I am building an application that has the a countup and countdown timer built in. The basic hosting activity for the timers is a FragmentActivity which hosts a FragementPagerAdapter that inflates two fragments within the code (I do not give an id to the fragments within the XML as they are not defined as fragments within the .xml). Everything works great until an orientation change and then the activity looks like it looses contact with the old fragments and just chooses to create new ones. This means that any current countdown is lost and any time chosen is also lost upon configuration change. I will need to keep the count going (if its started) and any numbers currently displayed....
I know that the Adapter is supposed to handle these things on its own, and I'm setting the SetRetainInstance(true) in both the OnCreate and OnCreateView.
I put in hooks into the Fragment code to let me know whenever the saveInstanceState is NOT null so at least I know what is going on, but it seems like the instance is always NULL, and it creates from scratch...always.
Some other solutions have me overriding the instantiateItem, but it seems that its is only used for getting callbacks reset, others changing a setting in the Manifest(frowned upon).... Other solutions look to me like I have things setup right...but obviously, I'm messing something up along the way. I'm only giving code for the FragmentActivity, FragementPagerAdapter, and a bit of the Fragment code as I don't want to spam the post with code that may not be the issue.
The FragmentActivity
public class Timer_Main extends FragmentActivity {
ViewPager pager;
Timer_Pager mAdapter;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.timer_pager);
mAdapter = new Timer_Pager(this, getSupportFragmentManager());
pager = (ViewPager) findViewById(R.id.timer_pager_display);
pager.setAdapter(mAdapter);
}
public static String getTitle(Context ctxt, int position) {
// TODO Auto-generated method stub
return null;
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("pageItem", pager.getCurrentItem());
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
}
}
The FragementPagerAdapter
public class Timer_Pager extends FragmentPagerAdapter{
Context ctxt = null;
public Timer_Pager(Context ctxt, FragmentManager fm) {
super(fm);
this.ctxt = ctxt;
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0: {
return(Countdown_Fragment.newInstance());
}
case 1:
return(Countup_Fragment.newInstance());
}
return null;
}
#Override
public String getPageTitle(int position) {
switch (position) {
case 0:
return(String.format(ctxt.getString(R.string.Countdown_label)));
case 1:
return(String.format(ctxt.getString(R.string.Countup_label)));
}
return null;
}
#Override
public int getCount() {
// doing only three pages, right now...one for countdown, one for countup, and one for Tabata.
return 2;
}
private static String makeFragmentName(int viewId, int position)
{
return "android:switcher:" + viewId + ":" + position;
}
}
The Fragment has a bit more code in it, so I'll push in just what should be the core of the problem....if more is needed, I'll splice it in.
public class Countdown_Fragment extends Fragment implements OnClickListener {
Calendar CountdownTime = Calendar.getInstance();
static AutoResizeTextView tv;
static ImageButton HoursUp;
static ImageButton HoursDown;
static ImageButton MinutesUp;
static ImageButton MinutesDown;
static ImageButton SecondsUp;
static ImageButton SecondsDown;
static ImageButton Reset;
static ImageButton StartPausecount;
static ImageButton Stopcount;
static Boolean Arewecounting = false;
static Boolean Arewecountingdown = false;
static AutoResizeTextView ThreetwooneGO;
static MyCountdownTimer Countdown_Timer_Activity;
ThreetwooneCount Start_countdown;
static Long MillstoPass;
static Countdown_Fragment newInstance() {
Countdown_Fragment frag = new Countdown_Fragment();
return (frag);
}
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putString("CurrentTimer", (String) tv.getText());
Log.v("status", "saved fragment state" + tv.getText());
super.onSaveInstanceState(outState);
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
}
#Override
public void onCreate(Bundle savedInstanceState) {
setRetainInstance(true);
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
setRetainInstance(true);
View result = inflater.inflate(R.layout.countdown_timer_layout,
container, false);
tv = (AutoResizeTextView) result.findViewById(R.id.timer_textview);
tv.setOnClickListener(new View.OnClickListener() {
public void onClick(View arg0) {
new TimePickerDialog(getActivity(), t, 0, 0, true).show();
}
});
if (savedInstanceState != null) {
Log.v("status", "fragment is NOT empty");
tv.setText(savedInstanceState.getString("CurrentTimer",
tv.toString()));
} else {
Log.v("status", "fragment is empty");
// tv.setText("00:00:00");
}
tv.resizeText();
ThreetwooneGO = (AutoResizeTextView) result
.findViewById(R.id.timer_countdown_text);
HoursUp = (ImageButton) result.findViewById(R.id.hours_up);
HoursDown = (ImageButton) result.findViewById(R.id.hours_down);
MinutesUp = (ImageButton) result.findViewById(R.id.minutes_up);
MinutesDown = (ImageButton) result.findViewById(R.id.minutes_down);
SecondsUp = (ImageButton) result.findViewById(R.id.seconds_up);
SecondsDown = (ImageButton) result.findViewById(R.id.seconds_down);
Reset = (ImageButton) result.findViewById(R.id.reset);
StartPausecount = (ImageButton) result
.findViewById(R.id.startpausecount);
Stopcount = (ImageButton) result.findViewById(R.id.stopcount);
HoursUp.setOnClickListener(this);
HoursDown.setOnClickListener(this);
SecondsUp.setOnClickListener(this);
SecondsDown.setOnClickListener(this);
MinutesUp.setOnClickListener(this);
MinutesDown.setOnClickListener(this);
Reset.setOnClickListener(this);
StartPausecount.setOnClickListener(this);
Stopcount.setOnClickListener(this);
return (result);
}
public void chooseTime(View v) {
new TimePickerDialog(getActivity(), t, 0, 0, true).show();
}
TimePickerDialog.OnTimeSetListener t = new TimePickerDialog.OnTimeSetListener() {
public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
CountdownTime.set(Calendar.HOUR_OF_DAY, hourOfDay);
CountdownTime.set(Calendar.MINUTE, minute);
CountdownTime.set(Calendar.SECOND, 0);
CountdownTime.set(Calendar.MILLISECOND, 0);
updateLabel();
}
};
private void updateLabel() {
String entiretime;
entiretime = String.format("%tT", CountdownTime);
tv.setText(entiretime);
}
Here is my timer that I'm using for Countdown....
public class MyCountdownTimer {
private long millisInFuture;
private long countDownInterval;
Countdown_Fragment ctxt;
AutoResizeTextView Timer_edit;
private volatile boolean IsStopped = false;
public MyCountdownTimer(long pMillisInFuture, long pCountDownInterval,
AutoResizeTextView Artv) {
this.millisInFuture = pMillisInFuture;
this.countDownInterval = pCountDownInterval;
Timer_edit = Artv;
}
public void Start() {
final Handler handler = new Handler();
final Runnable counter = new Runnable() {
public void run() {
if (IsStopped == false) {
if (millisInFuture <= 0) {
Countdown_Fragment.done_counting();
} else {
long sec = millisInFuture / 1000;
Timer_edit.setText(String.format("%02d:%02d:%02d",
sec / 3600, (sec % 3600) / 60, (sec % 60)));
millisInFuture -= countDownInterval;
handler.postDelayed(this, countDownInterval);
}
}
}
};
handler.postDelayed(counter, countDownInterval);
}
public void Cancel() {
IsStopped = true;
Timer_edit = null;
Log.v("status", "Main Timer cancelled");
// this.ctxt = null;
}
public long whatisourcount(){
return(millisInFuture);
}
}
As it turns out, the RetainInstance was causing a conflict between itself and the ViewPager/Adapter/FragmentManager doing their things . Removing it caused the Pager to properly rebuilt the Fragment, including the TextView I had, where it did not before. I also start to recieve a Bundle in the OnCreateView, where that was always null before with the RetainInstance set to True.
I had to remove the RetainInstance and utilize the OnSaveInstanceState and OnCreateView to pass in the current status of the Fragment before it was destroyed, and then re-create it in OnCreateView to reset the Fragment to its state before it was destroyed.
I was hoping that the Runnable that I was using to do the countdown would survive, or I would be able to reattach it, but I couldn't find a way. I had to save the current count in Milliseconds, and pass back to the Fragment to continue where it left off. Its not that big of a deal, but I am curious to see if you can truely re-attach all those things. The Runnable DOES still continue after the config change, but it doesn't update anything on the UI anymore, so I try to cancel the callbacks and null it when I'm inside OnSaveInstanceState.
I'm also reading items where I only need to use RetainInstance for items that have a AsyncTask attached or another similar item....otherwise, just rebuild it within the code.
I have an activity that contains a View Pager that has an adapter FragmentStatePagerAdapter.
each time enter the activity it will take up 200mb of memory, after going back out of the activity(finish()) and then re entering it it will append and double the memory used on the phone.
After troubleshooting the problem it seems as if the fragment manager is not releasing the fragments although im trying to remove them but its just not working.
I tried emptying the fragment that is being added to make sure its not something internal inside the fragment the the problem remains.
my adapter code is
private class ChildrenPagerAdapter extends FragmentStatePagerAdapter
{
private List<ChildBean> childrenBean;
public ChildrenPagerAdapter(FragmentManager fm, List<ChildBean> bean)
{
super(fm);
this.childrenBean = bean;
}
#Override
public int getItemPosition(Object object)
{
return PagerAdapter.POSITION_NONE;
}
#Override
public Fragment getItem(int position)
{
ReportFragment reportFragment = new ReportFragment();
reportFragment.childBean = childrenBean.get(position);
reportFragment.position = position;
reportFragment.mPager = mPager;
if(position == 0)
{
reportFragment.mostLeft = true;
}
if(position == childrenNumber - 1)
{
reportFragment.mostRight = true;
}
return reportFragment;
}
#Override
public int getCount()
{
return childrenNumber;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object)
{
// TODO Auto-generated method stub
super.destroyItem(container, position, object);
}
}
my activity code is
public class ReportActivity extends CustomActivity
{
public ImageLoader imageLoader;
private ViewPager mPager;
private PagerAdapter mPagerAdapter;
private int childrenNumber;
private int currentChild;
#Override
protected void onDestroy()
{
mPager.removeAllViews();
mPager.removeAllViewsInLayout();
mPager.destroyDrawingCache();
mPagerAdapter = null;
mPager = null;
System.gc();
super.onDestroy();
}
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setCustomTitle(string.title_activity_reports);
this.currentChild = getIntent().getIntExtra("itemselected", -1);
getSupportFragmentManager().
}
#Override
protected void onResume()
{
super.onResume();
mPager = (ViewPager) findViewById(R.id.vpchildren);
mPager.setOffscreenPageLimit(6);
childrenNumber = MainActivity.bean.size();
mPagerAdapter = new ChildrenPagerAdapter(getSupportFragmentManager(), MainActivity.bean);
mPager.setAdapter(mPagerAdapter);
mPager.setCurrentItem(currentChild);
}
}
Fragment code :
public class ReportFragment extends Fragment
{
public ChildBean childBean;
public int position;
public ImageView img;
public ImageLoader imageLoader;
public DisplayImageOptions options;
private int pee = 0;
private int poop = 0;
private double sleep = 0.0;
public ViewPager mPager;
public boolean mostLeft = false;
public boolean mostRight = false;
public ReportFragment()
{
}
#Override
public void onDestroyView()
{
super.onDestroyView();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.report_fragment, container, false);
if(mostLeft)
{
rootView.findViewById(id.btnleft).setVisibility(View.GONE);
}
if(mostRight)
{
rootView.findViewById(id.btnright).setVisibility(View.GONE);
}
rootView.findViewById(id.btnleft).setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v)
{
mPager.setCurrentItem(mPager.getCurrentItem() - 1);
}
});
rootView.findViewById(id.btnright).setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v)
{
mPager.setCurrentItem(mPager.getCurrentItem() + 1);
}
});
SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH);
Date dobchild = new Date();
((TextView) rootView.findViewById(id.tvday)).setText(sdf.format(dobchild));
ImageView childimg = (ImageView) rootView.findViewById(id.img_child);
((TextView) rootView.findViewById(id.tvchildname)).setText(childBean.childname);
((TextView) rootView.findViewById(id.tvclassname)).setText(((CustomApplication) getActivity().getApplication()).preferenceAccess.getCurrentClassName());
Date dob = null;
String age = "";
try
{
dob = sdf.parse(childBean.childdob);
age = GeneralUtils.getAge(dob.getTime(), getString(string.tv_day), getString(string.tv_month), getString(string.tv_year));
}
catch(ParseException e)
{
// TODO:
}
((CustomTextView) rootView.findViewById(id.tvchildage)).setText(age);
DisplayImageOptions options =
new DisplayImageOptions.Builder().showImageForEmptyUri(drawable.noimage).showImageOnFail(drawable.noimage).showStubImage(drawable.noimage).cacheInMemory()
.imageScaleType(ImageScaleType.NONE).build();
imageLoader = ImageLoader.getInstance();
imageLoader.displayImage(childBean.childphoto, childimg, options);
final TextView tvpee = (TextView) rootView.findViewById(id.tvpeetime);
final TextView tvpoop = (TextView) rootView.findViewById(id.tvpootimes);
final TextView tvsleep = (TextView) rootView.findViewById(id.tvsleeptime);
rootView.findViewById(id.btnaddpee).setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v)
{
pee = pee + 1;
if(pee > 9)
{
Toast.makeText(getActivity(), getString(string.tvareyousurepee), Toast.LENGTH_LONG).show();
}
tvpee.setText(String.format(getString(string.tvtimes), pee));
}
});
rootView.findViewById(id.btnminuspee).setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v)
{
if(pee > 0)
{
pee = pee - 1;
tvpee.setText(String.format(getString(string.tvtimes), pee));
}
}
});
rootView.findViewById(id.btnpluspoo).setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v)
{
poop = poop + 1;
if(poop > 9)
{
Toast.makeText(getActivity(), getString(string.tvareyousurepoop), Toast.LENGTH_LONG).show();
}
tvpoop.setText(String.format(getString(string.tvtimes), poop));
}
});
rootView.findViewById(id.btnminuspoo).setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v)
{
if(poop > 0)
{
poop = poop - 1;
tvpoop.setText(String.format(getString(string.tvtimes), poop));
}
}
});
rootView.findViewById(id.btnaddsleep).setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v)
{
sleep = sleep + 0.25;
tvsleep.setText(String.format(getString(string.tvhours), sleep));
}
});
rootView.findViewById(id.btnminussleep).setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v)
{
if(sleep > 0)
{
sleep = sleep - 0.25;
tvsleep.setText(String.format(getString(string.tvhours), sleep));
}
}
});
rootView.findViewById(id.btnsave).setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v)
{
Toast.makeText(getActivity(), "Report Saved.", Toast.LENGTH_LONG).show();
getActivity().finish();
}
});
return rootView;
}
}
Please advise... Thanks
ViewPager itself has a method setOffscreenPageLimit which allows you to specify number of pages kept by the adapter. So your fragments that are far away will be destroyed.
First of all looking at your code I don't see you doing any memory releasing measures in your fragments onDestroy(). The fact that fragment itself is destroyed and gc'ed does not mean all resources you allocated were removed too.
For example, my big concern is:
imageLoader = ImageLoader.getInstance();
imageLoader.displayImage(childBean.childphoto, childimg, options);
From what I see here it seems that there is a static instance of ImageLoader that gets poked every time a new fragment appears, but I can't see where a dying fragment would ask ImageLoader to unload its stuff. That looks suspicious to me.
If I were you I would dump an HPROF file of my application the moment it took extra 200mb (as you claim) after activity restart and analyze references via MAT (memory analyzer tool). You are clearly having memory leaks issue and I highly doubt the problem is in Fragments themselves not being destroyed.
In case you don't know how to analyze memory heap, here is a good video. I can't count how many times it helped me identifying and getting rid of memory leaks in my apps.
Don't store 'strong' references to ViewPager or ImageView in your Fragment. You're creating a cyclical reference that will keep everything in memory. Instead, if you must keep a reference to ViewPager or any other element that references its context outside of your Activity, try using a WeakReference, e.g:
private WeakReference<ViewPager> mPagerRef;
...
mPagerRef = new WeakReference<ViewPager>(mPager);
...
final ViewPager pager = mPagerRef.get();
if (pager != null) {
pager.setCurrentItem(...);
}
Following this pattern with Objects that store a reference to the Activity or Application context (hint: any ViewGroup, ImageView, Activity, etc.) should prevent "memory leaks" in the form of "retain cycles" from occurring.
it seems that your code is not destroying the view, check this Destroy item from the ViewPager's adapter might solve this issue.
After using the memory analyzer tool in eclipse i found out that what is sticking in my memory is the actual layout of my fragments.
Relative layout in specific.
The reason for this is a CustomTextView that i created that has a custom font set as a typeface.
Typeface face=Typeface.createFromAsset(context.getAssets(), "Helvetica_Neue.ttf");
this.setTypeface(face);
To solve the memory leak i simply did the following answer found here:
public class FontCache {
private static Hashtable<String, Typeface> fontCache = new Hashtable<String, Typeface>();
public static Typeface get(String name, Context context) {
Typeface tf = fontCache.get(name);
if(tf == null) {
try {
tf = Typeface.createFromAsset(context.getAssets(), name);
}
catch (Exception e) {
return null;
}
fontCache.put(name, tf);
}
return tf;
}
}
I want to create by code an array of objects that are subclasses of Button.
public class MyButton extends Button {
private Context ctx;
private int status;
public MyButton(Context context) {
super(context);
ctx = context;
status = 0;
}
private click() {
status = 1;
// OTHER CODE THAT NEEDS TO STAY HERE
}
}
In the main activity I do this:
public class myActivity extends Activity {
private MyButton[] myButtons = new MyButton[100];
#Override
public onCreate(Bundle si) {
super.onCreate(si);
createButtons();
}
private void createButtons() {
for (int w=0; w<100; w++) {
myButtons[w] = new MyButton(myActivity.this);
myButtons[w].setOnClickListener(new View.onClickListener() {
public void onClick(View v) {
// ... (A)
}
});
}
}
}
Now I want the click() method inside MyButton to be run each time the button is clicked.
Seems obvious but it is not at my eyes.
If I make the click() method public and run it directly from (A), I get an error because myButtons[w].click() is not static and cannot be run from there.
In the meantime, I an not able to understand where to put the code in the MyButton class to intercept a click and run click() from there. Should I override onClick? Or should I override onClickListener? Or what else should I do?
How can I run click() whenever one of myButtons[] object is clicked?
Thanks for the help.
You can cast View v you got in listener to MyButton and call click on it:
private void createButtons() {
View.OnClickListener listener = new View.onClickListener() {
public void onClick(View v) {
((MyButton) v).click();
}
};
for (int w=0; w<100; w++) {
myButtons[w] = new MyButton(myActivity.this);
myButtons[w].setOnClickListener(listener);
}
}
you can add:
View.onClickListener onclick = new View.onClickListener() {
public void onClick(View v) {
((MyButton)v).click();
//since v should be instance of MyButton
}
};
to your Activity
then use:
myButtons[w].setOnClickListener(onclick);
//one instance of onclick is enough, there is no need to create it for every button
in createButtons()
but ... why, oh why array of buttons we have ListView in android ...