通过之前的例子例子,我们学习了如何寻找hook点,并且做一些非常无聊的事情。比如是的粘贴板放一句无聊的句子,或者让系统在启动一个activity时打印一句话。这些看似无聊的事情其实都是为了本节做铺垫。
这一节会把之前的知识都贯穿起来——启动插件中的activity,不过这篇博客比较长,所以我分开成为两部分了第二部分

启动插件的activity还是非常难的一件事,因为在android中,所有的activity都必须在AndroidManifest.xml文件中声明。如果没有声明的话,启动它就会碰到下面的错误:

伤透脑筋啊~

由于android的机制,我们无法启动一个没有在AndroidManifest.xml中没有声明的activity,并且我们不能像平时写普通java代码一样,new一个Acticity对象出来就完事。因为android中的组件都是有生命的,不可以凭空产生,也不可以凭空消失,手动new出来的,它只是一个普通的对象,没有任何用处。那么我们是否可以,先在AndroidManifest.xml中声明一个activity,然后我们插件中的activity都通过它借尸还魂,以此来运行呢?想法有点大胆,不过也没办法,因为我们现在能想到的就这么多。

既然要借一个activity还魂,那么肯定得了解activity的启动原理啊,不然一切都真的是空谈。通过我们之前的学习,我们注意到,当启动一个activity时,Activity这个类中做这件事的其实是他的成员对象——mInstrumentation

在这个函数里面他最终是调用的是ActivityManagerNative.getDefault()的返回值来启动一个activity

ActivityManagerNative.getDefault返回的是一个Binder对象,他能够使用ActivityManagerService的服务(以下简称AMS)。正如其名,它正是管理activity的服务,由他赋予activity生命!


通过一系列的远程调用我们开始使用activity manager service的服务。其流程大概如下:
1:AMS调用ActivityStack的一系列方法来准备要启动的Activity的相关信息。我们平时说的什么任务栈啊都在这个类中有涉及
2:ActivityStack在完成一些准备工作后,通过ApplicationThread接口,远程通知当前的ui线程,我要准备调度了~注意!ApplicationThread这个接口是在activity启动另外一个activity的时候传入Activity的
关于它的信息在这里:

3:ApplicationThread不执行真正的启动操作,它通过调用ActivityManagerService.activityPaused接口进入到ActivityManagerService进程中,看看是否需要创建新的进程来启动Activity。你大概可以感觉到了吧,ui线程通过ActivityManagerProxy与AMS”取得联系”,而AMS呢,通过ApplicationThread与ui线程获得联系
4: 对于通过点击应用程序图标来启动Activity的情景来说,AMS在这一步中,会调用startProcessLocked来创建一个新的进程,而对于通过在Activity内部调用startActivity来启动新的Activity来说,这一步是不需要执行的,因为新的Activity就在原来的Activity所在的进程中进行启动
5: AMS调用ApplicationThread.scheduleLaunchActivity接口,通知相应的进程执行启动Activity的操作;
6: ApplicationThread把这个启动Activity的操作转发给ActivityThread,ActivityThread通过ClassLoader导入相应的Activity类,然后把它启动起来。

以上内容有部分摘自老罗的博客:老罗
不过他看的android源码有点老了,现在的源码变化不小~

现在就开始分析吧

我们切入到AMS中看下:

1


他调用了另外一个成员函数(这里唠叨下,看到第一个参数没?AMS通过他和我们的ui线程通信)

2


这里的AMS代码被重构了一遍,这里是要进入到ActivityStackSupervisor这个类中去处理了。从名字上我们很容易看出,这里就是进行之前我们说的——让ActivityStack做一些准备工作

