DroidPlugin源码分析处理Activity的启动

正常情况下启动一个Activity,首先需要在AndroidManifest文件中声明,其次需要把该应用安装到手机系统中。
而插件apk是没有正在安装到手机系统中的,也就按照正常的启动流程插件Activity是不能启动的。另外插件apk的类需要加载进来是需要指定ClassLoader。前面的文章也大概讲过,当启动一个插件Activity时,先是用预定义的代理Activity替换目标Activity(及插件Activity)去启动,当AMS处理完回调到应用空间时(及回到运行Activity的进程空间时)再用目标Activity替换回来,去调用目标Activity的声明周期。再这个过程中,要实例化目标Activity用到了PluginClassLoader,自定义的ClassLoader来加载实例化目标Activity.
总结:也就是说要启动插件Activity DroidPlugin至少需要解决以下三个问题:
1 在启动Activity进入到AMS前需要用预定义的代理Activity替换目标Activity(及插件Activity)。
2 请完成后进入到运行Activity的进程中时,需要将目标activity替换回来。
3 需要用自定义的PluginClassLoader加载目标Activity。

通过分析Android Framework源码可以了解到(这里没有分析全部的Activity启动流程,只是针对上面三个问题的源码处理流程,这样能帮助理解DroidPlugin的篡改处理流程)
1 启动一个Activity最终都会通过调用ActivityManagerProxy 也就是AMS服务的代理来请求AMS启动。
2 AMS做完相关Activity的启动工作以后
A 会通过ActivityThread的内部类ApplicationThread这个binder对象回调到运行Activity的进程空间。

B 然后通过调用ActivityThread成员变量mH这个继承自Handler类的对象往主线程发送一条LAUNCH_ACTIVITY的消息。
C 在Handler分发消息的流程函数dispatchMessage中可以了解到,Handler有一个成员变量mCallback,如果不为空则调用mCallback.handleMessage(msg),当callback的handleMessage函数返回True时直接返回了, 就不会执行handleMessage(msg);函数。Activity正常启动的情况下会调用mH.handleMessage函数。执行LAUNCH_ACTIVITY消息对应的处理函数handleLaunchActivity函数启动Activity(也就是调用Activity的生命周期)

3 实例化目标Activity过程。
A 在执行LAUNCH_ACTIVITY消息时,会调用ActivityThread的getPackageInfoNoCheck函数获取一个LoadedApk对象。获取过程中会先通过包名从ActivityThread的成员变量mPackages中去查找是否有包名对应的LoadedApk存在,如果不存在则创建一个LoadedApk对象并以当前包名为key保存到mPackages中。同时将创建的LoadedApk保存到ActivityClientRecord的成员变量packageInfo中。在LoadedApk的构造函数中还会初始化一个mClassLoader的成员变量,默认情况下这个mClassLoader是一个PathClassLoader的对象。

B 创建完LoadedApk对象之后就会调用handleLaunchActivity函数,在这个函数里面会调用performLaunchActivity函数中会获取ActivityClientRecord的成员变量packageInfo内部的mClassLoader来加实例化目标Activity对象。

DroidPlugin就是在上面三段代码处理流程中找到Hook点来解决上面三个问题实现插件Activity的启动的。
1 启动前用代理Activity替换目标Activity:
在DroidPlugin中这个ActivityManagerProxy也已经被Hook,(如何Hook 可以看IActivityManagerHook的Install函数,了解AMS的Hook过程)那么只需要在IActivityManagerHook对应的处理分发类IActivityManagerHookHandle中对ActivityManagerProxy的StartActivity函数进行处理Activity的替换就行了。

2 启动后用目标Activity替换回代理Activity。
在DroidPlugin中通过PluginCallbackHook Hook了ActivityThread中的成员变量mH这个Handler的成员变量mCallback,特别对Id为LAUNCH_ACTIVITY的消息做了将目标Activity替换回代理Activity的预处理。(如何Hook 可以看PluginCallbackHook 的Install函数)

