I have handled screen orientation using SensorManager and it's working fine
But there is some memory leak as it is set in BaseActivity like
abstract class BaseActivity : AppCompatActivity(), SensorOrientationChangeNotifier.Listener {
override fun onResume() {
super.onResume()
SensorOrientationChangeNotifier.getInstance(this)?.addListener(this)
}
override fun onPause() {
super.onPause()
SensorOrientationChangeNotifier.getInstance(this)?.remove(this)
}
}
SensorOrientationChangeNotifier.kt
class SensorOrientationChangeNotifier private constructor(applicationContext: Context) {
private val mListeners = ArrayList<WeakReference<Listener?>>(3)
var orientation = 0
private set
private val mSensorEventListener: SensorEventListener
private val mSensorManager: SensorManager
init {
mSensorEventListener = NotifierSensorEventListener()
mSensorManager =
applicationContext.getSystemService(Context.SENSOR_SERVICE) as SensorManager
}
/**
* Call on activity reset()
*/
private fun onResume() {
mSensorManager.registerListener(
mSensorEventListener,
mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
SensorManager.SENSOR_DELAY_NORMAL
)
}
/**
* Call on activity onPause()
*/
private fun onPause() {
mSensorManager.unregisterListener(mSensorEventListener)
}
private inner class NotifierSensorEventListener : SensorEventListener {
override fun onSensorChanged(event: SensorEvent) {
val x = event.values[0]
val y = event.values[1]
var newOrientation: Int = orientation
if (x < 5 && x > -5 && y > 5) newOrientation =
0 else if (x < -5 && y < 5 && y > -5) newOrientation =
90 else if (x < 5 && x > -5 && y < -5) newOrientation =
180 else if (x > 5 && y < 5 && y > -5) newOrientation = 270
///Timber.v("Matas_mOrientation = "+ newOrientation+" ["+event.values[0]+","+event.values[1]+","+event.values[2]+"]");
if (orientation != newOrientation) {
Timber.v("Matas_mOrientation-Orientation-> NEW")
orientation = newOrientation
notifyListeners()
} else {
///Timber.v("Matas_mOrientation-Orientation-> OLD");
}
}
override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {}
}
interface Listener {
fun onOrientationChange(orientation: Int)
}
fun addListener(listener: Listener) {
if (get(listener) == null) // prevent duplications
mListeners.add(WeakReference(listener))
if (mListeners.size == 1) {
onResume() // this is the first client
}
}
fun remove(listener: Listener) {
val listenerWR = get(listener)
remove(listenerWR)
}
private fun remove(listenerWR: WeakReference<Listener?>?) {
if (listenerWR != null) mListeners.remove(listenerWR)
if (mListeners.size == 0) {
onPause()
}
}
private operator fun get(listener: Listener): WeakReference<Listener?>? {
for (existingListener in mListeners) if (existingListener.get() === listener) return existingListener
return null
}
private fun notifyListeners() {
val deadLiksArr = ArrayList<WeakReference<Listener?>>()
for (wr in mListeners) {
if (wr.get() == null) deadLiksArr.add(wr) else wr.get()!!
.onOrientationChange(orientation)
}
// remove dead references
for (wr in deadLiksArr) {
mListeners.remove(wr)
}
}
val isPortrait: Boolean = orientation == 0 || orientation == 180
val isLandscape: Boolean = !isPortrait
companion object {
val TAG = javaClass.simpleName
private var mInstance: SensorOrientationChangeNotifier? = null
fun getInstance(applicationContext: Context): SensorOrientationChangeNotifier? {
if (mInstance == null) mInstance = SensorOrientationChangeNotifier(applicationContext)
return mInstance
}
}
}
I think my context
is leaking as user switch from one activity to another But mInstance
is not null due to singleton object
Can someone suggest a better approach to implement it once without memory leak in BaseActivity
I think I should directly pass SensorManager with applicationContext
val sensorManager = applicationContext.getSystemService(Context.SENSOR_SERVICE) as SensorManager
SensorOrientationChangeNotifier.getInstance(sensorManager)?.addListener(this)
Your SensorOrientationChangeNotifier
class holds a reference to the Context you pass to it. Since you pass the Activity as your Context parameter, it is holding a reference to your Activity for as long as it is alive, which is forever since it is a singleton. You were naming your parameters applicationContext
but in practice you were using an Activity instance instead.
To fix it, make sure you pull the applicationContext
out of the Activity and use that. You can't leak the Application's Context, because it lasts for the lifetime of the application instance.
companion object {
val TAG = javaClass.simpleName
private var mInstance: SensorOrientationChangeNotifier? = null
fun getInstance(context: Context): SensorOrientationChangeNotifier? {
if (mInstance == null) mInstance = SensorOrientationChangeNotifier(context.applicationContext)
return mInstance
}
}
Also, there's no reason to be returning a nullable from your getInstance()
function. And your TAG
right now evaluates to "Companion"
, which isn't very useful. Here it is with both of those things fixed:
companion object {
val TAG = SensorOrientationChangeNotifier::class.simpleName
private var instance: SensorOrientationChangeNotifier? = null
fun getInstance(context: Context): SensorOrientationChangeNotifier {
return instance ?:
SensorOrientationChangeNotifier(context.applicationContext).also { instance = it }
}
}