3

  final int startActivityMayWait(IApplicationThread caller, int callingUid,            String callingPackage, Intent intent, String resolvedType,            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,            IBinder resultTo, String resultWho, int requestCode, int startFlags,            ProfilerInfo profilerInfo, WaitResult outResult, Configuration config,            Bundle options, int userId, IActivityContainer iContainer, TaskRecord inTask) {        // Refuse possible leaked file descriptors        if (intent != null && intent.hasFileDescriptors()) {            throw new IllegalArgumentException("File descriptors passed in Intent");        }        //查看下是否有component        boolean componentSpecified = intent.getComponent() != null;        // Don't modify the client's object!        intent = new Intent(intent);        // Collect information about the target of the Intent.        ActivityInfo aInfo = resolveActivity(intent, resolvedType, startFlags,                profilerInfo, userId);            ...            int res = startActivityLocked(caller, intent, resolvedType, aInfo,                    voiceSession, voiceInteractor, resultTo, resultWho,                    requestCode, callingPid, callingUid, callingPackage,                    realCallingPid, realCallingUid, startFlags, options,                    componentSpecified, null, container, inTask);            ...            return res;        }    }

这里有个非常重要的部分!

        //查看下是否有component        boolean componentSpecified = intent.getComponent() != null;

我们平时可以有很多种方式启动一个activity,比如隐式,显式启动
隐式:

        Intent intent = new Intent("your action");        ...        startActivity(intent);

显式:

        Intent intent = new Intent(context, xxx.class);        startActivity(intent);

我们这里只考虑显式。我们看下源码:

这个mComponent是一个ComponentName类型,他是系统用于区分组件的一个类:

好像有那么种感觉就是,AMS通过它区分要启动的activity是什么。回忆一下之前我介绍的activity启动流程。ActivityStack准备好一切之后,会回到ui线程,然后UI线程再回头问下AMS我是在当前进程启动一个activity还是再创建一个进程启动。这个过程是否有一种机制,让AMS能够快速识别这个Ui线程是哪个app的,毕竟手机里不止一个应用嘛。
我们不急,继续往下看。
之后的代码就是解析出当前要启动的activity信息:

 // Collect information about the target of the Intent. ActivityInfo aInfo = resolveActivity(intent, resolvedType, startFlags, profilerInfo, userId);

切进去看下:

         @Override3027     public ResolveInfo resolveIntent(Intent intent, String resolvedType,3028             int flags, int userId) {3029         if (!sUserManager.exists(userId)) return null;3030         enforceCrossUserPermission(Binder.getCallingUid(),                  userId, false, false, "resolve intent");3031         List<ResolveInfo> query = queryIntentActivities(intent, resolvedType, flags, userId);             //选择出最优的activity3032         return chooseBestActivity(intent, resolvedType, flags, query, userId);3033     }

queryIntentActivities:

3349     @Override3350     public List<ResolveInfo> More ...queryIntentActivities(Intent intent,3351             String resolvedType, int flags, int userId) {3352         if (!sUserManager.exists(userId)) return Collections.emptyList();3353         enforceCrossUserPermission(Binder.getCallingUid(), userId, false, false, "query intent activities");3354         ComponentName comp = intent.getComponent();3355         if (comp == null) {3356             if (intent.getSelector() != null) {3357                 intent = intent.getSelector(); 3358                 comp = intent.getComponent();3359             }3360         }3361 3362         if (comp != null) {3363             final List<ResolveInfo> list = new ArrayList<ResolveInfo>(1);3364             final ActivityInfo ai = getActivityInfo(comp, flags, userId);3365             if (ai != null) {3366                 final ResolveInfo ri = new ResolveInfo();3367                 ri.activityInfo = ai;3368                 list.add(ri);3369             }3370             return list;3371         }3372 3373         // reader3374         synchronized (mPackages) {3375             final String pkgName = intent.getPackage();3376             if (pkgName == null) {3377                 List<CrossProfileIntentFilter> matchingFilters =3378                         getMatchingCrossProfileIntentFilters(intent, resolvedType, userId);3379                 // Check for results that need to skip the current profile.3380                 ResolveInfo resolveInfo  = querySkipCurrentProfileIntents(matchingFilters, intent,3381                         resolvedType, flags, userId);3382                 if (resolveInfo != null) {3383                     List<ResolveInfo> result = new ArrayList<ResolveInfo>(1);3384                     result.add(resolveInfo);3385                     return result;3386                 }3387                 // Check for cross profile results.3388                 resolveInfo = queryCrossProfileIntents(3389                         matchingFilters, intent, resolvedType, flags, userId);3390 3391                 // Check for results in the current profile.3392                 List<ResolveInfo> result = mActivities.queryIntent(3393                         intent, resolvedType, flags, userId);3394                 if (resolveInfo != null) {3395                     result.add(resolveInfo);3396                     Collections.sort(result, mResolvePrioritySorter);3397                 }3398                 return result;3399             }3400             final PackageParser.Package pkg = mPackages.get(pkgName);3401             if (pkg != null) {3402                 return mActivities.queryIntentForPackage(intent, resolvedType, flags,3403                         pkg.activities, userId);3404             }3405             return new ArrayList<ResolveInfo>();3406         }3407     }

显然是根据intent中提供的信息,检索出最匹配的结果

4

之后调用startActivityLocked:

  final int startActivityLocked(IApplicationThread caller,            Intent intent, String resolvedType, ActivityInfo aInfo,            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,            IBinder resultTo, String resultWho, int requestCode,            int callingPid, int callingUid, String callingPackage,            int realCallingPid, int realCallingUid, int startFlags, Bundle options,            boolean componentSpecified, ActivityRecord[] outActivity, ActivityContainer container,            TaskRecord inTask) {        int err = ActivityManager.START_SUCCESS;        //获得调用者进程信息        ProcessRecord callerApp = null;        if (caller != null) {            callerApp = mService.getRecordForAppLocked(caller);            ...        }        if (err == ActivityManager.START_SUCCESS) {              ...        }        //要启动一个activity的activity的信息        ActivityRecord sourceRecord = null;        ActivityRecord resultRecord = null;        if (resultTo != null) {            sourceRecord = isInAnyStackLocked(resultTo);            if (DEBUG_RESULTS) Slog.v(                TAG, "Will send result to " + resultTo + " " + sourceRecord);            if (sourceRecord != null) {                if (requestCode >= 0 && !sourceRecord.finishing) {                    resultRecord = sourceRecord;                }            }        }        //获得intent的flags        final int launchFlags = intent.getFlags();        ...        //要调用的activity信息        ActivityRecord r = new ActivityRecord(mService, callerApp, callingUid, callingPackage,                intent, resolvedType, aInfo, mService.mConfiguration, resultRecord, resultWho,                requestCode, componentSpecified, this, container, options);        ...        err = startActivityUncheckedLocked(r, sourceRecord, voiceSession, voiceInteractor,                startFlags, true, options, inTask);        ...        return err;    }

我们看下ActivityRecord的ctor:

有点长,我们再往下看:


可以得出的结论就是,AMS如何知道要启动的activity是谁呢?就是通过intent,先解析Intent得到一些基本信息。然后根据这些结果生成activity record,存放在活动栈里面。
我们看下显示启动时Intent的构造函数吧:

Intent还提供了以另外一个方法:

太酷了,我们完全可以这样显示启动一个activity啊:

    Intent intent = new Intent();    intent.setComponent(new ComponentName(MainActivity.this, Main2Activity.class));    startActivity(intent);

那么思路来了:我们是否可以先启动一个无意义的activity(一下成为stub),它只是一个载体,然后我们hook AMS的startActivity方法,通过修改component为stub欺骗AMS,让它误以为要启动的activity是stub,这个stub当然一定要在AndroidMenifest.xml注册下,这样一切都是合法的,之后我们再借尸还魂把那些资源转移到我们插件的activity下,这样,插件的activity就可以正常启动,也成功获得了生命周期,而AMS对于插件的activity的操作都被误认为是对于stub的!没瑕疵

talk is cheap, show u the code, just read the fucking code:

public class HookApplication extends Application {    @TargetApi(Build.VERSION_CODES.KITKAT)    @Override    protected void attachBaseContext(Context base) {        super.attachBaseContext(base);        try {            //获得ActivityManagerNative            Class<?> serviceManagerClz = Class.forName("android.app.ActivityManagerNative", false, getClassLoader());            //获得ActivityManagerNative.getDefault静态方法            Method getDefaultMethod = serviceManagerClz.getDeclaredMethod("getDefault");            //获得原始的IActivityManager对象            Object rawIActivityManagerInterface = getDefaultMethod.invoke(null);            //我们自己的Hook的对象            Object hookIActivityManagerInterface = Proxy.newProxyInstance(                    getClassLoader(),                    new Class[]{Class.forName("android.app.IActivityManager", false, getClassLoader())},                    new AMSHook(rawIActivityManagerInterface)            );            //反射ActivityManagerNative的gDefault域            Field gDefaultField = serviceManagerClz.getDeclaredField("gDefault");            gDefaultField.setAccessible(true);            Object gDefaultObject = gDefaultField.get(null);            //他的类型是Singleton            Class<?> singletonClz = Class.forName("android.util.Singleton", false, getClassLoader());            //把他的mInstance域替换掉 成为我们自己的Hook对象            Field mInstanceField = singletonClz.getDeclaredField("mInstance");            mInstanceField.setAccessible(true);            mInstanceField.set(gDefaultObject, hookIActivityManagerInterface);        } catch (ClassNotFoundException | IllegalAccessException |                NoSuchMethodException | InvocationTargetException | NoSuchFieldException e) {            e.printStackTrace();        }    }}
/** * Created by chan on 16/4/13. */public class AMSHook implements InvocationHandler {    private Object m_base;    public AMSHook(Object base) {        m_base = base;    }    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        //拦截startActivity方法        if ("startActivity".equals(method.getName())) {            //查找原始的intent对象            Intent raw = null;            final int size = (args == null ? 0 : args.length);            int i = 0;            for (; i < size; ++i) {                if (args[i] instanceof Intent) {                    raw = (Intent) args[i];                    break;                }            }            //看下是否是启动插件中的activity 下面的代码会有解释            if (raw.getBooleanExtra(Constant.EXTRA_INVOKE_PLUGIN, false)) {                //获得原始的ComponentName                ComponentName componentName = raw.getComponent();                //创建一个新的Intent                Intent intent = new Intent();                //把Component替换为StubActivity的 这样就不会被系统检测到 启动一个没有在AndroidManifest.xml                //中声明的activity                intent.setComponent(new ComponentName(componentName.getPackageName(),                        StubActivity.class.getCanonicalName()));                //保存原始的intent                intent.putExtra(Constant.EXTRA_RAW_INTENT, raw);                //替换为新的Intent                args[i] = intent;            }        }        //还是按往常一样调用各种函数        return method.invoke(m_base, args);    }}

一些工具类:

/** * Created by chan on 16/4/13. */public interface Constant {    String EXTRA_INVOKE_PLUGIN = "com.chan.hook.util.invoke_plugin";    String EXTRA_RAW_INTENT = "com.chan.hook.util.raw_intent";}
/** * Created by chan on 16/4/14. */public class Utils {    public static void invokePluginActivity(Activity activity, Class<?> who) {        Intent intent = new Intent(activity, who);        intent.putExtra(Constant.EXTRA_INVOKE_PLUGIN, true);        activity.startActivity(intent);    }}

AndroidManifest.xml:

  <application  android:name=".app.HookApplication" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme">        <activity android:name=".MainActivity">            <intent-filter>                <action android:name="android.intent.action.MAIN"/>                <category android:name="android.intent.category.LAUNCHER"/>            </intent-filter>        </activity>        <!--<activity-->            <!--android:name=".PluginActivity"-->            <!--android:label="@string/title_activity_main2"-->            <!--android:theme="@style/AppTheme.NoActionBar">-->        <!--</activity>-->        <activity android:name=".StubActivity">        </activity>    </application>

使用方式:

public class MainActivity extends AppCompatActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        findViewById(R.id.id_start).setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                Utils.invokePluginActivity(MainActivity.this, PluginActivity.class);            }        });    }}

效果:

我们原本要启动PluginActivity,但是借助StubActivity成功欺骗AMS获取启动一个activity所必须的资源。说明之前的思路都是正确的,下面就只剩正确的把AMS返回的资源给PluginActivity,让PluginActivity启动就行了。具体请参考第二部分的博客,我还会介绍上述代码的原理~

更多相关文章

  1. SpringBoot 2.0 中 HikariCP 数据库连接池原理解析
  2. Android(安卓)电源管理 -- wakelock机制
  3. Android关于第三方h5在webview调用摄像头及相机的处理
  4. Java中输入流的read()为阻塞式方法的相关实例
  5. android emulator 运行缓慢的解决办法
  6. Android(安卓)控制线程的暂停和恢复
  7. android studio 适配android7.0 android 6.0拍照调用系统裁剪工
  8. Android(安卓)反射调用方法并利用此方法展开android通知栏(兼容4.
  9. Android(安卓)Trick 11: 对Android中的AsyncTask进行函数化的封

随机推荐

  1. Activity启动流程详解
  2. 关于Android的开发经验总结
  3. android之命令行创建AVD
  4. Android(安卓)TTS 实战一:认识 TTS
  5. Android双机(网络和USB)调试及其完美ROOT
  6. Android入门教程(三)之------导入现有And
  7. 基于Android(安卓)5.0的源码编译过程
  8. Android(安卓)doc 之应用程序基础
  9. Windows Phone是一个严重被低估的移动操
  10. 【Android(安卓)开发】:数据存储之 SQLite