静态广播的调用:
定义静态广播:
对于插件中动态广播的调用在上一次https://www.cnblogs.com/webor2006/p/12268754.html中已经实现完了,接下来则来实现静态广播的调用,先来在插件中定义一个静态广播:
![](https://i-blog.csdnimg.cn/blog_migrate/849ae685fd0aacf52a9d71918122b50d.png)
此时就需要在清单文件中进行注册了:
![](https://i-blog.csdnimg.cn/blog_migrate/f6a0eeb8a6aeb12fc614995183bf1f49.png)
然后当收到广播消息之后,再往宿主也发送一个广播,这样就从宿主到插件,插件到宿主之间消息串通了,如下:
![](https://i-blog.csdnimg.cn/blog_migrate/cc4f375018a4018f3d79668b80363cb2.png)
接下来就是怎么来调用插件中广播的问题了。
思路寻找:
其实思路很简单,就是想办法将插件中清单文件中的所有广播给注册到宿主中既可,也就是将静态广播转为动态广播,那如何做到呢?此时就需要从何时扫描清单文件的系统源码入手,来找到我们想要的答案了,关于这块的流程其实在当时https://www.cnblogs.com/webor2006/p/11820036.html时已经有详细的描述,这里再针对性的进行梳理一下,先回顾一张图:
![](https://i-blog.csdnimg.cn/blog_migrate/1dc3a72ade4140141c3d2163bd093a45.png)
其中会从上图的SystemServer来进行分析,Android8.0的,它有个主入口函数:
![](https://i-blog.csdnimg.cn/blog_migrate/6257075fe664281e2323a70d7250a33c.png)
然后在这个run()方法中,会启动很多服务:
![](https://i-blog.csdnimg.cn/blog_migrate/398d9f868bcf9f3831948500779f7d89.png)
而对于清单文件的扫码是PKMS服务来负责的,关于它其实在之前https://www.cnblogs.com/webor2006/p/11890629.html也详细描述过,而它的启动是在这里面:
![](https://i-blog.csdnimg.cn/blog_migrate/ab7a6dde90d500ea8b37898daf1b2b54.png)
![](https://i-blog.csdnimg.cn/blog_migrate/029cfc59ca8b56679aa3044485250264.png)
此时看一下它的main()方法:
![](https://i-blog.csdnimg.cn/blog_migrate/8dda8b5e69f0c91e9a07f96cbac81356.png)
此时则看一下PKMS的构建细节:
![](https://i-blog.csdnimg.cn/blog_migrate/aa435ee21f03f1f0f8a7a078bf94c8f7.png)
然后这里面分为5个阶段:
![](https://i-blog.csdnimg.cn/blog_migrate/fee8eeb658aba241114dd0c182fa2707.png)
![](https://i-blog.csdnimg.cn/blog_migrate/3ea3fd6edc9133eb59d0a72b741f57ab.png)
![](https://i-blog.csdnimg.cn/blog_migrate/5d1fc27bd6bd452f64c72de01e6c10c2.png)
![](https://i-blog.csdnimg.cn/blog_migrate/b5795e46680934168e65f6c19d793846.png)
![](https://i-blog.csdnimg.cn/blog_migrate/dd0e212069339188ba7b7f78c654dc37.png)
其中我们所要看的是阶段3,因为它里面开启了清单文件了扫描:
![](https://i-blog.csdnimg.cn/blog_migrate/80b87a41b6f9f3e8560ded97c1f895be.png)
![](https://i-blog.csdnimg.cn/blog_migrate/d4aa22116a11ef5923a2daa5cb79b540.png)
继续往里跟踪:
![](https://i-blog.csdnimg.cn/blog_migrate/f16ebeea4084b6988b4c8ab499039024.png)
此时我们打开PackageParser类瞅一眼,会看到我们清单文件中的各个节点名字:
![](https://i-blog.csdnimg.cn/blog_migrate/0c48157c885fa140058d0cd4dcc24c2e.png)
貌似木有看到四大组件相关的东东呀,往后再分析就会发现答案滴。继续查看一下它解析的细节:
![](https://i-blog.csdnimg.cn/blog_migrate/4ec5dd965349d8faf7809d3af62bbaa1.png)
![](https://i-blog.csdnimg.cn/blog_migrate/ac416b7194245aa61c9ffe0b61c7bf6f.png)
其中Package是一个内部类:
![](https://i-blog.csdnimg.cn/blog_migrate/5bf9a641576962bfce26d0468eaf5614.png)
![](https://i-blog.csdnimg.cn/blog_migrate/74455ef98ae7ef411adfa90da2cdf98d.png)
那具体是怎么解析的呢?
![](https://i-blog.csdnimg.cn/blog_migrate/4f22b4cd83ce903fb59ca88de5936e2d.png)
![](https://i-blog.csdnimg.cn/blog_migrate/ff768f786fbf4dec37dfa77102828208.png)
![](https://i-blog.csdnimg.cn/blog_migrate/2329e2aa7988811d05e049de2ebb24d7.png)
继续往下走:
![](https://i-blog.csdnimg.cn/blog_migrate/79e81a2d1e908ffdab33a0be66e702dc.png)
![](https://i-blog.csdnimg.cn/blog_migrate/ade698029414abd430e4b4a092b380b6.png)
也就是:
![](https://i-blog.csdnimg.cn/blog_migrate/2c3b3db39fe0d316de7fe314fdb2cfa7.png)
跟进去就可以看到解析我们的四大组件了:
![](https://i-blog.csdnimg.cn/blog_migrate/c22c8e81855993c04de2c82d45db2790.png)
这里要特别的留心啦,都是重点,因为接下来的实现就是根据这些信息来进行的,其中解析完之后居然返回的是一个Activity??这是为啥呢?我们可以看到跟解析Activity返回的是同样的类型:
![](https://i-blog.csdnimg.cn/blog_migrate/20088e4653862969e431bb5c87daead5.png)
其实很好理解,因为Receiver的节点属性啥的跟Activity基本是一样的:
![](https://i-blog.csdnimg.cn/blog_migrate/4e23237ac30abd4f04f95406e86f3cc4.png)
那Receiver完全可以共同这个Activity结构嘛,注意!!!此时的Activity非我们平常所看到的Activity,这个Activity是一个PackageParser的一个内部结构:
![](https://i-blog.csdnimg.cn/blog_migrate/39281bcdead68dc4af9f8c0d6542495c.png)
接下来瞅一下具体解析的过程,这里面都是我们要的线索哈,眼睛睁大了:
![](https://i-blog.csdnimg.cn/blog_migrate/bd63cff66a3bf86b70054a754a5924c9.png)
![](https://i-blog.csdnimg.cn/blog_migrate/9328cbe3f90cdd4e03a0bebdf80327c0.png)
![](https://i-blog.csdnimg.cn/blog_migrate/cdb7e39cff57a46c6a81ec0828c784b4.png)
好,分析先到这,这里先来整理一下接下来我们要撸码的思路,就是想办法能获取到所有解析的Receiver集合信息是不是我们就可以通过反射来遍历注册到我们的宿主里面了,如下:
![](https://i-blog.csdnimg.cn/blog_migrate/226b7888acc34bd2a23d320f5593eb71.png)
具体怎么来获取,则下面开撸。
具体实现:
![](https://i-blog.csdnimg.cn/blog_migrate/1eeb96a1f245b7a48b44576bb5e4e227.png)
首先咱们得将我们插件传到这个方法进行解析,如下:
![](https://i-blog.csdnimg.cn/blog_migrate/e96d23c10dfbafd91c0ad84434af52e6.png)
所以采用反射代码如下:
![](https://i-blog.csdnimg.cn/blog_migrate/9ebf65709ac6e74fb5acd6a3f945c838.png)
然后此时返回的是一个Package对象,里面就有我们想要的receiver集合:
![](https://i-blog.csdnimg.cn/blog_migrate/918e52b2c3a96853e5f79ac4f8115531.png)
所以接下来通过反射来获取它:
![](https://i-blog.csdnimg.cn/blog_migrate/0f7a1eb52d2c669991a10d4e370755a9.png)
接下来则可以遍历所有的广播然后进行注册了:
![](https://i-blog.csdnimg.cn/blog_migrate/592a18387eff0b4244c33f59795ef81e.png)
我们在注册广播时还需要intent-filter属性,而每一个广播里面会有多个Intent-fitler结点,所以此时又需要进行一个遍历:
![](https://i-blog.csdnimg.cn/blog_migrate/c57c8bc8108c843b348fc5bd977dda85.png)
接下来则来看一下怎么来获取intent-filter信息,稍微麻烦一下,它存的地方如下:
![](https://i-blog.csdnimg.cn/blog_migrate/99b616beac56fca463a77f24f3f5cdea.png)
所以咱们得先来获取Component对象,再来获取这个intents属性:
![](https://i-blog.csdnimg.cn/blog_migrate/03ed500450b4a5c746dd0884c5a0af76.png)
好,接下来则需要来获取每个Receiver的具体类的路径,怎么获取?
![](https://i-blog.csdnimg.cn/blog_migrate/3a3fda715dee0ad1a8cb651c3af47cc4.png)
通过这个方法就能获取ActivityInfo信息,然后里面就可以获取到具体的类名啦,所以接下来咱们来写一下:
![](https://i-blog.csdnimg.cn/blog_migrate/68c2393e8ec5b08da327ad33421c8a6b.png)
看源码:
![](https://i-blog.csdnimg.cn/blog_migrate/911971ff970eab32e1fd83d150265519.png)
所以:
![](https://i-blog.csdnimg.cn/blog_migrate/b80c0cfb311e48c76cbb5c78c495b1bf.png)
至此,整个静态广播的注册逻辑就写完了,接下来咱们来应用一下,在宿主增加一个发送广播的按钮:
![](https://i-blog.csdnimg.cn/blog_migrate/4face9e7ff5f7f0b3f761b14d63fd01d.png)
![](https://i-blog.csdnimg.cn/blog_migrate/8f56ab2d342e9d940184a184ea535a6c.png)
好,更新一下插件,并运行一下宿主:
![](https://i-blog.csdnimg.cn/blog_migrate/cf9a9c21c0e5e4aa262551a5bdcb34f2.gif)
完美实现,还是挺复杂的,完全是通过阅读源码来得到解决方案。
Android9.0问题:
说到插件话这种黑科技而言,目前市面对于这个技术的发展也抱有很大的怀疑态度,主要是由于Android9.0的推出会对于@hide的方法是不允许进行反射了,关于这块反射禁止的可以参考这个博文的描述:https://blog.csdn.net/firedancer0089/article/details/82969969,博主的意思是说并非一刀切的做法,实际常用的隐藏方法还是以反射调用的,但是!!像插件化这种技术在Android9.0出来之后对在用插件化技术的公司来说还是有一些影响的,比如支付宝,下面打开一些插件化的模块之后,发现插件界面主要内容已经改为了WebView了,比如:
![](https://i-blog.csdnimg.cn/blog_migrate/c6db028f3544ae01ae97b05285b2c724.png)
打开它,会跳到相关插件化的页面:
![](https://i-blog.csdnimg.cn/blog_migrate/816fc1557fce443a5aad688e1254429c.png)
貌似看着像纯Native的原生页面,但是咱们打开布局边界来瞅一眼内容区:
![](https://i-blog.csdnimg.cn/blog_migrate/fe178cb0add168f835e9eebed2a2075e.png)
很明显的可以看到它不是原生的了,像它里面集成的很多插件应用打开基本上都是这样改为h5了,本想看一下它的布局层次,发现Android3.0以后hierarchyviewer没法用了,直接通过sdk打开Android Device Monitor又卡住无打动弹,而直接想到Android Studio代替hierarchyviewer的layout inspector吧,只能对带有调试功能的app进行使用,我手机又木有root权限,所以这里就大致猜测一下,也就是说对于插件化这样的技术在未来应该说会越来越受到限制,但是学习它的一些原理对于自己的知识技能提高还是挺有帮助的。对于这个隐藏方法的限制其实也有方法可以绕过去,那就是Hook技术,接下来则对这个Hook技术进行一个入门,关于怎么通过Hook技术来实现插件化待之后有时间再来研究,目前先对Hook技术有一个直观的认识,我记得当年面试时也被面试官问到过这个话题,所以也有必要好好了解一下它。
Hook技术初识:
概念:
Hook技术也称为钩子函数。钩子函数实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。在系统没有到调用该函数之前,钩子程序就先捕获该消息,这样钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该函数的执行行为,还可以强制结束消息的传递。
HOOK技术实现途径:
第一 找到hook点:该hook点是针对一个对象,且该对象一定是静态的。
第二 将hook方法放到系统之外执行?
实践----hook AMS服务实现大型登录架构:
对于一个界面A,里面有个功能点击之后是要跳到界面B的,但是呢界面B是需要让用户登录之后才能够浏览的,所以要实现这样的功能也很简单,在所有需要登录的事件上增加判断登录的逻辑既可,这是常规的做法,而其实像这样的功能比较高级的做法就是可以通过Hook来做,对于Activity的跳转最终是由ActivityManagerService来处理的,关于这一块在之前https://www.cnblogs.com/webor2006/p/11820036.html的AMS中已经详细阐述过了,而ActivityManagerService的获取是在这块:
![](https://i-blog.csdnimg.cn/blog_migrate/026945b63bf42d08ac7720ab4e7412ca.png)
所以我们可以Hook这个Singleton,将AMS对象进行偷梁换柱,最终增加我们判断登录的逻辑则就可以实现通用的一个登录效果了,下面先来看一下最终实现的效果:
![](https://i-blog.csdnimg.cn/blog_migrate/83eb98fa173a438e365aefdd9af3a26a.gif)
这种需求是比较常见的,就不多说了,下面则直接开撸通过Hook的方式实现这样的效果。
框架搭建:
先来将上图的界面给搭建一下,毫无技术含量,直接贴代码了:
主界面:
package com.android.hookamsloginstudy;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void jump2(View view) {
Intent intent = new Intent(this, SceondActivity.class);
startActivity(intent);
}
public void jump3(View view) {
Intent intent = new Intent(this, ThreeActivity.class);
startActivity(intent);
}
public void jump4(View view) {
Intent intent = new Intent(this,ThirdActivity.class);
startActivity(intent);
}
public void logout(View view) {
SharedPreferences share = this.getSharedPreferences("cexo", MODE_PRIVATE);
SharedPreferences.Editor editor = share.edit();
editor.putBoolean("login",false);
Toast.makeText(this, "退出登录成功",Toast.LENGTH_SHORT).show();
editor.commit();
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="jump2"
android:text="跳转界面2" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="jump3"
android:text="跳转界面3" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="jump4"
android:text="跳转界面4" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="logout"
android:text="退出登录" />
</LinearLayout>
三个业务页面:
SecondActivity:
package com.android.hookamsloginstudy;
import android.app.Activity;
import android.os.Bundle;
import androidx.annotation.Nullable;
public class SceondActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="这是第二个需要登陆的Activity" />
</LinearLayout>
ThirdActivity:
package com.android.hookamsloginstudy;
import android.app.Activity;
import android.os.Bundle;
import androidx.annotation.Nullable;
public class ThirdActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_thrid);
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="这是第四个需要登陆的Activity" />
</LinearLayout>
ThreeActivity:
package com.android.hookamsloginstudy;
import android.app.Activity;
import android.os.Bundle;
import androidx.annotation.Nullable;
public class ThreeActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_three);
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="这是第三个需要登陆的Activity" />
</LinearLayout>
登录页面:
package com.android.hookamsloginstudy;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.Nullable;
/**
* Created by Administrator on 2018/2/26 0026.
*/
public class LoginActivity extends Activity {
EditText name;
EditText password;
private String className;
SharedPreferences share;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
name = (EditText) findViewById(R.id.name);
password = (EditText) findViewById(R.id.password);
share = this.getSharedPreferences("cexo", MODE_PRIVATE);//实例化
className = getIntent().getStringExtra("extraIntent");
if (className != null) {
((TextView)findViewById(R.id.text)).setText(" 跳转界面:"+className);
}
}
public void login(View view) {
if ((name.getText() == null || password.getText() == null)) {
Toast.makeText(this, "请填写用户名 或密码",Toast.LENGTH_SHORT).show();
return;
}
if ("admin".equals(name.getText().toString()) && "admin".equals(password.getText()
.toString())) {
SharedPreferences share = super.getSharedPreferences("cexo", MODE_PRIVATE);//实例化
SharedPreferences.Editor editor = share.edit(); //使处于可编辑状态
editor.putString("name", name.getText().toString());
editor.putString("sex", password.getText().toString());
editor.putBoolean("login",true); //设置保存的数据
Toast.makeText(this, "登录成功",Toast.LENGTH_SHORT).show();
editor.commit(); //提交数据保存
if (className != null) {
ComponentName componentName = new ComponentName(this, className);
Intent intent = new Intent();
intent.setComponent(componentName);
startActivity(intent);
finish();
}
}else{
SharedPreferences.Editor editor = share.edit(); //使处于可编辑状态
editor.putBoolean("login",false); //设置保存的数据
Toast.makeText(this, "登录失败",Toast.LENGTH_SHORT).show();
editor.commit(); //提交数据保存
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center" />
<EditText
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginLeft="10dp"
android:hint=" 用户名"
android:textSize="15sp" />
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginLeft="10dp"
android:hint=" 密码"
android:textSize="15sp" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:gravity="center"
android:onClick="login"
android:padding="10dp"
android:text="登录"
android:textColor="#fff"
android:textSize="18sp" />
</LinearLayout>
![](https://i-blog.csdnimg.cn/blog_migrate/ade765c3dbd018f5749a915c13aa9359.png)
Hook登录实现:
Hook AMS:
接下来则来HookAMS,如下:
![](https://i-blog.csdnimg.cn/blog_migrate/b485accdff0eabfa09001577e4e9c7ce.png)
![](https://i-blog.csdnimg.cn/blog_migrate/b2ff377d6b144c0052f81d14072450e0.png)
接下来得获取AMS的对象,这里得区分版本,代码贴出来:
public class AMSCheckEngine {
public static void hookAMS(final Context mContext) throws Exception {
Object mIActivityManagerSingleton = null;
Object mIActivityManager = null;
if (AndroidSdkVersion.isAndroidOS_26_27_28()) {
// 获取系统的 IActivityManager.aidl
Class mActivityManagerClass = Class.forName("android.app.ActivityManager");
mIActivityManager = mActivityManagerClass.getMethod("getService").invoke(null);
// 获取IActivityManagerSingleton
Field mIActivityManagerSingletonField = mActivityManagerClass.getDeclaredField("IActivityManagerSingleton");
mIActivityManagerSingletonField.setAccessible(true);
mIActivityManagerSingleton = mIActivityManagerSingletonField.get(null);
} else if (AndroidSdkVersion.isAndroidOS_21_22_23_24_25()) {
Class mActivityManagerClass = Class.forName("android.app.ActivityManagerNative");
Method getDefaultMethod = mActivityManagerClass.getDeclaredMethod("getDefault");
getDefaultMethod.setAccessible(true);
mIActivityManager = getDefaultMethod.invoke(null);
//gDefault
Field gDefaultField = mActivityManagerClass.getDeclaredField("gDefault");
gDefaultField.setAccessible(true);
mIActivityManagerSingleton = gDefaultField.get(null);
}
}
}
这里看一下源码,对于Android8.0以上的系统,获取AMS是需要这样获取的:
![](https://i-blog.csdnimg.cn/blog_migrate/a1f15aeb47051973544fd9e21430292e.png)
看一下源码:
![](https://i-blog.csdnimg.cn/blog_migrate/35e488d2fba4c67a2a0b576200d331fb.png)
确实是这样的,好,接下来咱们来看一下8.0以下的则需要这样调用:
![](https://i-blog.csdnimg.cn/blog_migrate/a08b4083f475ddd70b596482a694637b.png)
![](https://i-blog.csdnimg.cn/blog_migrate/021e24314852dbe5fa860783d564f764.png)
![](https://i-blog.csdnimg.cn/blog_migrate/b77e39f53c75ba416426cadf1521b631.png)
都是返回IActivityManager对象,好,接下来我们生成一个它的代理对象:
public class AMSCheckEngine {
public static void hookAMS(final Context mContext) throws Exception {
Object mIActivityManagerSingleton = null;
Object mIActivityManager = null;
if (AndroidSdkVersion.isAndroidOS_26_27_28()) {
// 获取系统的 IActivityManager.aidl
Class mActivityManagerClass = Class.forName("android.app.ActivityManager");
mIActivityManager = mActivityManagerClass.getMethod("getService").invoke(null);
// 获取IActivityManagerSingleton
Field mIActivityManagerSingletonField = mActivityManagerClass.getDeclaredField("IActivityManagerSingleton");
mIActivityManagerSingletonField.setAccessible(true);
mIActivityManagerSingleton = mIActivityManagerSingletonField.get(null);
} else if (AndroidSdkVersion.isAndroidOS_21_22_23_24_25()) {
Class mActivityManagerClass = Class.forName("android.app.ActivityManagerNative");
Method getDefaultMethod = mActivityManagerClass.getDeclaredMethod("getDefault");
getDefaultMethod.setAccessible(true);
mIActivityManager = getDefaultMethod.invoke(null);
//gDefault
Field gDefaultField = mActivityManagerClass.getDeclaredField("gDefault");
gDefaultField.setAccessible(true);
mIActivityManagerSingleton = gDefaultField.get(null);
}
//获取动态代理
Class mIActivityManagerClass = Class.forName("android.app.IActivityManager");
final Object finalMIActivityManager = mIActivityManager;
Object mIActivityManagerProxy = Proxy.newProxyInstance(mContext.getClassLoader(),
new Class[]{mIActivityManagerClass},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("startActivity".equals(method.getName())) {
//TODO:这里就可以做跳转的拦截
}
return method.invoke(finalMIActivityManager, args);
}
});
if (mIActivityManagerSingleton == null || mIActivityManagerProxy == null) {
throw new IllegalStateException("实在是没有检测到这种系统,需要对这种系统单独处理...");
}
}
}
好,最后,咱们开始偷梁换助啦,我们将Sington中的对象替换成我们新生成的代理对象:
public class AMSCheckEngine {
public static void hookAMS(final Context mContext) throws Exception {
Object mIActivityManagerSingleton = null;
Object mIActivityManager = null;
if (AndroidSdkVersion.isAndroidOS_26_27_28()) {
// 获取系统的 IActivityManager.aidl
Class mActivityManagerClass = Class.forName("android.app.ActivityManager");
mIActivityManager = mActivityManagerClass.getMethod("getService").invoke(null);
// 获取IActivityManagerSingleton
Field mIActivityManagerSingletonField = mActivityManagerClass.getDeclaredField("IActivityManagerSingleton");
mIActivityManagerSingletonField.setAccessible(true);
mIActivityManagerSingleton = mIActivityManagerSingletonField.get(null);
} else if (AndroidSdkVersion.isAndroidOS_21_22_23_24_25()) {
Class mActivityManagerClass = Class.forName("android.app.ActivityManagerNative");
Method getDefaultMethod = mActivityManagerClass.getDeclaredMethod("getDefault");
getDefaultMethod.setAccessible(true);
mIActivityManager = getDefaultMethod.invoke(null);
//gDefault
Field gDefaultField = mActivityManagerClass.getDeclaredField("gDefault");
gDefaultField.setAccessible(true);
mIActivityManagerSingleton = gDefaultField.get(null);
}
//获取动态代理
Class mIActivityManagerClass = Class.forName("android.app.IActivityManager");
final Object finalMIActivityManager = mIActivityManager;
Object mIActivityManagerProxy = Proxy.newProxyInstance(mContext.getClassLoader(),
new Class[]{mIActivityManagerClass},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("startActivity".equals(method.getName())) {
//TODO:这里就可以做跳转的拦截
}
return method.invoke(finalMIActivityManager, args);
}
});
if (mIActivityManagerSingleton == null || mIActivityManagerProxy == null) {
throw new IllegalStateException("实在是没有检测到这种系统,需要对这种系统单独处理...");
}
Class mSingletonClass = Class.forName("android.util.Singleton");
Field mInstanceField = mSingletonClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
// 把系统里面的 IActivityManager 换成 我们自己写的动态代理对象
mInstanceField.set(mIActivityManagerSingleton, mIActivityManagerProxy);
}
}
好,这样是不是以后每一次Activity的跳转都会经由我们的代理对象进行方法拦截了,是的~~我们可以打下日志来确认一下:
![](https://i-blog.csdnimg.cn/blog_migrate/f2cec63c5079b58cc4b3ecff25ec5141.png)
运行:
![](https://i-blog.csdnimg.cn/blog_migrate/1f9ea4ef7fa07c32fe9cfcd041613bd9.gif)
妥妥的,那接下来这里面的逻辑怎么写呢?可以将判断是否登录的逻辑写在这:
![](https://i-blog.csdnimg.cn/blog_migrate/5a31b092bf5043dd5e5ed6c7c265e8d3.png)
其中有个参数类:
![](https://i-blog.csdnimg.cn/blog_migrate/4e2e1f07ebb1d125b649de758bbeb446.png)
运行结果确实如预期,不过这里还有另外一种处理方法,稍复杂一点,我们知道最终Activity的跳转是会调用ApplicationThread中mH来处理的:
Android8.0以上是它:
![](https://i-blog.csdnimg.cn/blog_migrate/231354b09a74dad231c369717e634e94.png)
而Android8.0以下则是它:
![](https://i-blog.csdnimg.cn/blog_migrate/5c309e89e4d2b6e1c50064ebb461f7c9.png)
那咱们来Hook一下ActivityThread的这个mH中的回调方法,来拦截相印的消息是不是也可以达到这样的效果呢?答案是的,这里可以借助于一个代理类来进行,如下:
![](https://i-blog.csdnimg.cn/blog_migrate/1bb1d5f69dd2898b7d3466fe9871ca9b.png)
![](https://i-blog.csdnimg.cn/blog_migrate/351e8fa207f38512cd08afc695bf84c8.png)
Hook ActivityThread:
接下来咱们来hook一下ActivityThread,如下:
![](https://i-blog.csdnimg.cn/blog_migrate/dd524ca5a0b63012204fb0afdf102ede.png)
然后由于有版本差异,所以这里面也有版本兼容的判断,这里就不过多解释了,将代码贴出来,对着源码也很容易理解,如下:
package com.android.hookamsloginstudy.os;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import com.android.hookamsloginstudy.LoginActivity;
import java.lang.reflect.Field;
import java.util.List;
/**
* 即将要加载的时候,需要把ProxyActiviy给换回来,换成目标LoginActivity,我们也称为【还原操作】
*/
public class MyActivityThread {
private Context context;
public MyActivityThread(Context context) {
this.context = context;
}
/**
* TODO 注意:此方法 适用于 21以下的版本 以及 21_22_23_24_25 26_27_28 等系统版本
*
* @param mContext
* @throws Exception
*/
public void hookActivityThreadmHAction(Context mContext) throws Exception {
context = mContext;
if (AndroidSdkVersion.isAndroidOS_26_27_28()) {
do_26_27_28_mHRestore();
} else if (AndroidSdkVersion.isAndroidOS_21_22_23_24_25()) {
do_21_22_23_24_25_mHRestore();
} else {
throw new IllegalStateException("实在是没有检测到这种系统,需要对这种系统单独处理...");
}
}
/**
* TODO 给 26_27_28 系统版本 做【还原操作】的
*/
private final void do_26_27_28_mHRestore() throws Exception {
Class mActivityThreadClass = Class.forName("android.app.ActivityThread");
Object mActivityThread = mActivityThreadClass.getMethod("currentActivityThread").invoke(null);
Field mHField = mActivityThreadClass.getDeclaredField("mH");
mHField.setAccessible(true);
Object mH = mHField.get(mActivityThread);
Field mCallbackField = Handler.class.getDeclaredField("mCallback");
mCallbackField.setAccessible(true);
// 把系统中的Handler.Callback实现 替换成 我们自己写的Custom_26_27_28_Callback
mCallbackField.set(mH, new Custom_26_27_28_Callback());
}
private class Custom_26_27_28_Callback implements Handler.Callback {
@Override
public boolean handleMessage(Message msg) {
if (Parameter.EXECUTE_TRANSACTION == msg.what) {
Object mClientTransaction = msg.obj;
try {
Class<?> mClientTransactionClass = Class.forName("android.app.servertransaction.ClientTransaction");
Field mActivityCallbacksField = mClientTransactionClass.getDeclaredField("mActivityCallbacks");
mActivityCallbacksField.setAccessible(true);
List mActivityCallbacks = (List) mActivityCallbacksField.get(mClientTransaction);
/**
* 高版本存在多次权限检测,所以添加 需要判断
*/
if (mActivityCallbacks.size() == 0) {
return false;
}
Object mLaunchActivityItem = mActivityCallbacks.get(0);
Class mLaunchActivityItemClass = Class.forName("android.app.servertransaction.LaunchActivityItem");
// TODO 需要判断
if (!mLaunchActivityItemClass.isInstance(mLaunchActivityItem)) {
return false;
}
Field mIntentField = mLaunchActivityItemClass.getDeclaredField("mIntent");
mIntentField.setAccessible(true);
// 需要拿到真实的Intent
Intent proxyIntent = (Intent) mIntentField.get(mLaunchActivityItem);
Log.d("hook", "proxyIntent:" + proxyIntent);
Intent targetIntent = proxyIntent.getParcelableExtra(Parameter.TARGET_INTENT);
if (targetIntent != null) {
//集中式登录
SharedPreferences share = context.getSharedPreferences("cexo",
Context.MODE_PRIVATE);
if (share.getBoolean("login", false)) {
// 登录 还原 把原有的意图
targetIntent.setComponent(targetIntent.getComponent());
} else {
ComponentName componentName = new ComponentName(context, LoginActivity.class);
targetIntent.putExtra("extraIntent", targetIntent.getComponent().getClassName());
targetIntent.setComponent(componentName);
}
mIntentField.set(mLaunchActivityItem, targetIntent);
}
} catch (Exception e) {
e.printStackTrace();
}
}
return false;
}
}
// >>>>>>>>>>>>>>>>>>>>>>>> 下面是 就是专门给 21_22_23_24_25 系统版本 做【还原操作】的
/**
* TODO 给 21_22_23_24_25 系统版本 做【还原操作】的
*/
private void do_21_22_23_24_25_mHRestore() throws Exception {
Class<?> mActivityThreadClass = Class.forName("android.app.ActivityThread");
Field msCurrentActivityThreadField = mActivityThreadClass.getDeclaredField("sCurrentActivityThread");
msCurrentActivityThreadField.setAccessible(true);
Object mActivityThread = msCurrentActivityThreadField.get(null);
Field mHField = mActivityThreadClass.getDeclaredField("mH");
mHField.setAccessible(true);
Handler mH = (Handler) mHField.get(mActivityThread);
Field mCallbackFile = Handler.class.getDeclaredField("mCallback");
mCallbackFile.setAccessible(true);
mCallbackFile.set(mH, new Custom_21_22_23_24_25_Callback());
}
private class Custom_21_22_23_24_25_Callback implements Handler.Callback {
@Override
public boolean handleMessage(Message msg) {
if (Parameter.LAUNCH_ACTIVITY == msg.what) {
Object mActivityClientRecord = msg.obj;
try {
Field intentField = mActivityClientRecord.getClass().getDeclaredField("intent");
intentField.setAccessible(true);
Intent proxyIntent = (Intent) intentField.get(mActivityClientRecord);
// TODO 还原操作,要把之前的LoginActivity给换回来
Intent targetIntent = proxyIntent.getParcelableExtra(Parameter.TARGET_INTENT);
if (targetIntent != null) {
//集中式登录
SharedPreferences share = context.getSharedPreferences("cexo",
Context.MODE_PRIVATE);
if (share.getBoolean("login", false)) {
// 登录 还原 把原有的意图 放到realyIntent
targetIntent.setComponent(targetIntent.getComponent());
} else {
String className = targetIntent.getComponent().getClassName();
ComponentName componentName = new ComponentName(context, LoginActivity.class);
targetIntent.putExtra("extraIntent", className);
targetIntent.setComponent(componentName);
}
// 反射的方式
intentField.set(mActivityClientRecord, targetIntent);
}
} catch (Exception e) {
e.printStackTrace();
}
}
return false;
}
}
}
至此整个关于Hook实现登录的效果就已经实现了,其实核心还是反射。。只是说反射的可能不是那些标了hide的从而绕开9.0的某些限制,关于hook实现插件化,有了这个hook的基础其实也很容易实现,只是说需要找到hook点,然后反射的代码一多性能上也会有损失,关于这个就不继续讨论了,将来有机会再进行插件化的进一步学习。