4

onConfigurationChanged method get called on an Activity by the system when there is(are) any changes in the configuration. This method only get called for those configurations which are defined in the manifest by the configChanges attribute.

I have multiple configurations (orientation|screenSize) defined in this configChanges attribute and I want to detect which configuration(s) has been changed so that I can do some work based on that change, how can I achieve this?

To solve this I have following code snippet:

public class MyActivity extends AppCompatActivity {
    private final String TAG = "my-activity";

    private Configuration prevConfig;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    protected void onResume () {
        super.onResume();
        prevConfig = new Configuration(getResources().getConfiguration());
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        int diff = newConfig.diff(prevConfig);

        if(((diff & ActivityInfo.CONFIG_ORIENTATION) != 0)
                || ((diff & ActivityInfo.CONFIG_SCREEN_SIZE) != 0)) {
            Log.d(TAG, "Orientation or Screen size has been changed.");
        }

        // other configuration change checking goes here

        prevConfig = new Configuration(newConfig);
    }
}

By this I can detect which configuration has been changed.

I want to know if this is the best way to achieve the goal?

Tapas Bose
  • 28,796
  • 74
  • 215
  • 331
  • see this page http://developer.android.com/guide/topics/resources/runtime-changes.html – Saeed Entezari Sep 27 '15 at 11:49
  • I think you can't go better than that - your activity after configuration change is a completely new activity, with no information from the previous one. Although I don't see you preserving `prevConfig` anywhere - so after rotation `prevConfig` would always be the *new* config – wasyl Sep 27 '15 at 12:01
  • 1
    @SaeedEntezari I already have seen this document. There, nothing is mentioned about the way to know which config has changed. – Tapas Bose Sep 27 '15 at 12:09
  • Because no need to know. You just have to handle all the situations separately. Just switch-case the orientation and add the proper code. Then switch-case the keyboard just like the orientation. And so to screen size... – Saeed Entezari Sep 27 '15 at 12:12
  • 1
    @wasyl, your statement is not completely true I think. An activity, after configuration change will be a new activity (or restart) when that configuration is not mentioned in the manifest. If you mentioned the name of the config in the manifest then android system, instead of restarting the activity, will provide you a signal which you can handle from this `onConfigurationChanged` method. – Tapas Bose Sep 27 '15 at 12:13
  • @TapasBose silly me, you're correct obviously! Then I think your answer is fully correct, and I can't think of better way to see which configuration has changed – wasyl Sep 27 '15 at 13:20
  • 2
    Just a note for `new Configuration(newConfig);` declaration - **important be written** rather than using `newConfing` instance because Android system changes the same `newConfig` instance, result in no difference between the new and the previous one. – Eido95 Jul 25 '17 at 20:22
  • @wasyl You will also get new config changes when you switch from 1/2 to 1/3 multi-window mode, the activity *will not* be recreated, and you don't have to specify a request for that config change in the manifest - they call it by default. I think it's minimum size changed – milosmns Nov 07 '17 at 00:07

1 Answers1

1

How to know which configuration has been changed on an Activity?

To simplify the problem. You don't have to define configChanges in AndroidManifest.xml if you don't want to take control yourself when configurations changed.

The way I used is just override the onSaveInstanceState to store the Configuration before the activity being killed, and then restore it to a property like yours as prevConfig. Since android.content.res.Configuration is a kind of Parcelable. It is very easy to put it into the Bundle by calling putParcelable and recover it on onCreate.

By doing so, You will get two Configuration in onCreate(). You get the previous one from the Bundle savedInstanceState and the current the from Activity.getResources().getConfiguration(). The Configuration has a public method diff, which we can use it to return a bit mask of the differences between them.

To me, the most important thing is how to distinguish the bit mask easily. Fortunately, there is a method from the official repo we can referenced. It's static and also public. But we can't call it because of the hide annotation in JavaDoc. So, my first try was using reflection to invoke the method. It seems like the method is completely removed from the Android runtime due to the exception that occurred. After looking at the source. I just copy it to my own sources. Coping seems like a bad idea. But, again. It's a public static method, So it is not too difficult to copy and easy to maintain.

Here is my solution written in Kotlin.

package com.example.android.configchangeapp

import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity

class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val config = resources.configuration
        val prevConfig = savedInstanceState?.getParcelable<Configuration>("config")
        if (prevConfig != null) {
            val diff = config.diff(prevConfig)
            val diffString = configurationDiffToString(diff)
            Log.d(TAG, "diff: $diffString")
        }
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        outState.putParcelable("config", resources.configuration)
    }

    companion object {
        const val TAG = "MainActivity"

        fun configurationDiffToString(diff: Int): String {
            val list = ArrayList<String>()
            if (diff and ActivityInfo.CONFIG_MCC != 0) {
                list.add("CONFIG_MCC")
            }
            if (diff and ActivityInfo.CONFIG_MNC != 0) {
                list.add("CONFIG_MNC")
            }
            if (diff and ActivityInfo.CONFIG_LOCALE != 0) {
                list.add("CONFIG_LOCALE")
            }
            if (diff and ActivityInfo.CONFIG_TOUCHSCREEN != 0) {
                list.add("CONFIG_TOUCHSCREEN")
            }
            if (diff and ActivityInfo.CONFIG_KEYBOARD != 0) {
                list.add("CONFIG_KEYBOARD")
            }
            if (diff and ActivityInfo.CONFIG_KEYBOARD_HIDDEN != 0) {
                list.add("CONFIG_KEYBOARD_HIDDEN")
            }
            if (diff and ActivityInfo.CONFIG_NAVIGATION != 0) {
                list.add("CONFIG_NAVIGATION")
            }
            if (diff and ActivityInfo.CONFIG_ORIENTATION != 0) {
                list.add("CONFIG_ORIENTATION")
            }
            if (diff and ActivityInfo.CONFIG_SCREEN_LAYOUT != 0) {
                list.add("CONFIG_SCREEN_LAYOUT")
            }
            if (diff and ActivityInfo.CONFIG_COLOR_MODE != 0) {
                list.add("CONFIG_COLOR_MODE")
            }
            if (diff and ActivityInfo.CONFIG_UI_MODE != 0) {
                list.add("CONFIG_UI_MODE")
            }
            if (diff and ActivityInfo.CONFIG_SCREEN_SIZE != 0) {
                list.add("CONFIG_SCREEN_SIZE")
            }
            if (diff and ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE != 0) {
                list.add("CONFIG_SMALLEST_SCREEN_SIZE")
            }
            if (diff and ActivityInfo.CONFIG_DENSITY != 0) {
                list.add("CONFIG_DENSITY")
            }
            if (diff and ActivityInfo.CONFIG_LAYOUT_DIRECTION != 0) {
                list.add("CONFIG_LAYOUT_DIRECTION")
            }
            if (diff and ActivityInfo.CONFIG_FONT_SCALE != 0) {
                list.add("CONFIG_FONT_SCALE")
            }
            if (diff and ActivityInfo.CONFIG_FONT_WEIGHT_ADJUSTMENT != 0) {
                list.add("CONFIG_AUTO_BOLD_TEXT")
            }

            return buildString {
                append("{")
                append(list.joinToString(", "))
                append("}")
            }
        }
    }
}
Gasol
  • 2,319
  • 17
  • 27