Initial Exploration of Taobao atlas Framework atlas-core(2): Bundle Plug-in

Last article< A Preliminary Study of Taobao Atlas Framework Atlas-core (I) > We studied the modular apk: bundle system of Taobao, and the hook system of bundle system.

In this article, we will continue to study the plug-in of the four components of the atlas framework.

In the first analysis, we learned that the bundle architecture was ready for plug-in of components. Now let's look at how atlas uses the bundle mechanism to dynamically load bundles. Let's see how AtlasDemo MainActivity does it.

    private ActivityGroupDelegate mActivityDelegate;
    private ViewGroup mActivityGroupContainer;

    private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
            = new BottomNavigationView.OnNavigationItemSelectedListener() {

        @Override
        public boolean onNavigationItemSelected(@NonNull MenuItem item) {
            switch (item.getItemId()) {
                case R.id.navigation_home:
                    switchToActivity("home","com.taobao.firstbundle.FirstBundleActivity");
                    return true;
                case R.id.navigation_dashboard:
                    switchToActivity("second","com.taobao.secondbundle.SecondBundleActivity");
                    return true;
                case R.id.navigation_notifications:

                    return true;
            }
            return false;
        }

    };


    public void switchToActivity(String key,String activityName){
        Intent intent = new Intent();
        intent.setClassName(getBaseContext(),activityName);
        mActivityDelegate.startChildActivity(mActivityGroupContainer,key,intent);
    }
MainActivity implements Bundle switching through startChildActivity of Activity Group Delegate

    public void startChildActivity(ViewGroup container, String key, Intent intent){
        //Remove all View s from the content section
        container.removeAllViews();
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);

        Activity contentActivity = mLocalActivityManager.getActivity(key);
        if(contentActivity!=null) {
            container.addView(
                    mLocalActivityManager.getActivity(key)
                            .getWindow().getDecorView(),
                    new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                            ViewGroup.LayoutParams.MATCH_PARENT));
            mLocalActivityManager.switchToChildActivity(key);
        }else{
            execStartChildActivityInternal(container, key, intent);
        }
    }

We found the key Local Activity Manager, which is functionally identical to the normal Active Manager.


It manages all the states of all activities and controls the switching between activities. Turn normal remote Binder calls into local administration.

    public void execStartChildActivityInternal(ViewGroup container,String key, Intent intent){
        String packageName = null;
        String componentName = null ;
        Context context = container.getContext();
        if (intent.getComponent() != null) {
            packageName = intent.getComponent().getPackageName();
            componentName = intent.getComponent().getClassName();
        } else {
            ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(intent, 0);
            if (resolveInfo != null && resolveInfo.activityInfo != null) {
                packageName = resolveInfo.activityInfo.packageName;
                componentName = resolveInfo.activityInfo.name;
            }
        }
        if (componentName == null){
            Log.e("ActivityGroupDelegate","can not find componentName");
        }
        if (!StringUtils.equals(context.getPackageName(), packageName)) {
            Log.e("ActivityGroupDelegate","childActivity can not be external Activity");
        }

        String bundleName = AtlasBundleInfoManager.instance().getBundleForComponet(componentName);
        if(!TextUtils.isEmpty(bundleName)){
            BundleImpl impl = (BundleImpl) Atlas.getInstance().getBundle(bundleName);
            if(impl!=null&&impl.checkValidate()) {
                performLaunchChildActivity(container,key,intent);
            }else {
                if(ActivityTaskMgr.getInstance().peekTopActivity()!=null && Looper.getMainLooper().getThread().getId()==Thread.currentThread().getId()) {
                    asyncStartActivity(container,key,bundleName,intent);
                }else{
                    performLaunchChildActivity(container,key,intent);
                }
            }
        }else{
            // Try to get class from system Classloader
            try {
                Class<?> clazz = null;
                clazz = Framework.getSystemClassLoader().loadClass(componentName);
                if (clazz != null) {
                    performLaunchChildActivity(container,key,intent);
                }
            } catch (ClassNotFoundException e) {
                Log.e("ActivityGroupDelegate",e.getCause().toString());
            }
        }

    }

The first time you enter Activity, you should go to execStart Child Activity Internal (container, key, intent).

public String getBundleForComponet(String componentName){
        InitBundleInfoByVersionIfNeed();
        if (mCurrentBundleListing == null ||
                   mCurrentBundleListing.getBundles() == null || 
                   mCurrentBundleListing.getBundles().size() == 0){
            return findBundleByComponentName(componentName);
        }
......
}

 /**
     * Load the list by version
     */
    private synchronized void InitBundleInfoByVersionIfNeed(){
        if(mCurrentBundleListing==null){
            String bundleInfoStr = null;
            try {
                Field field = FrameworkProperties.class.getDeclaredField("bundleInfo");
                field.setAccessible(true);
                bundleInfoStr = (String)field.get(FrameworkProperties.class);
            }catch(Throwable e){
                e.printStackTrace();
            }

            if(!TextUtils.isEmpty(bundleInfoStr)) {
                try {
                    LinkedHashMap<String,BundleListing.BundleInfo> infos = BundleListingUtil.parseArray(bundleInfoStr);
                    BundleListing listing = new BundleListing();
                    listing.setBundles(infos);
                    mCurrentBundleListing = listing;
                }catch(Throwable e){
                    e.printStackTrace();
                }
            }else{
                throw new RuntimeException("read bundleInfo failed");
            }
        }

    }