3 篡改实例化目标Activity(插件Activity)的过程。
前面分析了正常Activity对象是通过LoadedApk的成员变量mClassLoader来是实例化的,而在前面的文章也说过插件Activity要想加载,必须通过自定义的继承自DexClassLoader的PluginClassLoader来实例化,因此DroidPlugin用插件Apk的包名为key,伪造了一个LoadedApk对象为Value,保存到了ActivityThread的成员变量mPackages中同时用PluginClassLoader的对象替换了原本保存在LoadedApk成员变量mClassLoader中的ClassLoader对象。

接下来就通过源码分析DroidPlugin对上面三个流程源码的处理:
一 IActivityManagerHook对目标Activity的替换流程。
通过Hook的过程分析可以知道,IActivityManagerHook是通过IActivityManagerHookHandle对AMS各个函数分发处理类,而startActivity是通过startActivity类的函数beforeInvoke来处理的。
startActivity的beforeInvoke函数如下:

    protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable {
            RunningActivities.beforeStartActivity();
            boolean bRet = true;
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
                bRet = doReplaceIntentForStartActivityAPILow(args);
            } else {
                bRet = doReplaceIntentForStartActivityAPIHigh(args);
            }
            if (!bRet) {
                setFakedResult(Activity.RESULT_CANCELED);
                return true;
            }
            return super.beforeInvoke(receiver, method, args);
        }
}

A 此函数中首先调用的是RunningActivities.beforeStartActivity();
分析此函数前线了解RunningActivities类的一个内部类和几个成员变量的作用。
内部类RunningActivityRecord及其四个成员变量: 记录运行时Activity的一个数据类,
activity:Activity类,保存目标Activity对象。
targetActivityInfo: ActivityInfo类,保存目标ActivityInfo对象。
stubActivityInfo: ActivityInfo类,保存预定义代理ActivityInfo对象。
index: 记录activity启动时的顺序。
mRunningActivityList : 以activity对象为key, RunningActivityRecord对象为Value。保存所有当前进程所有运行的Activity。
mRunningSingleStandardActivityList,mRunningSingleTopActivityList,mRunningSingleTaskActivityList, mRunningSingleInstanceActivityList,
以activity的启动顺序为key,以RunningActivityRecord为Value分别保存四种不同启动模式的Activity。

RunningActivities.beforeStartActivity()函数如下:

    public static void beforeStartActivity() {
        synchronized (mRunningActivityList) {
            for (RunningActivityRecord record : mRunningActivityList.values()) {
                if (record.stubActivityInfo.launchMode == ActivityInfo.LAUNCH_MULTIPLE) {
                    continue;
                } else if (record.stubActivityInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP) {
                    doFinshIt(mRunningSingleTopActivityList);
                } else if (record.stubActivityInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) {
                    doFinshIt(mRunningSingleTopActivityList);
                } else if (record.stubActivityInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
                    doFinshIt(mRunningSingleTopActivityList);
                }
            }
        }
}

这个函数主要是遍历mRunningActivityList,然后根据RunningActivityRecord的类型,除Standard启动模式外,其他三种启动模式分别调用doFinshIt并以分别保存正在运行的其他三种启动模式的map对象为参数。
继续看doFinshIt函数:

    private static void doFinshIt(Map<Integer, RunningActivityRecord> runningActivityList) {
        if (runningActivityList != null && runningActivityList.size() >= PluginManager.STUB_NO_ACTIVITY_MAX_NUM - 1) {
            List<RunningActivityRecord> activitys = new ArrayList<>(runningActivityList.size());
            activitys.addAll(runningActivityList.values());
            Collections.sort(activitys, sRunningActivityRecordComparator);
            RunningActivityRecord record = activitys.get(0);
            if (record.activity != null && !record.activity.isFinishing()) {
                record.activity.finish();
            }
        }
}

这个函数主要就是,获取保存对应启动模式Activity的map对象不为空,且的size大于等于3的情况下,调用activity的finish函数结束最早启动的activity。
也就是说doFinishIt函数是当预定义的Activity不够用时,在启动新的Activity前把最早启动的activity Finish掉。
看完这段代码我有两个疑问:
疑问1: 为什么要在beforeStartActivity函数中遍历mRunningActivityList而不是直接调用doFinishIt函数三次?遍历可能会多次调用同一种启动模式的doFinishIt函数啊。多次调用和调用一次,结果是一样的啊?
疑问2: 为什么doFinishIt函数中最大值是三呢?而不是4?除Standard启动模式之外的三种启动模式预定义了四个啊?还有DialogTheme的Activity也预定义了4个?如果存在如下情况,启动了一个Activity,三个DialogThemeActivity,现在又要启动一个Activity,那不是第一个Activity错杀了?(可能哪里没有理解透彻,希望大虾解答!)

