virtualapp安装应用流程源码分析

Posted 明月照江江的技术博客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了virtualapp安装应用流程源码分析相关的知识,希望对你有一定的参考价值。

1. HomeActivity 为处理的入口

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) 
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK && data != null) 
            List<AppInfoLite> appList = data.getParcelableArrayListExtra(VCommends.EXTRA_APP_INFO_LIST);
            if (appList != null) 
                for (AppInfoLite info : appList) 
                    mPresenter.addApp(info);
                
            
        
    

调用了 mPresenter.addApp, 这里还是使用了一个MVP的设计模式,对应的是HomePresenterImpl.java

 @Override
    public void addApp(AppInfoLite info) 
        class AddResult 
            private PackageAppData appData;
            private int userId;
            private boolean justEnableHidden;
        
        AddResult addResult = new AddResult();
        VUiKit.defer().when(() -> 
            InstalledAppInfo installedAppInfo = VirtualCore.get().getInstalledAppInfo(info.packageName, 0);
            addResult.justEnableHidden = installedAppInfo != null;
            if (addResult.justEnableHidden) 
                int[] userIds = installedAppInfo.getInstalledUsers();
                int nextUserId = userIds.length;
                /*
                  Input : userIds = 0, 1, 3
                  Output: nextUserId = 2
                 */
                for (int i = 0; i < userIds.length; i++) 
                    if (userIds[i] != i) 
                        nextUserId = i;
                        break;
                    
                
                addResult.userId = nextUserId;

                if (VUserManager.get().getUserInfo(nextUserId) == null) 
                    // user not exist, create it automatically.
                    String nextUserName = "Space " + (nextUserId + 1);
                    VUserInfo newUserInfo = VUserManager.get().createUser(nextUserName, VUserInfo.FLAG_ADMIN);
                    if (newUserInfo == null) 
                        throw new IllegalStateException();
                    
                
                boolean success = VirtualCore.get().installPackageAsUser(nextUserId, info.packageName);
                if (!success) 
                    throw new IllegalStateException();
                
             else 
                InstallResult res = mRepo.addVirtualApp(info);
                if (!res.isSuccess) 
                    throw new IllegalStateException();
                
            
        ).then((res) -> 
            addResult.appData = PackageAppDataStorage.get().acquire(info.packageName);
        ).done(res -> 
            boolean multipleVersion = addResult.justEnableHidden && addResult.userId != 0;
            if (!multipleVersion) 
                PackageAppData data = addResult.appData;
                data.isLoading = true;
                mView.addAppToLauncher(data);
                handleOptApp(data, info.packageName, true);
             else 
                MultiplePackageAppData data = new MultiplePackageAppData(addResult.appData, addResult.userId);
                data.isLoading = true;
                mView.addAppToLauncher(data);
                handleOptApp(data, info.packageName, false);
            
        );
    

这里有一行

InstalledAppInfo installedAppInfo = VirtualCore.get().getInstalledAppInfo(info.packageName, 0);

本质是去访问自定义的VAppManagerService

    public InstalledAppInfo getInstalledAppInfo(String packageName, int flags) 
        synchronized (PackageCacheManager.class) 
            if (packageName != null) 
                PackageSetting setting = PackageCacheManager.getSetting(packageName);
                if (setting != null) 
                    return setting.getAppInfo();
                
            
            return null;
        
    

这里是从自己定义的包缓存信息中查询是否有安装过这个包,(后面再分析是怎么记录安装信息的)
显然现在还没有安装过这个包,那么会走到else里,即执行InstallResult res = mRepo.addVirtualApp(info);

AppRepository.java

    @Override
    public InstallResult addVirtualApp(AppInfoLite info) 
        int flags = InstallStrategy.COMPARE_VERSION | InstallStrategy.SKIP_DEX_OPT;
        if (info.fastOpen) 
            flags |= InstallStrategy.DEPEND_SYSTEM_IF_EXIST;
        
        return VirtualCore.get().installPackage(info.path, flags);
    

VirtualCore.java

    public InstallResult installPackage(String apkPath, int flags) 
        try 
            return getService().installPackage(apkPath, flags);
         catch (RemoteException e) 
            return VirtualRuntime.crash(e);
        
    

那么这里在借助aidl RPC的能力 ,利用VAppManagerService来安装包
VAppManagerService.java(方法有点大,会删除非核心逻辑的代码)

public synchronized InstallResult installPackage(String path, int flags, boolean notify) 
...
        // 真实的apk文件路径
        File packageFile = new File(path);
        if (!packageFile.exists() || !packageFile.isFile()) 
            return InstallResult.makeFailure("Package File is not exist.");
        
        VPackage pkg = null;
        try 
            // 解析apk.生成VPackage对象
            pkg = PackageParserEx.parsePackage(packageFile);
         catch (Throwable e) 
            e.printStackTrace();
        
...
        InstallResult res = new InstallResult();
        res.packageName = pkg.packageName;
