Unity はサーバーからリソースをダウンロードし、Android インターフェイスを呼び出して APK のインストールを実装します。

I.はじめに

        Unity の UnityWebRequest クラスを通じてサーバーから APK ファイルをダウンロードし、Android Studio でアプリケーション パッケージ インターフェイスをカプセル化してインストールし、Unity が呼び出すための AAR パッケージをエクスポートし、ブロードキャスト レシーバーを通じてアプリケーションがインストールされているかどうかを監視し、ダウンロードされた APK ファイルを削除します完了後; Unity 端末は、C# スクリプトを通じて AAR 内にカプセル化されたインターフェイスを呼び出します。

2. Androidインタラクティブ機能パッケージ

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 ファイル構成

        権限を追加し、プロバイダー ノードを構成します。エクスポートはプラグイン パッケージであり、アプリケーションに効果的に登録できないため、ブロードキャスト登録はこのファイルに静的に登録されず、コードが動的登録に使用されます。

(1) 権限の追加

<uses-permission android:name="android.permission.REPLACE_EXISTING_PACKAGE"/>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

(2) プロバイダノードの設定

<application>
    <provider
        android:name="androidx.core.content.FileProvider"
        android:authorities="${applicationId}.fileProvider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths"/>
    </provider>
</application>

        ここで、android:authorities の値が、InstallApkUtils クラスの mContext.getPackageName() + ".fileProvider" の値と一致していることを確認する必要があります。これは、${applicationId}.fileProvider を使用することで保証できます。

3. 新しい XML リソース ファイルを作成します

        module -->src-->main の下に新しい res ファイルのパスを作成し、次に res の下に新しい xml ファイルのパスを作成し、最後に xml フォルダーの下に新しい file_paths.xml ファイルを作成し、次の内容を書き込みます。

<?xml version="1.0" encoding="utf-8" ?>
<paths>
    <external-path
        name="apkFiles"
        path="."/>
</paths>

4. AAR パッケージをビルドする

        モジュールを選択し、「ビルド」-->「モジュールの作成」をクリックするか、直接「ビルド」-->「プロジェクトの再構築」をクリックします。

        コンパイルが完了すると、コンパイルされた AAR パッケージが module -->build-->outputs-->aar に表示されます。

3. ユニティコール

1. AAR パッケージの配置

        AAR パッケージを Unity の「アセット」-->「プラグイン」-->「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<in T>(T arg);

    public Slider downloadProgressSlider;//下载进度条
    public Text downloadProgressText;//下载进度显示文本
    public Button downloadButton;//下载按钮

    // Start is called before the first frame update
    void Start()
    {
        DownloadButtonAddListener();
    }

    /// <summary>
    /// 从服务器下载文件
    /// </summary>
    /// <param name="url"> 文件地址 </param>
    /// <param name="fileName"> 文件名 </param>
    /// <param name="downloadCallBack"> 回调函数 </param>
    private void DownloadFileFromServer(string url,string fileName,DownloadCallBack<string> downloadCallBack)
    {
        UnityWebRequest request = UnityWebRequest.Get(url);
        StartCoroutine(DownloadFileFromServer(request, fileName, downloadCallBack));
    }

    /// <summary>
    /// 从服务器下载文件协程
    /// </summary>
    /// <param name="unityWebRequest"> UnityWebRequest </param>
    /// <param name="fileName"> 文件名 </param>
    /// <param name="downloadCallBack"> 回调函数 </param>
    /// <returns></returns>
    IEnumerator DownloadFileFromServer(UnityWebRequest unityWebRequest,string fileName,DownloadCallBack<string> 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>().text = "下载完成";
                downloadButton.onClick.RemoveAllListeners();
            }
        }
    }

    /// <summary>
    /// 保存文件
    /// </summary>
    /// <param name="results"> 下载得到的数据 </param>
    /// <param name="savePath"> 保存路径 </param>
    /// <param name="fileName"> 文件名 </param>
    /// <param name="downloadCallBack"> 回调函数 </param>
    private void SavaFile(byte[] results,string savePath,string fileName,DownloadCallBack<string> 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);
    }

    /// <summary>
    /// 下载按钮添加事件
    /// </summary>
    public void DownloadButtonAddListener()
    {
        downloadButton.onClick.AddListener(delegate ()
        {
            DownloadFileFromServer();
        });
    }

    /// <summary>
    /// 从服务器下载文件
    /// </summary>
    public void DownloadFileFromServer()
    {
        downloadButton.GetComponentInChildren<Text>().text = "下载中...";
        DownloadFileFromServer("http://IP地址及端口/Apk安装包名称.apk", "Apk安装包名称.apk", InstallApk);
    }
}

