一种Android插件开发的思想与实现


一、背景:

 

    前段时间有机会接触了轻应用相关的东西,虽然没有深入去开发,但通过阅读Runtime那边的一些相关文档,大概了解了开发的一个轻应用的流程和项目结构,轻应用不是一个完整的Android应用,因为它对Android四大组件只支持Activity,其实就是一个插件,这个插件是运行在一个叫做Runtime的环境上。首先了解下轻应用的结构:

一共4部分,js部分先忽略。

APK文件:这个APK文件包含了轻应用所有的资源和class(包括activity)。

libs目录:这个目录下可以放入一些动态链接库(so)。

data:这个类似于普通安卓应用下面的data目录,用于存放应用运行时的数据。

看到这里,发现这个轻应用和普通的应用没啥区别,但不用安装就能够调起APK里面的activity并且使用APK里面的资源,就像安装了该APK一样,很神奇。由此我猜想了一下Runtime的大概实现方式,并通过编码实现。

 

二、切入点ActivityGroup

 

Activity是Android系统的一大组件,它的一般是由ActivityManagerService这个系统服务来调起的,这里的前提就是APK必须安装。所以我想轻应用里面的activity并没有作为真正的activity来启动,而是一个假的activity。看到这里你也许有些糊涂了,说明你需要了解下ActivityGroup这个类,这个类虽说已经快被fragment取代了,但它正是猜想Runtime实现方式的起点。ActivityGroup其实也是一个activity,但它内部却可以包含多个子activity,其内部展现的只是子activity的window。其实看下这个类的代码就会发现其子activiy的启动方式和一般activity启动方式是不一样的,是由LocalActivityManager这个类来启动的。看下构造方法:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

 /**

     * This field should be made private, so it is hidden from the SDK.

     * {@hide}

     */

    protected LocalActivityManager mLocalActivityManager;

     

    public ActivityGroup() {

        this(true);

    }

     

    public ActivityGroup(boolean singleActivityMode) {

        mLocalActivityManager = new LocalActivityManager(this, singleActivityMode);

    }

     

     public LocalActivityManager(Activity parent, boolean singleMode) {

        mActivityThread = ActivityThread.currentActivityThread();

        mParent = parent;

        mSingleMode = singleMode;

    }

然后看下一个activity如何被LocalActivityManager启动的(省略了部分代码):

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

public Window startActivity(String id, Intent intent) {

       //......

        boolean adding = false;

        boolean sameIntent = false;

        ActivityInfo aInfo = null;

         

        // Already have information about the new activity id?

        LocalActivityRecord r = mActivities.get(id);

        if (r == null) {

            // Need to create it...

            r = new LocalActivityRecord(id, intent);

            adding = true;

        } else if (r.intent != null) {

           //......

        }

        if (aInfo == null) {

            aInfo = mActivityThread.resolveActivityInfo(intent);

        }

         

        //......

 

        if (adding) {

            // It's a brand new world.

            mActivities.put(id, r);

            mActivityArray.add(r);

        } else if (r.activityInfo != null) {

        //......

        }

         

        r.intent = intent;

        r.curState = INITIALIZING;

        r.activityInfo = aInfo;

 

        moveToState(r, mCurState);

 

        // When in single mode keep track of the current activity

        if (mSingleMode) {

            mResumed = r;

        }

        return r.window;

    }

     

    private void moveToState(LocalActivityRecord r, int desiredState) {

        if (r.curState == RESTORED || r.curState == DESTROYED) {

            // startActivity() has not yet been called, so nothing to do.

            return;

        }

         

        if (r.curState == INITIALIZING) {

            // Get the lastNonConfigurationInstance for the activity

            HashMap<String, Object> lastNonConfigurationInstances =

                    mParent.getLastNonConfigurationChildInstances();

            Object instanceObj = null;

            if (lastNonConfigurationInstances != null) {

                instanceObj = lastNonConfigurationInstances.get(r.id);

            }

            Activity.NonConfigurationInstances instance = null;

            if (instanceObj != null) {

                instance = new Activity.NonConfigurationInstances();

                instance.activity = instanceObj;

            }

             

            // We need to have always created the activity.

            if (localLOGV) Log.v(TAG, r.id + ": starting " + r.intent);

            if (r.activityInfo == null) {

                r.activityInfo = mActivityThread.resolveActivityInfo(r.intent);

            }

            r.activity = mActivityThread.startActivityNow(

                    mParent, r.id, r.intent, r.activityInfo, r, r.instanceState, instance);

            if (r.activity == null) {

                return;

            }

            r.window = r.activity.getWindow();

            r.instanceState = null;

            r.curState = STARTED;

             

             

            return;

        }

        //......

     }

