在之前使用ASE解密ab包时,使用了FileStream这个API,在安卓平台是无法读取的。这是因为安卓平台将streamAssets文件夹下的文件都压缩成了jar包。

当安卓平台下载大版本apk时,需要将StreamAssets目录下的ab包解压到PersistentDatapath再进行解密使用。

解压的流程是:

  1. 先读取Resource文件夹下的ABMD5配置文件,获得需要解压的ab包的信息。

  2. 验证persistentDatapath下的解压路径,看看有没有已经解压过的ab包,对比一下这些解压过的包有没有被修改,最后将需要解压的ab包名都放到一个list当中。

  3. 最后开启一个协程将这些包进行解压,解压利用的核心API是UnityWebRequest.Get,和我们下载热更包一样,这个API也可以处理jar包路径。

    我们之前在安卓端没有加密的时候,使用的是AssetBundle.LoadFromFile,这个API也不用关心jar包问题。

修改HotPatchManger

HotPatchManager进行一些修改,添加一些全局变量和方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
public class HotPatchManager : SingletonPattern<HotPatchManager>
{
//...
private string m_UnpackPath = Application.persistentDataPath + "/Origin";
/// <summary>
/// 需要进行解压的列表
/// </summary>
private List<string> m_UnpackedList = new List<string>();
/// <summary>
/// 原包记录的MD5码
/// </summary>
private Dictionary<string, ABMD5Base> m_PackedMd5 = new Dictionary<string, ABMD5Base>();

private bool m_IsStartUnpack = false;
/// <summary>
/// 是否已经开始解压
/// </summary>
public bool IsStartUnpack => m_IsStartUnpack;
private float m_UnpackSumSize;
/// <summary>
/// 解压文件总大小
/// </summary>
public float UnpackSumSize => m_UnpackSumSize;
private float m_AlreadyUnpackSize;
/// <summary>
/// 已解压文件大小
/// </summary>
public float AlreadyUnpackSize => m_AlreadyUnpackSize;

public void Init(MonoBehaviour mono)
{
m_Mono = mono;
ReadMD5();//+++
}
/// <summary>
/// 读取本地资源MD5码
/// </summary>
void ReadMD5()
{
m_PackedMd5.Clear();
TextAsset md5 = Resources.Load<TextAsset>("ABMD5");
if (md5 == null)
{
Debug.LogError("未读取到本地ABMD5");
return;
}

using (MemoryStream ms = new MemoryStream(md5.bytes))
{
BinaryFormatter bf = new BinaryFormatter();
ABMD5 aBMD5 = bf.Deserialize(ms) as ABMD5;
foreach (ABMD5Base aBMD5Base in aBMD5.ABMD5List)
{
m_PackedMd5.Add(aBMD5Base.Name, aBMD5Base);
}
}
}

/// <summary>
/// 计算需要解压的文件
/// </summary>
/// <returns>是否有需要解压的文件</returns>
public bool ComputeUnpackFile()
{
#if UNITY_ANDROID
if(!Directory.Exists(m_UnpackPath))
{
Directory.CreateDirectory(m_UnpackPath);
}
m_UnpackedList.Clear();
foreach (string fileName in m_PackedMd5.Keys)
{
string filePath = m_UnpackPath + "/" + fileName;
if(File.Exists(filePath))
{
string md5 = MD5Manager.Instance.BuildFileMD5(filePath);
if (m_PackedMd5[fileName].MD5 != md5)
{
m_UnpackedList.Add(fileName);
}
}
else
{
m_UnpackedList.Add(fileName);
}
}
foreach(string fileName in m_UnpackedList)
{
if(m_PackedMd5.ContainsKey(fileName))
{
m_UnpackSumSize += m_PackedMd5[fileName].Size;
}
}
return m_UnpackedList.Count > 0;
#else
return false;
#endif
}
/// <summary>
/// 获取解压进度
/// </summary>
/// <returns>解压进度</returns>
public float GetUnpackProgress()
{
return m_AlreadyUnpackSize / m_UnpackSumSize;
}
/// <summary>
/// 开始解压
/// </summary>
/// <param name="callback">回调</param>
public void StartUnpackFile(Action callback)
{
m_IsStartUnpack = true;
m_Mono.StartCoroutine(UnpackToPersistantDatapath(callback));
}
IEnumerator UnpackToPersistantDatapath(Action callback)
{
foreach (string fileName in m_UnpackedList)
{
UnityWebRequest unityWebRequest = UnityWebRequest.Get(Application.streamingAssetsPath + "/" + fileName);
unityWebRequest.timeout = 30;
yield return unityWebRequest.SendWebRequest();
if(unityWebRequest.result == UnityWebRequest.Result.ConnectionError ||
unityWebRequest.result == UnityWebRequest.Result.DataProcessingError ||
unityWebRequest.result == UnityWebRequest.Result.ProtocolError)
{
Debug.Log("Unpack Error" + unityWebRequest.error);
}
else
{
byte[] bytes = unityWebRequest.downloadHandler.data;
FileTool.CreateFile(m_UnpackPath + "/" + fileName, bytes );
}

if(m_PackedMd5.ContainsKey(fileName))
{
m_AlreadyUnpackSize += m_PackedMd5[fileName].Size;
}

unityWebRequest.Dispose();
}

callback?.Invoke();

m_IsStartUnpack = false;
}
//...
}



