-android: Optimization of EventLogActivity and message retrieval

This commit is contained in:
Joachim Fritzsch 2013-04-17 15:55:14 +02:00
parent c084536dce
commit 5ae33f9648
7 changed files with 245 additions and 133 deletions

View File

@ -17,51 +17,39 @@
You should have received a copy of the GNU Lesser General Public License
along with BOINC. If not, see <http://www.gnu.org/licenses/>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/msgsRow"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
android:layout_height="match_parent" >
<CheckBox
android:id="@+id/msgs_check"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
android:focusable="false" />
android:focusable="false"
android:layout_margin="5dp" />
<LinearLayout
android:id="@+id/msgsRowVertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView android:textAppearance="?android:attr/textAppearanceSmall"
android:id="@+id/msgs_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="2dp"
android:layout_toRightOf="@+id/msgs_check" />
<TextView android:textAppearance="?android:attr/textAppearanceSmall"
android:id="@+id/msgs_project"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="2dp"
android:layout_below="@+id/msgs_date"
android:layout_alignLeft="@+id/msgs_date"/>
<TextView
android:id="@+id/msgs_message"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_margin="2dp" />
<LinearLayout
android:id="@+id/msgsRowHorizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<TextView android:textAppearance="?android:attr/textAppearanceSmall"
android:id="@+id/msgs_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="2dp" />
<TextView android:textAppearance="?android:attr/textAppearanceSmall"
android:id="@+id/msgs_project"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_margin="2dp" />
</LinearLayout>
</LinearLayout>
<TextView
android:id="@+id/msgs_message"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/msgs_project"
android:layout_alignLeft="@+id/msgs_project" />
</LinearLayout>
</RelativeLayout>

View File