我们看到,是通过ActivityThread的startActivityNow来启动的,ActivityThread是一个android应用进程的入口,main方法就在这个类里面。

接下来就是分析startActivityNow这个方法了,这个方法主要进行一些参数准备,准备好后调用performLaunchActivity来实例化activity并调起,关键代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

  private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {

 

        ActivityInfo aInfo = r.activityInfo;

        if (r.packageInfo == null) {

            r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,

                    Context.CONTEXT_INCLUDE_CODE);

        }

 

        ComponentName component = r.intent.getComponent();

        if (component == null) {

            component = r.intent.resolveActivity(

                mInitialApplication.getPackageManager());

            r.intent.setComponent(component);

        }

 

        if (r.activityInfo.targetActivity != null) {

            component = new ComponentName(r.activityInfo.packageName,

                    r.activityInfo.targetActivity);

        }

 

        Activity activity = null;

        try {

            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();

            activity = mInstrumentation.newActivity(

                    cl, component.getClassName(), r.intent);

            StrictMode.incrementExpectedActivityCount(activity.getClass());

            r.intent.setExtrasClassLoader(cl);

            if (r.state != null) {

                r.state.setClassLoader(cl);

            }

        } catch (Exception e) {

            if (!mInstrumentation.onException(activity, e)) {

                throw new RuntimeException(

                    "Unable to instantiate activity " + component

                    + ": " + e.toString(), e);

            }

        }

 

        try {

            Application app = r.packageInfo.makeApplication(false, mInstrumentation);

 

            if (localLOGV) Slog.v(TAG, "Performing launch of " + r);

            if (localLOGV) Slog.v(

                    TAG, r + ": app=" + app

                    + ", appName=" + app.getPackageName()

                    + ", pkg=" + r.packageInfo.getPackageName()

                    + ", comp=" + r.intent.getComponent().toShortString()

                    + ", dir=" + r.packageInfo.getAppDir());

 

            if (activity != null) {

                Context appContext = createBaseContextForActivity(r, activity);

                CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());

                Configuration config = new Configuration(mCompatConfiguration);

                if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "

                        + r.activityInfo.name + " with config " + config);

                activity.attach(appContext, this, getInstrumentation(), r.token,

                        r.ident, app, r.intent, r.activityInfo, title, r.parent,

                        r.embeddedID, r.lastNonConfigurationInstances, config);

 

                if (customIntent != null) {

                    activity.mIntent = customIntent;

                }

                r.lastNonConfigurationInstances = null;

                activity.mStartedActivity = false;

                int theme = r.activityInfo.getThemeResource();

                if (theme != 0) {

                    activity.setTheme(theme);

                }

 

                activity.mCalled = false;

                mInstrumentation.callActivityOnCreate(activity, r.state);

                if (!activity.mCalled) {

                    throw new SuperNotCalledException(

                        "Activity " + r.intent.getComponent().toShortString() +

                        " did not call through to super.onCreate()");

                }

                r.activity = activity;

                r.stopped = true;

                if (!r.activity.mFinished) {

                    activity.performStart();

                    r.stopped = false;

                }

                if (!r.activity.mFinished) {

                    if (r.state != null) {

                        mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);

                    }

                }

                if (!r.activity.mFinished) {

                    activity.mCalled = false;

                    mInstrumentation.callActivityOnPostCreate(activity, r.state);

                    if (!activity.mCalled) {

                        throw new SuperNotCalledException(

                            "Activity " + r.intent.getComponent().toShortString() +

                            " did not call through to super.onPostCreate()");

                    }

                }

            }

            r.paused = true;

 

            mActivities.put(r.token, r);

 

        } catch (SuperNotCalledException e) {

            throw e;

 

        } catch (Exception e) {

            if (!mInstrumentation.onException(activity, e)) {

                throw new RuntimeException(

                    "Unable to start activity " + component

                    + ": " + e.toString(), e);

            }

        }

 

        return activity;

    }

