撩妹神器,打造圣诞雪花特效附加线上热修复

2024-01-21 19:50

本文主要是介绍撩妹神器,打造圣诞雪花特效附加线上热修复,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

相信去年圣诞节打开过手机淘宝的童鞋都会对当时的特效记忆犹新吧:全屏飘雪,旁边还有个小雪人来控制八音盒背景音乐的播放,让人有种身临其境的感觉,甚至忍不住想狠狠购物了呢(误),大概就是下面这个样子滴:

嗯,确实很炫,那么我们一步步去分析是如何实现的:

一、实现下雪的 View

首先,最上面一层的全屏雪花极有可能是一个顶层的View,而这个View是通过动态加载去控制显示的(不更新淘宝也能看到这个效果)。那么我们先得实现雪花效果的 View,人生苦短,拿来就用。打开 gank.io,搜索"雪花":

看样子第7个库就是我们想要的了,点进源码,直接 download 不解释,记得 star 一个支持作者。那么现在我们的项目中就有一个完整的下雪效果 View 了。

二、实现雪人播放器 View

这个一张雪人图片+一个按钮即可实现,就不多解释了。接下来需要一段圣诞节音频,直接进行在线音频播放无疑是节省空间的好方案。『我的滑板鞋』烘托出的寂寞而甜蜜的氛围无疑是最适合圣诞节的,因此我们得到了『神曲』URL 一枚:

http://cdn.ifancc.com/TomaToDo/bgms/my_hbx.mp3

接下来要找一个小雪人的图片当作播放器的背景,那么阿姆斯特朗...不对,是这个: 

