UE4連結Android並呼叫解壓縮zip的介面

螢火啦啦啦發表於2020-12-27

參考的前輩大佬們的文章

正文

建立一個新的UE4外掛/模組

  1. 建立外掛:在專案中點選Plugin並建立一個新的空白外掛,這個不再追誰
  2. 建立模組:如果你想把這個功能合併到某個外掛,可以選擇建立模組,建立新的模組檔案、.build.cs、與模組名稱相同的.h與.cpp檔案放置在目標外掛或專案的source檔案下,最簡單的方法是直接複製一個空白外掛的模組,並更改名稱類名等統一。模組檔案路徑
    注意,模組需要在uplugin或uproject檔案中註冊,我將其填寫 為runtiome型別,載入時間為預設。

新增xml檔案

  1. 建立xml檔案並放置在build檔案目錄的同級位置,並將名稱改為xxx_UPL,所有複製檔案連結java類庫的操作都在這個xml中實現,我的建議是在複製一份引擎外掛AndroidCamera中的xml檔案,其中已經實現好了各種標籤。
    在這裡插入圖片描述
    2.在build.cs中註冊該檔案,載入launch模組,並新增同路徑下的UPL檔案
    在這裡插入圖片描述

新增JAVA程式碼

APL寫法參考了這篇

  • 因為我們這個功能很簡單,不需要link庫,所以只用到了 gameActivityImportAdditions與gameActivityClassAdditions這兩個標籤,前者用於引入包含檔案,後者用來新增函式。
  • 我新增了兩個函式,一個用於Java呼叫C++,一個用於C++呼叫Java子執行緒進行解壓縮並回撥,這裡我遇到了一些問題,我看到某教程的課程名顯示Java可以呼叫C++的類成員函式,我並沒有找到方法(教程好貴捨不得花錢),我這裡只是呼叫了類外函式,並且暫時是準備用一個單例接受回撥實現功能了。
 <!-- imports -->
  <gameActivityImportAdditions>
   <insert>
     import java.io.*;
     import java.util.ArrayList;
     import android.os.AsyncTask;
     import java.util.Collection;
     import java.util.Enumeration;
     import java.util.zip.ZipEntry;
     import java.util.zip.ZipException;
     import java.util.zip.ZipFile;
     import java.util.zip.ZipOutputStream;
     import java.util.zip.ZipInputStream;
   </insert> 
  </gameActivityImportAdditions>

 <!-- Action -->
	<gameActivityClassAdditions>
    <insert>
  <!-- 01 -->
     <!-- Java Jni Call C++  Native-->
      public native void unZipCallBack(boolean value);
   <!-- 02-->
    /**
    * 解壓操作  ******    ******
    *
    * @param zipFileString 被解壓的檔案路徑 sdcard/0/a/b/test.zip
    * @param outPathString 解壓的目的路徑 sdcard/0/a/b
    */
      public void AsyncUnZipFolder(String zipFileString,String outPathString) {
      // 子執行緒執行
      final String fzipFileString = zipFileString;
      final String foutPathString = outPathString;
      Thread otherThread = new Thread(new Runnable() {
      @Override
      public void run()
      {
      FileInputStream fis = null;
      ZipInputStream inZip = null;
      boolean res = true;
      try {
      fis = new FileInputStream (fzipFileString);
      inZip = new ZipInputStream (fis);
      ZipEntry zipEntry; //zip實體
      String szName = "";
      while ((zipEntry = inZip.getNextEntry()) != null) {

      szName = zipEntry.getName();
      if (zipEntry.isDirectory()) {
      szName = szName.substring(0, szName.length() - 1);
      File folder = new File(foutPathString + File.separator + szName);
      folder.mkdirs();
      } else {
      File file = new File(foutPathString + File.separator + szName);
      file.createNewFile();
      FileOutputStream out = new FileOutputStream(file);
      int length;
      byte[] buffer = new byte[1024];
      while ((length = inZip.read(buffer)) != -1) {
      out.write(buffer, 0, length);
      out.flush();
      }
      if (out != null) {
      out.close();
      }
      }
      }
      }
      catch (FileNotFoundException e)
      {
      res = false;
      e.printStackTrace();
      }
      catch (IOException e) {
      res = false;
      e.printStackTrace();
      }
      catch (Exception e)
      {
      res = false;
      e.printStackTrace();
      }
      finally {
      if (fis != null) {
      try
      {
      fis.close();
      }
      catch (IOException e)
      {
      res = false;
      e.printStackTrace();
      }
      }
      if (inZip != null) {
      try
      {
      inZip.close();
      }
      catch (IOException e)
      {
      res = false;
      e.printStackTrace();
      }
      }
      final boolean fres = res;
      runOnUiThread(new Runnable() {
      @Override
      public void run() {
      //呼叫上面宣告的函式進行回撥
      unZipCallBack(fres);
      }
      });
      }
      }
      });
      otherThread.start();
      }
    </insert>
  </gameActivityClassAdditions>

新增C++呼叫和實現

這裡示例用了BlueprintFuncition,實際應該需要一個物件或單例物件來接受回撥。

  • h檔案
#include "Kismet/BlueprintAsyncActionBase.h"
//呼叫用到的JNI標頭檔案
#if PLATFORM_ANDROID
#include "Android/AndroidJNI.h"
#include "Android/AndroidJava.h"
#include "Android/AndroidApplication.h"
#endif
#include "MyBlueprintFunctionLibrary.generated.h"



UCLASS()
class ANDROIDZIP_API UMyBlueprintFunctionLibrary : public UBlueprintFunctionLibrary
{
	GENERATED_BODY()

	UFUNCTION(BlueprintCallable, Category = Compress)
	    static void AsyncUnZip(FString targetPath, FString zipFilePath);
};
  • 對Java的呼叫,因為我所呼叫的函式傳入兩個String變數且無返回值,所以拼寫函式標籤為(Ljava/lang/String;Ljava/lang/String;)V,具體可以參考連結的文章或者百度搜尋JNI函式簽名表。
void UMyBlueprintFunctionLibrary::AsyncUnZip(FString targetPath, FString zipFilePath)
{
#if PLATFORM_ANDROID
	if (JNIEnv* Env = FAndroidApplication::GetJavaEnv())
	{
		jstring jsTargetPath = Env->NewStringUTF(TCHAR_TO_UTF8(*targetPath));
		jstring jsZipFilePath = Env->NewStringUTF(TCHAR_TO_UTF8(*zipFilePath));

		static jmethodID AsyncUnZipFolder = FJavaWrapper::FindMethod(Env, FJavaWrapper::GameActivityClassID, "AsyncUnZipFolder", "(Ljava/lang/String;Ljava/lang/String;)V", false);
		FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, AsyncUnZipFolder, jsZipFilePath, jsTargetPath);
	}
#endif
}
  • native函式的實現
#if PLATFORM_ANDROID
extern "C" 
void  Java_com_epicgames_ue4_GameActivity_unZipCallBack(JNIEnv * LocalJNIEnv, jobject LocalThiz, jboolean value)
{
    bool b = value;
	GEngine->AddOnScreenDebugMessage(-1, 90.f, FColor::Green, FString::Printf(TEXT("JavaCallBack")));
}
#endif

後續

  • 目前來說是會使用一個subsystem接受回撥,之後看情況學習更改
  • 個人感覺合理的設計應該是將其封裝為一個繼承UBlueprintAsyncActionBase的函式節點,在執行到節點時候建立一個臨時物件,接受回撥,呼叫委託後銷燬自己。

相關文章