Skip to content

Instantly share code, notes, and snippets.

@sandin
Last active June 29, 2021 09:43
Show Gist options
  • Save sandin/4fd2c1ae42eae61eee44d5c2de9db5de to your computer and use it in GitHub Desktop.
Save sandin/4fd2c1ae42eae61eee44d5c2de9db5de to your computer and use it in GitHub Desktop.
Unity项目接入华为NetworkKit说明
using System;
using System.IO;
using UnityEngine;
#if UNITY_ANDROID
namespace HmsNetworkKitUnofficial
{
/**
* 文件上传/下载任务执行过程中的回调接口。
*
* java class: com.huawei.hms.network.file.download.api.FileRequestCallback
* @see https://developer.huawei.com/consumer/cn/doc/development/HMSCore-References-V5/network-file-api-callback-0000001077821946-V5#ZH-CN_TOPIC_0000001077821946__section668132695614
* @see https://developer.huawei.com/consumer/cn/doc/development/HMSCore-References-V5/network-file-download-filerequestcallback-0000001077628860-V5
*/
public interface FileRequestCallback
{
/**
* 任务开始执行时的回调。
*
* @param request Request的子类(GetRequest或者BodyRequest)对象。
*/
void OnStart(GetRequest request);
/**
* 任务执行进度回调,可以获取上传/下载进度、实时速度等相关信息。
*
* @param request Request的子类(GetRequest或者BodyRequest)对象。
* @param progress 任务进度实体类。
*/
void OnProgress(GetRequest request, Progress progress);
/**
* 任务成功执行完的回调。
*
* @param request Request的子类(GetRequest或者BodyRequest)对象。
* @param response 最终请求结果响应实体类。
*/
void OnSuccess(Response response);
/**
* 任务发生异常时的回调,您主动暂停或取消同样会回调此接口。
*
* @param request Request的子类(GetRequest或者BodyRequest)对象。
* @param exception 上传/下载异常实体类。
* @param response 最终请求结果响应实体类。当网络请求返回前任务出现异常,此参数有为空值的情况。
*/
void OnException(GetRequest request, DownloadException exception, Response response);
}
public class AndroidJavaObjectWrapper : IDisposable
{
protected AndroidJavaObject mJavaObject;
public AndroidJavaObjectWrapper(AndroidJavaObject javaObject, string className = null)
{
mJavaObject = className != null ? CastTo(javaObject, className) : javaObject;
}
public AndroidJavaObject GetJavaObject()
{
return mJavaObject;
}
// Same Java type is generics type, so we need static cast it from "java.lang.Object" to className
// TODO: need to handle Java Cast Exception
protected AndroidJavaObject CastTo(AndroidJavaObject javaObject, string className)
{
using (AndroidJavaClass clazz = new AndroidJavaClass("java.lang.Class"))
{
using (AndroidJavaObject destClass = clazz.CallStatic<AndroidJavaObject>("forName", className))
{
return destClass.Call<AndroidJavaObject>("cast", javaObject);
}
}
}
public void Dispose()
{
if (mJavaObject != null)
{
mJavaObject.Dispose();
}
}
}
/**
* Wrapper for Java class: com.huawei.hms.network.file.download.api.GetRequest
*
* 下载请求实体类:用于设置下载请求相关参数。
* @see https://developer.huawei.com/consumer/cn/doc/development/HMSCore-References-V5/network-file-download-getrequest-0000001077788932-V5
* @see https://developer.huawei.com/consumer/cn/doc/development/HMSCore-References-V5/network-file-request-0000001077340598-V5
*/
public class GetRequest : AndroidJavaObjectWrapper
{
public GetRequest(AndroidJavaObject javaObject) : base(javaObject, "com.huawei.hms.network.file.download.api.GetRequest")
{
}
/**
* 获用请求地址。
*/
public string GetUrl()
{
return mJavaObject.Call<string>("getUrl");
}
/**
* 获取下载文件的本地保存路径。
*/
public string GetFilePath()
{
return mJavaObject.Call<string>("getFilePath");
}
/**
* 获取文件大小。
*/
public long GetFileSize()
{
return mJavaObject.Call<long>("getFileSize");
}
/**
*获取请求ID。
*/
public long GetId()
{
return mJavaObject.Call<long>("getId");
}
/**
*获取请求名称。
*/
public string GetName()
{
return mJavaObject.Call<string>("getName");
}
}
/**
* Wrapper for Java class: com.huawei.hms.network.file.api.Response<GetRequest, File, Closeable>
*
* 文件上传/下载请求结果的实体类。
* @see https://developer.huawei.com/consumer/cn/doc/development/HMSCore-References-V5/network-file-response-0000001077788716-V5
*/
public class Response : AndroidJavaObjectWrapper
{
public Response(AndroidJavaObject javaObject) : base(javaObject, "com.huawei.hms.network.file.api.Response")
{
}
/**
* 获取请求结果响应码。
*/
public int GetCode()
{
return mJavaObject.Call<int>("getCode");
}
/**
* 获取响应的结果信息。
*/
public string GetMessage()
{
return mJavaObject.Call<string>("getMessage");
}
/**
* 获取请求结果内容。
*/
public FileInfo GetContent()
{
using (AndroidJavaObject context = mJavaObject.Call<AndroidJavaObject>("getContent"))
{
if (context != null)
{
using (AndroidJavaObject file = CastTo(context, "java.io.File")) // cast `java.lang.Object` -> `java.io.File`
{
return new FileInfo(file.Call<string>("getAbsolutePath")); // convert Java `java.io.File` -> C# `System.IO.FileInto`
}
}
}
return null;
}
}
/**
* Wrapper for Java class: com.huawei.hms.network.file.api.Progress
*
* 文件上传/下载任务执行过程相关信息实体类。
* @see https://developer.huawei.com/consumer/cn/doc/development/HMSCore-References-V5/network-file-progress-0000001077469134-V5
*/
public class Progress : AndroidJavaObjectWrapper
{
public Progress(AndroidJavaObject javaObject) : base(javaObject, "com.huawei.hms.network.file.api.Progress")
{
}
/**
* 获取当前已上传/下载的文件大小,单位:Byte。
*/
public long GetFinishedSize()
{
return mJavaObject.Call<long>("getFinishedSize");
}
/**
* 获取当前请求进度,范围:[0, 100]。
*/
public int GetProgress()
{
return mJavaObject.Call<int>("getProgress");
}
/**
* 获取实时文件上传/下载的速度,单位:Byte/s。
*/
public long GetSpeed()
{
return mJavaObject.Call<long>("getSpeed");
}
/**
* 获取整个文件的大小,单位:Byte。
*/
public long GetTotalSize()
{
return mJavaObject.Call<long>("getTotalSize");
}
}
/**
* Wrapper for Java class: com.huawei.hms.network.file.api.exception.NetworkException
*
* 文件上传/下载异常基类。
* @see https://developer.huawei.com/consumer/cn/doc/development/HMSCore-References-V5/network-file-exception-networkexception-0000001091598055-V5
*/
public class DownloadException : AndroidJavaObjectWrapper
{
public DownloadException(AndroidJavaObject javaObject) : base(javaObject, "com.huawei.hms.network.file.api.exception.NetworkException")
{
}
/**
* 获取错误信息
*/
public string GetMessage()
{
return mJavaObject.Call<string>("getMessage");
}
/**
* 打印错误堆栈(Java堆栈)
*/
public void printStackTrace()
{
mJavaObject.Call("printStackTrace");
}
}
/**
* Wrapper for Java class: com.huawei.hms.network.file.api.Result
*
* 文件上传/下载相关操作(发起请求、取消请求、暂停下载、继续下载、销毁请求下载缓存和关闭线程池)返回的实体类。
* @see https://developer.huawei.com/consumer/cn/doc/development/HMSCore-References-V5/network-file-result-0000001091587549-V5
*/
public class Result : AndroidJavaObjectWrapper
{
/**
* 表示任务成功的常量。
*/
public static readonly int SUCCESS;
/**
* 表示任务暂停的常量。
*/
public static readonly int PAUSE;
/**
* 表示任务取消的常量
*/
public static readonly int CANCEL;
static Result() {
using (AndroidJavaClass resultClass = new AndroidJavaClass("com.huawei.hms.network.file.api.Result"))
{
SUCCESS = resultClass.GetStatic<int>("SUCCESS");
PAUSE = resultClass.GetStatic<int>("PAUSE");
CANCEL = resultClass.GetStatic<int>("CANCEL");
}
}
public Result(AndroidJavaObject javaObject) : base(javaObject)
{
}
/**
* 获取请求操作的结果码。
*/
public int GetCode()
{
return mJavaObject.Call<int>("getCode");
}
/**
* 获取请求操作的结果信息。
*/
public string GetMessage()
{
return mJavaObject.Call<string>("getMessage");
}
}
/**
* HMS Network Kit Downloader Manager
* @see https://developer.huawei.com/consumer/cn/doc/development/HMSCore-Guides-V5/network-introduction-0000001050440045-V5
*
* Java class: com.huawei.hms.network.file.download.api.DownloadManager
* 下载任务管理实体类。
* @see https://developer.huawei.com/consumer/cn/doc/development/HMSCore-References-V5/network-file-download-downloadmanager-0000001077469352-V5
*/
public class HmsDownloadManager : IDisposable
{
private static bool DEBUG = true;
private const string DOWNLOAD_MANAGER_NAME = "HmsDownloadManager";
private const string CLASS_DOWNLOAD_MANAGER ="com.huawei.hms.network.file.download.api.DownloadManager";
private const string CLASS_DOWNLOAD_MANAGER_BUILDER = "com.huawei.hms.network.file.download.api.DownloadManager$Builder";
private const string CLASS_GET_REQUEST_BUILDER = "com.huawei.hms.network.file.download.api.GetRequest$Builder";
private const string CLASS_CALLBACK = "com.huawei.hms.network.file.api.Callback";
private AndroidJavaClass mDownloadManagerCls = null;
private AndroidJavaClass mDownloadManagerBuilderCls = null;
private AndroidJavaClass mGetRequestBuilderCls = null;
private AndroidJavaClass mCallbackCls = null;
private AndroidJavaObject mDownloadManager = null;
public HmsDownloadManager()
{
mDownloadManagerCls = new AndroidJavaClass(CLASS_DOWNLOAD_MANAGER);
mDownloadManagerBuilderCls = new AndroidJavaClass(CLASS_DOWNLOAD_MANAGER_BUILDER);
mGetRequestBuilderCls = new AndroidJavaClass(CLASS_GET_REQUEST_BUILDER);
mCallbackCls = new AndroidJavaClass(CLASS_CALLBACK);
if (DEBUG)
{
Debug.Log("[HmsDownloadManager] " + CLASS_DOWNLOAD_MANAGER + " " + mDownloadManagerCls);
Debug.Log("[HmsDownloadManager] " + CLASS_DOWNLOAD_MANAGER_BUILDER + " " + mDownloadManagerBuilderCls);
Debug.Log("[HmsDownloadManager] " + CLASS_GET_REQUEST_BUILDER + " " + mGetRequestBuilderCls);
Debug.Log("[HmsDownloadManager] " + CLASS_CALLBACK + " " + mCallbackCls);
}
// TODO: Java class not found will throw AndroidJavaException, how to handle this Java exception in Unity?
}
public void Dispose()
{
if (mDownloadManagerCls != null)
{
mDownloadManagerCls.Dispose();
}
if (mDownloadManagerBuilderCls != null)
{
mDownloadManagerBuilderCls.Dispose();
}
if (mGetRequestBuilderCls != null)
{
mGetRequestBuilderCls.Dispose();
}
if (mCallbackCls != null)
{
mCallbackCls.Dispose();
}
if (mDownloadManager != null)
{
mDownloadManager.Dispose();
}
}
private bool EnsureDownloadManager()
{
if (mDownloadManager == null)
{
mDownloadManager = this.BuildDownloadManager(DOWNLOAD_MANAGER_NAME);
if (DEBUG)
{
Debug.Log("[HmsDownloadManager] create new DownloadManager");
}
}
return mDownloadManager != null;
}
/**
* Build a new DownloadManager
*
* Java code:
* <code>
* DownloadManager downloadManager = new DownloadManager.Builder(name).build(context);
* </code>
*
* @param name name
* @return DownloadManager(Java Object)
*/
private AndroidJavaObject BuildDownloadManager(string name)
{
using (AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
{
using (AndroidJavaObject context = jc.GetStatic<AndroidJavaObject>("currentActivity"))
{
using (AndroidJavaObject builder = new AndroidJavaObject(CLASS_DOWNLOAD_MANAGER_BUILDER, name))
{
if (DEBUG)
{
Debug.Log("DownloadManagerBuilder: " + builder);
}
return builder.Call<AndroidJavaObject>("build", context);
}
}
}
}
/**
* 构建一个GetRequest请求
*
* Java code:
* <code>
* GetRequest getRequest = DownloadManager.newGetRequestBuilder()
* .filePath(downloadFilePath)
* .url(normalUrl)
* .build();
* </code>
*
* @param url input url
* @param filepath output filepath
* @return GetRequest(Java Object)
*/
public GetRequest NewGetRequest(string url, string filepath)
{
using (AndroidJavaObject requestBuilder = new AndroidJavaObject(CLASS_GET_REQUEST_BUILDER))
{
requestBuilder.Call<AndroidJavaObject>("url", url);
requestBuilder.Call<AndroidJavaObject>("filePath", filepath);
return new GetRequest(requestBuilder.Call<AndroidJavaObject>("build"));
}
}
/**
* 发起一次异步的下载任务。
*
* Java code:
* <code>
* Result result = downloadManager.start(getRequest, callback);
* </code>
*
* @param url input url
* @param filepath output filepath
* @param callback download callback
* @return Result or null
*/
public Result Start(GetRequest request, FileRequestCallback callback)
{
if (EnsureDownloadManager())
{
AndroidJavaObject javaResultObj = mDownloadManager.Call<AndroidJavaObject>("start", request.GetJavaObject(), new JavaFileRequestCallback(callback));
Result result = new Result(javaResultObj);
if (DEBUG)
{
Debug.Log("HmsDownloadManager.Start(), url=" + request.GetUrl() + ", filepath=" + request.GetFilePath()
+ ", result=" + (result != null ? result.GetCode() + " " + result.GetMessage() : "Fail"));
}
return result;
}
return null;
}
/**
* 暂停对应ID的任务。
*
* Java code:
* <code>
* Result result = downloadManager.pauseRequest(requestId);
* </code>
*
* @param requestId requestId
* @return Result or null
*/
public Result PauseRequest(long requestId)
{
if (EnsureDownloadManager())
{
AndroidJavaObject javaResultObj = mDownloadManager.Call<AndroidJavaObject>("pauseRequest", requestId);
Result result = new Result(javaResultObj);
if (DEBUG)
{
Debug.Log("HmsDownloadManager.PauseRequest(), requestId=" + requestId
+ ", result=" + (result != null ? result.GetCode() + " " + result.GetMessage() : "Fail"));
}
return result;
}
return null;
}
/**
* 继续对应下载请求的下载任务。
*
* Java code:
* <code>
* Result result = downloadManager.resumeRequest(getRequest, callback);
* </code>
*
* @param url input url
* @param filepath output filepath
* @param callback download callback
* @return Result or null
*/
public Result ResumeRequest(GetRequest request, FileRequestCallback callback)
{
if (EnsureDownloadManager())
{
AndroidJavaObject javaResultObj = mDownloadManager.Call<AndroidJavaObject>("resumeRequest", request, new JavaFileRequestCallback(callback));
Result result = new Result(javaResultObj);
if (DEBUG)
{
Debug.Log("HmsDownloadManager.Start(), url=" + request.GetUrl() + ", filepath=" + request.GetFilePath()
+ ", result=" + (result != null ? result.GetCode() + " " + result.GetMessage() : "Fail"));
}
return result;
}
return null;
}
/**
* Java Callback for DownloadManager#start method
* com.huawei.hms.network.file.download.api.FileRequestCallback implements Callback<GetRequest, File>
* 它会直接将所有Java的Callback都直接回调给Unity的Callback
*/
class JavaFileRequestCallback : AndroidJavaProxy
{
private FileRequestCallback mUnityCallback;
public JavaFileRequestCallback(FileRequestCallback unityCallback)
: base(CLASS_CALLBACK)
{
mUnityCallback = unityCallback;
}
AndroidJavaObject onStart(AndroidJavaObject request)
{
using (var _request = new GetRequest(request))
{
mUnityCallback.OnStart(_request);
}
return request; // TODO: Java接口的onStart是返回request对象的,用于在onStart时修改request对象,Unity接口暂时未提供该功能
}
void onProgress(AndroidJavaObject request, AndroidJavaObject progress)
{
using (var _request = new GetRequest(request))
{
using (var _progress = new Progress(progress))
{
mUnityCallback.OnProgress(_request, _progress);
}
}
}
void onSuccess(AndroidJavaObject response)
{
using (var _response = new Response(response))
{
mUnityCallback.OnSuccess(_response);
}
}
void onException(AndroidJavaObject request, AndroidJavaObject exception, AndroidJavaObject response)
{
using (var _request = new GetRequest(request))
{
using (var _exception = new DownloadException(exception))
{
using (var _response = new Response(response))
{
mUnityCallback.OnException(_request, _exception, _response);
}
}
}
}
}
} // class HmsDownloadManager
} // namespace HmsNetworkKitUnofficial
#endif // UNITY_ANDROID
@sandin
Copy link
Author

sandin commented Jun 29, 2021

Unity项目接入HmsNetworkKit说明

简介

Hms Network Kit是华为的一款网络基础服务套件,官方文档可参考 Network Kit指南 - 业务介绍

因为Network Kit目前只提供Java的接口,官方暂时未提供Unity的插件或接口,因此本文档主要说明如何在Unity项目中接入Network Kit网络库。

注:华为目前有Network Kit库的官方Unity插件开发计划,在官方插件出来之前可暂时先使用本文档的接入方式,官方插件推出后推荐直接使用官方的接入方式。

接入SDK

修改Gradle

因为Hms Network Kit只提供gradle的方式接入,不提供jar包或者aar包,因此必须修改Unity的Gradle配置文件来接入。

Gradle的修改参考的是Hms Network Kit的官方文档:Network Kit指南 - 集成HMS Core SDK

Unity项目修改Gradle配置文件的方法是参考Unity的官方文档:Gradle for Android

这里综合上述两份官方文档,以Unity 2019.4.15f1为例,示范如何修改Gradle来接入华为的Hms Network Kit。

第一步:打开自定义Gradle模板文件

在Unity的菜单[Build] - [Build Settings]中选择Android平台,然后选择[Player Settings] - [Publishing Settings] 下勾选:

  • Custom Launcher Gradle Template (Assets\Plugins\Android\launcherTemplate.gradle)
  • Custom Base Gradle Template (Assets\Plugins\Android\baseProjectTemplate.gralde)

第二步:修改自定义Gradle模板文件

修改Assets\Plugins\Android\baseProjectTemplate.gralde 配置文件,在 allprojects - buildscript - repositoriesallprojects - repositories 节点下增加Hms的Gradle仓库:

// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN

allprojects {
    buildscript {
        repositories {**ARTIFACTORYREPOSITORY**
            google()
            jcenter()
            
             // 配置HMS Core SDK的Maven仓地址。
            maven {url 'https://developer.huawei.com/repo/'}
        }
        
        // ...
    }

    repositories {**ARTIFACTORYREPOSITORY**
        google()
        jcenter()
        flatDir {
            dirs "${project(':unityLibrary').projectDir}/libs"
        }
        
         // 配置HMS Core SDK的Maven仓地址。
        maven {url 'https://developer.huawei.com/repo/'}
    }
}

// ...

修改 Assets\Plugins\Android\launcherTemplate.gradle 文件,在 dependencies 节点下增加Hms Network Kit库的Gradle依赖:

dependencies {
    implementation project(':unityLibrary')

    // 使用Network Kit的网络请求功能
    implementation 'com.huawei.hms:network-embedded:5.0.3.300'
    // 使用Network Kit的文件上传/下载功能
    implementation 'com.huawei.hms:filemanager:5.0.3.300'

    // 下面这个库不在官方文档里,是华为技术通过微信支持提供的信息:
    // com.android.tools.build:gradle:3.4.0和gradle-5.1.1下必须依赖如下库否则编译不过。
    // 如果在com.android.tools.build:gradle:3.6.0和gradle-5.6.4下则不需要依赖该库
    implementation ('com.huawei.hms:dynamic-api:1.0.16.300') { force=true; }
}

修改AndroidManifest

Hms Network Kit按照官方文档Network Kit指南 - 添加权限, 需要在 AndroidManifest.xml 中增加如下权限,主要是:网络权限和读写SDCard权限。

<!--Network Kit提供的网络请求功能,需要获取网络状态,用于检测当前的网络连接是否有效。-->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission>
<!--Network Kit提供的网络请求功能,需要访问网络。-->
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
<!--(可选)Network Kit提供的网络请求功能,需要获取Wi-Fi状态,用于检测当前Wi-Fi的连接情况,当未获取此权限时,Network Kit的网络探测功能将受到影响。-->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"></uses-permission>

<!--Network Kit提供的文件上传/下载功能,需要有手机内存的读权限。-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"></uses-permission>
<!--Network Kit提供的文件上传/下载功能,需要有手机内存的写权限。-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>

注:如果没有将需要下载的文件保存到SDCard,则不需要申请读写SDCard权限,但如果有需要将文件下载到SDCard目录,则不仅仅需要在AndroidManifest.xml中静态申请权限,还需要动态申请该权限。可参考Android官方文档 Android开发者文档-请求应用权限

调用API

因为Network Kit提供的都是Java API,因此需要通过Unity的 AndroidJavaObject 等API来调用。(注意这个Java API必须在Unity主线程或者Attach到JVM的子线程才能调用,否则会崩溃,具体可参考Unity官方文档AndroidJNI.AttachCurrentThread)

大部分需要使用的Java API都封装成了Unity API,例如华为官方的Java调用方法可参考官网文档Network Kit指南 - 文件上传/下载 该示例中的Java代码转成Unity接口的调用方式示例如下:

HmsDownloadManager hmsDownloadManager = new HmsDownloadManager(); 
GetRequest request = hmsDownloadManager.NewGetRequest(url, filePath);
hmsDownloadManager.Start(request, new MyCallback());

回调函数如下:

class MyCallback : FileRequestCallback
{
    void OnStart(GetRequest request)
    {
        Debug.Log("[HmsDownloadManager] OnStart, request=" + request.GetUrl());
    }

    void OnProgress(GetRequest request, Progress progress)
    {
        Debug.Log("[HmsDownloadManager] OnProgress, request=" + request.GetUrl() + ", progress=" + progress.GetProgress());
    }

    void OnSuccess(Response response)
    {
        Debug.Log("[HmsDownloadManager] OnSuccess, response=" + response);
        if (response.GetContext() != null)
        {
            Debug.Log("[HmsDownloader] OnSuccess, filepath=" + response.GetContext().FullName);
        }
    }

    void OnException(GetRequest request, DownloadException exception, Response response)
    {
        Debug.LogError("[HmsDownloadManager] OnException, request=" + request.GetUrl() + ", exception=" + exception + ", response=" + response);
        if (exception != null)
        {
            Debug.LogError(exception.GetMessage());
            exception.printStackTrace();
        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment