一、前言
在Android Studio中封装获取应用列表接口,包括应用图标、应用名称以及应用包名等,导出AAR包供Unity调用;Unity端通过C#脚本调用AAR内部封装的接口,并通过UGUI进行应用列表的展示及管理。
二、安卓交互功能封装
通过Android Studio将获取应用列表相关调用接口封装成AAR包,以供后续Unity调用。
1、新建安卓工程
此处不在赘述,参见:安卓WebView的使用
2、创建模块
此处不在赘述,参见:安卓WebView的使用
3、Java类创建
(1)创建方法调用类
此类用于创建供Unity端调用的方法,具体代码如下:
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.provider.Settings;
import java.util.ArrayList;
import java.util.List;
public class ObtainApplicationListUtils {
private final String tag = "ObtainApplicationListUtils";
private Context mContext;
private PackageManager packageManager;
private static ObtainApplicationListUtils mObtainApplicationListUtils = null;
public ObtainApplicationListUtils(Context context) {
this.mContext = context;
packageManager = mContext.getPackageManager();
}
public static ObtainApplicationListUtils getInstance(Context context){
if (mObtainApplicationListUtils == null) {
mObtainApplicationListUtils = new ObtainApplicationListUtils(context);
}
return mObtainApplicationListUtils;
}
//获取所有应用列表
public List<AppInfo> getAllApplicationList(boolean isFilterSystem){
List<AppInfo> allAppInfoList = new ArrayList<>();
if(allAppInfoList != null){
allAppInfoList.clear();
}
AppInfo appInfo = null;
List<PackageInfo> packageInfoList = packageManager.getInstalledPackages(0);
for(PackageInfo packageInfo : packageInfoList){
appInfo = new AppInfo();
appInfo.setAppName(packageManager.getApplicationLabel(packageInfo.applicationInfo).toString());
appInfo.setPackageName(packageInfo.applicationInfo.packageName);
appInfo.setAppIcon(AppIconConverter.getAppIcon(packageInfo,packageManager));
int flags = packageInfo.applicationInfo.flags;
//判断是否是属于系统的应用,过滤系统应用
if((flags & ApplicationInfo.FLAG_SYSTEM) != 0 && isFilterSystem){
}
else{
allAppInfoList.add(appInfo);
}
}
return allAppInfoList;
}
//获取特定应用列表
public List<AppInfo> getSpecificApplicationList(boolean isFilterSystem){
List<AppInfo> specificAppInfoList = new ArrayList<>();
if(specificAppInfoList != null){
specificAppInfoList.clear();
}
AppInfo appInfo = null;
List<PackageInfo> packageInfoList = packageManager.getInstalledPackages(0);
for(PackageInfo packageInfo : packageInfoList){
appInfo = new AppInfo();
appInfo.setAppName(packageManager.getApplicationLabel(packageInfo.applicationInfo).toString());
appInfo.setPackageName(packageInfo.applicationInfo.packageName);
appInfo.setAppIcon(AppIconConverter.getAppIcon(packageInfo,packageManager));
int flags = packageInfo.applicationInfo.flags;
//判断是否是属于系统的应用,过滤系统应用
if((flags & ApplicationInfo.FLAG_SYSTEM) != 0 && isFilterSystem){
}
else{
//过滤其他应用,只保留指定包名格式的应用
if(appInfo.getPackageName().contains("被保留应用的包名前两部分,即com.xx")){
specificAppInfoList.add(appInfo);
}
}
}
return specificAppInfoList;
}
//通过包名打开应用
public void startApp(String packageName) {
try {
Intent intent = packageManager.getLaunchIntentForPackage(packageName);
mContext.startActivity(intent);
} catch (Exception exception) {}
}
//打开系统设置
public void openSystemSettings(){
try {
Intent intent = new Intent(Settings.ACTION_SETTINGS);
mContext.startActivity(intent);
}catch (Exception exception){}
}
}
(2)创建应用图标转换类
获取应用图标后,会得到Drawable格式数据,需要将其转化为Bitmap,之后再次转化为Byte数组,以便后续Unity调用。图标获取注意事项参见:安卓通过包名获取应用信息并打开应用
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import java.io.ByteArrayOutputStream;
public class AppIconConverter {
//获取应用图标
public static byte[] getAppIcon(PackageInfo packageInfo,PackageManager packageManager){
try{
Drawable drawable;
drawable = packageInfo.applicationInfo.loadIcon(packageManager);
Bitmap bitmap = drawableToBitmap(drawable);
byte[] appIcon;
appIcon = bitmapToByte(bitmap);
return appIcon;
}catch (Exception e){
e.printStackTrace();
return null;
}
}
//Drawable转Bitmap
private static Bitmap drawableToBitmap(Drawable drawable){
//取drawable的长宽
int width = drawable.getIntrinsicWidth();
int height = drawable.getIntrinsicHeight();
//取drawable的颜色格式
Bitmap.Config config = Bitmap.Config.ARGB_8888;
//创建对应Bitmap
Bitmap bitmap = Bitmap.createBitmap(width,height,config);
//建立对应Bitmap的画布
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0,0,width,height);
//把drawable内容画到画布中
drawable.draw(canvas);
return bitmap;
}
//Bitmap转Byte
private static byte[] bitmapToByte(Bitmap bitmap){
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
return stream.toByteArray();
}
}
(3)创建应用信息保存类
用于保存应用名称、包名及图标等信息。
public class AppInfo {
public String appName; //应用名称
public String packageName;//应用包名
public byte[] appIcon; //应用图标,这里以字节数组形式存储,便于Unity端进行解析
public AppInfo(){
appName = "";
packageName = "";
appIcon = null;
}
public void setAppName(String _appName){
this.appName = _appName;
}
public void setPackageName(String _packageName){
this.packageName = _packageName;
}
public String getPackageName(){
return this.packageName;
}
public void setAppIcon(byte[] _appIcon){
this.appIcon = _appIcon;
}
}
4、AndroidManifest.xml文件配置
添加一个权限,避免应用获取不全,如下:
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
5、修改Build变量为release
点击Build-->Select Build Variant...,在打开的界面中将模块的Build Variant选择为release。
6、针对AAR包混淆处理
(1)build.gradle修改
在模块中,找到并打开build.gradle文件,将buildTypes中release块的minifyEnabled修改为true。
(2)混淆文件修改
在模块中,找到并打开proguard-rules.pro文件,写入以下内容:
-keep class 包名.ObtainApplicationListUtils{public <methods>;}
-keepclasseswithmembers class 包名.AppInfo{<fields>;}
第一行表示保留ObtainApplicationListUtils类中的public方法不被混淆处理,第二行表示保留AppInfo类中的成员(变量)不被混淆处理。这些都是在Unity端被调用的。
7、构建AAR包
选中模块,点击Build-->Make Module,或者直接Build-->Rebuild Project。
待编译完成后,在模块-->build-->outputs-->aar里便可找到编译好的AAR包。
三、Unity调用
1、AAR包放置
直接将AAR包拖放至Unity的Assets-->Plugins-->Android路径下。
2、创建C#脚本,调用AAR包
(1)调用类
写入以下内容,用来调用AAR包里的函数接口。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ObtainApplicationListManager : MonoBehaviour
{
public GameObject appItemPrefab;//每一个应用信息预制件
public Transform[] appListParent; //应用列表父物体
private AndroidJavaObject getApplicationListUtils;
private List<AppInfo> appInfoLists = new List<AppInfo>();
//应用信息结构体
public struct AppInfo
{
public string appName;
public string packageName;
public byte[] appIcon;
public AppInfo(string _appName,string _packageName,byte[] _appIcon)
{
appName = "";
packageName = "";
appIcon = null;
}
public void SetAppName(string _appName)
{
appName = _appName;
}
public void SetPackageName(string _packageName)
{
packageName = _packageName;
}
public void SetAppIcon(byte[] _appIcon)
{
appIcon = _appIcon;
}
}
private void Awake()
{
AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
AndroidJavaObject currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
AndroidJavaClass getApplicationListUtilsClass = new AndroidJavaClass("包名.ObtainApplicationListUtils");
getApplicationListUtils = getApplicationListUtilsClass.CallStatic<AndroidJavaObject>("getInstance", currentActivity);
}
// Start is called before the first frame update
void Start()
{
//开始默认设置所有应用列表
SetAllApplicationList();
}
IEnumerator AppListSpawn(int action)
{
yield return 0;
//查找已存在的应用列表,若有,销毁
GameObject[] apps;
apps = GameObject.FindGameObjectsWithTag("App");
if (apps != null)
{
for(int i = 0; i < apps.Length; i++)
{
Destroy(apps[i]);
}
apps = null;
}
//在UI界面展示应用列表
for (int i = 0; i < appInfoLists.Count; i++)
{
int index = i;//对于每一个i重新开辟内存空间,在按钮添加委托事件时防止自动闭包
//生成应用UI
GameObject app = Instantiate(appItemPrefab);
app.tag = "App";
app.transform.SetParent(appListParent[action]);
app.transform.localScale = Vector3.one;
//设置应用信息
var appItem = app.GetComponent<AppItem>();
if (appItem != null)
{
//设置应用名称
appItem.appNameText.text = appInfoLists[i].appName;
//设置应用图标
Texture2D texture2D = new Texture2D(100, 100);
texture2D.LoadImage(appInfoLists[i].appIcon);
appItem.appIcon.texture = texture2D;
//设置应用图标点击事件,打开自身
appItem.openAppButton.onClick.AddListener(delegate ()
{
OpenApplication(appInfoLists[index].packageName);
});
}
}
}
/// <summary>
/// 获取除系统应用以外的所有应用列表
/// </summary>
/// <param name="isFilterSystem"> 是否过滤系统应用 </param>
/// <returns></returns>
public List<AppInfo> GetAllApplicationList(bool isFilterSystem)
{
AndroidJavaObject[] appInfoList = getApplicationListUtils.Call<AndroidJavaObject>("getAllApplicationList", isFilterSystem).Call<AndroidJavaObject[]>("toArray");
List<AppInfo> appInfos = new List<AppInfo>();
foreach(AndroidJavaObject obj in appInfoList)
{
appInfos.Add(UnpackAppInfoObj(obj));
}
return appInfos;
}
/// <summary>
/// 获取除系统应用以外的指定应用列表
/// </summary>
/// <param name="isFilterSystem"> 是否过滤系统应用 </param>
/// <returns></returns>
public List<AppInfo> GetSpecificApplicationList(bool isFilterSystem)
{
AndroidJavaObject[] appInfoList = getApplicationListUtils.Call<AndroidJavaObject>("getSpecificApplicationList", isFilterSystem).Call<AndroidJavaObject[]>("toArray");
List<AppInfo> appInfos = new List<AppInfo>();
foreach (AndroidJavaObject obj in appInfoList)
{
appInfos.Add(UnpackAppInfoObj(obj));
}
return appInfos;
}
/// <summary>
/// 应用信息赋值
/// </summary>
/// <param name="appInfoObj"> 安卓端AppInfo </param>
/// <returns></returns>
private AppInfo UnpackAppInfoObj(AndroidJavaObject appInfoObj)
{
AppInfo appInfo = new AppInfo();
appInfo.SetAppName(appInfoObj.Get<string>("appName"));
appInfo.SetPackageName(appInfoObj.Get<string>("packageName"));
appInfo.SetAppIcon(appInfoObj.Get<byte[]>("appIcon"));
return appInfo;
}
/// <summary>
/// 打开应用
/// </summary>
/// <param name="packageName"> 应用包名 </param>
public void OpenApplication(string packageName)
{
getApplicationListUtils.Call("startApp", packageName);
}
/// <summary>
/// 设置除系统应用以外的所有应用列表
/// </summary>
public void SetAllApplicationList()
{
//获取应用列表
appInfoLists = GetAllApplicationList(true);
//在UI界面展示应用列表
StartCoroutine(AppListSpawn(0));
}
/// <summary>
/// 设置除系统应用以外的指定应用列表
/// </summary>
public void SetSpecificApplicationList()
{
//获取应用列表
appInfoLists = GetSpecificApplicationList(true);
//在UI界面展示应用列表
StartCoroutine(AppListSpawn(1));
}
/// <summary>
/// 打开系统设置
/// </summary>
public void OpenSystemSettings()
{
getApplicationListUtils.Call("openSystemSettings");
}
/// <summary>
/// 退出
/// </summary>
public void ExitApp()
{
Application.Quit();
}
}
(2)UI信息类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class AppItem : MonoBehaviour
{
public Text appNameText; //应用名称显示文本
public Button openAppButton;//打开应用按钮
public RawImage appIcon; //应用图标
}
(3)滑动条初始化类
本案例会有两部分View来显示全部应用和指定包名应用,为了防止其中一个View内部的滑动条在展示完另一个View后再次展示时,因之前的滑动操作导致滑动条一开始不在顶部位置的问题,此处对其位置进行初始化,如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class ScrollbarInitialize : MonoBehaviour
{
//public Scrollbar scrollbar;
public RectTransform rectTransform;
private void OnEnable()
{
//scrollbar.value = 0;
rectTransform.anchoredPosition = new Vector2(rectTransform.anchoredPosition.x, 0);
}
}
3、UGUI设计
(1)布局概述
UI布局包含两个Toggle,分别用于调用ObtainApplicationListManager脚本中SetAllApplicationList和SetSpecificApplicationList方法,实现展示全部应用和指定包名应用;
两个Scroll View,作为生成的应用列表的父物体;
一个退出按钮,调用ObtainApplicationListManager脚本中ExitApp方法;
一个系统设置按钮,调用ObtainApplicationListManager脚本中OpenSystemSettings方法,实现打开安卓系统设置。
UI布局如下图所示:
(2)Toggle设置
创建一个空物体,挂载Toggle Group组件,如下图:
可以设置Toggle组件的图片展示,如点击时高亮等。将上方的Toggle Group赋值,在Toggle组件调用执行的方法,如下图所示。
Background的Image组件设置成非高亮图片,这样点击其他Toggle时,自身会根据Toggle组件图片切换里的设置变成非高亮状态。
Background的子物体Checkmark会在自身Toggle被选中时显示出来,所以将其Image组件的图片设置成高亮图片。
(3)Scroll View设置
将Scroll Rect的Horizontal取消勾选,并删除其下的Scrollbar Horizontal。挂载ScrollbarInitialize脚本组件,将其下的Content赋值给RectTransform变量。
给其下的Content物体挂载Grid Layout Group组件和Content Size Fitter组件,根据实际需要设置Grid Layout Group组件的Cell Size、Spacing及Child Alignment,设置Content Size Fitter组件的Vertical Fit。
4、切换至安卓平台,打包apk,安装测试便可
四、AAR包资源下载入口
如果有小伙伴看了以上教程还不知道如何封包,可以直接下载以下AAR包直接使用,里面有插件的包名,替换掉ObtainApplicationListManager脚本里的“包名”便可。