Unity从服务器下载资源并调用安卓接口实现Apk安装
创始人
2025-01-19 10:02:56
0

一、前言

        通过Unity的UnityWebRequest类来实现从服务器下载Apk文件;在Android Studio中封装安装应用包接口,导出AAR包供Unity调用,并通过广播接收器监测应用是否安装完成,完成则删除下载的Apk文件;Unity端通过C#脚本调用AAR内部封装的接口。

二、安卓交互功能封装

1、Java类创建

(1)创建方法调用类

        此类用于创建供Unity端调用的方法,具体代码如下:

import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.Uri; import android.os.Build;  import androidx.core.content.FileProvider;  import java.io.File;  public class InstallApkUtils {     private Context mContext;     private static InstallApkUtils mInstallApkUtils = null;     private InitializeApkBroadcastReceiver apkBroadcastReceiver;     public static String apkFilePath;      public InstallApkUtils(Context context) {         this.mContext = context;     }      public static InstallApkUtils getInstance(Context context){         if (mInstallApkUtils == null) {             mInstallApkUtils = new InstallApkUtils(context);         }         return mInstallApkUtils;     }      //安装Apk     public void installApk(String filePath){         apkFilePath = filePath;         File apkFile = new File(filePath);         if(!apkFile.exists()){             return;         }          Intent intent = new Intent(Intent.ACTION_VIEW);         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);           if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){//安卓7.0以上             Uri apkUri = FileProvider.getUriForFile(mContext,mContext.getPackageName() + ".fileProvider",apkFile);             intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);             intent.setDataAndType(apkUri,"application/vnd.android.package-archive");         }         else {//安卓7.0以下             intent.setDataAndType(Uri.parse("file://" + apkFile.toString()),"application/vnd.android.package-archive");         }         if(mContext.getPackageManager().queryIntentActivities(intent,0).size() > 0){             mContext.startActivity(intent);         }     }      //注册广播     public void registerBroadcast(){         if(apkBroadcastReceiver == null){             apkBroadcastReceiver = new InitializeApkBroadcastReceiver();         }         IntentFilter intentFilter = new IntentFilter();         intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);         intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);         intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);         intentFilter.addDataScheme("package");         mContext.registerReceiver(apkBroadcastReceiver,intentFilter);     }      //取消注册广播     public void unregisterBroadcast(){         if(apkBroadcastReceiver != null){             mContext.unregisterReceiver(apkBroadcastReceiver);             apkBroadcastReceiver = null;         }     }      //删除apk文件     public static void removeApkFile(String path){         File apkFile = new File(path);         if(apkFile.isFile() && apkFile.exists()){             apkFile.delete();         }     } }

(2)创建广播接收器

        广播接收器用于监测应用安装包是否安装完成,以便删除安装包。

import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent;  public class InitializeApkBroadcastReceiver extends BroadcastReceiver {     @Override     public void onReceive(Context context, Intent intent) {         if(Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())){             InstallApkUtils.removeApkFile(InstallApkUtils.apkFilePath);         }         if(Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())){          }         if(Intent.ACTION_PACKAGE_REPLACED.equals(intent.getAction())){                      }     } }

2、AndroidManifest.xml文件配置

        添加权限及配置provider节点。广播的注册不在此文件里静态注册,采用代码动态注册,因为导出的是插件包,无法有效针对应用进行注册。

(1)添加权限

   

(2)配置provider节点

                    

        此处,需要保证android:authorities的值与InstallApkUtils类中的mContext.getPackageName() + ".fileProvider"的值保持一致,采用${applicationId}.fileProvider即可保证一致。

3、新建xml资源文件

        在模块-->src-->main下新建res文件路径,然后在res下新建xml文件路径,最后在xml文件夹下新建file_paths.xml文件,写入以下内容。

       

4、构建AAR包

        选中模块,点击Build-->Make Module,或者直接Build-->Rebuild Project。

        待编译完成后,在模块-->build-->outputs-->aar里便可找到编译好的AAR包。

三、Unity调用

1、AAR包放置

        直接将AAR包拖放至Unity的Assets-->Plugins-->Android路径下。

2、创建C#脚本,调用AAR包

(1)从服务器下载方法

