I've been trying to test the performance of Realm with large datasets. It seems to perform really well inserting and with queries that result in moderate resultset sizes but as soon as there is a result set that is large it starts impacting the main thread even though the actual writes are running async. I've written a test activity that shows the issue below. A few notes:
Attendee is a model with a primary key of "name" and an indexed integer field of "age".
The view has a text field showing the count, a button that calls reloadData on click, and an element that is interactive (I used a slider) to see skipped frames when using it.
If you change the query for the list so that it's causes a smaller result set (instead of less then change to equalsTo) then the issue goes away.
Is this a bug or am I doing something wrong?
package com.test.ui;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.test.R;
import com.test.model.Attendee;
import io.realm.OrderedRealmCollection;
import io.realm.Realm;
import io.realm.RealmAsyncTask;
import io.realm.RealmRecyclerViewAdapter;
import io.realm.RealmResults;
public class LoginActivity extends AppCompatActivity {
private static final int RECORD_COUNT = 200000;
private static final int TRANSACTION_SIZE = 1000;
private RealmAsyncTask mTask = null;
private RecyclerView mAttendeeList;
private TextView mCountText;
private Button mButton;
private Handler handler;
private Realm mRealm;
private RealmResults<Attendee> mActualList;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
mRealm = Realm.getDefaultInstance();
mCountText = (TextView) findViewById(R.id.count_text);
mButton = (Button) findViewById(R.id.button);
handler = new Handler(Looper.getMainLooper());
setCountText((int) mRealm.where(Attendee.class).count());
mActualList = mRealm.where(Attendee.class).lessThan("age", 20).findAllAsync();
mRealm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
realm.deleteAll();
setCountText(0);
}
}, new Realm.Transaction.OnSuccess() {
#Override
public void onSuccess() {
mButton.setEnabled(true);
}
}, new Realm.Transaction.OnError() {
#Override
public void onError(Throwable error) {
String text = "Error deleting";
Log.e("ANRTEST", text, error);
Toast.makeText(LoginActivity.this, text, Toast.LENGTH_LONG).show();
}
});
}
private void setCountText(int size) {
mCountText.setText(String.format("Count: %s", size));
}
#Override
protected void onDestroy() {
mActualList = null;
if(mTask != null && !mTask.isCancelled()) {
mTask.cancel();
mTask = null;
}
if(mRealm != null && !mRealm.isClosed()) {
mRealm.close();
}
super.onDestroy();
}
public void loadData(final TimerUtil t) {
Realm.Transaction.OnError onError = new Realm.Transaction.OnError() {
#Override
public void onError(Throwable error) {
mTask = null;
Toast.makeText(LoginActivity.this, "Finished should show now", Toast.LENGTH_LONG).show();
}
};
Realm.Transaction.OnSuccess onSuccess = new Realm.Transaction.OnSuccess() {
#Override
public void onSuccess() {
mTask = null;
int count = (int) mRealm.where(Attendee.class).count();
if (count >= RECORD_COUNT) {
Log.v("ANRTEST", String.format("Finished in %s seconds", t.currentElapsed() / 1000));
mButton.setEnabled(false);
} else {
handler.postDelayed(new Runnable() {
#Override
public void run() {
loadData(t);
}
}, 300);
}
setCountText(count);
}
};
mTask = mRealm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
int innerStart = (int) realm.where(Attendee.class).count();
int i = 0;
while (i < TRANSACTION_SIZE && i + innerStart < RECORD_COUNT) {
Attendee attendee = new Attendee();
int innerValue = innerStart + i;
attendee.name = "name " + (innerValue + 1);
attendee.age = innerValue % 50;
realm.insert(attendee);
i++;
}
Log.v("ANRTEST", String.format("Checkpoint %s (%s seconds)", Math.min(innerStart + i, RECORD_COUNT), t.currentElapsed() / 1000));
}
}, onSuccess, onError);
}
public void reloadData(View view) {
//Setup start of process
mButton.setEnabled(false);
final TimerUtil t = TimerUtil.start();
loadData(t);
}
public static class TimerUtil {
private final long mStartTime;
public TimerUtil(long startTime) {
mStartTime = startTime;
}
public static TimerUtil start() {
return new TimerUtil(System.currentTimeMillis());
}
public long currentElapsed() {
return System.currentTimeMillis() - mStartTime;
}
}
}
Related
I am new to Agora.io and I am creating an android app for 1 to 1 video calling. Everything is working fine in my app - I can join/leave the channel and even see my own local image through my camera. However, in the call, only the audio is being transmitted and video is not being transmitted.
When I debug the app, I can see that the event 'onFirstRemoteVideoFrame' is not being triggered. I tried changing it to 'onFirstRemoteVideoDecoded'(which I saw in many tutorials but android studio says the method is depreciated) but it is still not working.
Also, please note that when I add the line '-keep class io.agora.**{;}' in my proguard-rules.pro file, it says that it can't find the class. So instead I am using '-keep class io.agora.{*;}'.
Below is the java code for my activity. I am using agora 3.1.3.
package com.guideu.helloagora;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.SurfaceView;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.Toast;
import io.agora.rtc.IRtcEngineEventHandler;
import io.agora.rtc.RtcEngine;
import io.agora.rtc.video.VideoCanvas;
import io.agora.rtc.video.VideoEncoderConfiguration;
public class MainActivity extends AppCompatActivity {
private static final String TAG=MainActivity.class.getSimpleName();
private static final int PERMISSION_REQ_ID=22;
private static final String[] REQUESTED_PERMISSIONS={
Manifest.permission.RECORD_AUDIO,
Manifest.permission.CAMERA,
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
private RtcEngine mRtcEngine; // Agora engine reference
private FrameLayout mLocalContainer;
private RelativeLayout mRemoteContainer;
private SurfaceView mLocalView;
private SurfaceView mRemoteView;
private ImageView mCallBtn;
private ImageView mMuteBtn;
private ImageView mSwitchCameraBtn;
private boolean mCallEnd;
private boolean mMuted;
//Agora engine event handler
private final IRtcEngineEventHandler mRTCHandler=new IRtcEngineEventHandler() {
#Override
public void onJoinChannelSuccess(String channel, final int uid, int elapsed) {
super.onJoinChannelSuccess(channel, uid, elapsed);
runOnUiThread(new Runnable() {
#Override
public void run() {
Log.i("agora","Join channel success, uid: " + (uid & 0xFFFFFFFFL));
}
});
}
#Override
public void onUserOffline(final int uid, int reason) {
super.onUserOffline(uid, reason);
runOnUiThread(new Runnable() {
#Override
public void run() {
Log.i("agora","User offline, uid: " + (uid & 0xFFFFFFFFL));
removeRemoteView();
}
});
}
#Override
public void onFirstRemoteVideoFrame(final int uid, int width, int height, int elapsed) {
super.onFirstRemoteVideoFrame(uid, width, height, elapsed);
runOnUiThread(new Runnable() {
#Override
public void run() {
Log.i("agora","First remote video decoded, uid: " + (uid & 0xFFFFFFFFL));
setupRemoteVideo(uid);
}
});
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initUI();
if(checkSelfPermission(REQUESTED_PERMISSIONS[0],PERMISSION_REQ_ID) &&
checkSelfPermission(REQUESTED_PERMISSIONS[1],PERMISSION_REQ_ID) &&
checkSelfPermission(REQUESTED_PERMISSIONS[2],PERMISSION_REQ_ID)){
//init engine
initEngineAndJoinChannel();
}
}
#Override
protected void onDestroy(){
super.onDestroy();
if(!mCallEnd){
leaveChannel();
}
RtcEngine.destroy();
}
private void initUI(){
mLocalContainer=findViewById(R.id.local_video_view_container);
mRemoteContainer=findViewById(R.id.remote_video_view_container);
mCallBtn=findViewById(R.id.btn_call);
mMuteBtn=findViewById(R.id.btn_mute);
mSwitchCameraBtn=findViewById(R.id.btn_switch_camera);
}
private void initEngineAndJoinChannel(){
//initialize engine
initializeEngine();
//setup video config
setupVideoConfig();
//setup local video
setupLocalVideo();
//join channel
joinChannel();
}
private void initializeEngine(){
try {
mRtcEngine = RtcEngine.create(getBaseContext(), getString(R.string.agora_app_id), mRTCHandler);
}
catch (Exception e){
Log.e(TAG,Log.getStackTraceString(e));
throw new RuntimeException("Need to check rtc sdk init fatal error\n" + Log.getStackTraceString(e));
}
}
private void setupVideoConfig(){
mRtcEngine.enableVideo();
mRtcEngine.setVideoEncoderConfiguration(new VideoEncoderConfiguration(
VideoEncoderConfiguration.VD_640x360,
VideoEncoderConfiguration.FRAME_RATE.FRAME_RATE_FPS_15,
VideoEncoderConfiguration.STANDARD_BITRATE,
VideoEncoderConfiguration.ORIENTATION_MODE.ORIENTATION_MODE_FIXED_PORTRAIT
));
}
private void setupLocalVideo(){
mRtcEngine.enableVideo();
mLocalView=RtcEngine.CreateRendererView(getBaseContext());
mLocalView.setZOrderMediaOverlay(true);
mLocalContainer.addView(mLocalView);
VideoCanvas localVideoCanvas=new VideoCanvas(mLocalView,VideoCanvas.RENDER_MODE_HIDDEN,0);
mRtcEngine.setupLocalVideo(localVideoCanvas);
}
private void setupRemoteVideo(int uid){
/*int count=mRemoteContainer.getChildCount();
View view=null;
for(int i=0;i<count;i++){
View v=mRemoteContainer.getChildAt(i);
if(v.getTag() instanceof Integer && ((int) v.getTag())==uid){
view=v;
}
}
if(view!=null){
return;
}*/
mRemoteView=RtcEngine.CreateRendererView(getBaseContext());
mRemoteContainer.addView(mRemoteView);
mRtcEngine.setupRemoteVideo(new VideoCanvas(mRemoteView,VideoCanvas.RENDER_MODE_HIDDEN,uid));
mRemoteView.setTag(uid);
}
private void removeRemoteView(){
if(mRemoteView!=null){
mRemoteContainer.removeView(mRemoteView);
}
mRemoteView=null;
}
private void joinChannel(){
String token=getString(R.string.agora_access_token);
if(TextUtils.isEmpty(token)){
token=null;
}
mRtcEngine.joinChannel(token,"HelloAgora","",0);
}
private void leaveChannel(){
mRtcEngine.leaveChannel();
}
public void onLocalAudioMuteClicked(View view){
mMuted=!mMuted;
mRtcEngine.muteLocalAudioStream(mMuted);
int res=mMuted?R.drawable.btn_mutecall:R.drawable.btn_unmute;
mMuteBtn.setImageResource(res);
}
public void onSwitchCameraClicked(View view){
mRtcEngine.switchCamera();
}
public void onCallClicked(View view){
if(mCallEnd){
startCall();
mCallEnd=false;
mCallBtn.setImageResource(R.drawable.btn_endcall);
}
else{
endCall();
mCallEnd=true;
mCallBtn.setImageResource(R.drawable.btn_startcall);
}
showButtons(!mCallEnd);
}
private void startCall(){
setupLocalVideo();
joinChannel();
}
private void endCall(){
removeLocalVideo();
removeRemoteView();
leaveChannel();
}
private void removeLocalVideo(){
if(mLocalView!=null){
mLocalContainer.removeView(mLocalView);
}
mLocalView=null;
}
private void showButtons(boolean show){
int visibility=show?View.VISIBLE:View.GONE;
mMuteBtn.setVisibility(visibility);
mSwitchCameraBtn.setVisibility(visibility);
}
private boolean checkSelfPermission(String permission, int requestCode){
if(ContextCompat.checkSelfPermission(this,permission)!= PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(this,REQUESTED_PERMISSIONS,requestCode);
Toast.makeText(this,"No Permission",Toast.LENGTH_LONG);
return false;
}
return true;
}
}
'''
You can try "onRemoteVideoStateChanged" callback instead of "onFirstRemoteVideoFrame".
Here is the code snippet:
#Override
public void onRemoteVideoStateChanged(final int uid, int state, int reason, int elapsed) {
super.onRemoteVideoStateChanged(uid, state, reason, elapsed);
if (state == Constants.REMOTE_VIDEO_STATE_STARTING) {
runOnUiThread(new Runnable() {
#Override
public void run() {
setupRemoteVideo(uid);
}
});
}
}
From the logcat i realized that countDownTimer is just skipped in my code, and the firebase listener is not always working correctly.
Any idea what might be wrong?
All i'm trying to do is make my customer wait for employer response for 15 seconds, if he accepted within the time we move on, if not we try with the next employer in the list and so on.
My code:
package com.example.eltobgy.yala.Activities.OrderPackageActivities;
import android.content.Intent;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.os.Handler;
import android.os.Looper;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import com.example.eltobgy.yala.Activities.MainActivity;
import com.example.eltobgy.yala.Models.Package;
import com.example.eltobgy.yala.PackageOnTheWayActivity;
import com.example.eltobgy.yala.R;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.ValueEventListener;
import static com.example.eltobgy.yala.Activities.MainActivity.currentUser;
import static com.example.eltobgy.yala.Activities.MainActivity.mDatabaseUsersReference;
public class WaitingForDeliverymanAcceptanceActivity extends AppCompatActivity {
private static final String LOG_TAG=WaitingForDeliverymanAcceptanceActivity.class.getSimpleName();
private int i = 0;
private String id;
private DatabaseReference acceptedReference;
private String onlineSetPackage = "";
private Package mPackage;
boolean isAccepted;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_watingfor_deliveryman_acceptance);
mPackage = (Package) getIntent().getSerializableExtra("package");
findDeliveryman();
}
private void findDeliveryman() {
Log.e("OrderPackageMapActivity", "entered find delivery man function");
while (i < OrderPackageMapActivity.onlineDeliveryMenList.size()) {
id = OrderPackageMapActivity.onlineDeliveryMenList.get(i).getId();
acceptedReference = MainActivity.mDatabaseOnlineDeliverymenReference.child(id).child("acceptedOrder");
acceptedReference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if ((boolean) dataSnapshot.getValue() == true) {
isAccepted = true;
Log.e(LOG_TAG,"isAccepted is true");
} else {
isAccepted = false;
Log.e(LOG_TAG, "isAccepted is false");
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
onlineSetPackage = OrderPackageMapActivity.onlineDeliveryMenList.get(i).getCurrentPackageID();
DatabaseReference onlineCurrentPackageRef = MainActivity.mDatabaseOnlineDeliverymenReference.child(id).child("currentPackageID");
if ((onlineSetPackage.equals("")) || (onlineSetPackage == null) || (onlineSetPackage.isEmpty())) {
onlineCurrentPackageRef.setValue(packageID);
MainActivity.currentUser.setAttachedDeliveryId(id);
Log.e(LOG_TAG,"package is set to:"+id);
CountDownTimer countDownTimer=new CountDownTimer(15000, 1000) {
public void onTick(long millisUntilFinished) {
//mTextField.setText("seconds remaining: " + millisUntilFinished / 1000);
//here you can have your logic to set text to edittext
Log.e(LOG_TAG,"seconds remaining: " + millisUntilFinished / 1000);
}
public void onFinish() {
checkRequestAcceptance();
}
};
countDownTimer.start();
} else {
i++;
Log.e("i =", String.valueOf(i));
}
}
}
private void checkRequestAcceptance() {
if (isAccepted) {
Log.e(LOG_TAG, "I accepet");
mDatabaseUsersReference.child(currentUser.getId()).child("attachedDeliveryId").setValue(id);
Intent intent = new Intent(WaitingForDeliverymanAcceptanceActivity.this, PackageOnTheWayActivity.class);
startActivity(intent);
finish();
} else {
Log.e(LOG_TAG, "I refuse");
i++;
}
}
}
this is the view that crashes when i click start but i dont know why
i think it has something to do with the threding in the bruteForce function
package com.my.app;
import android.app.*;
import android.os.*;
import android.view.*;
import android.view.View.*;
import android.widget.*;
import android.content.*;
import android.graphics.*;
import android.media.*;
import android.net.*;
import android.text.*;
import android.util.*;
import java.util.*;
import java.text.*;
import android.test.*;
import android.view.animation.*;
import android.widget.Button;
public class TestActivity extends Activity {
private LinearLayout linear1;
private TextView original;
private TextView match;
private TextView text;
private Button bstart;
String attempt = new String();
boolean running;
int counter;
private String hash = "";
public String username = new String();
public static String password = "ZZZZZ";
public static char[] charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
private static char[] currentGuess = new char[1];
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test);
initialize();
initializeLogic();
}
private void initialize() {
linear1 = (LinearLayout) findViewById(R.id.linear1);
original = (TextView) findViewById(R.id.original);
match = (TextView) findViewById(R.id.match);
text = (TextView) findViewById(R.id.text);
bstart = (Button) findViewById(R.id.bstart);
bstart.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View _v) {
bruteForce();
}
});
}
private void initializeLogic() {
Intent test = getIntent();
hash = test.getStringExtra("hash");
original.setText(password);
}
public void increment()
{
int index = currentGuess.length - 1;
while (index >= 0)
{
if (currentGuess[index] == charset[charset.length - 1])
{
if (index == 0)
{
currentGuess = new char[currentGuess.length + 1];
Arrays.fill(currentGuess, charset[0]);
break;
}
else
{
currentGuess[index] = charset[0];
index--;
}
}
else
{
currentGuess[index] = charset[Arrays.binarySearch(charset, currentGuess[index]) + 1];
break;
}
}
}
public String toString()
{
return String.valueOf(currentGuess);
}
public void bruteForce()
{
Animation animation = new Animation(){
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
text.setText(attempt);
}
};
animation.setRepeatCount(Animation.INFINITE);
text.startAnimation(animation);
new Thread(){
#Override
public void run() {
running = true;
while(running)
if (attempt.equals(password))
{
text.setText(attempt);
this.stop();
}
attempt = toString();
text.setText(attempt);
increment();
}
}.start();
}
#Override
protected void onStop() {
super.onStop();
running = false;
}
public void BruteForceTest()
{
Arrays.fill(currentGuess, charset[0]);
}
// created automatically
private void ShowMessage(String _s) {
Toast.makeText(getApplicationContext(), _s, Toast.LENGTH_SHORT).show();
}
private int GetRandom(int _minValue ,int _maxValue){
Random random = new Random();
return random.nextInt(_maxValue - _minValue + 1) + _minValue;
}
public ArrayList<Integer> getCheckedItemPositionsToArray(ListView _list) {
ArrayList<Integer> _result = new ArrayList<Integer>();
SparseBooleanArray _arr = _list.getCheckedItemPositions();
for (int _iIdx = 0; _iIdx < _arr.size(); _iIdx++) {
if (_arr.valueAt(_iIdx))
_result.add(_arr.keyAt(_iIdx));
}
return _result;
}
}
Your problem lies here
new Thread(){
#Override
public void run() {
running = true;
while(running)
if (attempt.equals(password))
{
text.setText(attempt);
this.stop();
}
attempt = toString();
text.setText(attempt);
increment();
}
}.start();
You can only update an UI element from the main thread. If you want to do it likes this you need to wrap text.setText(attempt) with a handler that posts it to the main thread. e.g.
new Handler(Looper.getMainLooper()).post(new Runnable(){
void run() {
text.setText(attempt);
}
});
Looper.getMainLooper() (docs) will get the main application thread which the handler then posts the Runnable to .
if (attempt.equals(password))
{
getActivity().runOnUiThread(new Runnable()
{
#Override
public void run()
{
text.setText(attempt);
}
});
this.stop();
}
Similarly wherever you are changing ui elements like setting text or changin colors do it in runOnUithread as I did above.
I am trying to understand how this stuff works a little better.
So I learned about Runnables and Threads and ASyncTasks but apparently they have some serious drawbacks when it comes to configuration changes like rotating the screen.
Is it better to instead use IntentService for anything that should run in the background like SQL database commands, file-system procedures, Internet input/output processes, etc -- and then use LocalBroadcastReceiver to pass results back to the Activity?
Is it better to instead use IntentService for anything that should run in the background like SQL database commands, file-system procedures, Internet input/output processes, etc -- and then use LocalBroadcastReceiver to pass results back to the Activity?
A service is needed if your UI might move to the background while the work is going on, and you are concerned that your process might be terminated while the work is going on. I tend to only worry about this if the work might exceed a second or so. Otherwise, a plain thread suffices.
Using an event bus, like LocalBroadcastManager, is a reasonable approach to let other components know when your service/thread is done with its work. This sample app demonstrates this. Personally, I tend to use greenrobot's EventBus — this sample app is a clone of the first one, but using EventBus instead of LocalBroadcastManager.
Follow an example of a chat using one activity (chat activity) that run a class in service (realtime class). I use a mvc webapi to controll chat between chatters. When realtime receive a message "onConnected" or "ReceivedMessageServer" automatically send by LocalBroadcastManager to chat activity. This way in "onReceive" from BroadcastReceiver receives the message and runs other tasks.
a) ChatActivity.class
package br.com.yourapp;
import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.res.Configuration;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import java.util.Calendar;
import br.com.yourapp.model.MessageReceived;
import util.RealTime;
public class ChatActivity extends AppCompatActivity implements View.OnClickListener,
View.OnKeyListener , TextWatcher {
AppController obj;
private ProgressDialog pDialog;
MediaPlayer mp;
private ListView lstChatLog;
private ArrayAdapter<String> listAdapter;
private EditText txtChatMessage;
private TextView lblTyping;
private Button btnSendChat;
private boolean resultRequest = true;
private String errorMessage = "Internet is not working";
private AlertDialog alertDialog;
private AlertDialog.Builder alertBuilder;
String userType = "V";
String spaces = "\u0020\u0020\u0020\u0020";
Calendar time;
MessageReceived msg;
private RealTime realTime;
private final Context mContext = this;
private boolean mBound = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chat);
pDialog = new ProgressDialog(this);
pDialog.setMessage("Loading...");
pDialog.setCancelable(false);
showProgressDialog();
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
obj = (AppController) getApplicationContext();
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
lblTyping = (TextView) findViewById(R.id.lblTyping);
txtChatMessage = (EditText) findViewById(R.id.txtChatMessage);
txtChatMessage.setOnKeyListener(this);
txtChatMessage.addTextChangedListener(this);
btnSendChat = (Button) findViewById(R.id.btnSendChat);
btnSendChat.setOnClickListener(this);
alertDialog = new AlertDialog.Builder(this).create();
alertDialog.setTitle("Alert");
alertBuilder = new AlertDialog.Builder(this);
lstChatLog = (ListView) findViewById(R.id.lstChatLog);
listAdapter = new ArrayAdapter<String>(ChatActivity.this, android.R.layout.simple_list_item_1, android.R.id.text1) {
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = super.getView(position, convertView, parent);
TextView tv = (TextView) view.findViewById(android.R.id.text1);
tv.setHeight(20);
tv.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12);
return view;
}
};
Intent intent = new Intent();
intent.setClass(mContext, RealTime.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
IntentFilter filter = new IntentFilter("Connect");
filter.addAction("RecMsg");
LocalBroadcastManager.getInstance(ChatActivity.this).registerReceiver(mMessageReceiver, filter);
}
private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().toString().equals("Connect")) {
msg = (MessageReceived) intent.getExtras().getParcelable("msg");
if (msg == null || msg.Message == null)
return;
listAdapter.add(msg.Destiny + "" + msg.CurrentTime + " : " + msg.Message);
lstChatLog.setAdapter(listAdapter);
realTime.SendToSpecific(msg.Sender, "Hi !", userType, obj.getRiderId(), obj.getDriverId());
hideProgressDialog();
}
else
if (intent.getAction().toString().equals("RecMsg")) {
msg = (MessageReceived) intent.getExtras().getParcelable("msg");
if (msg == null || msg.Message == null)
return;
if (msg.Message.equals("*0.+9=&!*#_&1|8%digi")) {
lblTyping.setVisibility(View.VISIBLE);
}
else
if (msg.Message.equals("*0.+9=&!*#_&1|8%"))
{
lblTyping.setVisibility(View.INVISIBLE);
}
else
{
lblTyping.setVisibility(View.INVISIBLE);
listAdapter.add(msg.Sender + "" + msg.CurrentTime + " : " + msg.Message);
lstChatLog.setAdapter(listAdapter);
mp = MediaPlayer.create(ChatActivity.this, R.raw.notify);
mp.setLooping(false);
mp.setVolume(100, 100);
mp.start();
}
}
}
};
private final ServiceConnection mConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className, IBinder service) {
RealTime.LocalBinder binder = (RealTime.LocalBinder) service;
realTime = binder.getService();
mBound = true;
realTime.Connect(obj.getRiderName(), userType, obj.getRiderId(), obj.getLatitudeRider(), obj.getLongitudeRider(), obj.getDriverId(), obj.getVehicieId());
hideProgressDialog();
}
#Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
#Override
protected void onStop() {
if (mBound) {
unbindService(mConnection);
mBound = false;
}
super.onStop();
}
public void SendToSpecific(String sender, String message, String userType, long userId, long operatorId) {
realTime.SendToSpecific(sender, message, userType, userId,operatorId);
if (!message.equals("*0.+9=&!*#_&1|8%digi") && !message.equals("*0.+9=&!*#_&1|8%"))
{
time = Calendar.getInstance();
listAdapter.add(spaces + sender + " " + "(" + time.get(Calendar.HOUR) + ":" + time.get(Calendar.MINUTE) + ")" + " : " + message);
lstChatLog.setAdapter(listAdapter);
txtChatMessage.setText("");
}
}
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode==KeyEvent.KEYCODE_ENTER) {
if (txtChatMessage.getText().length() > 0)
{
InputMethodManager imm = (InputMethodManager) this.getSystemService(Context.
INPUT_METHOD_SERVICE);
if (this.getCurrentFocus() != null)
imm.hideSoftInputFromWindow(this.getCurrentFocus().getWindowToken(), 0);
SendToSpecific(obj.getRiderName(), txtChatMessage.getText().toString(), userType, obj.getRiderId(), obj.getDriverId());
}
}
return false;
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (txtChatMessage.getText().toString().length() > 0) {
if (txtChatMessage.getText().length() == 1) {
SendToSpecific(obj.getRiderName(), "*0.+9=&!*#_&1|8%digi", userType, obj.getRiderId(), obj.getDriverId());
}
} else {
SendToSpecific(obj.getRiderName(), "*0.+9=&!*#_&1|8%", userType, obj.getRiderId(), obj.getDriverId());
}
}
#Override
public void afterTextChanged(Editable s) {
}
public void onClick(View v) {
switch (v.getId()) {
case R.id.btnSendChat:
if (txtChatMessage != null && txtChatMessage.getText().length() > 0) {
InputMethodManager imm = (InputMethodManager) this.getSystemService(Context.
INPUT_METHOD_SERVICE);
if (this.getCurrentFocus() != null)
imm.hideSoftInputFromWindow(this.getCurrentFocus().getWindowToken(), 0);
SendToSpecific(obj.getRiderName(), txtChatMessage.getText().toString(), userType, obj.getRiderId(), obj.getDriverId());
}
break;
}
}
private void showProgressDialog() {
if (!pDialog.isShowing())
pDialog.show();
}
private void hideProgressDialog() {
if (pDialog.isShowing())
pDialog.hide();
}
#Override
protected void onDestroy() {
try {
if (pDialog != null && pDialog.isShowing())
pDialog.dismiss();
} catch (Exception e) {
e.printStackTrace();
}
super.onDestroy();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.
INPUT_METHOD_SERVICE);
if (getCurrentFocus() != null)
imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
mMessageReceiver.clearAbortBroadcast();
Intent i = new Intent(this, MenuPageActivity.class);
obj.setLastActivity("Chat");
startActivity(i);
return true;
}
public void ShowAlert() {
hideProgressDialog();
if (resultRequest)
errorMessage = "Internet is not working";
alertBuilder.setTitle("Alert");
alertBuilder.setMessage(errorMessage);
alertBuilder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface arg0, int arg1) {
arg0.dismiss();
}
});
alertDialog = alertBuilder.create();
alertDialog.show();
errorMessage = "";
resultRequest = true;
}
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}
}
b) Realtime.class
package util;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import java.util.concurrent.ExecutionException;
import br.com.yourapp.model.MessageReceived;
import microsoft.aspnet.signalr.client.Platform;
import microsoft.aspnet.signalr.client.SignalRFuture;
import microsoft.aspnet.signalr.client.http.android.AndroidPlatformComponent;
import microsoft.aspnet.signalr.client.hubs.HubConnection;
import microsoft.aspnet.signalr.client.hubs.HubProxy;
import microsoft.aspnet.signalr.client.hubs.SubscriptionHandler1;
import microsoft.aspnet.signalr.client.transport.ClientTransport;
import microsoft.aspnet.signalr.client.transport.ServerSentEventsTransport;
public class RealTime extends Service {
private HubConnection mHubConnection;
private HubProxy mHubProxy;
private Handler mHandler;
private final IBinder mBinder = new LocalBinder();
public RealTime() { }
#Override
public void onCreate() {
super.onCreate();
mHandler = new Handler(Looper.getMainLooper());
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
int result = super.onStartCommand(intent, flags, startId);
startSignalR();
return result;
}
#Override
public void onDestroy() {
mHubConnection.stop();
super.onDestroy();
}
#Override
public IBinder onBind(Intent intent) {
startSignalR();
return mBinder;
}
public class LocalBinder extends Binder {
public RealTime getService() {
return RealTime.this;
}
}
public void Connect(String userName, String userType, long userId, double latitude, double longitude, long driverId, long vehicleId) {
mHubProxy.invoke("Connect", userName, userType, userId, latitude, longitude, driverId, vehicleId);
}
public void SendMessageToGroup(String userName, String userGroup, String userType, long userId, double latitude, double longitude, long vehicleId, short option)
{
mHubProxy.invoke("SendMessageToGroup", userName, userGroup, userType, userId, latitude, longitude, vehicleId, option);
}
public void SendToSpecific(String sender, String message, String userType, long userId, long operatorId)
{
mHubProxy.invoke("SendToSpecific", sender, message, userType, userId, operatorId);
}
private void startSignalR() {
Platform.loadPlatformComponent(new AndroidPlatformComponent());
mHubConnection = new HubConnection("http://webapi.com");
mHubProxy = mHubConnection.createHubProxy("ChatHub");
ClientTransport clientTransport = new ServerSentEventsTransport(mHubConnection.getLogger());
SignalRFuture<Void> signalRFuture = mHubConnection.start(clientTransport);
try {
signalRFuture.get();
} catch (InterruptedException | ExecutionException e) {
Log.e("SimpleSignalR", e.toString());
return;
}
mHubProxy.on("onConnected",
new SubscriptionHandler1<MessageReceived>() {
#Override
public void run(final MessageReceived msg) {
mHandler.post(new Runnable() {
#Override
public void run() {
Intent intent = new Intent("Connect");
intent.putExtra("msg", msg);
LocalBroadcastManager.getInstance(RealTime.this).sendBroadcast(intent);
}
});
}
}
, MessageReceived.class);
}
}
mHubProxy.on("ReceivedMessageServer",
new SubscriptionHandler1<MessageReceived>() {
#Override
public void run(final MessageReceived msg) {
mHandler.post(new Runnable() {
#Override
public void run() {
Intent intent = new Intent("RecMsg");
intent.putExtra("msg", msg);
LocalBroadcastManager.getInstance(RealTime.this).sendBroadcast(intent);
}
});
}
}
, MessageReceived.class);
}
I'm doing some background processing in my activity and have a ListView that shows the progress. I 'update' the listView with
adapter.notifyDataSetChanged();
However when I leave the activity and then come back, the background processes are still running, but the list view isn't updated. This is presumably because it's a new object, not the previous one. How do I maintain my list view object between activity loads?
This is my activity. I think the issue is that the 'adapter' variable that the download uses to bind to to show updates, is recreated in the onCreate method of the activity, and the 'adapter' variable that the download originally bound to is not in the activity anymore.
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.model.ProgressEvent;
import com.amazonaws.services.s3.model.ProgressListener;
import com.amazonaws.services.s3.transfer.TransferManager;
//import com.amazonaws.auth.AWSCredentials;
//import com.amazonaws.auth.BasicAWSCredentials;
public class VideosActivity extends Activity {
ListView video_list;
CustomList adapter;
File storage_dir;
SharedPreferences completed_downloads; //http://developer.android.com/guide/topics/data/data-storage.html
SharedPreferences downloaded_data; //maps position to percent downloaded
SharedPreferences queue; //holds which fiiles are awaiting download
String images[]; //holds references to all thumnails for vids
String volume; //something like vol1 or vol2
String s3_dir; //the directory after the boucket that the files are stored in (do not add first slash)
String s3_bucket = "com--apps";
Handler handler = new Handler(); //'tunnel' through whci other threads communicate with main thread
Map<String, String[][]> video_config = new HashMap<String, String[][]>(); //holds info about video thumbs and filenames for all apps
ArrayList<String> arr_videos = new ArrayList<String>(); //holds video names
DownloadQueue queue_manager = new DownloadQueue();
#Override
public void onCreate(Bundle savedInstanceState) {
Log.v("dev", "onCreateCalled");
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
volume = getPackageName().split("\\.")[2]; //something like vol1 or vol2
s3_dir = "android/" + volume + "/"; //the directory after the boucket that the files are stored in (do not add first slash)
completed_downloads = getSharedPreferences("completed_downloads", 0);
downloaded_data = getSharedPreferences("downloaded_data", 0);
queue = getSharedPreferences("queue", 0);
storage_dir = getApplicationContext().getExternalFilesDir(null); //private to app, removed with uninstall
adapter = new CustomList(this, R.layout.customlist, arr_videos);
video_list = (ListView)findViewById(R.id.list);
video_list.setAdapter(adapter); //set adapter that specifies list contents
ensureStorageDirExists( storage_dir ); //make sure storage dir exists
set_video_data(); //store vid dat in array
ensure_connection_or_warn();
}
public boolean ensure_connection_or_warn()
{
if(have_connection())
{
return true;
}
else
{
Toast.makeText(this, "No Internet connection", Toast.LENGTH_LONG).show();
return false;
}
}
protected void ensureStorageDirExists( File dir )
{
if (!dir.exists())
{
dir.mkdirs();
}
}
public void set_video_data()
{
config_video_data(); //run config
images = video_config.get(getPackageName())[0]; //set images
for (String name : video_config.get(getPackageName())[1] ) { //set video info, this should be streamlined but is due to legacy code and my crap Java skills, consider doing like this: http://developer.android.com/guide/topics/resources/more-resources.html#TypedArray
arr_videos.add(name);
}
}
public SharedPreferences stored_vals()
{
return PreferenceManager.getDefaultSharedPreferences(this);
}
public boolean have_connection()
{
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
if(cm.getActiveNetworkInfo()!=null && cm.getActiveNetworkInfo().isConnected() && cm.getActiveNetworkInfo().isAvailable())
{
Log.v("dev", "have internet connection");
return true;
}
else
{
Log.v("dev", "No internet connection");
return false;
}
}
public class DownloadQueue
{
protected void process()
{
if (queue.size() > 0 && queue.get(0).download_status == "queued") //this is meant to force one download at a time
{
queue.get(0).start();
adapter.notifyDataSetChanged();
}
}
protected void add(Integer position)
{
queue.edit.putInt(position+"");
d.download_status = "queued";
adapter.notifyDataSetChanged();
}
protected boolean position_is_queued(Integer position)
{
for (Download d : queue ) {
if(d.position == position)
{
return true;
}
}
return false;
}
protected void remove(Download d)
{
queue.remove(d);
adapter.notifyDataSetChanged();
}
}
public class CustomList extends ArrayAdapter<String>
{
View view;
int position;
Button btn;
public CustomList(Context context, int layout_id, ArrayList<String> objects)
{
super(context, layout_id, objects);
}
#Override
public View getView(final int position, View convertView, ViewGroup view_group)
{
set_view(convertView);
this.position = position;
TextView text_view = (TextView) view.findViewById(R.id.name);
ImageView image = (ImageView) view.findViewById(R.id.img);
btn = (Button) view.findViewById(R.id.play);
prepare_btn();
text_view.setText( list_text() );
image.setImageResource(
getResources().getIdentifier(images[position], "drawable", getPackageName())
);
return view;
}
public String list_text()
{
String s = arr_videos.get( position ).replace("_", " ").replace(".m4v", "");
s = s.substring(2, s.length());
return s;
}
public void set_view(View convertView)
{
if(convertView == null)
{
LayoutInflater inflater = getLayoutInflater();
view = inflater.inflate(R.layout.customlist, null);
}
else
{
view = convertView;
}
}
public Boolean is_downloaded()
{
return completed_downloads.getBoolean(position + "", false);
}
public void prepare_btn()
{
btn.setTag((Integer) position);
if(is_downloaded() == true)
{
btn.setText("Play ");
btn.setEnabled(true);
btn.setOnClickListener( new OnClickListener()
{
public void onClick(View btn)
{
int position = (Integer) btn.getTag();
Intent i = new Intent(VideosActivity.this, PlayVideoActivity.class);
String video_path = storage_dir + "/" + arr_videos.get(position);
Log.v("video_path", video_path);
i.putExtra("video_path", video_path);
startActivity(i);
}
});
}
else if( downloaded_data.contains(position+"") ) //it it's currently being downloaded
{
btn.setText(downloaded_data.getInt(position+"", 0) + "%");
btn.setEnabled(false);
}
else if( queue_manager.position_is_queued(position) ) //it's in the queue
{
btn.setText("Queued");
btn.setEnabled(false);
}
else
{
btn.setText("Download");
btn.setEnabled(true);
btn.setOnClickListener( new OnClickListener()
{
public void onClick(View btn)
{
int position = (Integer) btn.getTag();
btn.setEnabled(false);
queue_manager.add(position);
}
});
}
}
}
public class Download
{
File new_video_file;
int position;
String download_status;
com.amazonaws.services.s3.transfer.Download download;
protected void queue(int position)
{
this.position = position;
queue_manager.add(this);
queue_manager.process();
//put this download in the queue
//start downloading if it's the only one in the queue
}
protected void start()
{
if(!ensure_connection_or_warn())
{
return;
}
this.download_status = "started";
this.new_video_file = new File(storage_dir, arr_videos.get(position)); //local file to be writtent to
TransferManager tx = new TransferManager(credentials);
//http://stackoverflow.com/questions/6976317/android-http-connection-exception
this.download = tx.download(s3_bucket, s3_dir + arr_videos.get(position), new_video_file);
download.addProgressListener(new ProgressListener()
{
public void progressChanged(final ProgressEvent pe)
{
handler.post( new Runnable()
{
#Override
public void run()
{
if ( pe.getEventCode() == ProgressEvent.COMPLETED_EVENT_CODE )
{
Download.this.onComplete();
}
else
{
Download.this.onProgressUpdate();
}
}
});
}
});
}
//protected void onProgressUpdate(Double progress)
protected void onProgressUpdate()
{
this.download_status = "downloading";
Double progress = this.download.getProgress().getPercentTransfered();
Integer percent = progress.intValue();
//Log.v("runnable", percent + "");
downloaded_data.edit().putInt(position+"", percent).commit();
adapter.notifyDataSetChanged();
}
protected void onComplete()
{
Log.v("dev", "download complete!!!");
//downloaded_data.remove(position);
this.download_status = "complete";
completed_downloads.edit().putBoolean(position + "", true).commit();
queue_manager.remove(this);
queue_manager.process();
adapter.notifyDataSetChanged();
// this.download.abort();
}
}
}
Not exactly sure what happens during background processing and what do you mean by ListView state, but my suggestion would be:
try the following:
#Override
protected void onResume() {
super.onResume();
yourAdapter.clear();
yourAdapter.addAll(yourData); // API level [1..10] use for(..){ yourAdapter.add(yourData.get(index)) }
yourAdapter.notifyDataSetChanged();
}
Reason:
An adapter keeps a copy of all the elements, so when you call notifyDataSetChanged, it checks its own copy of the elements