...
        // 生成新的安装目录
        //地址大概是/data/data/io.virtualapp/virtual/data/app/应用包名/
        File appDir = VEnvironment.getDataAppPackageDirectory(pkg.packageName);
        // 生成新的so安装目录
        File libDir = new File(appDir, "lib");
...
        // 判断是不是外部安装,比如sdcard安装
        boolean dependSystem = (flags & InstallStrategy.DEPEND_SYSTEM_IF_EXIST) != 0
                && VirtualCore.get().isOutsideInstalled(pkg.packageName);
....
        // 把原应用的lib下的数据copy过来
        NativeLibraryHelperCompat.copyNativeBinaries(new File(path), libDir);

        if (!dependSystem) 
           //假设是基于手机已安装的应用安装,那么就是走的这里
            File privatePackageFile = new File(appDir, "base.apk");
            File parentFolder = privatePackageFile.getParentFile();
            if (!parentFolder.exists() && !parentFolder.mkdirs()) 
                VLog.w(TAG, "Warning: unable to create folder : " + privatePackageFile.getPath());
             else if (privatePackageFile.exists() && !privatePackageFile.delete()) 
                VLog.w(TAG, "Warning: unable to delete file : " + privatePackageFile.getPath());
            
            try 
                //把原package的数据copy过来
                FileUtils.copyFile(packageFile, privatePackageFile);
             catch (IOException e) 
                privatePackageFile.delete();
                return InstallResult.makeFailure("Unable to copy the package file.");
            
            packageFile = privatePackageFile;
        
        if (existOne != null) 
            PackageCacheManager.remove(pkg.packageName);
        
        //修改新路径的权限
        chmodPackageDictionary(packageFile);
       // 生成新包安装的配置信息,用于持久化和后续查询用
        PackageSetting ps;
        if (existSetting != null) 
            ps = existSetting;
         else 
            ps = new PackageSetting();
        
        ps.dependSystem = dependSystem;
        ps.apkPath = packageFile.getPath();
        ps.libPath = libDir.getPath();
        ps.packageName = pkg.packageName;
        //这里为这个app生成一个appId
        ps.appId = VUserHandle.getAppId(mUidSystem.getOrCreateUid(pkg));
        if (res.isUpdate) 
            ps.lastUpdateTime = installTime;
         else 
            ps.firstInstallTime = installTime;
            ps.lastUpdateTime = installTime;
            for (int userId : VUserManagerService.get().getUserIds()) 
                boolean installed = userId == 0;
                ps.setUserState(userId, false/*launched*/, false/*hidden*/, installed);
            
        
        //在/data/data/io.virtualapp/virtual/data/app/应用包名/下持久化一个 package.ini,用于记录VPackage的信息, 下次读取可以直接用
        PackageParserEx.savePackageCache(pkg);
        //缓存一下信息
		PackageCacheManager.put(pkg, ps);
        mPersistenceLayer.save();
        if (!dependSystem) 
            boolean runDexOpt = false;
            if (VirtualRuntime.isArt()) 
                try 
                    ArtDexOptimizer.interpretDex2Oat(ps.apkPath, VEnvironment.getOdexFile(ps.packageName).getPath());
                 catch (IOException e) 
                    e.printStackTrace();
                    runDexOpt = true;
                
             else 
                runDexOpt = true;
            
            if (runDexOpt) 
                try 
                    DexFile.loadDex(ps.apkPath, VEnvironment.getOdexFile(ps.packageName).getPath(), 0).close();
                 catch (IOException e) 
                    e.printStackTrace();
                
            
        
        BroadcastSystem.get().startApp(pkg);
        if (notify) 
            notifyAppInstalled(ps, -1);
        
        res.isSuccess = true;
        return res;
    
可以看到,在VA内部安装虚拟应用,VA主要做了这几件事
  1. 反射创建android.pm.PackageParser实例,解析虚拟应用apk包的四大组件以及其他信息;
  2. 把so库复制到对应包的虚拟路径下;
  3. 保存、持久化部分apk包数据到硬盘内;
  4. 把apk包复制到对应的虚拟路径下;

Android 插件化VirtualApp 源码分析 ( 启动应用源码分析 | HomePresenterImpl 启动应用方法 | VirtualCore 启动插件应用最终方法 )





一、启动应用源码分析




1、HomeActivity 启动应用点击方法


io.virtualapp.home.HomeActivity 页面中 , 点击列表中的应用后 , 可以启动应用 ;

直接到 io.virtualapp.home.adapters.LaunchpadAdapter 适配器中查找点击事件源码 , 调用的是 private OnAppClickListener mAppClickListener; 成员变量的 onAppClick 方法 ;

该成员变量是通过 public void setAppClickListener(OnAppClickListener mAppClickListener) 方法注入的 ;

public class LaunchpadAdapter extends RecyclerView.Adapter<LaunchpadAdapter.ViewHolder> {

    private LayoutInflater mInflater;
    private List<AppData> mList;
    private SparseIntArray mColorArray = new SparseIntArray();
    private OnAppClickListener mAppClickListener;

