×

Android HookActivity一行代码实现开屏广告

96
酸菜xwdz
2018.05.09 18:11* 字数 554

@酸菜个人站点
Github
如博客中有不恰当之处欢迎留言交流
转载请注明原创出处

需求背景

多个产品线都需要实现开屏广告,我们产品广告都是接的我们自家广告SDK,而SplashActivity只是几行代码请求我们广告,广告SDK会把View封装好返回来,SplashActivity要做的事情只是获取响应结果,并且show出来.

如何实现一行代码实现开屏广告

  1. 通过Hook Instrumentation监听到App主界面启动并且回调出来,然后跳转到内置的一个activity里面实现开屏广告广告结束再finish。
  2. 如何鉴别是否是主LAUNCHER(App第一个启动界面)

关于如何判断APP的第一个启动界面请看AndroidManifest.xml一段代码

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

intent-filter标签里面两个字段分别表示如下意思

  • android.intent.action.MAIN 决定应用程序最先启动的Activity

  • android.intent.category.LAUNCHER表示activity应该被列入系统的启动器(launcher)(允许用户启动它)。Launcher是安卓系统中的桌面启动器,是桌面UI的统称。

也就是说根据如上标签可以判断是否是App第一个入口Activity代码如下:

 if (Intent.ACTION_MAIN.equals(intent.getAction()) && intent.hasCategory(Intent.CATEGORY_LAUNCHER)) {

 }

如何Hook Instrumentation实现监听Activity生命周期

我们在Activity调用的startActivity(intent)方法最后都会调用到Instrumentation的execStartActivity方法,而最后activity的创建会被回调到InstrumentationcallActivityOnCreate方法。强烈推荐Activity的启动过程

也就是说只需要我们通过反射替换Instrumentation然后在callActivityOnCreate方法里面回调出来即可。反射基础用法

代码如下:

实现Instrumentation实现一个代理类:

/**
 * @author 黄兴伟 (xwd9989@gamil.com)
 * @since 2018/5/9
 */
public class ProxyInstrumentation extends Instrumentation {
    
    private OnActivityCreateListener mOnActivityCreateListener;
    
    public ProxyInstrumentation() {
        Log.e("TAG", "ProxyInstrumentation created");
    }
    
    
    public void setActivityCreateListener(OnActivityCreateListener onActivityCreateListener) {
        this.mOnActivityCreateListener = onActivityCreateListener;
    }
    
    @Override
    public void callActivityOnCreate(Activity activity, Bundle icicle) {
        super.callActivityOnCreate(activity, icicle);
        final Intent intent = activity.getIntent();

        if (Intent.ACTION_MAIN.equals(intent.getAction()) && intent.hasCategory(Intent.CATEGORY_LAUNCHER)) {
               //判断是否是住入口 回调
            if (mOnActivityCreateListener != null) {
                mOnActivityCreateListener.onHookActivityCreated(activity, icicle);
            }
        }
    }
    
    /*回调接口*/
    public interface OnActivityCreateListener {
    
        void onHookActivityCreated(Activity activity, Bundle icicle);
    }
}

反射获取Instrumentation,将代理ProxyInstrumentation注入到ActivityThread中

代码如下:

/**
 * @author 黄兴伟 (xwd9989@gamil.com)
 * @since 2018/5/9
 */
public class HookActivityHelper {
    
    private static final HookActivityHelper INSTANCE = new HookActivityHelper();
    private ProxyInstrumentation mProxyInstrumentation;
    
    public static HookActivityHelper get() {
        return INSTANCE;
    }
    
    
    public void open() {
        mProxyInstrumentation.setActivityCreateListener(new ProxyInstrumentation.OnActivityCreateListener() {
            @Override
            public void onHookActivityCreated(Activity activity, Bundle icicle) {
                Intent intent = new Intent(activity, WelcomeActivity.class);
                activity.startActivity(intent);
            }
        });
    }
    
    
    private HookActivityHelper() {
        try {
            Class<?> clazz = Class.forName("android.app.ActivityThread");
            Method currentActivityThread = clazz.getDeclaredMethod("currentActivityThread");
            Object object = currentActivityThread.invoke(null);
    
    
            Field field = clazz.getDeclaredField("mInstrumentation");
            field.setAccessible(true);
            mProxyInstrumentation = new ProxyInstrumentation();
            field.set(object, mProxyInstrumentation);
    
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

新建内置WelcomeActivity

/**
 * 再这个内置的Activity实现开屏广告UI或请求广告SDK即可
 * @author 黄兴伟 (xwd9989@gamil.com)
 * @since 2018/5/9
 */
public class WelcomeActivity extends AppCompatActivity {

    private TextView mTextView;

    private CountDownTimer mCountDownTimer = new CountDownTimer(6000, 1000) {
        @Override
        public void onTick(long millisUntilFinished) {
            mTextView.setText("Hook成功,欢迎来到酸菜个人站点 huangxingwei.cn 6秒后返回MainActivity = " + millisUntilFinished / 1000);
        }

        @Override
        public void onFinish() {
            finish();
            Log.e("TAG", "WelcomeActivity finished");
        }
    };


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.e("TAG", "WelcomeActivity created success");
        setContentView(R.layout.welcome_layout);
        mTextView = findViewById(R.id.text);
        mCountDownTimer.start();
    }
}

AndroidManifest.xml声明如下

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.xwdz.simple">

    <application
        android:name=".TestApp"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        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=".hook.WelcomeActivity"/>

    </application>

</manifest>

最后再程序的Application编写一行代码即可实现开屏广告。

public class TestApp extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        HookActivityHelper.get().open();
    }
}

Log如下

05-09 17:27:40.832 3997-3997/? E/TAG: ProxyInstrumentation created
05-09 17:27:40.853 3997-3997/? E/TAG: MainActivity created success
05-09 17:27:41.072 3997-3997/? E/TAG: WelcomeActivity created success
05-09 17:27:51.091 3997-3997/com.xwdz.simple E/TAG: WelcomeActivity finished

测试效果如下


hook6.gif

总结

完整代码参照simple-code,其实HookActivity还可以做更多的事,比如插件Activity等,希望能给大家提供到思路,感谢阅读。

Android
Web note ad 1