I believe that those who opened Taobao on their mobile phones last Christmas will remember the special effects at that time: snow fell all over the screen, and there was a little snowman next to it to control the background music of the music box, which made people feel as if they were in the scene, and even couldn't help but want to shop hard (wrong). It was probably like this: Well, it's really cool, so let's analyze how it is achieved step by step: 1. Implementing the Snow View First of all, the full-screen snowflakes on the top layer are most likely a top-level View, and this View is controlled by dynamic loading (this effect can be seen without updating Taobao). So we must first implement the View with the snowflake effect. Life is short, so use it as it comes. Open gank.io and search for "snowflakes": It seems that the 7th library is what we want. Click into the source code and download it directly. Remember to star it to support the author. Now we have a complete snow effect View in our project. 2. Implement the Snowman Player View This can be achieved with a snowman picture + a button, so I won't explain it in detail. Next, we need a Christmas audio clip, and directly playing the audio clip online is undoubtedly a good solution to save space. The lonely and sweet atmosphere created by "My Skateboard Shoes" is undoubtedly the most suitable for Christmas, so we got a "divine song" URL: http://cdn.ifancc.com/TomaToDo/bgms/my_hbx.mp3 Next, we need to find a picture of a snowman as the background of the player, so Armstrong... No, it's this: [[179119]] Well, it's quite cute and festive. The core code of the player is as follows: - package com.kot32.christmasview.player;
-
- import android.content.Context;
- import android.media.AudioManager;
- import android.media.MediaPlayer;
- import android.util.AttributeSet;
- import android. view . View ;
- import android.widget.Toast;
-
- import com.kot32.christmasview.R;
-
- import java.io.IOException;
-
- /**
- * Created by kot32 on 16/12/8.
- */
- public class MyPlayer extends View {
-
- public MediaPlayer mediaPlayer;
-
- public MyPlayer(Context context) {
- super(context);
- init();
- }
-
- public MyPlayer(Context context, AttributeSet attrs) {
- super(context, attrs);
- init();
- }
-
- private void init() {
- setBackgroundResource(R.drawable.pig);
- mediaPlayer = new MediaPlayer();
- mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
- playUrl( "http://172.20.248.106/IXC5b415fcacfc3c439e25a3e74533d2239/TomaToDo/bgms/my_hbx.mp3" );
- Toast.makeText(getContext(), "Start playing" , Toast.LENGTH_SHORT).show();
- setOnClickListener(new OnClickListener() {
- @Override
- public void onClick( View v) {
- if (!mediaPlayer.isPlaying()) {
- mediaPlayer.start();
- Toast.makeText(getContext(), "Continue playing" , Toast.LENGTH_SHORT).show();
- } else {
- mediaPlayer.pause();
- Toast.makeText(getContext(), "Pause playback" , Toast.LENGTH_SHORT).show();
- }
- }
- });
- }
-
- public void playUrl(String videoUrl) {
- try {
- mediaPlayer.reset();
- mediaPlayer.setDataSource(videoUrl);
- mediaPlayer.prepare (); // Automatically play after prepare
- mediaPlayer.start();
- } catch (IllegalArgumentException e) {
- e.printStackTrace();
- } catch (IllegalStateException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- try {
- media
3. Dynamic loading ideas The above basically realizes the local snowflake and music playing effects. So how to dynamically load these two Views into the main program without updating the main program? First of all, we understand that Android's DexClassloader has the ability to load any class in any APK, but there are the following limitations: - Since the loaded Activity is not declared in the host Manifest file, the framework cannot find and initialize the Activity.
- The loaded Activity does not have a life cycle for the same reason as above.
- The Resource file id of the loaded class will be mixed with the main program.
Since we are only loading the View, not the entire Activity, the first two problems will not be encountered, and the third problem can be solved. We also need to do these three things in the main program: - Leave space for the ViewGroup that can load the View
- To get the updated patch package
- After loading the View from the apk package, put it into the reserved ViewGroup. In this way, you can load the active View online not only on Christmas but also in various subsequent activities.
4. Start loading Before loading the View, we must first realize that this View references image resources (pig images), so we need to solve the resource problem: - private void initResource() {
- Resources resources = getContext().getResources();
- try {
- AssetManager newManager = AssetManager.class.newInstance();
- Method addAssetPath = newManager.getClass().getMethod( "addAssetPath" , String.class);
- addAssetPath.invoke(newManager, DynamicViewManager.getInstance().getUpdateFileFullPath());
- Resources newResources = new Resources(newManager,
- resources.getDisplayMetrics(), resources.getConfiguration());
- Reflect.onObject(getContext()). set ( "mResources" , newResources);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
The purpose of the above code is to assign the resource manager with the external update package path added to the original resource manager of the App, which means that the plug-in resources can now be accessed in the host. The core loading code is as follows: - DexClassLoader classLoader = new DexClassLoader(apkFile.getAbsolutePath()
- , "dex_out_put_dir"
- , null
- , getClass().getClassLoader());
- Class newViewClazz = classLoader.loadClass( "view's package name" );
- Constructor con = newViewClazz.getConstructor(Context.class);
- // first use Activity's Resource lie to View
- if (dynamicView == null ) {
- dynamicView = ( View ) con.newInstance(getContext());
- }
- // Replace the View 's mResources and recover the Activity's avoid disorder of Resources
- Reflect.onObject(getContext()). set ( "mResources" , null );
- getContext().getResources();
-
- RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(DisplayUtil.dip2px(getContext(), viewInfo.layoutParams.width),
- DisplayUtil.dip2px(getContext(), viewInfo.layoutParams.height));
- layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout. TRUE );
- addView(dynamicView, layoutParams);
The purpose of the operation on mResources in the middle is to reset the mResources of the host Activity to avoid conflicts with plug-ins when using resources in the Activity. However, I have cleverly packaged the update package download, version management, and dynamic loading, so the correct way to load is: Refer to it: https://github.com/kot32go/dynamic-load-view Then: 1.Host declaration: - <RelativeLayout
- xmlns:android= "http://schemas.android.com/apk/res/android"
- xmlns:app= "http://schemas.android.com/apk/res-auto"
- android:layout_width= "match_parent"
- android:layout_height= "match_parent"
- android:background= "@drawable/tb_bg"
- >
-
- <com.kot32.dynamicloadviewlibrary.core.DynamicViewGroup
- android:layout_width= "match_parent"
- android:layout_height= "match_parent"
- app:uuid= "activity_frame" >
-
- <TextView
- android:layout_width= "wrap_content"
- android:layout_height= "wrap_content"
- android:text= "Original page"
- />
-
- </com.kot32.dynamicloadviewlibrary.core.DynamicViewGroup>
-
- <com.kot32.dynamicloadviewlibrary.core.DynamicViewGroup
- android:layout_width= "60dp"
- android:layout_height= "60dp"
- android:layout_alignParentRight= "true"
- android:layout_centerVertical= "true"
- app:uuid= "activity_player" >
-
- </com.kot32.dynamicloadviewlibrary.core.DynamicViewGroup>
-
-
- </RelativeLayout>
The above declares the layout of the main interface. Of course, before dynamic loading, there will be nothing except the original "original page" TextView, that is, the program before Christmas. Note: The uuid will match the online package. 2. Install the plugin package In fact, it is to package the program that contains the two Views (snowflake and snowman) we wrote before into an apk. It is not necessary to sign it. 3. Put the plug-in package on the server Declare the plugin package address and some parameters of the dynamic View in the JSON returned by the server. The request address of the demonstration program here is: http://tomatodo.ifancc.com/php/dynamicView.php The return value is: - {
- "version" : 54,
- "downLoadPath" : "http://obfgb7oet.bkt.clouddn.com/patch106.apk" ,
- "fileName" : "patch106.apk" ,
- "viewInfo" : [
- {
- "packageName" : "com.kot32.testdynamicviewproject.snow.widgets.SnowingView" ,
- "uuid" : "activity_frame" ,
- "layoutParams" : {
- "width" : -1,
- "height" : -1
- } },
- {
- "packageName" : "com.kot32.testdynamicviewproject.player.MyPlayer" ,
- "uuid" : "activity_player" ,
- "layoutParams" : {
- "width" : -1,
- "height" : -1
- } }
- ]}
We declare the version of the online package, the package name and layout parameters of each View, and most importantly, the uuid that is aligned with the declaration in the host program. In addition, Dynamic-load-view can dynamically load Views and resources in external apk, hot-fix online Views, and perform modular updates. Screenshots Features - Plug-ins are completely independent of the host.
- Use View as a module for modular development and update.
- You can also fill the entire Activity with View, which is equivalent to updating the Activity.
- There are few side effects and there are no life cycle issues caused by loading the Activity.
- Good compatibility. No problem with Android 4.0~6.0.
- Simple. The core code does not exceed 400 lines. You can download the source code and modify the update rules yourself.
How to use - Download the library and reference it as a library.
- It needs to be initialized in the onCreate of the host program's Application. The code is as follows:
- DynamicViewConfig config = new DynamicViewConfig.Builder()
- .context(this)
- .getUpdateInfoApi( "http://vpscn.ifancc.com/php/dynamicView.php" )
- .build();
- DynamicViewManager.getInstance(config).init();
The getUpdateInfoApi method needs to pass in an API address, which provides updated information to the client. In the above address, the server returns the following JSON string: - {
- "version" : 39,
- "downLoadPath" : "http://obfgb7oet.bkt.clouddn.com/patch101.apk" ,
- "fileName" : "patch101.apk" ,
- "viewInfo" : [
- {
- "packageName" : "com.kot32.testdynamicviewproject.MyButton" ,
- "uuid" : "test" ,
- "layoutParams" : {
- "width" : 100,
- "height" : 100
- }
- },
- {
- "packageName" : "com.kot32.testdynamicviewproject.MyButton1" ,
- "uuid" : "test_activity" ,
- "layoutParams" : {
- "width" : -1,
- "height" : -1
- }
- }
- ]
- }
The JSON string above defines the updated version and the address of the update package, and provides detailed update information for each View. packageName : The full package name of the View in the plugin APK. uuid : The same UUID as the View to be updated in the host application. layoutParams: layout parameters. You can also modify the parameters that the server needs to provide by changing the model class in the com.kot32.dynamicloadviewlibrary.model package. - The View to be updated needs to be declared as follows in the xml layout file. Note that the uuid attribute must be assigned a value. When updating, the View with the same uuid will be matched.
- <com.kot32.dynamicloadviewlibrary.core.DynamicViewGroup
- android:id= "@+id/dv"
- android:layout_width= "200dp"
- android:layout_height= "200dp"
- app:uuid= "test"
- android:layout_centerInParent= "true" >
-
- <!
- <ImageView
- android:layout_width= "match_parent"
- android:layout_height= "match_parent"
- android:src= "@mipmap/ic_launcher" />
-
- </com.kot32.dynamicloadviewlibrary.core.DynamicViewGroup>
For plug-ins, you only need to define the View and then directly package it into an APK package. For more detailed information, please download the sample source code directly. The source code is not much and is easy to understand. defect - Now you can load string and drawable resources in the plug-in, but there are still some problems with the loading of style.xml and dimens.xml.
- It is best not to duplicate the names of resource files in the plug-in program with those in the main program.
- To access resources in a plugin, use: getContext().getResources()
|