B 回到beforeInvoke继续分析,下面的代码中正对不同阶段的版本调用了不同的函数,最后这两个函数在正常处理完成的逻辑下会返回True,也就是返回super.beforeInvoke,而这个函数默认是返回false的,当beforeInvoke返回false时,就会直接调用被代理的函数去执行。也就是说替换一定发生在这两个函数中,这里就以高版本的处理函数来分析(基本包含低版本的处理逻辑)。
doReplaceIntentForStartActivityAPIHigh函数如下:

        protected boolean doReplaceIntentForStartActivityAPIHigh(Object[] args) throws RemoteException {
            int intentOfArgIndex = findFirstIntentIndexInArgs(args);
            if (args != null && args.length > 1 && intentOfArgIndex >= 0) {
                Intent intent = (Intent) args[intentOfArgIndex];
                if (!PluginPatchManager.getInstance().canStartPluginActivity(intent)) {
                    PluginPatchManager.getInstance().startPluginActivity(intent);
                    return false;
                }
                ActivityInfo activityInfo = resolveActivity(intent);
                if (activityInfo != null && isPackagePlugin(activityInfo.packageName)) {
                    ComponentName component = selectProxyActivity(intent);
                    if (component != null) {
                        Intent newIntent = new Intent();
                            ClassLoader pluginClassLoader = PluginProcessManager.getPluginClassLoader(component.getPackageName());
                            setIntentClassLoader(newIntent, pluginClassLoader);
                        newIntent.setComponent(component);
                        newIntent.putExtra(Env.EXTRA_TARGET_INTENT, intent);
                        newIntent.setFlags(intent.getFlags());

                        String callingPackage = (String) args[1];
                        if (TextUtils.equals(mHostContext.getPackageName(), callingPackage)) {
                            newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        }
             }
                        args[intentOfArgIndex] = newIntent;
                        args[1] = mHostContext.getPackageName();
                    }
                }
            }

            return true;
        }

1 从参数中找到Intent参数的位置,取出Intent,然后判断是否能启动插件Activity,如果不能则调用PluginPatchManager的startPluginActivity延迟启动。
能否启动取决于一下两个条件:
条件1:PluginManagerService是否已经启动。
条件2:启动Activity的包名和宿主进程的包名不一样。

2 调用resolveActivity函数从插件服务中找到启动插件插件ActivityInfo。resolveActivity函数不做过多分,看过插件安装的文章中讲过保存的过程,这里只是获取。

3 判断获取到的activityInfo不为空,且是插件中的Activity,然后调用selectProxyActivity(此函数在进程管理文章中已近有分析)找到合适的代理Activity。

4 创建一个新的Intent,设置启动Activity为前面找到的代理Activity,并把目标activity保存到新的Intent中.最后判断调用者的包名是不是宿主进程包名,然后把新的Intent替换掉之前找到的Intent参数的位置。
至此替换过程已经完成。