@ -72,7 +72,7 @@ public class BOINCActivity extends TabActivity {
private BroadcastReceiver mClientStatusChangeRec = new BroadcastReceiver() {
@Override
public void onReceive(Context context,Intent intent) {
Log.d(TAG, "ClientStatusChange - onReceive()");
//Log.d(TAG, "ClientStatusChange - onReceive()");
determineStatus();
}
@ -142,10 +142,10 @@ public class BOINCActivity extends TabActivity {
try {
if(mIsBound) {
newStatus = Monitor.getClientStatus().setupStatus;
Log.d(TAG,"determineStatus() old clientSetupStatus: " + clientSetupStatus + " - newStatus: " + newStatus);
if(newStatus != clientSetupStatus) { //only act, when status actually different form old status
Log.d(TAG,"determineStatus() client setup status changed! old clientSetupStatus: " + clientSetupStatus + " - new: " + newStatus);
clientSetupStatus = newStatus;
layout();
layout();
}
if(intialStart && (clientSetupStatus == ClientStatus.SETUP_STATUS_NOPROJECT)) { // if it is first start and no project attached, show login activity
startActivity(new Intent(this,AttachProjectListActivity.class));

View File

@ -19,20 +19,24 @@
package edu.berkeley.boinc;
import java.util.ArrayList;
import java.util.List;
import java.lang.StringBuffer;
import edu.berkeley.boinc.adapter.EventLogListAdapter;
import edu.berkeley.boinc.client.Monitor;
import edu.berkeley.boinc.rpc.Message;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MenuInflater;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ListView;
import android.widget.TextView;
@ -40,47 +44,26 @@ import android.widget.TextView;
public class EventLogActivity extends FragmentActivity {
private final String TAG = "BOINC EventLogActivity";
private Monitor monitor;
private Boolean mIsBound = false;
private ListView lv;
private EventLogListAdapter listAdapter;
private ArrayList<Message> data = new ArrayList<Message>();
private int lastSeqno = 0;
// Controls whether initialization of view elements of "projects_layout"
// is required. This is the case, every time the layout switched.
private Boolean initialSetupRequired = true;
// BroadcastReceiver event is used to update the UI with updated information from
// the client. This is generally called once a second.
//
private IntentFilter ifcsc = new IntentFilter("edu.berkeley.boinc.clientstatuschange");
private BroadcastReceiver mClientStatusChangeRec = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "ClientStatusChange - onReceive()");
populateLayout();
}
};
// message retrieval
private Integer pastMsgsLoadingRange = 50; // amount messages loaded when end of list is reached
//
// Message Activity
//
@Override
public void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate()");
doBindService();
setLayoutLoading();
super.onCreate(savedInstanceState);
}
@Override
public void onPause() {
Log.d(TAG, "onPause()");
unregisterReceiver(mClientStatusChangeRec);
super.onPause();
}
@Override
public void onResume() {
@ -88,47 +71,87 @@ public class EventLogActivity extends FragmentActivity {
super.onResume();
populateLayout();
registerReceiver(mClientStatusChangeRec, ifcsc);
new RetrieveRecentMsgs().execute(); // refresh messages
}
@Override
protected void onDestroy() {
Log.d(TAG, "onDestroy()");
doUnbindService();
super.onDestroy();
}
private void populateLayout() {
/*
* Service binding part
* only necessary, when function on monitor instance has to be called
*/
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
Log.d(TAG,"onServiceConnected");
monitor = ((Monitor.LocalBinder)service).getService();
mIsBound = true;
initializeLayout();
}
public void onServiceDisconnected(ComponentName className) {
monitor = null;
mIsBound = false;
}
};
private void doBindService() {
if(!mIsBound) {
getApplicationContext().bindService(new Intent(this, Monitor.class), mConnection, 0); //calling within Tab needs getApplicationContext() for bindService to work!
}
}
private void doUnbindService() {
if (mIsBound) {
getApplicationContext().unbindService(mConnection);
mIsBound = false;
}
}
// updates data list with most recent messages
private void loadRecentMsgs(ArrayList<Message> tmpA) {
// Prepend new messages to the event log
try {
// Read messages from state saved in ClientStatus
ArrayList<Message> tmpA = Monitor.getClientStatus().getMessages();
if(tmpA == null) {
int y = 0;
for (int x = tmpA.size()-1; x >= 0; x--) {
data.add(y, tmpA.get(x));
y++;
}
} catch (Exception e) {} //IndexOutOfBoundException
listAdapter.notifyDataSetChanged();
}
// appends older messages to data list
private void loadPastMsgs(List<Message> tmpA) {
// Append old messages to the event log
try {
for(int x = tmpA.size()-1; x >= 0; x--) {
data.add(tmpA.get(x));
}
} catch (Exception e) {} //IndexOutOfBoundException
listAdapter.notifyDataSetChanged();
}
private void initializeLayout() {
try {
// check whether monitor is bound
if(!mIsBound) {
setLayoutLoading();
return;
}
// Switch to a view that can actually display messages
if (initialSetupRequired) {
initialSetupRequired = false;
setContentView(R.layout.eventlog_layout);
lv = (ListView) findViewById(R.id.eventlogList);
listAdapter = new EventLogListAdapter(EventLogActivity.this, lv, R.id.eventlogList, data);
}
// Add new messages to the event log
for (Message msg: tmpA) {
if (msg.seqno > lastSeqno) {
data.add(msg);
lastSeqno = msg.seqno;
}
}
// Force list adapter to refresh
listAdapter.notifyDataSetChanged();
setContentView(R.layout.eventlog_layout);
lv = (ListView) findViewById(R.id.eventlogList);
listAdapter = new EventLogListAdapter(EventLogActivity.this, lv, R.id.eventlogList, data);
lv.setOnScrollListener(new EndlessScrollListener(5));
// initial data retrieval
new RetrievePastMsgs().execute();
} catch (Exception e) {
// data retrieval failed, set layout to loading...
setLayoutLoading();
@ -139,7 +162,6 @@ public class EventLogActivity extends FragmentActivity {
setContentView(R.layout.generic_layout_loading);
TextView loadingHeader = (TextView)findViewById(R.id.loading_header);
loadingHeader.setText(R.string.eventlog_loading);
initialSetupRequired = true;
}
@Override
@ -217,4 +239,94 @@ public class EventLogActivity extends FragmentActivity {
startActivity(Intent.createChooser(emailIntent, "Send mail..."));
}
// onScrollListener for list view, implementing "endless scrolling"
public final class EndlessScrollListener implements OnScrollListener {
private int visibleThreshold = 5;
private int previousTotal = 0;
private boolean loading = true;
public EndlessScrollListener(int visibleThreshold) {
this.visibleThreshold = visibleThreshold;
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (loading) {
if (totalItemCount > previousTotal) {
loading = false;
previousTotal = totalItemCount;
}
}
if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)) {
new RetrievePastMsgs().execute();
loading = true;
}
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
}
private final class RetrieveRecentMsgs extends AsyncTask<Void,Void,ArrayList<Message>> {
private Integer mostRecentSeqNo = 0;
@Override
protected void onPreExecute() {
if(!mIsBound) cancel(true); // cancel execution if monitor is not bound yet
try {
mostRecentSeqNo = data.get(0).seqno;
} catch (Exception e) {} //IndexOutOfBoundException
}
@Override
protected ArrayList<Message> doInBackground(Void... params) {
return monitor.getEventLogMessages(mostRecentSeqNo);
}
@Override
protected void onPostExecute(ArrayList<Message> result) {
// back in UI thread
loadRecentMsgs(result);
}
}
private final class RetrievePastMsgs extends AsyncTask<Void,Void,List<Message>> {
private Integer mostRecentSeqNo = null;
private Integer pastSeqNo = null;
@Override
protected void onPreExecute() {
if(!mIsBound) cancel(true); // cancel execution if monitor is not bound yet
try {
mostRecentSeqNo = data.get(0).seqno;
pastSeqNo = data.get(data.size()-1).seqno;
Log.d("RetrievePastMsgs","mostRecentSeqNo: " + mostRecentSeqNo + " ; pastSeqNo: " + pastSeqNo);
if(pastSeqNo==0) {
Log.d("RetrievePastMsgs", "cancel, all past messages are present");
cancel(true); // cancel if all past messages are present
}
} catch (Exception e) {} //IndexOutOfBoundException
}
@Override
protected List<Message> doInBackground(Void... params) {
Integer startIndex = 0;
if(mostRecentSeqNo != null && pastSeqNo != null && mostRecentSeqNo != 0 && pastSeqNo != 0) startIndex = mostRecentSeqNo - pastSeqNo + 1;
Integer lastIndexOfList = 0;
if(mostRecentSeqNo != null) lastIndexOfList = mostRecentSeqNo - 1;
//Log.d("RetrievePastMsgs", "calling monitor with: " + startIndex + lastIndexOfList);
return monitor.getEventLogMessages(startIndex, pastMsgsLoadingRange, lastIndexOfList);
}
@Override
protected void onPostExecute(List<Message> result) {
// back in UI thread
loadPastMsgs(result);
}
}
}

View File

@ -71,8 +71,6 @@ public class PrefsActivity extends FragmentActivity {
/*
* Service binding part
* only necessary, when function on monitor instance has to be called
* currently in Prefs- and DebugActivity
*
*/
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {

View File

@ -117,7 +117,12 @@ public class EventLogListAdapter extends ArrayAdapter<Message> implements OnItem
viewEventLog.cbCheck.setChecked(listView.isItemChecked(position));
viewEventLog.tvMessage.setText(getMessage(position));
viewEventLog.tvDate.setText(getDate(position));
viewEventLog.tvProjectName.setText(getProject(position));
if(getProject(position).isEmpty()){
viewEventLog.tvProjectName.setVisibility(View.GONE);
} else {
viewEventLog.tvProjectName.setVisibility(View.VISIBLE);
viewEventLog.tvProjectName.setText(getProject(position));
}
return vi;
}

View File

@ -27,7 +27,6 @@ import android.os.PowerManager.WakeLock;
import android.util.Log;
import edu.berkeley.boinc.rpc.CcStatus;
import edu.berkeley.boinc.rpc.GlobalPreferences;
import edu.berkeley.boinc.rpc.Message;
import edu.berkeley.boinc.rpc.Project;
import edu.berkeley.boinc.rpc.Result;
import edu.berkeley.boinc.rpc.Transfer;
@ -51,7 +50,6 @@ public class ClientStatus {
private ArrayList<Project> projects;
private ArrayList<Transfer> transfers;
private GlobalPreferences prefs;
private ArrayList<Message> messages; //debug tab
// setup status
public Integer setupStatus = 0;
@ -130,12 +128,11 @@ public class ClientStatus {
/*
* called frequently by Monitor to set the RPC data. These objects are used to determine the client status and parse it in the data model of this class.
*/
public synchronized void setClientStatus(CcStatus status,ArrayList<Result> results,ArrayList<Project> projects, ArrayList<Transfer> transfers, ArrayList<Message> msgs) {
public synchronized void setClientStatus(CcStatus status,ArrayList<Result> results,ArrayList<Project> projects, ArrayList<Transfer> transfers) {
this.status = status;
this.results = results;
this.projects = projects;
this.transfers = transfers;
this.messages = msgs;
parseClientStatus();
Log.d(TAG,"parsing results: computing: " + computingParseError + computingStatus + computingSuspendReason + " - network: " + networkParseError + networkStatus + networkSuspendReason);
if(!computingParseError && !networkParseError && !setupStatusParseError) {
@ -203,23 +200,6 @@ public class ClientStatus {
return projects;
}
//Debug Tab
public synchronized ArrayList<Message> getMessages() {
if(messages == null) { //check in case monitor is not set up yet (e.g. while logging in)
Log.d(TAG, "messages is null");
return null;
}
return messages;
}
public synchronized void resetMessages() {
if(messages == null) { //check in case monitor is not set up yet (e.g. while logging in)
Log.d(TAG, "messages is null");
return;
}
messages.clear();
}
/*
* parses RPC data to ClientStatus data model.
*/

View File

@ -32,6 +32,8 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Intent;
@ -851,6 +853,40 @@ public class Monitor extends Service {
return auth;
}
// returns client messages to be shown in EventLog tab.
// start is counting from beginning, e.g. start=0 number=50
// needs lastIndexOfList as reference for consistency
// returns 50 most recent messages
public List<Message> getEventLogMessages(int startIndex, int number, int lastIndexOfList) {
//Log.d(TAG,"getEventLogMessage from start: " + startIndex + " amount: " + number + "lastIndexOfList: " + lastIndexOfList);
Integer requestedLastIndex = startIndex + number;
if(lastIndexOfList == 0) { // list is empty, initial start
lastIndexOfList = rpc.getMessageCount() - 1; // possible lastIndexOfList if all messages were read
//Log.d(TAG,"list ist empty, initial start, read message count: " + (lastIndexOfList+1));
}
if(requestedLastIndex > lastIndexOfList + 1) { // requesting more messages than actually present
number = lastIndexOfList - startIndex + 1;
//Log.d(TAG,"getEventLogMessage requesting more messages than left, number changed to " + number);
}
Integer param = lastIndexOfList + 1 - startIndex - number;
//Log.d(TAG,"getEventLogMessage calling RPC with: " + param);
try {
ArrayList<Message> tmpL = rpc.getMessages(param);
List<Message> msgs = tmpL.subList(0, tmpL.size() - startIndex); // tmp.size - start is amount of actually new values. Usually equals number, except for end of list
//Log.d(TAG,"getEventLogMessages returning " + msgs.size() + " messages with oldest element seq no: " + msgs.get(0).seqno + " and recent seq no: " + msgs.get(msgs.size()-1).seqno);
return msgs;
} catch (Exception e) {
//Log.w(TAG,"error in retrieving sublist", e);
return null;
}
}
// returns client messages that are more recent than given seqNo
public ArrayList<Message> getEventLogMessages(int seqNo) {
//Log.d(TAG, "getEventLogMessage more recent than seqNo: " + seqNo);
return rpc.getMessages(seqNo);
}
private final class ClientMonitorAsync extends AsyncTask<Integer, String, Boolean> {
private final String TAG = "BOINC ClientMonitorAsync";
@ -884,16 +920,9 @@ public class Monitor extends Service {
ArrayList<Project> projects = rpc.getProjectStatus();
if(showRpcCommands) Log.d(TAG, "getTransers");
ArrayList<Transfer> transfers = rpc.getFileTransfers();
ArrayList<Message> msgs = new ArrayList<Message>();
// retrieve messages only, if tabs are actually enabled. very resource intense with logging on emulator!
if(getResources().getBoolean(R.bool.tab_eventlog)) {
Integer count = rpc.getMessageCount();
msgs = rpc.getMessages(count - 250); //get the most recent 250 messages
if(showRpcCommands) Log.d(TAG, "getMessages, count: " + count);
}
if( (status != null) && (results != null) && (projects != null) && (transfers != null)) {
Monitor.getClientStatus().setClientStatus(status, results, projects, transfers, msgs);
Monitor.getClientStatus().setClientStatus(status, results, projects, transfers);
} else {
Log.d(TAG, "client status connection problem");
}