Dynamically switch app ICONS

Note: our company has adopted this method and put it into production. This scheme has defects and is not perfect. Please evaluate whether to accept the defects before adopting it.

rendering

Product demand

Many apps in the market can dynamically switch App ICONS according to specific activities to achieve the purpose of publicity, such as Taobao Double 11, National Day and so on. So how can we dynamically switch app ICONS without releasing a new version?

The specific plan

1. Logo: the Activity of the entrance of the AndroidManifest set application alias, then through setComponentEnabledSetting dynamic enable or disable the alias icon to switch

2. Control icon display: When cold startup App, call interface to determine whether icon needs to be switched

3. Trigger timing: Monitor the switch between the front and back of the App, and switch ICONS when the App is in the background, so that the user does not feel.

Code implementation

Set activity-alias to the entry Activity in androidmanifest.xml
<application android:name=".MyApplication" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/Theme.SwitchIcon"> <! <activity android:name=".mainActivity "/> <! -- Fixed a default alias, MainActivity --> <activity-alias Android :name=".DefaultAliasActivity" Android :enabled="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:targetActivity=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity-alias> <! -- Alias 1, icon required for a specific activity such as: Double 11, <activity-alias Android :name=".alias1Activity "Android :enabled="false" Android :icon="@mipmap/ IC_launcher_show" android:label="@string/app_name" android:targetActivity=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity-alias> </application>Copy the code

The attributes in the activity-alias tag are as follows

The label role
android:name Alias, same naming rules as simple
android:enabled Whether or not to enable aliases, the main function here is to control the display of application ICONS
android:icon Application icon
android:label The application of
android:targetActivity Must point to the original entry Activity
In MainActivity, switch ICONS by enabling or disabling aliases
/** * Set the default alias to boot */ public void setDefaultAlias() {PackageManager PackageManager = getPackageManager(); ComponentName name1 = new ComponentName(this, "com.fengfeibiao.switchicon.DefaultAliasActivity"); packageManager.setComponentEnabledSetting(name1, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); ComponentName name2 = new ComponentName(this, "com.fengfeibiao.switchicon.Alias1Activity"); packageManager.setComponentEnabledSetting(name2, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); } /** * Public void setAlias1() {PackageManager PackageManager = getPackageManager(); ComponentName name1 = new ComponentName(this, "com.fengfeibiao.switchicon.DefaultAliasActivity"); packageManager.setComponentEnabledSetting(name1, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); ComponentName name2 = new ComponentName(this, "com.fengfeibiao.switchicon.Alias1Activity"); packageManager.setComponentEnabledSetting(name2, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); }Copy the code
ForegroundCallbacks listens for switching between the foreground and background of the App
/ * * * listening in Taiwan before and after switching App * / public class ForegroundCallbacks implements Application. ActivityLifecycleCallbacks {public static final long CHECK_DELAY = 500; public static final String TAG = ForegroundCallbacks.class.getName(); public interface Listener { void onForeground(); void onBackground(); } private static ForegroundCallbacks instance; private boolean foreground = false, paused = true; private Handler handler = new Handler(); private List<Listener> listeners = new CopyOnWriteArrayList<Listener>(); private Runnable check; public static ForegroundCallbacks init(Application application) { if (instance == null) { instance = new ForegroundCallbacks(); application.registerActivityLifecycleCallbacks(instance); } return instance; } public static ForegroundCallbacks get(Application application) { if (instance == null) { init(application); } return instance; } public static ForegroundCallbacks get(Context ctx) { if (instance == null) { Context appCtx = ctx.getApplicationContext(); if (appCtx instanceof Application) { init((Application) appCtx); } throw new IllegalStateException( "Foreground is not initialised and " + "cannot obtain the Application object"); } return instance; } public static ForegroundCallbacks get() { if (instance == null) { throw new IllegalStateException( "Foreground is not initialised - invoke " + "at least once with parameterised init/get"); } return instance; } public boolean isForeground() { return foreground; } public boolean isBackground() { return ! foreground; } public void addListener(Listener listener) { listeners.add(listener); } public void removeListener(Listener listener) { listeners.remove(listener); } @Override public void onActivityResumed(Activity activity) { paused = false; boolean wasBackground = ! foreground; foreground = true; if (check ! = null) handler.removeCallbacks(check); if (wasBackground) { Log.d(TAG, "went foreground"); for (Listener l : listeners) { try { l.onForeground(); } catch (Exception exc) { Log.d(TAG, "Listener threw exception! :" + exc.toString()); } } } else { Log.d(TAG, "still foreground"); } } @Override public void onActivityPaused(Activity activity) { paused = true; if (check ! = null) handler.removeCallbacks(check); handler.postDelayed(check = new Runnable() { @Override public void run() { if (foreground && paused) { foreground = false; Log.d(TAG, "went background"); for (Listener l : listeners) { try { l.onBackground(); } catch (Exception exc) { Log.d(TAG, "Listener threw exception! :" + exc.toString()); } } } else { Log.d(TAG, "still foreground"); } } }, CHECK_DELAY); } @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) { } @Override public void onActivityStarted(Activity activity) { } @Override public void onActivityStopped(Activity activity) { } @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) { } @Override public void onActivityDestroyed(Activity activity) { } }Copy the code

You need to call foregroundCallbacks.init (this) in your Application for initialization

Implementation in the MainActivity ForegroundCallbacks. The Listener to listen of App, determine whether switching application icon when in the background

The complete MainActivity code:

public class MainActivity extends AppCompatActivity implements ForegroundCallbacks.Listener { private int position = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Foregroundcallback.get (this).addListener(this); findViewById(R.id.tv_default).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { position = 0; }}); findViewById(R.id.tv_alias1).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { position = 1; }}); } @override protected void onDestroy() {foregroundCallback.get (this).removelistener (this); super.onDestroy(); } @override public void access foreground () {} @override public void access foreground () {} @override public void access foreground () (position == 0) { setDefaultAlias(); } else { setAlias1(); }} /** * Set the default alias to boot */ public void setDefaultAlias() {PackageManager PackageManager = getPackageManager(); ComponentName name1 = new ComponentName(this, "com.fengfeibiao.switchicon.DefaultAliasActivity"); packageManager.setComponentEnabledSetting(name1, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); ComponentName name2 = new ComponentName(this, "com.fengfeibiao.switchicon.Alias1Activity"); packageManager.setComponentEnabledSetting(name2, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); } /** * Public void setAlias1() {PackageManager PackageManager = getPackageManager(); ComponentName name1 = new ComponentName(this, "com.fengfeibiao.switchicon.DefaultAliasActivity"); packageManager.setComponentEnabledSetting(name1, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); ComponentName name2 = new ComponentName(this, "com.fengfeibiao.switchicon.Alias1Activity"); packageManager.setComponentEnabledSetting(name2, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); }}Copy the code
The specific defects are as follows:
  1. Switching icon will shut down the application process, not crash so bugly will not be reported
  2. It takes time to switch icon, and it takes about 10 seconds for some Huawei models, after which it can be opened normally
  3. During icon switching, the application cannot be opened on some models, indicating that the application is not installed

Our company has adopted this scheme and put it into production. If defects are found and solutions are updated in time. If you have any other questions, please leave a message.

Refer to the article

Listening to the App to enter Taiwan before and after 】 【 blog.csdn.net/mapboo/arti…