BundleEditorReadMD5Calc方法中也读取了ABMD5.bytes文件,在ReadMD5Calc方法中是使用FileStream读取的

解压UI界面

修改HotFixWndAwake方法和OnUpdate方法,这里我们加上宏,在编辑器环境下不解压

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
    public override void OnAwake(params object[] args)
{
m_SumTime = 0;
m_Panel = GameObject.GetComponent<HotFixPanel>();
m_Panel.m_Image.fillAmount = 0;
m_Panel.m_Text.text = string.Format("{0:F}M/S", 0);
HotPatchManager.Instance.ServerInfoError += ServerInfoError;
HotPatchManager.Instance.ItemDownloadError += ItemError;
#if UNITY_EDITOR
StartOnFinish();
#else
if (HotPatchManager.Instance.ComputeUnpackFile())
{
m_Panel.m_ProcessText.text = "解压中......";
HotPatchManager.Instance.StartUnpackFile(() =>
{
HotFix();
m_SumTime = 0;
});
}
else
{
HotFix();
}
#endif
}
//...
public override void OnUpdate()
{
if (HotPatchManager.Instance.IsStartUnpack)//+++
{
m_SumTime += Time.deltaTime;
m_Panel.m_Image.fillAmount = HotPatchManager.Instance.GetUnpackProgress();
float speed = (HotPatchManager.Instance.AlreadyUnpackSize / 1024.0f) / m_SumTime;
m_Panel.m_Text.text = string.Format("{0:F}m/s", speed);
}
if (HotPatchManager.Instance.IsStartDownload)
{
//...
}
}

可以根据需要给HotPatchManger的解压阶段添加错误回调,这里省略了

修改加载路径

ab包解压后,就需要改变安卓的ab包加载路径了

修改AssetBundleManager的ABLoadPath,加上宏,只在安卓平台下修改加载路径

1
2
3
4
5
6
7
8
9
10
11
    protected string ABLoadPath 
{
get
{
#if UNITY_ANDROID
return Application.persistentDataPath + "/Origin/";
#else
return Application.streamingAssetsPath + "/";
#endif
}
}

测试解压

ResourceManagerm_LoadFromAssetBundle设为true。

选择Tools——打AB包,然后进入主工程文件夹,将AssetBundle——Android(这里使用的是安卓工程)下的ab包复制粘贴到——Assets——StreamingAssets文件夹下

打开wamp服务器,再打开游戏,就能看到解压过程,然后到persistentDataPath里面找到Origin文件夹,里面就有了解压后的AB包。

测试热更

可以尝试测试一下热更,要注意要重新打一下热更包,因为之前的热更包是没有加密过的。