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; } };
MainActivity implements Bundle switching through startChildActivity of Activity Group Delegatepublic void switchToActivity(String key,String activityName){ Intent intent = new Intent(); intent.setClassName(getBaseContext(),activityName); mActivityDelegate.startChildActivity(mActivityGroupContainer,key,intent); }
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); } ...... }
Loading different listings according to the version allows you to set up large versions of Bundle information in Framework Properties. class/** * 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"); } } }
If the BundleList is empty, enter getBundleForComponet of AtlasBundleInfoManager
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.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) { } } } } ...... }
<?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 Activityprivate 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.