Loading different listings according to the version allows you to set up large versions of Bundle information in Framework Properties. class

If the BundleList is empty, enter getBundleForComponet of AtlasBundleInfoManager

    private String findBundleByComponentName(String componentClassName){
        getComponentInfoFromManifestIfNeed();
        ComponentName componentName = new ComponentName(RuntimeVariables.androidApplication.getPackageName(),componentClassName);
        if(activityInfos!=null){
            ActivityInfo info = activityInfos.get(componentClassName);
            if(info!=null){
                if(info.metaData!=null){
                    return info.metaData.getString("bundleLocation");
                }else {
                    try {
                        ActivityInfo detailInfo = RuntimeVariables.androidApplication.getPackageManager().getActivityInfo(componentName, PackageManager.GET_META_DATA);
                        if (detailInfo != null && detailInfo.metaData != null) {
                            info.metaData = detailInfo.metaData;
                            return detailInfo.metaData.getString("bundleLocation");
                        } else {
                            return null;
                        }
                    } catch (Throwable e) {
                    }
                }
            }
        }
......
}
getComponemntInfoFromManifestIfNeed gets the information of the four components in Manifest of all corresponding package names, then saves it to AtlasBundleInfoManager locally, and provides it to ActivityGroup Delegate call later. Let's continue to see the manifest of the corresponding Activity.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.taobao.firstbundle">

    <application
        android:allowBackup="true"
        android:supportsRtl="true">
        <activity
            android:name=".FirstBundleActivity"
            android:theme="@style/AppTheme.NoActionBar" />

        <service
            android:name=".FirstBundleService"
            android:enabled="true"
            android:exported="true"></service>
    </application>

</manifest>

There is no "bundleLocation" meta information in demo.

        if(activityInfos!=null){
            ActivityInfo info = activityInfos.get(componentClassName);
            if(info!=null){
                if(info.metaData!=null){
                    return info.metaData.getString("bundleLocation");
                }else {
                    try {
                        ActivityInfo detailInfo = RuntimeVariables.androidApplication.getPackageManager().getActivityInfo(componentName, PackageManager.GET_META_DATA);
                        if (detailInfo != null && detailInfo.metaData != null) {
                            info.metaData = detailInfo.metaData;
                            return detailInfo.metaData.getString("bundleLocation");
                        } else {
                            return null;
                        }
                    } catch (Throwable e) {
                    }
                }
            }
        }

Look at the process, without the meta-value bundleLocation, the activity of bundles can not be started, so this bundle value may be set dynamically at compile time. We are continuing to analyze this bundleLocation in the update section, and we will continue here. Let's go back to the ExcStartChildActivityInternational method of ActivityGroupDelegate

 public void execStartChildActivityInternal(ViewGroup container,String key, Intent intent){

    ......
        String bundleName = AtlasBundleInfoManager.instance().getBundleForComponet(componentName);
        if(!TextUtils.isEmpty(bundleName)){
            BundleImpl impl = (BundleImpl) Atlas.getInstance().getBundle(bundleName);
            if(impl!=null&&impl.checkValidate()) {
                performLaunchChildActivity(container,key,intent);
            }else {
                if(ActivityTaskMgr.getInstance().peekTopActivity()!=null && Looper.getMainLooper().getThread().getId()==Thread.currentThread().getId()) {
                    asyncStartActivity(container,key,bundleName,intent);
                }else{
                    performLaunchChildActivity(container,key,intent);
                }
            }
        }else{
            // Try to get class from system Classloader
            try {
                Class<?> clazz = null;
                clazz = Framework.getSystemClassLoader().loadClass(componentName);
                if (clazz != null) {
                    performLaunchChildActivity(container,key,intent);
                }
            } catch (ClassNotFoundException e) {
                Log.e("ActivityGroupDelegate",e.getCause().toString());
            }
        }

    }

If the bundleName is empty, use the system Classloader to get the class and execute the performance Launch Child Activity

If the bundleName is not empty, get the BundleImpl from the bundleName and execute the performance Launch Child Activity

    private void performLaunchChildActivity(ViewGroup container,String key,Intent intent ){
        if(intent==null){
            Log.e("ActivityGroupDelegate","intent is null stop performLaunchChildActivity");
            return ;
        }
        mLocalActivityManager.startActivity(key,intent);
        container.addView(
                mLocalActivityManager.getActivity(key)
                        .getWindow().getDecorView(),
                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.MATCH_PARENT));
    }

The performance Launch Child Activity method is simple. The Activity Manager starts the Activity and then adds the view to the ViewGroup.

The Componentization of atlas is quite thorough. Like the 360 framework, it has established its own component system and compatible with the native Activity components.


Keywords: Android xml encoding

Added by drtanz on Tue, 09 Jul 2019 00:54:09 +0300