Android中利用動(dòng)態(tài)加載實(shí)現(xiàn)手機(jī)淘寶的節(jié)日特效
相信去年圣誕節(jié)打開(kāi)過(guò)手機(jī)淘寶的童鞋都會(huì)對(duì)當(dāng)時(shí)的特效記憶猶新吧:全屏飄雪,旁邊還有個(gè)小雪人來(lái)控制八音盒背景音樂(lè)的播放,讓人有種身臨其境的感覺(jué),甚至忍不住想狠狠購(gòu)物了呢(誤),大概就是下面這個(gè)樣子滴:
嗯,確實(shí)很炫,那么我們一步步去分析是如何實(shí)現(xiàn)的:
一、實(shí)現(xiàn)下雪的 View
首先,最上面一層的全屏雪花極有可能是一個(gè)頂層的View,而這個(gè)View是通過(guò)動(dòng)態(tài)加載去控制顯示的(不更新淘寶也能看到這個(gè)效果)。那么我們先得實(shí)現(xiàn)雪花效果的 View,人生苦短,拿來(lái)就用。打開(kāi) gank.io,搜索"雪花":
看樣子第7個(gè)庫(kù)就是我們想要的了,點(diǎn)進(jìn)源碼,直接 download 不解釋?zhuān)浀?star 一個(gè)支持作者。那么現(xiàn)在我們的項(xiàng)目中就有一個(gè)完整的下雪效果 View 了。
二、實(shí)現(xiàn)雪人播放器 View
這個(gè)一張雪人圖片+一個(gè)按鈕即可實(shí)現(xiàn),就不多解釋了。接下來(lái)需要一段圣誕節(jié)音頻,直接進(jìn)行在線(xiàn)音頻播放無(wú)疑是節(jié)省空間的好方案。『我的滑板鞋』烘托出的寂寞而甜蜜的氛圍無(wú)疑是最適合圣誕節(jié)的,因此我們得到了『神曲』URL 一枚:
http://cdn.ifancc.com/TomaToDo/bgms/my_hbx.mp3
接下來(lái)要找一個(gè)小雪人的圖片當(dāng)作播放器的背景,那么阿姆斯特朗...不對(duì),是這個(gè):
嗯,相當(dāng)可愛(ài)喜慶。那么播放器核心代碼如下:
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(), "開(kāi)始播放", Toast.LENGTH_SHORT).show(); setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (!mediaPlayer.isPlaying()) { mediaPlayer.start(); Toast.makeText(getContext(), "繼續(xù)播放", Toast.LENGTH_SHORT).show(); } else { mediaPlayer.pause(); Toast.makeText(getContext(), "暫停播放", Toast.LENGTH_SHORT).show(); } } }); } public void playUrl(String videoUrl) { try { mediaPlayer.reset(); mediaPlayer.setDataSource(videoUrl); mediaPlayer.prepare();//prepare之后自動(dòng)播放 mediaPlayer.start(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalStateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); try { mediaPlayer.stop(); mediaPlayer.release(); }catch (Exception e){ e.printStackTrace(); } } }
三、動(dòng)態(tài)加載思路
上面基本實(shí)現(xiàn)了在本地的雪花以及播放音樂(lè)效果,那么在不更新主程序的情況下,如何將這兩個(gè)View動(dòng)態(tài)加載到主程序當(dāng)中去呢?
首先我們明白,Android 的DexClassloader 是擁有加載任意APK 中任意類(lèi)的能力的,只是有以下限制:
加載出的Activity 由于不在宿主 Manifest 文件中聲明,因此框架無(wú)法找到并初始化這個(gè)Activity。
加載出的Activity 不具備生命周期,理由同上。
加載出的類(lèi)的Resource 文件id 會(huì)和主程序混淆在一起。
由于我們只是加載View,并不是加載整個(gè)Activity,所以前兩個(gè)問(wèn)題并不會(huì)遇到,而第三個(gè)問(wèn)題可以想辦法解決掉。
在主程序中我們也要做這三件事:
把能夠裝載View的ViewGroup 的空位留出來(lái)
去獲取更新的patch包
把View 從apk包中加載出來(lái)之后,放進(jìn)留好的ViewGroup 中。
這樣一來(lái),不僅是圣誕節(jié),在之后的各種活動(dòng)上都可以在線(xiàn)去加載活動(dòng)的View。
四、開(kāi)始加載
在加載View 之前,首先要意識(shí)到這個(gè)View 是引用了圖片資源的(小豬圖片),因此我們要解決資源問(wèn)題:
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(); } }
上面代碼的作用是:把添加了外部更新包路徑的資源管理器賦值給了App原來(lái)的資源管理器,也就是說(shuō)現(xiàn)在可以在宿主中訪(fǎng)問(wèn)插件資源了。
核心加載代碼如下:
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 recovery 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);
中間對(duì) mResources 的操作的作用是:將宿主的Activity 的mResources 重置,避免在Activity 中使用資源時(shí)和插件沖突。
然而機(jī)智的我已經(jīng)把更新包下載、版本管理、動(dòng)態(tài)加載都封裝好了,所以正確的加載方式是:
引用它:https://github.com/kot32go/dynamic-load-view
然后:
1.宿主聲明:
<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="原始頁(yè)面" /> </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>
以上聲明了主界面的布局,當(dāng)然,在動(dòng)態(tài)加載之前除了原有的"原始頁(yè)面"TextView,是不會(huì)有任何其他東西的,也就是圣誕節(jié)來(lái)臨之前的程序。注意:uuid 會(huì)和在線(xiàn)包相匹配。
2.打插件包
其實(shí)就是把之前包含了我們所寫(xiě)的兩個(gè)View(雪花和雪人)的程序打包成apk。可以不簽名。
3.把插件包放到服務(wù)器
在服務(wù)器返回的JSON中聲明插件包地址和動(dòng)態(tài)View 的一些參數(shù),這里的演示程序請(qǐng)求地址為:
http://tomatodo.ifancc.com/php/dynamicView.php
返回值為:
{ "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 } } ] }
我們聲明了這次在線(xiàn)包的版本,每個(gè)View 的包名和布局參數(shù), 以及最重要的 和宿主程序中聲明對(duì)齊的uuid。
以上所述是小編給大家介紹的Android中利用動(dòng)態(tài)加載實(shí)現(xiàn)手機(jī)淘寶的節(jié)日特效,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
- Android實(shí)現(xiàn)Listview異步加載網(wǎng)絡(luò)圖片并動(dòng)態(tài)更新的方法
- Android實(shí)現(xiàn)listview動(dòng)態(tài)加載數(shù)據(jù)分頁(yè)的兩種方法
- Android listview動(dòng)態(tài)加載列表項(xiàng)實(shí)現(xiàn)代碼
- Android應(yīng)用開(kāi)發(fā)中Fragment的靜態(tài)加載與動(dòng)態(tài)加載實(shí)例
- android動(dòng)態(tài)加載布局文件示例
- Android 中動(dòng)態(tài)加載.jar的實(shí)現(xiàn)步驟
相關(guān)文章
Android編程之EditText常見(jiàn)操作示例
這篇文章主要介紹了Android編程之EditText常見(jiàn)操作,結(jié)合實(shí)例形式分析了Android EditText光標(biāo)與文本操作相關(guān)技巧,需要的朋友可以參考下2017-03-03Android 啟動(dòng)第三方程序的方法總結(jié)
這篇文章主要介紹了Android 啟動(dòng)第三方程序的方法總結(jié)的相關(guān)資料,需要的朋友可以參考下2017-04-04Android開(kāi)發(fā)仿bilibili刷新按鈕的實(shí)現(xiàn)代碼
這篇文章主要介紹了Android 仿bilibili刷新按鈕的實(shí)現(xiàn)代碼,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-10-10Android Studio中使用lambda表達(dá)式的方法
這篇文章主要介紹了Android Studio中使用lambda表達(dá)式的方法,需要的朋友可以參考下2017-06-06Android DialogUtils彈出窗工具類(lèi)詳解
這篇文章主要為大家詳細(xì)介紹了Android DialogUtils彈出窗工具類(lèi),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10WorkManager解決應(yīng)用退出后繼續(xù)運(yùn)行后臺(tái)任務(wù)
這篇文章主要為大家介紹了WorkManager解決應(yīng)用退出后繼續(xù)運(yùn)行后臺(tái)任務(wù)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07