- Android原生库aar导出
使用Androidstudio创建一个空项目,如下,
将项目模式切换成:project模式,方便查看工程结构,如下:
导入unity相关依赖文件
classes.jar
从当前的unity引擎版本目录下找到classes.jar文件,至于用mono 还是 用il2cpp模式的classes.jar,可以根据你最后打包的模式来选,我最后用的打包模式是mono,所有我选的jar包也是mono模式的。
我的电脑下路径:
/Applications/Unity/Hub/Editor/2019.4.30f1c1/PlaybackEngines/AndroidPlayer/Variations/mono/Release/Classes/classes.jar
将classes.jar文件拷贝到Android工程lib目录下面,如下:
然后右键选:Add As Library,弹框中选OK,如下:
此时build文件dependencies节点下会自动添加:implementation files('libs/classes.jar')
注意:我们打包的时候只需要打需要的类文件,其余的杂七杂八的东西不需要打进去,所以,需要将:implementation files('libs/classes.jar') 修改成:compileOnly files('libs/classes.jar'),二者的区别自行百度!!!
添加依赖库: implementation 'androidx.core:core:1.2.0',这个是内容提供者需要的类库
修改后如下:
导入UnityPlayerActivity.java类文件
因为2019.4版本的unity,classes.jar并不包含UnityPlayerActivity.java类,因此需要从安装目录中手动拷贝,这个需要特别注意。我的电脑目录如下:
/Applications/Unity/Hub/Editor/2019.4.30f1c1/PlaybackEngines/AndroidPlayer/Source/com/unity3d/player/UnityPlayerActivity.java
将UnityPlayerActivity.java文件拷贝到如下目录下:
修改MainActivity.java文件如下:
对AndroidManifest.xml文件做修改,MainActivity采用全名称注册,不要用.MainActivity方式,否则后面unity打出的包会提示找不到类文件,这点非常重要!
对build.gradle文件(app目录下)做如下修改,目的是为了能编译出aar包,而不是打出apk文件:
确保两个地方配置保持一致
1.1.2 移除文件及配置移除掉不需要的文件和引用库,箭头所指的目录都移除
将该文件下报红色的配置都移除掉
移除后如下:
1.1.2 实现业务逻辑(安装apk)注册权限:
为MainActivity 设置属性,让其能被unity调用,如下:
注册内容提供者:
特别注意,authorities 必须对应实际包名,我最后用unity打包的包名是:com.test.cc,而不是:com.test.bb ,这个尤其要注意!
对应目录下创建file_paths.xm文件(如果没有目录自行创建),如下:
file_paths.xm文件内容如下:
MainActivity.java代码片段:
package com.test.bb;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.Settings;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import java.io.File;
import java.util.List;
public class MainActivity extends UnityPlayerActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestPermission();
}
private static final int REQUEST_CODE = 1024;
//动态申请权限
private void requestPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// 先判断有没有权限
if (Environment.isExternalStorageManager()) {
writeFile();
} else {
Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
intent.setData(Uri.parse("package:" + this.getPackageName()));
startActivityForResult(intent, REQUEST_CODE);
}
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// 先判断有没有权限
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
writeFile();
} else {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE);
}
} else {
writeFile();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CODE) {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
writeFile();
} else {
Log.d("test","---失败");
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (Environment.isExternalStorageManager()) {
writeFile();
} else {
Log.d("test","---失败");
}
}
}
private void writeFile() {
Log.d("test","---成功");
PersimmionsHelper.getPersimmions(this,100);
}
//供unity调用的接口
public void toInstall(){
Log.d("test","---unity 调用java开始安装...");
String apkPath = Environment.getExternalStorageDirectory()+"/Download/aa.apk";
File apkFile = new File(apkPath);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (Build.VERSION.SDK_INT >= 26){//8.0以上
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intent.addCategory("android.intent.category.DEFAULT");
Uri contentUri = FileProvider.getUriForFile(this, "com.test.cc.fileprovider", apkFile);//注意,这里必须是实际包名
intent.setDataAndType(contentUri, this.getContentResolver().getType(contentUri));
List resInfoList = this.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
this.grantUriPermission(resolveInfo.activityInfo.packageName, contentUri,Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
}else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
Uri contentUri = FileProvider.getUriForFile(this, "com.test.cc.fileprovider",apkFile);
intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
} else {
intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
}
startActivity(intent);
}
}
PersimmionsHelper.java代码片段
package com.test.bb;
import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Build;
import java.util.ArrayList;
public class PersimmionsHelper {
private PersimmionsHelper(){}
public static void getPersimmions(Activity activity,int permissionRequestCode) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
ArrayList permissions = new ArrayList();
// 定位精确位置
// if (activity.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// permissions.add(Manifest.permission.ACCESS_FINE_LOCATION);
// }
// if (activity.checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// permissions.add(Manifest.permission.ACCESS_COARSE_LOCATION);
// }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
if (activity.checkSelfPermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION) != PackageManager.PERMISSION_GRANTED) {
permissions.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION);
}
}
// 读写权限
if (PersimmionsHelper.addPermission(activity,permissions, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
System.out.println("=Manifest.permission.WRITE_EXTERNAL_STORAGE Deny==");
}
if (PersimmionsHelper.addPermission(activity,permissions, Manifest.permission.READ_EXTERNAL_STORAGE)) {
System.out.println("=Manifest.permission.READ_EXTERNAL_STORAGE==");
}
if (permissions.size() > 0) {
activity.requestPermissions(permissions.toArray(new String[permissions.size()]), permissionRequestCode);
}
}
}
@TargetApi(23)
private static boolean addPermission(Activity activity,ArrayList permissionsList, String permission) {
// 如果应用没有获得对应权限,则添加到列表中,准备批量申请
if (activity.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
if (activity.shouldShowRequestPermissionRationale(permission)) {
return true;
} else {
permissionsList.add(permission);
return false;
}
} else {
return true;
}
}
}
1.1.3 编译aar文件
编译前先同步一下,如下:
选择rebuild选项,如下:
如果一切顺利,可在如下目录找到aar文件
- unity调用aar接口
新建一个unity普通工程
2.2 引入aar等文件将上面得到的aar文件以及项目中的AndroidManifest.xml 两个文件拷贝到unity的如下目录(如果没有该目录则自行创建):
2.3 修改配置勾选如下选项,
从而自动生成如下文件:
用编辑器打开该文件,添加一行依赖,如下:
2.4 测试按钮UI上创建一个测试按钮
canvas上挂上脚本GameManager(自行创建),按钮添加监听函数,如下:
GameManager脚本代码如下:
using UnityEngine;
public class GameManager : MonoBehaviour
{
///
/// 按钮监听函数,调用Android端接口
///
public void ToInstall() {
print("---》开始安装");
AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
AndroidJavaObject jo = jc.GetStatic("currentActivity");
jo.Call("toInstall");
print("---->执行完毕");
}
}
2.5 打包apk
一定要注意包名设置,设置如下:
- 真机测试
首先将任意一个apk文件(也可以是当前软件的低版本apk)移动到手机sd卡Download目录下,并且命名为:aa.apk
3.2 安装工具包安装工具软件cc.apk,如果在Android6.0以上手机上运行,第一次会提示授权窗口,点击:始终允许,有的Android手机可能需要手动设置cc.apk软件:允许安装未知来源应用 选项。
3.3 测试cc.apk启动之后,点击:测试按钮,如果一切正常,此时系统会调起安装aa.apk的操作界面,此时表示整个功能已实现。
4、开发及测试环境 4.1 开发平台mac pro(苹果笔记本)
4.2 android stuido版本版本:4.1
4.3 unity版本2019.4.30f1c1
4.4 移动设备 4.4.1 设备1品牌及型号:小米5,系统:Android8.0
4.4.2 设备2品牌及型号:红米note8,系统:Android10
4.4.3 设备3品牌及型号:红米11,系统:Android11
5、注意事项 5.1 UnityPlayerActivity文件导入UnityPlayerActivity.java类文件时:
因为2019.4版本的unity,classes.jar并不包含UnityPlayerActivity.java类,因此需要从安装目录中手动拷贝,这个需要特别注意。
5.2 动态权限Android 6.0之后引入了动态权限机制,所以所有的敏感权限都需要动态申请,而不是仅仅在清单文件中配置,这一点非常重要。
5.3 Api差异化不同Android版本,实现相同功能提供的方案不一样,有时候需要对目标系统版本进行判断,这一点在业务逻辑代码中体现得尤为明显。