嗯,相当可爱喜庆。那么播放器核心代码如下:

 
  1. package com.kot32.christmasview.player; 
  2.  
  3. import android.content.Context; 
  4. import android.media.AudioManager; 
  5. import android.media.MediaPlayer; 
  6. import android.util.AttributeSet; 
  7. import android.view.View
  8. import android.widget.Toast; 
  9.  
  10. import com.kot32.christmasview.R; 
  11.  
  12. import java.io.IOException; 
  13.  
  14. /** 
  15.  * Created by kot32 on 16/12/8. 
  16.  */ 
  17. public class MyPlayer extends View { 
  18.  
  19.     public MediaPlayer mediaPlayer; 
  20.  
  21.     public MyPlayer(Context context) { 
  22.         super(context); 
  23.         init(); 
  24.     } 
  25.  
  26.     public MyPlayer(Context context, AttributeSet attrs) { 
  27.         super(context, attrs); 
  28.         init(); 
  29.     } 
  30.  
  31.     private void init() { 
  32.         setBackgroundResource(R.drawable.pig); 
  33.         mediaPlayer = new MediaPlayer(); 
  34.         mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); 
  35.         playUrl("http://172.20.248.106/IXC5b415fcacfc3c439e25a3e74533d2239/TomaToDo/bgms/my_hbx.mp3"); 
  36.         Toast.makeText(getContext(), "开始播放", Toast.LENGTH_SHORT).show(); 
  37.         setOnClickListener(new OnClickListener() { 
  38.             @Override 
  39.             public void onClick(View v) { 
  40.                 if (!mediaPlayer.isPlaying()) { 
  41.                     mediaPlayer.start(); 
  42.                     Toast.makeText(getContext(), "继续播放", Toast.LENGTH_SHORT).show(); 
  43.                 } else { 
  44.                     mediaPlayer.pause(); 
  45.                     Toast.makeText(getContext(), "暂停播放", Toast.LENGTH_SHORT).show(); 
  46.                 } 
  47.             } 
  48.         }); 
  49.     } 
  50.  
  51.     public void playUrl(String videoUrl) { 
  52.         try { 
  53.             mediaPlayer.reset(); 
  54.             mediaPlayer.setDataSource(videoUrl); 
  55.             mediaPlayer.prepare();//prepare之后自动播放 
  56.             mediaPlayer.start(); 
  57.         } catch (IllegalArgumentException e) { 
  58.             e.printStackTrace(); 
  59.         } catch (IllegalStateException e) { 
  60.             e.printStackTrace(); 
  61.         } catch (IOException e) { 
  62.             e.printStackTrace(); 
  63.         } 
  64.     } 
  65.  
  66.     @Override 
  67.     protected void onDetachedFromWindow() { 
  68.         super.onDetachedFromWindow(); 
  69.         try { 
  70.             media  

三、动态加载思路

上面基本实现了在本地的雪花以及播放音乐效果,那么在不更新主程序的情况下,如何将这两个View动态加载到主程序当中去呢?

首先我们明白,Android 的DexClassloader 是拥有加载任意APK 中任意类的能力的,只是有以下限制:

  • 加载出的Activity 由于不在宿主 Manifest 文件中声明,因此框架无法找到并初始化这个Activity。
  • 加载出的Activity 不具备生命周期,理由同上。
  • 加载出的类的Resource 文件id 会和主程序混淆在一起。

由于我们只是加载View,并不是加载整个Activity,所以前两个问题并不会遇到,而第三个问题可以想办法解决掉。

在主程序中我们也要做这三件事:

  • 把能够装载View的ViewGroup 的空位留出来
  • 去获取更新的patch包
  • 把View 从apk包中加载出来之后,放进留好的ViewGroup 中。这样一来,不仅是圣诞节,在之后的各种活动上都可以在线去加载活动的View。

四、开始加载

在加载View 之前,首先要意识到这个View 是引用了图片资源的(小猪图片),因此我们要解决资源问题:

 
  1. private void initResource() { 
  2.         Resources resources = getContext().getResources(); 
  3.         try { 
  4.             AssetManager newManager = AssetManager.class.newInstance(); 
  5.             Method addAssetPath = newManager.getClass().getMethod("addAssetPath", String.class); 
  6.             addAssetPath.invoke(newManager, DynamicViewManager.getInstance().getUpdateFileFullPath()); 
  7.             Resources newResources = new Resources(newManager, 
  8.                     resources.getDisplayMetrics(), resources.getConfiguration()); 
  9.             Reflect.onObject(getContext()).set("mResources", newResources); 
  10.         } catch (Exception e) { 
  11.             e.printStackTrace(); 
  12.         } 
  13.     }  

上面代码的作用是:把添加了外部更新包路径的资源管理器赋值给了App原来的资源管理器,也就是说现在可以在宿主中访问插件资源了。

核心加载代码如下:

 
  1. DexClassLoader classLoader = new DexClassLoader(apkFile.getAbsolutePath() 
  2.                     , "dex_out_put_dir" 
  3.                     , null 
  4.                     , getClass().getClassLoader()); 
  5. Class newViewClazz = classLoader.loadClass("view's package name"); 
  6. Constructor con = newViewClazz.getConstructor(Context.class); 
  7. //first use Activity's Resource lie to View 
  8. if (dynamicView == null) { 
  9.     dynamicView = (View) con.newInstance(getContext()); 
  10. //Replace the View's mResources and recovery the Activity's avoid disorder of Resources 
  11. Reflect.onObject(getContext()).set("mResources"null); 
  12. getContext().getResources(); 
  13.  
  14. RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(DisplayUtil.dip2px(getContext(), viewInfo.layoutParams.width), 
  15.          DisplayUtil.dip2px(getContext(), viewInfo.layoutParams.height)); 
  16. layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE); 
  17. addView(dynamicView, layoutParams);  

中间对 mResources 的操作的作用是:将宿主的Activity 的mResources 重置,避免在Activity 中使用资源时和插件冲突。

然而机智的我已经把更新包下载、版本管理、动态加载都封装好了,所以正确的加载方式是:

引用它:https://github.com/kot32go/dynamic-load-view

然后:

1.宿主声明:

 
  1. <RelativeLayout 
  2.     xmlns:android="http://schemas.android.com/apk/res/android" 
  3.     xmlns:app="http://schemas.android.com/apk/res-auto" 
  4.     android:layout_width="match_parent" 
  5.     android:layout_height="match_parent" 
  6.     android:background="@drawable/tb_bg" 
  7.     > 
  8.  
  9.     <com.kot32.dynamicloadviewlibrary.core.DynamicViewGroup 
  10.         android:layout_width="match_parent" 
  11.         android:layout_height="match_parent" 
  12.         app:uuid="activity_frame"
  13.  
  14.         <TextView 
  15.             android:layout_width="wrap_content" 
  16.             android:layout_height="wrap_content" 
  17.             android:text="原始页面" 
  18.             /> 
  19.  
  20.     </com.kot32.dynamicloadviewlibrary.core.DynamicViewGroup> 
  21.  
  22.     <com.kot32.dynamicloadviewlibrary.core.DynamicViewGroup 
  23.         android:layout_width="60dp" 
  24.         android:layout_height="60dp" 
  25.         android:layout_alignParentRight="true" 
  26.         android:layout_centerVertical="true" 
  27.         app:uuid="activity_player"
  28.  
  29.     </com.kot32.dynamicloadviewlibrary.core.DynamicViewGroup> 
  30.  
  31.  
  32. </RelativeLayout>  

以上声明了主界面的布局,当然,在动态加载之前除了原有的"原始页面"TextView,是不会有任何其他东西的,也就是圣诞节来临之前的程序。注意:uuid 会和在线包相匹配。

2.打插件包

其实就是把之前包含了我们所写的两个View(雪花和雪人)的程序打包成apk。可以不签名。

3.把插件包放到服务器

在服务器返回的JSON中声明插件包地址和动态View 的一些参数,这里的演示程序请求地址为:

http://tomatodo.ifancc.com/php/dynamicView.php

返回值为:

 
  1.   "version": 54, 
  2.   "downLoadPath""http://obfgb7oet.bkt.clouddn.com/patch106.apk"
  3.   "fileName""patch106.apk"
  4.   "viewInfo": [ 
  5.     { 
  6.       "packageName""com.kot32.testdynamicviewproject.snow.widgets.SnowingView"
  7.       "uuid""activity_frame"
  8.       "layoutParams": { 
  9.         "width": -1, 
  10.         "height": -1 
  11.       }    }, 
  12.     { 
  13.       "packageName""com.kot32.testdynamicviewproject.player.MyPlayer"
  14.       "uuid""activity_player"
  15.       "layoutParams": { 
  16.         "width": -1, 
  17.         "height": -1 
  18.       }    } 
  19.   ]}  

我们声明了这次在线包的版本,每个View 的包名和布局参数, 以及最重要的 和宿主程序中声明对齐的uuid。

另外,Dynamic-load-view 能够动态加载外部apk中的View以及资源,能够热修复线上View,以及模块化更新。

屏幕截图

特点

  • 插件程序完全独立于宿主。
  • 以 View作为模块进行模块化开发更新。
  • 你也可以把View 铺满整个Activity,相当于更新Activity。
  • 副作用小,没有加载Activity 带来的生命周期等问题。
  • 兼容性好。Android 4.0~6.0 都没有问题。
  • 简单。核心代码不超过400行。可以自行下载源码,修改更新规则。

如何使用

  • 下载库,并作为library 引用。
  • 需要在宿主程序的Application 的onCreat 中初始化,代码如下:.
 
  1. DynamicViewConfig config = new DynamicViewConfig.Builder() 
  2.     .context(this) 
  3.     .getUpdateInfoApi("http://vpscn.ifancc.com/php/dynamicView.php"
  4.     .build(); 
  5. DynamicViewManager.getInstance(config).init();  

getUpdateInfoApi 这个方法需要传入一个API地址,这个API地址给客户端提供更新的信息. 在上面的地址中,服务器返回了下面这样的JSON 串:

 
  1.   "version": 39, 
  2.   "downLoadPath""http://obfgb7oet.bkt.clouddn.com/patch101.apk"
  3.   "fileName""patch101.apk"
  4.   "viewInfo": [ 
  5.     { 
  6.       "packageName""com.kot32.testdynamicviewproject.MyButton"
  7.       "uuid""test"
  8.       "layoutParams": { 
  9.         "width": 100, 
  10.         "height": 100 
  11.       } 
  12.     }, 
  13.     { 
  14.       "packageName""com.kot32.testdynamicviewproject.MyButton1"
  15.       "uuid""test_activity"
  16.       "layoutParams": { 
  17.         "width": -1, 
  18.         "height": -1 
  19.       } 
  20.     } 
  21.   ] 
  22.  

上面的JSON 串定义了本次更新的版本以及更新包的地址,并且提供了对每个View 的详细更新信息。

packageName :插件APK 中View 的完整包名.

uuid : 和宿主程序中待更新 View 相同的 UUID.

layoutParams:布局参数.

你也可以自己修改服务器需要提供的参数,更改com.kot32.dynamicloadviewlibrary.model 包中的模型类即可。

  • 待更新的View 需要xml 布局文件中如下声明.注意uuid 属性必须赋值。更新时会匹配uuid 相同的View。
 
  1. <com.kot32.dynamicloadviewlibrary.core.DynamicViewGroup 
  2.         android:id="@+id/dv" 
  3.         android:layout_width="200dp" 
  4.         android:layout_height="200dp" 
  5.         app:uuid="test" 
  6.         android:layout_centerInParent="true"
  7.  
  8.         <!--default view --> 
  9.         <ImageView 
  10.             android:layout_width="match_parent" 
  11.             android:layout_height="match_parent" 
  12.             android:src="@mipmap/ic_launcher" /> 
  13.  
  14. </com.kot32.dynamicloadviewlibrary.core.DynamicViewGroup>  

对于插件程序,只需要定义View 就好了,之后直接打成APK 包即可。

更多详细信息,请直接下载示例源码查看,源码不多,也很好理解。

缺陷

  • 现在可以加载插件程序中的string和drawable 资源,但是style.xml 和 dimens.xml 的加载还存在一些问题。
  • 插件程序中的资源文件的名字最好不要和主程序中重复。
  • 在插件中访问资源请使用:getContext().getResources()




作者:kot32
来源:51CTO

这篇关于撩妹神器,打造圣诞雪花特效附加线上热修复的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/630644

相关文章

mss32.dll文件丢失怎么办? 电脑提示mss32.dll丢失的多种修复方法

《mss32.dll文件丢失怎么办?电脑提示mss32.dll丢失的多种修复方法》最近,很多电脑用户可能遇到了mss32.dll文件丢失的问题,导致一些应用程序无法正常启动,那么,如何修复这个问题呢... 在电脑常年累月的使用过程中,偶尔会遇到一些问题令人头疼。像是某个程序尝试运行时,系统突然弹出一个错误提

电脑提示找不到openal32.dll文件怎么办? openal32.dll丢失完美修复方法

《电脑提示找不到openal32.dll文件怎么办?openal32.dll丢失完美修复方法》openal32.dll是一种重要的系统文件,当它丢失时,会给我们的电脑带来很大的困扰,很多人都曾经遇到... 在使用电脑过程中,我们常常会遇到一些.dll文件丢失的问题,而openal32.dll的丢失是其中比较

电脑win32spl.dll文件丢失咋办? win32spl.dll丢失无法连接打印机修复技巧

《电脑win32spl.dll文件丢失咋办?win32spl.dll丢失无法连接打印机修复技巧》电脑突然提示win32spl.dll文件丢失,打印机死活连不上,今天就来给大家详细讲解一下这个问题的解... 不知道大家在使用电脑的时候是否遇到过关于win32spl.dll文件丢失的问题,win32spl.dl

电脑提示msvcp90.dll缺少怎么办? MSVCP90.dll文件丢失的修复方法

《电脑提示msvcp90.dll缺少怎么办?MSVCP90.dll文件丢失的修复方法》今天我想和大家分享的主题是关于在使用软件时遇到的一个问题——msvcp90.dll丢失,相信很多老师在使用电脑时... 在计算机使用过程中,可能会遇到 MSVCP90.dll 丢失的问题。MSVCP90.dll 是 Mic

电脑报错cxcore100.dll丢失怎么办? 多种免费修复缺失的cxcore100.dll文件的技巧

《电脑报错cxcore100.dll丢失怎么办?多种免费修复缺失的cxcore100.dll文件的技巧》你是否也遇到过“由于找不到cxcore100.dll,无法继续执行代码,重新安装程序可能会解... 当电脑报错“cxcore100.dll未找到”时,这通常意味着系统无法找到或加载这编程个必要的动态链接库

Spring AI与DeepSeek实战一之快速打造智能对话应用

《SpringAI与DeepSeek实战一之快速打造智能对话应用》本文详细介绍了如何通过SpringAI框架集成DeepSeek大模型,实现普通对话和流式对话功能,步骤包括申请API-KEY、项目搭... 目录一、概述二、申请DeepSeek的API-KEY三、项目搭建3.1. 开发环境要求3.2. mav

mysql线上查询之前要性能调优的技巧及示例

《mysql线上查询之前要性能调优的技巧及示例》文章介绍了查询优化的几种方法,包括使用索引、避免不必要的列和行、有效的JOIN策略、子查询和派生表的优化、查询提示和优化器提示等,这些方法可以帮助提高数... 目录避免不必要的列和行使用有效的JOIN策略使用子查询和派生表时要小心使用查询提示和优化器提示其他常

mysql8.0无备份通过idb文件恢复数据的方法、idb文件修复和tablespace id不一致处理

《mysql8.0无备份通过idb文件恢复数据的方法、idb文件修复和tablespaceid不一致处理》文章描述了公司服务器断电后数据库故障的过程,作者通过查看错误日志、重新初始化数据目录、恢复备... 周末突然接到一位一年多没联系的妹妹打来电话,“刘哥,快来救救我”,我脑海瞬间冒出妙瓦底,电信火苲马扁.

Ubuntu系统怎么安装Warp? 新一代AI 终端神器安装使用方法

《Ubuntu系统怎么安装Warp?新一代AI终端神器安装使用方法》Warp是一款使用Rust开发的现代化AI终端工具,该怎么再Ubuntu系统中安装使用呢?下面我们就来看看详细教程... Warp Terminal 是一款使用 Rust 开发的现代化「AI 终端」工具。最初它只支持 MACOS,但在 20

用Java打造简易计算器的实现步骤

《用Java打造简易计算器的实现步骤》:本文主要介绍如何设计和实现一个简单的Java命令行计算器程序,该程序能够执行基本的数学运算(加、减、乘、除),文中通过代码介绍的非常详细,需要的朋友可以参考... 目录目标:一、项目概述与功能规划二、代码实现步骤三、测试与优化四、总结与收获总结目标:简单计算器,设计