Potential bug in Android Wear SensorManager? - android

I have two fragments, fragment A and fragment B. Fragment A uses a shaking gesture to switch to fragment B, and fragment B uses a different gesture to switch back to fragment A. So when I'm in fragment A, I register gesture A with the SensorManager, and when a shake is detected, I unregister gesture A, switch to fragment B, and register gesture B with the SensorManager.
Fragment A:
public class FragmentA extends Fragment {
private MainWearActivity mMainWearActivity;
private SensorManager mSensorMgr;
private GestureA gestureA;
private OnShakeListener gestureAListener;
private View view;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mMainWearActivity = (MainWearActivity) getActivity();
mSensorMgr = (SensorManager) mMainWearActivity.getSystemService(Activity.SENSOR_SERVICE);
gestureA = new GestureA();
gestureAListener = new OnShakeListener() {
#Override
public void onShake() {
gestureADetected();
}
};
gestureA.setOnShakeListener(gestureAListener);
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_a, container, false);
return view;
}
#Override
public void onResume() {
super.onResume();
startListening();
}
#Override
public void onPause() {
stopListening();
super.onPause();
}
private void gestureADetected(){
mMainWearActivity.replaceFragment(mMainWearActivity.getFrag("B"));
}
private void startListening(){
mMainWearActivity.registerListener(gestureA);
}
private void stopListening(){
mMainWearActivity.unregisterListener(gestureA);
}
}
Fragment B:
public class FragmentB extends Fragment {
private MainWearActivity mMainWearActivity;
private FragmentManager fm;
private SensorManager mSensorMgr;
private GestureB gestureB;
private OnShakeListener gestureBListener;
private View view;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mMainWearActivity = (MainWearActivity) getActivity();
fm = mMainWearActivity.getFragmentManager();
mSensorMgr = (SensorManager) mMainWearActivity.getSystemService(Activity.SENSOR_SERVICE);
gestureB = new GestureB();
gestureBListener = new OnShakeListener() {
#Override
public void onShake() {
gestureBDetected();
}
};
gestureB.setOnShakeListener(gestureBListener);
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_b, container, false);
return view;
}
#Override
public void onResume() {
super.onResume();
startListening();
}
#Override
public void onPause() {
stopListening();
super.onPause();
}
private void gestureBDetected(){
fm.popBackStackImmediate();
}
private void startListening(){
mMainWearActivity.registerListener(gestureB);
}
private void stopListening(){
mMainWearActivity.unregisterListener(gestureB);
}
}
If I run this app, and continuously switch between fragment A and fragment B, after a few times the SensorManager stops detecting gestures. This is not the case on an Android phone where this setup works fine.
This is just a small test app I made to check whether the behaviour could be replicated, but my actual app is much larger and with many more gestures, so simply registering all the gestures once with the SensorManager and checking for the different fragments/gestures is not an ideal solution as it becomes very messy and complicated. Does anyone know of a way to "clean" the SensorManager so that it loses all references to any previously registered/unregistered listeners? Or is this just a bug in Android Wear. The device I'm using is a Moto360. Thanks.

In your questions of recent days, which contain similar code and I presume are related, I have not seen a posting of the code for your event-listener/gesture-detectors. It appears that at least one of them uses the gyro sensor. In one posted version of your code you were using a fast update rate (SENSOR_DELAY_GAME).
Is there logic in your listening/detection processing to ensure that a given wrist motion only causes the gesture to be reported once? I'm thinking about the control flow in your design: sensor event, gesture detection, fragment transaction, listener unregister. Because all this is happening on the main thread, and the fragment events are asynchronous, I suspect your listener will get a few more events after the one that triggered the gesture detection. This is more likely with a faster update rate. In my experience, the execution of the steps involved in a fragment transaction, along with the fragment callbacks (onCreateView(), onResume(), etc.), can take 20 milliseconds or more. Until onPause() for the fragment being removed is called, the listener for that fragment is still registered and events are being queued for the listener. If multiple gesture detections are fired, your fragment management could get messed up, perhaps causing the same fragment to be added multiple times. Adding Log statements to your gestureXDetected() would confirm that the gestures are being detected once, as your design expects.
To your question about a bug in the SensorManager, well, anything is possible, but I think it's very unlikely. You could easily confirm your suspicion by putting debug code in the onCreate() method of your MainActivity to reg/unreg a listener 100 times before starting normal operation, then observe if your app is immediately sluggish or the delays develop after you switch fragments repeatedly.

Related

Is having a managed static reference to a Fragment or Activity ok?

