Unity调用安卓接口实现获取应用列表

一、前言

        在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脚本里的“包名”便可。

        安卓端获取应用列表AAR插件包

猜你喜欢

转载自blog.csdn.net/qq_40364278/article/details/132171465