using System.Collections; using System.Collections.Generic; using System.IO; using UnityEngine; using UnityEngine.Networking; using UnityEngine.UI;  public class ApplicationStoreManager : MonoBehaviour {     //回调     public delegate void DownloadCallBack();     public delegate void DownloadCallBack(T arg);      public Slider downloadProgressSlider;//下载进度条     public Text downloadProgressText;//下载进度显示文本     public Button downloadButton;//下载按钮      // Start is called before the first frame update     void Start()     {         DownloadButtonAddListener();     }      ///      /// 从服务器下载文件     ///      ///  文件地址      ///  文件名      ///  回调函数      private void DownloadFileFromServer(string url,string fileName,DownloadCallBack downloadCallBack)     {         UnityWebRequest request = UnityWebRequest.Get(url);         StartCoroutine(DownloadFileFromServer(request, fileName, downloadCallBack));     }      ///      /// 从服务器下载文件协程     ///      ///  UnityWebRequest      ///  文件名      ///  回调函数      ///      IEnumerator DownloadFileFromServer(UnityWebRequest unityWebRequest,string fileName,DownloadCallBack downloadCallBack)     {         unityWebRequest.SendWebRequest();         if (unityWebRequest.result == UnityWebRequest.Result.ConnectionError)         {             Debug.Log("Download Error:" + unityWebRequest.error);         }         else         {             //下载进度条显示             while (!unityWebRequest.isDone)             {                 downloadProgressSlider.value = unityWebRequest.downloadProgress;                                  //下载进度转化为百分比数值后保留一位小数                 float progress = Mathf.Round((unityWebRequest.downloadProgress * 100f) * 10f) / 10f;                 downloadProgressText.text = progress.ToString() + "%";                 yield return 0;             }              if (unityWebRequest.isDone)             {                 downloadProgressSlider.value = 1;                 downloadProgressText.text = "100%";                 byte[] results = unityWebRequest.downloadHandler.data;                 string pathUrl = Application.persistentDataPath + "/ApkFile";                 //保存文件                 SavaFile(results, pathUrl, fileName, downloadCallBack);                 //取消下载按钮事件                 downloadButton.GetComponentInChildren().text = "下载完成";                 downloadButton.onClick.RemoveAllListeners();             }         }     }      ///      /// 保存文件     ///      ///  下载得到的数据      ///  保存路径      ///  文件名      ///  回调函数      private void SavaFile(byte[] results,string savePath,string fileName,DownloadCallBack downloadCallBack)     {         if (!File.Exists(savePath))         {             Directory.CreateDirectory(savePath);         }          string path = savePath + "/" + fileName;         FileInfo fileInfo = new FileInfo(path);         Stream stream;         stream = fileInfo.Create();         stream.Write(results, 0, results.Length);         stream.Close();         stream.Dispose();         downloadCallBack(path);     }      ///      /// 下载按钮添加事件     ///      public void DownloadButtonAddListener()     {         downloadButton.onClick.AddListener(delegate ()         {             DownloadFileFromServer();         });     }      ///      /// 从服务器下载文件     ///      public void DownloadFileFromServer()     {         downloadButton.GetComponentInChildren().text = "下载中...";         DownloadFileFromServer("http://IP地址及端口/Apk安装包名称.apk", "Apk安装包名称.apk", InstallApk);     } } 

(2)安卓接口调用方法

        在以上ApplicationStoreManager脚本中增加以下内容。

using System.Collections; using System.Collections.Generic; using System.IO; using UnityEngine; using UnityEngine.Networking; using UnityEngine.UI;  public class ApplicationStoreManager : MonoBehaviour {     private AndroidJavaObject installApkUtils;      private void Awake()     {         AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");         AndroidJavaObject currentActivity = unityPlayer.GetStatic("currentActivity");         AndroidJavaClass installApkUtilsClass = new AndroidJavaClass("包名.InstallApkUtils");         installApkUtils = installApkUtilsClass.CallStatic("getInstance", currentActivity);     }      // Start is called before the first frame update     void Start()     {         installApkUtils.Call("registerBroadcast");     }      private void OnDestroy()     {         installApkUtils.Call("unregisterBroadcast");     }      ///      /// 安装Apk     ///      ///  Apk文件路径      public void InstallApk(string filePath)     {         installApkUtils.Call("installApk", filePath);     } } 

        在Start方法里注册广播接收器,在OnDestroy方法里取消注册广播接收器。InstallApk方法用于调用安装Apk方法,其作为下载完成的回调,下载完成后自动调用安装。

3、Http文件服务器

        本项目采用HFS用来创建文件服务器,来测试从网络下载文件。详见:HFS官网

        HFS下载地址:Download

        下载后只有一个exe文件,为免安装文件,将其放于合适的路径下便可。

        打开后将需要下载的文件拖放至Home内,也可以直接将整个文件保存文件夹拖入,选中要下载的文件,窗口上方显示的地址即为下载地址。 

4、Unity工程设置

        此处至关重要,不设置会导致无法使用安卓的androidx。

(1)勾选Publishing Settings内部选项

(2)修改配置文件

        勾选上以上两个选项后,会在Unity工程的Assets-->Plugins-->Android文件夹内生成gradleTemplate.properties和mainTemplate.gradle两个文件,需要分别修改这两个文件。

1)修改mainTemplate.gradle文件

        在dependencies 块中添加一行代码: implementation 'androidx.appcompat:appcompat:1.2.0’。

dependencies {     implementation fileTree(dir: 'libs', include: ['*.jar'])     implementation 'androidx.appcompat:appcompat:1.2.0' **DEPS**}
2)修改gradleTemplate.properties文件

        在文件最后面添加以下内容:

android.overridePathCheck=true android.useAndroidX=true android.enableJetifier=true

5、UGUI设计

        在Unity编辑器里创建一个Button、Slider及Text,分别用来绑定下载方法、展示下载进度条及展示下载进度百分比。 

6、切换至安卓平台,打包apk,安装测试便可

四、AAR包资源下载入口

        如果有小伙伴看了以上教程还不知道如何使用,可以直接下载以下AAR包直接使用,里面有插件的包名,替换掉ApplicationStoreManager脚本里的“包名”便可。

        安卓端安装Apk及安装完成回调AAR插件包

相关内容

热门资讯

5分钟规律!大亨互娱辅助,小闲... 5分钟规律!大亨互娱辅助,小闲昭通棋牌有挂吗(其实一直都是有挂);1)小闲昭通棋牌有挂吗辅助挂:进一...
实测分享!奥云长岭麻将有挂吗,... 实测分享!奥云长岭麻将有挂吗,宝宝浙江游戏辅助有人在用吗,雀神微信小程序辅助器下载ios(详细教程)...
八分钟辅助挂!星星武汉麻将有老... 八分钟辅助挂!星星武汉麻将有老千吗,上海哈灵的确是有挂的,线上教程(有挂脚本)1、星星武汉麻将有老千...
实测发现!wpk数据分析,逗娱... 实测发现!wpk数据分析,逗娱碰胡外 挂,2025教程(有挂介绍)1.逗娱碰胡外 挂 ai辅助创建新...
两分钟攻略!老友圈手机麻将输赢... 两分钟攻略!老友圈手机麻将输赢规律,哈哈贵阳捉鸡手机麻将有挂吗(原来真的有挂)1、起透看视 哈哈贵阳...
推荐一款!途乐竞技有挂吗,桂林... 推荐一款!途乐竞技有挂吗,桂林字牌十打九赢的方法,广东雀神麻雀辅助软件(详细教程)1、途乐竞技有挂吗...
推荐一款!!微扑克可以用模拟器... 推荐一款!!微扑克可以用模拟器,白金岛自创房有外 挂怎么破,微扑克教程(有挂规律)1、白金岛自创房有...
六分钟辅助挂!蜀山四川麻将输赢... 六分钟辅助挂!蜀山四川麻将输赢规律技巧,欢乐龙城3欢乐斗牛都是真的有挂,必赢方法(有挂辅助)暗藏猫腻...
3分钟技巧!邳州友友麻将有假吗... 3分钟技巧!邳州友友麻将有假吗,微信闽南漳浦麻将有挂吗(其实总是有挂)该软件可以轻松地帮助玩家将微信...
专业讨论!!wpk微扑克最新辅... 专业讨论!!wpk微扑克最新辅助,悠闲麻将川南版有假吗,可靠技巧(有挂工具)1、超多福利:超高返利,...