    public void setAppClickListener(OnAppClickListener mAppClickListener) {
        this.mAppClickListener = mAppClickListener;
    }
    
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.itemView.setBackgroundColor(holder.color);
        holder.itemView.setOnClickListener(v -> {
            if (mAppClickListener != null) {
                mAppClickListener.onAppClick(position, data);
            }
        });
    }
}

2、HomePresenterImpl 启动应用方法


调用 LaunchpadAdapter.setAppClickListener 方法注入应用启动点击事件 ,

public class HomeActivity extends VActivity implements HomeContract.HomeView {

    private static final String TAG = HomeActivity.class.getSimpleName();

    private HomeContract.HomePresenter mPresenter;
    private LaunchpadAdapter mLaunchpadAdapter;

    private void initLaunchpad() {
        mLaunchpadAdapter.setAppClickListener((pos, data) -> {
            if (!data.isLoading()) {
                if (data instanceof AddAppButton) {
                    onAddAppButtonClick();
                }
                mLaunchpadAdapter.notifyItemChanged(pos);
                mPresenter.launchApp(data);
            }
        });
    }
}

应用启动 , 实际上调用的是 io.virtualapp.home.HomePresenterImplpublic void launchApp(AppData data) 方法 ;

class HomePresenterImpl implements HomeContract.HomePresenter {

    @Override
    public void launchApp(AppData data) {
        try {
            if (data instanceof PackageAppData) {
                PackageAppData appData = (PackageAppData) data;
                Log.i("HSL", "PackageAppData : " + appData.toString());
                appData.isFirstOpen = false;
                LoadingActivity.launch(mActivity, appData.packageName, 0);
            } else if (data instanceof MultiplePackageAppData) {
                MultiplePackageAppData multipleData = (MultiplePackageAppData) data;
                Log.i("HSL", "MultiplePackageAppData : " + multipleData.toString());
                multipleData.isFirstOpen = false;
                LoadingActivity.launch(mActivity, multipleData.appInfo.packageName, ((MultiplePackageAppData) data).userId);
            }
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }
}

拦截一下数据 , 上述两个位置打印出来的日志如下 :

I/HSL: PackageAppData : PackageAppData{packageName='kim.hsl.svg', name='SVG', icon=android.graphics.drawable.BitmapDrawable@8d312bf, fastOpen=false, isFirstOpen=false, isLoading=false}


3、VirtualCore 启动插件应用最终方法


最终启动应用的方法是 io.virtualapp.home.LoadingActivity 类中的 public static void launch(Context context, String packageName, int userId) 方法 ;

先从 VirtualCore 中获取启动的 Intent 1 , 然后启动一个 VActivity , 创建 intent 2 , 将 从 VirtualCore 中获取启动的 Intent 放到 intent 2 中 ;

public class LoadingActivity extends VActivity {

    private static final String PKG_NAME_ARGUMENT = "MODEL_ARGUMENT";
    private static final String KEY_INTENT = "KEY_INTENT";
    private static final String KEY_USER = "KEY_USER";
    private PackageAppData appModel;

    public static void launch(Context context, String packageName, int userId) {
        Intent intent = VirtualCore.get().getLaunchIntent(packageName, userId);
        Log.i("HSL", "packageName : " + packageName + " , userId : " + userId);
        if (intent != null) {
            Intent loadingPageIntent = new Intent(context, LoadingActivity.class);
            loadingPageIntent.putExtra(PKG_NAME_ARGUMENT, packageName);
            loadingPageIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            loadingPageIntent.putExtra(KEY_INTENT, intent);
            loadingPageIntent.putExtra(KEY_USER, userId);
            context.startActivity(loadingPageIntent);
        }
    }
}

打印的数据 :

I/HSL: packageName : kim.hsl.svg , userId : 0

以上是关于virtualapp安装应用流程源码分析的主要内容,如果未能解决你的问题,请参考以下文章

Android 插件化VirtualApp 源码分析 ( 安装应用源码分析 | HomePresenterImpl 添加应用 | AppRepository.addVirtualApp )

Android 插件化VirtualApp 源码分析 ( 目前的 API 现状 | 安装应用源码分析 | 安装按钮执行的操作 | 返回到 HomeActivity 执行的操作 )

Android 插件化VirtualApp 源码分析 ( 目前的 API 现状 | 安装应用源码分析 | 安装按钮执行的操作 | 返回到 HomeActivity 执行的操作 )(代码片段

Android 插件化VirtualApp 源码分析 ( 添加应用源码分析 | LaunchpadAdapter 适配器 | 适配器添加元素 | PackageAppData 元素 )

Android 插件化VirtualApp 源码分析 ( 添加应用源码分析 | LaunchpadAdapter 适配器 | 适配器添加元素 | PackageAppData 元素 )(代

Android 插件化VirtualApp 源码分析 ( 启动应用源码分析 | HomePresenterImpl 启动应用方法 | VirtualCore 启动插件应用最终方法 )