我们看到该方法中通过packageInfo中的classloader来load相应的activity类,然后创建一个实例,调用attach来初始化一些成员变量,最后执行activity的部分生命周期,onCreate,onStart,onRestoreInstanceState,onPostCreate。为什么部分呢?因为我只需要这个子activity的window。

看到这里,有人可能会问,我直接load这个activity的class并new出实例,然后手动掉生命周期不就行了?但你漏了关键一步,那就是attach,因为activity默认的onCreate里面会需要attach的参数,而我们写一个activity的时候,每个生命周期方法是必须调用super的相应方法的,如果缺少了这些参数,就会crash。到这里,我们的假activity的启动就完成了。

 

三、关键点LoadedApk

 

上面我们看到activity是由packageInfo里面的classloader来加载的,这个packageInfo是什么类的实例呢?在2.3以前叫做PackageInfo(是ActivityThread里面的内部类,不是SDK里面的PackageInfo),后来改名叫LoadedApk,这个名字就能很好理解它的作用了,描述了一个已经加载了的APK。我们从上面的performLaunchActivity方法中知道了它的获取方法getPackageInfo:

1

2

3

4

5

ActivityInfo aInfo = r.activityInfo;

        if (r.packageInfo == null) {

            r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,

                    Context.CONTEXT_INCLUDE_CODE);

        }

继续看:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

 private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,

            ClassLoader baseLoader, boolean securityViolation, boolean includeCode) {

        synchronized (mResourcesManager) {

            WeakReference<LoadedApk> ref;

            if (includeCode) {

                ref = mPackages.get(aInfo.packageName);

            } else {

                ref = mResourcePackages.get(aInfo.packageName);

            }

            LoadedApk packageInfo = ref != null ? ref.get() : null;

            if (packageInfo == null || (packageInfo.mResources != null

                    && !packageInfo.mResources.getAssets().isUpToDate())) {

                if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package "

                        : "Loading resource-only package ") + aInfo.packageName

                        + " (in " + (mBoundApplication != null

                                ? mBoundApplication.processName : null)

                        + ")");

                packageInfo =

                    new LoadedApk(this, aInfo, compatInfo, this, baseLoader,

                            securityViolation, includeCode &&

                            (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0);

                if (includeCode) {

                    mPackages.put(aInfo.packageName,

                            new WeakReference<LoadedApk>(packageInfo));

                } else {

                    mResourcePackages.put(aInfo.packageName,

                            new WeakReference<LoadedApk>(packageInfo));

                }

            }

            return packageInfo;

        }

    }

看到了么?有个mPackages变量:

1

2

 final ArrayMap<String, WeakReference<LoadedApk>> mPackages

            = new ArrayMap<String, WeakReference<LoadedApk>>();

原来一个android应用程序可以加载多个APK,每个APK由一个LoadedApk实例来描述,同时每个LoadedApk中有一个ClassLoader,用来加载APK中的类。具体参见Android源码。

 

四、概要设计:

 

经过上面的分析,大概能够理解Rutime的实现方式了:

1、每个轻应用的APK都会被load成LoadedApk实例(具体方法参见ActivityThread getPackageInfoNoCheck),通过反射给其mClassLoader赋值成APK对应的DexClassLoader,并加入到mPackages中。

2、每个APK中的activity作为假的activity来load,并创建一个空的宿主activity来显示。

大概有点类似tomcat加载web应用的结构。


五、实现:

参加 https://github.com/zhaoxuyang/koala--Android-Plugin-Runtime-

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值