3

1. Background

Recently, Weibo officially released a new function, which can dynamically change the display icon style of Weibo in the App settings. According to the official statement of Weibo, in addition to the most original icons, Weibo has also launched 10 different styles, including 3D Weibo, Colorful Weibo and other new styles that retain the shape of the eyes, as well as Cheese Sweet, Chocolate and other "new flavors" named after food, as well as new abstract shapes such as fantasy purple and fantasy starry sky, give Weibo users a variety of freedom of choice.

However, it should be noted that this function is not open to everyone, and only Weibo annual members can enjoy it. In addition, iOS 10.3 and above and Android 10 and above system versions support this function, but iPad and OnePlus 8 Pro mobile phones cannot use this function. Due to system differences in some mobile phones, this function will be unavailable, and Weibo will further optimize this function in the future.
image.png

2. Technical realization

In fact, in the final analysis, the above functions use the technology of dynamically changing desktop icons. If many years ago, switching icons was still a fashionable technology, then we can directly use PackageManager to dynamically change desktop icons.

The details of the implementation are to use labels to prepare multiple Activity entries in the Manifest file, each activity points to the entry Activity, and set a separate icon and application name for each activity with a label, and finally call the SystemService service to kill the launcher, and Execute the restart operation of the launcher.

First, we add the following code to the AndroidManifest.xml file:

 <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.xzh.demo">
    
    <!-- 权限-->
    <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES"/>
    
    <application
        android:allowBackup="true"
        android:icon="@mipmap/wb_default_logo"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/wb_default_logo"
        android:supportsRtl="true"
        android:theme="@style/Theme.AndroidDemo">
         
       ...//省略其他代码
        
        <!-- 默认微博-->
        <activity-alias
            android:name="com.xzh.demo.default"
            android:targetActivity=".MainActivity"
            android:label="@string/app_name"
            android:enabled="false"
            android:icon="@mipmap/wb_default_logo"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity-alias>
        
        <!-- 3D微博-->
        <activity-alias
            android:name=".threedweibo"
            android:targetActivity=".MainActivity"
            android:label="@string/wb_3d"
            android:enabled="false"
            android:icon="@mipmap/wb_3dweibo"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity-alias>
        
        ... //省略其他
        
    </application>
</manifest>

The properties involved in the above configuration are as follows:

  • android:name: The registered component name, the name of the startup component.
  • android:enabled: Whether to enable this component, that is, whether to display this entry.
  • android:icon: icon
  • android:label: name
  • android:targetActivity: The default activity does not have this attribute. The specified target activity is the same as the name attribute in the default activity, and a corresponding java class file is required.

Next, we trigger the Logo icon replacement logic in MainActivity. The code is as follows:

 class MainActivity : AppCompatActivity() {
    var list: List<LogoBean> = ArrayList()
    var recyclerView: RecyclerView? = null
    var adapter: LogoAdapter? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initView()
        initData()
        initRecycle()
    }
    private fun initView() {
        recyclerView = findViewById(R.id.recycle_view)
    }
    private fun initData() {
        list = Arrays.asList(
            LogoBean(R.mipmap.wb_default_logo, "默认图标", true),
            LogoBean(R.mipmap.wb_3dweibo, "3D微博", false),
            LogoBean(R.mipmap.wb_cheese_sweetheart, "奶酪甜心", false),
            LogoBean(R.mipmap.wb_chocolate_sweetheart, "巧克力", false),
            LogoBean(R.mipmap.wb_clear_colorful, "清透七彩", false),
            LogoBean(R.mipmap.wb_colorful_sunset, "多彩日落", false),
            LogoBean(R.mipmap.wb_colorful_weibo, "炫彩微博", false),
            LogoBean(R.mipmap.wb_cool_pool, "清凉泳池", false),
            LogoBean(R.mipmap.wb_fantasy_purple, "梦幻紫", false),
            LogoBean(R.mipmap.wb_fantasy_starry_sky, "幻想星空", false),
            LogoBean(R.mipmap.wb_hot_weibo, "热感微博", false),
        )
    }
    private fun initRecycle() {
        adapter =LogoAdapter(this,list);
        val layoutManager = GridLayoutManager(this, 3)
        recyclerView?.layoutManager = layoutManager
        recyclerView?.adapter = adapter
        adapter?.setOnItemClickListener(object : OnItemClickListener {
            override fun onItemClick(view: View?, position: Int) {
                 if(position==1){
                     changeLogo("com.xzh.demo.threedweibo")
                 }else if (position==2){
                     changeLogo("com.xzh.demo.cheese")
                 }else if (position==3){
                     changeLogo("com.xzh.demo.chocolate")
                 }else {
                     changeLogo("com.xzh.demo.default")
                 }
            }
        })
    }

    fun changeLogo(name: String) {
        val pm = packageManager
        pm.setComponentEnabledSetting(
            componentName,
            PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP
        )
        pm.setComponentEnabledSetting(
            ComponentName(this, name),
            PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP
        )
        reStartApp(pm)
    }
    fun reStartApp(pm: PackageManager) {
        val am = getSystemService(ACTIVITY_SERVICE) as ActivityManager
        val intent = Intent(Intent.ACTION_MAIN)
        intent.addCategory(Intent.CATEGORY_HOME)
        intent.addCategory(Intent.CATEGORY_DEFAULT)
        val resolveInfos = pm.queryIntentActivities(intent, 0)
        for (resolveInfo in resolveInfos) {
            if (resolveInfo.activityInfo != null) {
                am.killBackgroundProcesses(resolveInfo.activityInfo.packageName)
            }
        }
    }
}

Note that the string in the above changeLogo() method needs to correspond to the name of <activity-alias> in the AndroidManifest.xml file. Run the above code, and then click an icon in the application to change the desktop icon of the application, as shown in the following figure.

image.png

However, some adaptation problems were encountered during testing:

  • Xiaomi Mi 9: When the version is upgraded, the new version deletes A3 in the Android Manifest, the old version switches the icon to A3, and installs the new version directly for uninstalling, and the mobile phone desktop icon disappears.
  • magic 4: When the version is upgraded, the new version deletes A3 in the AndroidManifest, the old version switches the icon to A3, and installs the new version directly for uninstallation, and the mobile phone desktop icon is switched to the default icon, but the APP cannot be opened after clicking.

xiangzhihong
5.9k 声望15.3k 粉丝

著有《React Native移动开发实战》1,2,3、《Kotlin入门与实战》《Weex跨平台开发实战》、《Flutter跨平台开发与实战》1,2和《Android应用开发实战》