I was wondering if having a managed static reference to a Fragment or Activity is ok? By managed I mean releasing the static reference on the relevant lifecycle callback. Consider the following code please:
public class StaticReferencedFragment extends Fragment {
public static StaticReferencedFragment instance;
public StaticReferencedFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_static_referenced, container, false);
}
#Override
public void onStart() {
super.onStart();
instance = this;
}
#Override
public void onStop() {
super.onStop();
instance = null;
}
}
Do I run the risk of leaking the Fragment/Activity object?
Do I run the risk of leaking the Fragment/Activity object?
Yes. For example, an unhandled exception while your fragment is visible will bypass your lifecycle methods and cause you to fail to null out the static field.
Beyond that, it's unclear what this buys you:
An activity hosting this fragment can simply hold onto the fragment in a regular field
Other fragments in the activity should neither know nor care that this fragment exists (fragments should worry about themselves and their activity, not peer fragments)
Other components, like services, and other threads should neither know nor care that this entire activity exists, let alone this fragment (use an event bus or other loosely-coupled modes of communication)

Android Destroy current view from parent

I have a Parent activity that sets a view on Resume based on some check like this :
public class AppLockActivity extends AppCompatActivity {
#BindView(R.id.btnSubmit)
Button submitButton;
private static final String TAG = "AppLockActivity";
private static TimeElapsed timeElapsedInstance;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
timeElapsedInstance = TimeElapsed.getInstance();
timeElapsedInstance.resetTime();
timeElapsedInstance.setStartTime();
}
#Override
protected void onResume() {
super.onResume();
//check if app has passed a time threshold
if(timeElapsedInstance.getStartTime() != 0){
timeElapsedInstance.setEndTime(Calendar.getInstance().getTimeInMillis());
long threshold = timeElapsedInstance.getEndTime()-timeElapsedInstance.getStartTime();
Log.d(TAG,"Threshold : "+threshold);
//Current timeout threshold set to 30s
if(threshold>30000){
setContentView(R.layout.activity_app_lock);
ButterKnife.bind(this);
}else{
}
}
}
#OnClick(R.id.btnSubmit) void onSubmit() {
//destroy current(Parent) view and show the previous
}
}
This activity is extended by other activities like MainAcitivty ,etc...
public class MainActivity extends AppLockActivity{
#Override
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_main);
}
}
When the app goes in background and is resumed the onResume function is called and based on the check the new View is set - R.layout.activity_app_lock. What I want to do is onClick of the submit button in this view I want to destroy the current view i.e (R.layout.activity_app_lock) and show the previous view that was in the child activity like MainActivity (R.layout.activiyt_main)...
Anybody have any idea how can I do this?
Thanks
You can actually call setContentView again with a different view. All your bindings need to be reset and your On_____Listeners need to be cleared or else you'll get a memory leak. Other than that, it'll be up and ready for you to go.
Though I suggest an alternative approach to changing the layout. Instead, create a new Activity that you start in replacement of the layout your currently submitting. Then, rather than worrying about leaks, you just call finish() on the lock Activity when the user submits. The effect would be the same and it would be more versatile (In my opinion).

Is ButterKnife memory safe with retained Fragment?

public class GenericRetainedFragment extends GenericFragment {
#Bind(R.id.some_button)
Button button;
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setRetainInstance(true);
}
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ButterKnife.bind(this, view);
}
#Override
public void onDestroyView() {
super.onDestroyView();
ButterKnife.unbind(this);
}
}
Since I have no idea how ButterKnife work on unbind method, affter configuration change, will memory leak occur ?
Does this a good practice using ButterKnife and Retained Fragment ?
I don't see that there would be any problems with retained fragments, because the instance of the fragment should always be "alive" until it's removed. It doesn't go into the back stack, and therefore have its views destroyed and possibly recreated (this is the normal situation with Fragments that requires a special pattern with ButterKnife). One instance of a retained fragment will only have one set of views unless you do something to change that.

how to update activity method in fragment screen android?