二:AMS处理完成回到运行Activity进程空间,完成目标Activity替换回来,以及目标Activity实例化过程。
A 前面已经分析过了具体的处理类,直接看PluginCallback对LAUNCH_ACTIVITY的处理函数:handleLaunchActivity:考虑到函数比较长分段阅读:
1 找出目标Activity

    private boolean handleLaunchActivity(Message msg) {
        try {
            Object obj = msg.obj;
            Intent stubIntent = (Intent) FieldUtils.readField(obj, "intent");
            stubIntent.setExtrasClassLoader(mHostContext.getClassLoader());
            Intent targetIntent = stubIntent.getParcelableExtra(Env.EXTRA_TARGET_INTENT);
            // 这里多加一个isNotShortcutProxyActivity的判断,因为ShortcutProxyActivity的很特殊,启动它的时候,也会带上一个EXTRA_TARGET_INTENT的数据,就会导致这里误以为是启动插件Activity,所以这里要先做一个判断。之前ShortcutProxyActivity错误复用了key,但是为了兼容,所以这里就先这么判断吧。
            if (targetIntent != null && !isShortcutProxyActivity(stubIntent)) {
                IPackageManagerHook.fixContextPackageManager(mHostContext);
                ComponentName targetComponentName = targetIntent.resolveActivity(mHostContext.getPackageManager());
                ActivityInfo targetActivityInfo = PluginManager.getInstance().getActivityInfo(targetComponentName, 0);
                if (targetActivityInfo != null) {

                    if (targetComponentName != null && targetComponentName.getClassName().startsWith(".")) {
                        targetIntent.setClassName(targetComponentName.getPackageName(), targetComponentName.getPackageName() + targetComponentName.getClassName());
                    }

                    ResolveInfo resolveInfo = mHostContext.getPackageManager().resolveActivity(stubIntent, 0);
                    ActivityInfo stubActivityInfo = resolveInfo != null ? resolveInfo.activityInfo : null;
                    if (stubActivityInfo != null) {
                        PluginManager.getInstance().reportMyProcessName(stubActivityInfo.processName, targetActivityInfo.processName, targetActivityInfo.packageName);
                    }

1.1 是从msg.obj中获取代理Intent参数,然后从代理Intent中获取启动之前保存的目标intent。
1.2 判断目标Intent是否存在,而且不是一个ShortcutProxyActivity然后调用IPackageManagerHook.fixContextPackageManager(mHostContext);
要创建的Activity(及ContextImpl)有一个成员变量mPackageManager 是一个ApplicationPackageManager类对象,再ApplicationPackageManager类里面有一个成员变量,mPM,保存了PackageManager服务的代理对象。
这一步要做的事情就是保证这个代理对象是之前Hook过的对象。
1.3 接下来的代码就是设置目标intent具体的类,然后找到目标ActivityInfo和代理ActivityInfo。最后提交管理。

2 创建插件Activity加载环境:

                    PluginProcessManager.preLoadApk(mHostContext, targetActivityInfo);
                    ClassLoader pluginClassLoader = PluginProcessManager.getPluginClassLoader(targetComponentName.getPackageName());
                    setIntentClassLoader(targetIntent, pluginClassLoader);
                    setIntentClassLoader(stubIntent, pluginClassLoader);

这里就是前面DroidPlugin中需要解决的第二个问题,预存LoadedApk,并替换LoadedApk中的ClassLoader。以便加载插件Activity。
先分析完这个函数在分析PluginProcessManager.preLoadApk函数的实现。

3 最后一段代码比较长,而且都是在做兼容处理,就不贴代码了。
具体做的事情就是把目标ActivityInfo和代理ActivityInfo保存到目标Intent中。在msg.obj中把目标Intent替换到原来的代理intent,再把目标ActivityInfo替换掉代理ActivityInfo。

4 PluginProcessManager.preLoadApk还是分段阅读:

    public static void preLoadApk(Context hostContext, ComponentInfo pluginInfo) throws IOException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, PackageManager.NameNotFoundException, ClassNotFoundException {

        /*添加插件的LoadedApk对象到ActivityThread.mPackages*/
        boolean found = false;
        synchronized (sPluginLoadedApkCache) {
            Object object = ActivityThreadCompat.currentActivityThread();
            if (object != null) {
                Object mPackagesObj = FieldUtils.readField(object, "mPackages");
                Object containsKeyObj = MethodUtils.invokeMethod(mPackagesObj, "containsKey", pluginInfo.packageName);
                if (containsKeyObj instanceof Boolean && !(Boolean) containsKeyObj) {
                    final Object loadedApk;
                    if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) {
                        loadedApk = MethodUtils.invokeMethod(object, "getPackageInfoNoCheck", pluginInfo.applicationInfo, CompatibilityInfoCompat.DEFAULT_COMPATIBILITY_INFO());
                    } else {
                        loadedApk = MethodUtils.invokeMethod(object, "getPackageInfoNoCheck", pluginInfo.applicationInfo);
                    }
                    sPluginLoadedApkCache.put(pluginInfo.packageName, loadedApk);

这段代码主要做了如下工作:获取ActivityThread对象的成员变量mPackages,并判断mPackages是否包含当前插件包名为key的LoadedApk对象。如果不包含则调用ActivityThread的getPackageInfoNoCheck函数创建LoadedApk对象。以插件包名为key将创建的LoadedApk对象保存到sPluginLoadedApkCache中。

                    String optimizedDirectory = PluginDirHelper.getPluginDalvikCacheDir(hostContext, pluginInfo.packageName);
                    String libraryPath = PluginDirHelper.getPluginNativeLibraryDir(hostContext, pluginInfo.packageName);
                    String apk = pluginInfo.applicationInfo.publicSourceDir;
                    if (TextUtils.isEmpty(apk)) {
                        pluginInfo.applicationInfo.publicSourceDir = PluginDirHelper.getPluginApkFile(hostContext, pluginInfo.packageName);
                        apk = pluginInfo.applicationInfo.publicSourceDir;
                    }
                    if (apk != null) {
                        ClassLoader classloader = null;
                        try {
                            classloader = new PluginClassLoader(apk, optimizedDirectory, libraryPath, ClassLoader.getSystemClassLoader());
                        } catch (Exception e) {
                        }
                        if(classloader==null){
                            PluginDirHelper.cleanOptimizedDirectory(optimizedDirectory);
                            classloader = new PluginClassLoader(apk, optimizedDirectory, libraryPath, ClassLoader.getSystemClassLoader());
                        }
                        synchronized (loadedApk) {
                            FieldUtils.writeDeclaredField(loadedApk, "mClassLoader", classloader);
                        }
                        sPluginClassLoaderCache.put(pluginInfo.packageName, classloader);
                        Thread.currentThread().setContextClassLoader(classloader);
                        found = true;
                    }
                    ProcessCompat.setArgV0(pluginInfo.processName);
                }
            }
        }

这段代码的主要工作是:
1 获取optimizedDirectory,libraryPath,apk三个插件路径(看过插件安装应该非常熟悉了),创建PluginClassLoader对象classloader。
2 替换到LoadedApk中的成员变量mClassLoader.然后以插件包名为key保存到sPluginClassLoaderCache中。
3 设置当前线程ClassLoader为PluginClassLoader对象classloader。
4 设置当前进程名字为插件包名,这个不一定能实现。至少我的手机上还是显示的预定义的pluginXX.

        if (found) {
            PluginProcessManager.preMakeApplication(hostContext, pluginInfo);
        }
}

最后这段代码主要是为之前创建的LoadedApk对象内部的成员mApplication。而preMakeApplication其实就是调用了LoadedApk的makeApplication函数。就不贴代码了。

三: 创建插件Activity创建之前的其他工作:
阅读DroidPlugin的源码可以发现,其实还通过InstrumentationHook Hook了Instrumentation类,查看InstrumentationHook的onInstall函数可以知道实际就是通过PluginInstrumentation 类对象替换了ActivityThread的成员变量mInstrumentation原来的值。
通过源码分析了解到,在performLaunchActivity函数中会调用mInstrumentation.callActivityOnCreate(activity, r.state);而在callActivityOnCreate函数中才会整整调用activity.performCreate来真正执行activity的onCreate函数。也就是说DroidPlugin Hook mInstrumentation变量就是想在执行activity的onCreate函数之前做了一些工作:
具体来看PluginInstrumentation的callActivityOnCreate函数:

    @Override
    public void callActivityOnCreate(Activity activity, Bundle icicle) {
        if (enable) {
            IWindowManagerBinderHook.fixWindowManagerHook(activity);
            IPackageManagerHook.fixContextPackageManager(activity);
            PluginProcessManager.fakeSystemService(mHostContext, activity);
            onActivityCreated(activity);//放到最后分析
            fixBaseContextImplOpsPackage(activity.getBaseContext());
  fixBaseContextImplContentResolverOpsPackage(activity.getBaseContext());
        }
//上面代码是实际调用activity onCreate函数前的处理。
        if (mTarget != null) {
            mTarget.callActivityOnCreate(activity, icicle);
        } else {
            super.callActivityOnCreate(activity, icicle);
        }
}

1 IWindowManagerBinderHook.fixWindowManagerHook(activity);
在Activity对象内部有一个PhoneWindow类的成员变量 mWindow, 在PhoneWindow类中有一个内部类WindowManagerHolder,他里面有一个成员变量sWindowManager 保存了WindowManager服务的代理。
这一步要做的事情就是保证PhoneWindow 内部类WindowManager的静态成员变量SwindowManager这个WindowManager代理对象也是我们Hook过的WindowManager。

2 IPackageManagerHook.fixContextPackageManager(activity);这个前面已经分析过了。

3 PluginProcessManager.fakeSystemService(mHostContext, activity);
分析前现需要理解两个变量 SYSTEM_SERVICE_MAP 以及 mServiceCache 是做什么的?
ContextImpl 在类加载的时候会有静态代码块,初始化封装一些系统服务。
静态代码块中,会以要封装服务的名字为key ,以ServiceFetcher 对象为value 保存在SYSTEM_SERVICE_MAP中,其中在创建ServiceFetcher对象时实现了创建封装服务的方法。
当用户需要获取某一个系统服务时,通过ContextImpl getSystemService函数以及服务名字,这个函数就会从SYSTEM_SERVICE_MAP中获取ServiceFetcher对象,
然后调用ServiceFetcher对象的getService来获取。getService中,会先从ContextImp的成员变量mServiceCache,ServiceFetcher对象中有一个mContextCacheIndex保存了
一个位置,如果ServiceFetcher中的服务已近缓存了,那就必定就在mServiceCache的mContextCacheIndex位置,
如果不存在那么就直接调用ServiceFetcher的createService,创建服务对象并缓存到mServiceCache的mContextCacheIndex位置。
有了上面的理解,接下来fakeSystemService这个函数就好理解了:
这个函数调用了另外一个函数fakeSystemServiceInner
在fakeSystemServiceInner中主要做的事情就是:
事情一:保证新创建的Activity(及ContextImpl)中mServiceCache缓存的服务对象都是我们Hook过的。
事情二:为了兼容性跳过一些不能篡改的服务sSkipService。
事情三:在确保ContextImplement成员变量mServiceCache中缓存的服务是已经Hook过的以后,把这个ContextImplement 保存到mFakedContext。
这个函数的目的,因为系统服务可能缓存在多个地方,所以需要保证多个地方的服务都是Hook过的,从而保证插件正常运行。

4 fixBaseContextImplOpsPackage(activity.getBaseContext());
修改ContextImpl中的成员变量mOpPackageName 为宿主进程的包名。

5 fixBaseContextImplContentResolverOpsPackage
修改ContentResolver的成员变量,mPackageName

6 onActivityCreated
这个函数主要做的事情如下:
事情一:从目标Intent中获取目标ActivityInfo和代理ActivityInfo,调用RunningActivities.onActivtyCreate记录运行的Activity信息。
事情二:设置目标Activity屏幕请求方向。
事情三:调用PluginManager.getInstance().onActivityCreated添加到进程已经Activity管理中。

到这里DroidPlugin对插件Activity的处理全部分析完了。

本页内容版权归属为原作者,如有侵犯您的权益,请通知我们删除。
从DroidPlugin的官方文档中我们知道。 2 在AndroidManifest.xml中使用插件的com.morgoo.droidplugin.PluginApplication: 或者在自定义的Application的onCreate()函数中,调用PluginHelper.getInstance().applicationOnCreate(getBaseContext()); 在Application的attachBaseContext()函数中,调用 PluginHelper.getInsta

字母雨的实现 - 2016-07-23 14:07:06

有段时间没写博文了,前段时间比较忙,这几天闲下来,想着写点东西,脑袋一下就闪过以前学习Android的时候见到的别人实现的黑客帝国的字母雨效果,当时对于小菜鸟的自己,那叫一个膜拜啊,时隔几年,自己实现一下,算是对以前的自己一个交代吧。 先看效果: 一、实现原理 在实现过程中,主要考虑整个界面由若干个字母组成的子母线条组成,这样的话把固定数量的字母封装成一个字母线条,而每个字母又封装成一个对象,这样的话,就形成了如下组成效果: 字母对象--》字母线条对象--》界面效果 每个字母都应该知道自己的位置坐标,自己

DroidPlugin源码分析Hook过程 - 2016-07-23 14:07:06

插件运行环境初始化过程中我们知道,Hook的初始化是在PluginHelper的initPlugin函数中通过调用PluginProcessManager.installHook来实现的。而在分析DroidPlugin Hook过程之前需要先简单了解一下Java的动态代理。 Java动态代理与之相关的一个类Proxy,一个接口InvocationHandler,一个函数invoke他们之间的关系。就通过DroidPlugin 的BinderHook类的部分代码来解释一下他们的关系; abstract cl
大概半年之前,看过鸿洋大神的一篇博客 Android 自定义控件玩转字体变色 打造炫酷ViewPager指示器 他说大概想了32秒就知道了实现思路,这深深的刺痛了我。最近又看了一遍,决定做点什么 我要自定义的控件是一个盖世英雄, 它不仅仅是一个 Loading控件 ,同时还支持 进度条 (ProgressBar) 功能 。 它会在你需要的时候出现, 它支持 left , top , right , bottom 四个方向加载(变色),最重要的是,它可以是 文字 ,也可以是 图片 ,能够满足开发者一切需求。
首先为权限: uses-permission android:name="android.permission.INTERNET" / uses-permission android:name="com.android.vending.BILLING" / uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" / uses-permission android:name="android.permission.AC
Day01 html与css基础入门 1.html的常见标签和实战 1.1 a标签 a href=#> 1.2 img标签 img src="plmm.jpg" width="100px" height="200px" alt="图片信息丢失!"/// alt属性的值表示当图片找不到时显示的文字信息 1.3 列表标签 ol type="I" start="1" li我是天才1号/li li我是天才2号/li li我是天才3号/li li我是天才4号/li/olul type="circle" li我是逗逼

EventBus 源码分析 - 2016-07-23 14:07:10

0. 前言 EventBus 是一款针对Android优化的发布/订阅事件总线。主要功能是替代 Intent , Handler , BroadCast 在 Fragment , Activity , Service ,线程之间传递消息。优点是开销小,代码更优雅,以及将发送者和接收者解耦。此文将对最新的 EventBus 3.0 的源码进行简要的分析。 1. 用法回顾 EventBus 3.0 的用法较之前的版本有所变化,它使用了最近较为流行的注解形式取代以前的 onEvent 开头作为方法名,但使用步骤

Qt之资源系统 - 2016-07-23 14:07:10

简述 Qt 的资源系统用于存储应用程序的可执行二进制文件,它采用平台无关的机制。当你的程序总需要这样的一系列文件(图标、翻译文件等)并且不想冒丢失某些文件的风险时,这就显得十分有用。 资源系统基于 qmake、rcc(Qt 资源编译器) 和 QFile 之间的紧密合作。 简述 资源集合文件qrc 外部二进制资源 内编译资源 压缩 在程序中使用资源 在库中使用资源 更多参考 资源集合文件(.qrc) 与程序相关的资源在被指定在一个 .qrc 文件中,其基于 XML 的文件格式列出了磁盘上的文件,可以为它们指
一.前提条件 1.纯熟扎实的语言基础   如果你学java,却对反射、泛型、注解一直半解,还是不要去读什么框架了,回去把java基础打扎实反而对你自身更有益。 2.UML能力   在软件工程中,UML在软件的不同生命周期阶段扮演着非常重要的角色,没有好的UML水平,面对大型的项目源码会束手无策。 3.对业务的理解   如果你要阅读的项目业务性比较强,事先对业务有一定的了解是必须的。 4.设计模式、重构的掌握   编程语言什么的没什么好说。着重提一个:设计模式由于Android源代码用到各种各样的设计模式,
在上一篇博文《 LTE下行物理层传输机制(7)-DCI2格式和预编码矩阵的选择 》中已经提到,如果当前UE的传输模式是TM4,且可以执行空分复用(一个PDSCH信道传输2个TB块),那么需要采用DCI2格式来承载控制信息域,使用的预编码矩阵需要参考UE反馈的PMI值,因此属于闭环性质的空分复用。相应的,LTE系统中也有一种开环的空分复用: 如果当前UE的传输模式是TM3,且可以执行空分复用,那么此时PDCCH需要采用DCI2A格式发送,这时的空分复用就属于开环性质的空分复用,不需要参考UE反馈的PMI值。