(2) Androidインターフェースの呼び出し方法

        上記の 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<AndroidJavaObject>("currentActivity");
        AndroidJavaClass installApkUtilsClass = new AndroidJavaClass("包名.InstallApkUtils");
        installApkUtils = installApkUtilsClass.CallStatic<AndroidJavaObject>("getInstance", currentActivity);
    }

    // Start is called before the first frame update
    void Start()
    {
        installApkUtils.Call("registerBroadcast");
    }

    private void OnDestroy()
    {
        installApkUtils.Call("unregisterBroadcast");
    }

    /// <summary>
    /// 安装Apk
    /// </summary>
    /// <param name="filePath"> Apk文件路径 </param>
    public void InstallApk(string filePath)
    {
        installApkUtils.Call("installApk", filePath);
    }
}

        Start メソッドでブロードキャスト レシーバーを登録し、OnDestroy メソッドでブロードキャスト レシーバーの登録を解除します。InstallApk メソッドは、ダウンロード完了のコールバックとして使用される install Apk メソッドを呼び出すために使用され、ダウンロードの完了後にインストールが自動的に呼び出されます。

3.HTTPファイルサーバー

        このプロジェクトでは、HFS を使用してファイル サーバーを作成し、ネットワークからのファイルのダウンロードをテストします。詳細はこちら:HFS公式サイト

        HFS ダウンロードリンク:ダウンロード

        ダウンロード後、exe ファイルは 1 つだけですが、ファイルのインストールを回避するには、適切なパスに配置してください。

        開いたらダウンロードしたいファイルをホームにドラッグ&ドロップするか、ファイル保存フォルダごと直接ドラッグしてダウンロードしたいファイルを選択し、ウィンドウ上部に表示されているアドレスがダウンロードアドレスです。 

4.Unityプロジェクトの設定

        ここがとても重要で、設定しないとAndroidのandroidxが使えなくなります。

(1) 公開設定の内部オプションを確認します。

(2) 設定ファイルを変更する

        上記 2 つのオプションをチェックすると、2 つのファイル、gradleTemplate.properties と mainTemplate.gradle が Unity プロジェクトの Assets-->Plugins-->Android フォルダーに生成されます。これら 2 つのファイルは個別に変更する必要があります。

1) mainTemplate.gradle ファイルを変更します。

        依存関係ブロックにコード行「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 エディターでボタン、スライダー、テキストを作成します。これらはダウンロード方法のバインド、ダウンロードの進行状況バーの表示、ダウンロードの進行状況のパーセンテージの表示にそれぞれ使用されます。 

6. Android プラットフォームに切り替え、apk をパッケージ化し、インストールしてテストします。

4. AAR パッケージリソースのダウンロード入口

        上記のチュートリアルを読んでも使い方がわからない場合は、次の AAR パッケージを直接ダウンロードして使用できます (プラグインのパッケージ名が含まれています)。「パッケージ名」を置き換えるだけです。 ApplicationStoreManager スクリプト内。

        Android 側に APK をインストールし、インストールが完了したら AAR プラグイン パッケージをコールバックします。

おすすめ

転載: blog.csdn.net/qq_40364278/article/details/132321625