I have method in fragment activity and if that method trigger, I need to update fragment listView. I am dealing with database. Where I am clearing the database of particular user and i will update fragment.
Problem is: if user is in same screen means, how to update fragment listview if fragment activity method triggers? It only works when I need to go back to activity and once again need to come to same screen.
Here is code:
public class ActivityExpertDasboard extends ActivityBase {
// this method is calling when particular user closes the screen. when I am in fragment screen..
#Override
protected void onChatInvitation(String msgKeys, String userId) {
String msgKey = mApplication.returnEmptyStringIfNull(msgKeys);
LogMessage.e("username", mPreference.getStringFromPreference(Constants.CLOSE_CHAT_USERNAME));
if (userId.equalsIgnoreCase(mPreference.getStringFromPreference(Constants.CLOSE_CHAT_USERNAME))) {
if (msgKey.equalsIgnoreCase(Constants.CODE_CHAT_END)) {
AAEDatabaseHelper.deleteUsername(mPreference.getStringFromPreference(Constants.CLOSE_CHAT_USERNAME));
// I need to update in Fragment screen if this is triggered.
}
}
super.onChatInvitation(msgKey, userId);
}
}
FragmentExpertLiveChats:
public class FragmentExpertLiveChats extends Fragment {
private List<LiveChatDetails> list;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_chat_history, container,
Constants.ATTACH_TO_ROOT);
list = AAEDatabaseHelper.getLiveChatDetails(Constants.TABLE_LIVE_CHAT);
}
#Override
public void onStart() {
LogMessage.e("onStart", "onStart");
super.onStart();
updateUI();
}
}
If phone is in FragmentExpertLiveChat screen without doing any perfomance and if method in activity calls, how to update the row? I need to use broadcast receiver? If yes, where and how?
For that, and many more cases, where you need to communicate amongst different components, I suggest using EventBus. It's usage is very simple:
Define events: public class MessageEvent { /* Additional fields if
needed */ }
Prepare subscribers Register your subscriber (in your onCreate or in a
constructor): eventBus.register(this);
Declare your subscribing method: #Subscribe public void
onEvent(AnyEventType event) {/* Do something */};
Post events: eventBus.post(event);
Don't forget to unregister afterwards. I suggest you do registration/unregistration in start/stop or pause/resume, or, in case of fragments, attach/dettach.
In your case, register in Fragment, and in Activity, when user does his things, post event.

OnClickListener fired after onPause?

The project that I'm working uses a view-presenter abstraction.
Here is a simplified version of all the main classes.
The abstract activity (wire Presenter instance, with View)
public abstract class MvpActivity<Presenter extends MvpPresenter>
extends ActionBarActivity {
protected Presenter mPresenter;
#Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPresenter = getPresenterInstance();
}
#Override protected void onResume() {
super.onResume();
mPresenter.onResume(this);
}
#Override protected void onPause() {
mPresenter.onPause();
super.onPause();
}
}
The view interface
public interface MyView {
void redirect();
}
The view implementation
public class MyActivity
extends MvpActivity<MyPresenter>
implements MyView {
#Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_view);
Button myButton = (Button)findViewById(R.id.my_button);
myButton.setOnClickListener(v -> mPresenter.onButtonPressed());
}
#Override protected MyPresenter getPresenterInstance() {
return new MyPresenter();
}
#Override void redirect(){
startActivity(new Intent(this, MyOtherActivity.class));
}
The abstract presenter
public abstract class MvpPresenter<ViewType> {
private ViewType mView;
public void onResume(ViewType view) {
mView = view;
}
public void onPause() {
mView = null;
}
protected ViewType getView() {
if (mView == null) {
throw new IllegalStateException("Presenter view is null");
}
return mView;
}
}
And the presenter implementation
public class MyPresenter extends MvpPresenter<MyView> {
#Override public void onResume(MyView myView){
super.onResume(myView);
Log.("MyPresenter", "Presenter resumed");
}
#Override public void onPause(){
super.onPause()
Log.("MyPresenter", "Presenter paused");
}
public void onButtonPressed(){
getView().redirect();
}
}
The issue comes up as an "IllegalStateException: Presenter view is null" triggered by getView().redirect(); when called from the MyPresenter.onButtonPressed() method.
This doesn't make any sense to me, as the view should always be not null if the listener is fired. The view is only set to null if the MvpPresenter.onPause() is executed which is only being called from MvpActivity.onPause(). I wouldn't expect to receive any click events after this happens, so what am I missing here?
Sadly, I can not reproduce this issue by manually testing the application. The reports are coming in from Crashlytics.
Note: retrolambda is in use for the button click listener
Update 10/07/2017
Some ways of fixing this issues:
-
https://developer.android.com/reference/android/view/View.html#cancelPendingInputEvents()
-
https://github.com/JakeWharton/butterknife/blob/master/butterknife/src/main/java/butterknife/internal/DebouncingOnClickListener.java
Short answer: don't do that.
Unfortunately, you're relying on an order of events that is undefined. Activity lifecycle events and Window events are two different things, even though they're often closely related. You'll get onPause() when the activity is paused for any reason. But the View touch events aren't unhooked until the View's window loses focus.
It's very common for an activity to pause right when its window loses focus--for instance, when the screen is locked or when another activity is launched. But as you've seen, you can get pauses without a focus change and focus changes without a pause. Even when the two events occur together, there's a narrow window of time when onPause() has been called but the window input handlers are still active.
As with any undefined behavior, the actual results you see will vary by OS version and hardware type.
If you need to make sure that you don't receive View messages after onPause, you should unhook your handlers in onPause.

Categories

Resources