Background demand

Currently, Android APP skin can be broadly divided into two categories:

  • Switch between two sets of themes (day/night, for example) using a switch button.
  • Multiple sets of topics are downloaded and updated online.

The first implementation basically works by setting a local Theme, packaging all the resources into the APP and switching them around by Theme. The second implementation is not possible with the first, because packing all resources into the APP is not flexible enough to update activities, and also makes the APK package larger. All implementations of the second must support online downloads.

Scheme selection

To meet the needs of the product and realize the flexibility of skin change, we choose the second scheme mentioned above. After the previous group discussion of Android and IOS members, we agreed that we could download the compressed package and read the resources through parsing the compressed package to replace it.

How to read resources after downloading the compressed package? There are two ways to do this:

  • Unzip the downloaded skin package and read the picture resources and file resources inside by way of file stream.
  • Load the downloaded skin package into the assetManager manager and create a Resource object through which the control to be skinned can read resources.

In the first way, file streams need to be opened manually, and different file streams have different file streams, such as pictures and text files, etc. In addition, different devices load different resources due to different resolutions, so how to reasonably select the appropriate resources to load is also a problem to be solved.

The second way is to load the skin package into the assetManager manager. The newly generated Resource objects in the assetManager manager are different objects of the same class as the Resource objects in our main project. Resources can be loaded in familiar ways (resource. GetColor, resource. GetDrawable, etc.).

Based on the above two methods of loading resources, select the second method to load and read resources.

Specific implementation

1, download the required skin package to the local network. The skin package here is an APK file. In order to make the APK package small enough, it only contains resource files. There can be multiple skin packages, such as theme1.skin, theme2.skin……

Obtain the name of the skin package to be loaded, such as theme1.skin, by calling the addAssetPath method of the AssetManager object and generating a new Resource object.

AssetManager assetManager = AssetManager.class.newInstance(); // The addAssetPath() method is hidden, so it cannot be accessed directly from the object. Method addAssetPath = assetManager.getClass().getMethod()"addAssetPath"
, String.class);
addAssetPath.invoke(assetManager, skinPath);

Resources skinResource = new Resources(
  assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());
Copy the code

SkinInflaterFactory: onCreateView(View, String, Context, AttributeSet); The properties of the controls to be skinned are parsed and stored, and then the skinned controls are loaded into the Resource object of the second step and set into these controls.

Create a SkinInflaterFactory object in the BaseActivity onCreate method and set the SkinInflateFactory object to the Activity’s LayoutInflater object.

protected void onCreate(@Nullable Bundle savedInstanceState) {
    mSkinInflaterFactory = new SkinInflaterFactory();
    LayoutInflaterCompat.setFactory(
      getLayoutInflater(), mSkinInflaterFactory);
    super.onCreate(savedInstanceState);
}
Copy the code

The flow chart

Other problems

1, how to support control click trigger different business processes? You can customize a property such as skin:click=”@string/clickAction”, main project clickAction=”muapp://app/testDefault”, ClickAction =”muapp://app/testClick” in the skin package triggers a different jump action via the routing mechanism currently in the project. For example, the default jump above is to jump to the Invoke method of the TestDefaultAction class of the main project (app = Module name). This jumps to the Invoke method of the TestClickAction (annotated actionName=”testClick”) class of the main project (app is module name).

2. How to support different behaviors of controls? For example, different animation effects and so on. This problem is similar to the first problem. It can also deal with different behavior modes through the main project and skin package with different tag (String copy).

3, how to deal with the skin needs of custom View? You can add a method that passes in the name of the property (such as background) and the value of the property (such as the Resource ID of the image to which background corresponds) to the custom View to be skinned, and then searches the Resource object of the skin package to see if there is a corresponding replaceable skin or replaceable behavior.