首先,我们先来看一下 startActivityForResult 方法,当 mParent 为 null 的时候,会调用到 mInstrumentation.execStartActivity 方法。当 mParent 不为 null 时,都会调用到 mParent.startActivityFromChild 方法。而 mParent 为 Activity 实例,接下来我们一起看一下 startActivityFromChild 方法。
public void startActivityFromChild(@NonNull Activity child, @RequiresPermission Intent intent,
int requestCode, @Nullable Bundle options) {
options = transferSpringboardActivityOptions(options);
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, child,
intent, requestCode, options);
if (ar != null) {
mMainThread.sendActivityResult(
mToken, child.mEmbeddedID, requestCode,
ar.getResultCode(), ar.getResultData());
}
cancelInputsAndStartExitTransition(options);
}
可以看到 startActivityFromChild 中也会调用 mInstrumentation.execStartActivity 方法。因此,即我们通过 Activity startActivity 的方法启动 activity,最终都会调用到 mInstrumentation.execStartActivity 方法。因此,如果我们想要拦截的话,可以 hook 住 mInstrumentation。
由于 mInstrumentation 是类,不是 interface,不能使用动态代理的方式,因此,这里我们使用静态代理的方式。
下面让我们一起看一下 怎样 hook activity 的 mInstrumentation
-
第一步:拿到当前 activity 的 mInstrumentation
-
第二步:创建代理对象
-
第三步:将我们的代理替换原 activity 的 mInstrumentation
public static void replaceInstrumentation(Activity activity) throws Exception {
Class<?> k = Activity.class;
//通过Activity.class 拿到 mInstrumentation字段
Field field = k.getDeclaredField(“mInstrumentation”);
field.setAccessible(true);
//根据activity内mInstrumentation字段 获取Instrumentation对象
Instrumentation instrumentation = (Instrumentation) field.get(activity);
//创建代理对象
Instrumentation instrumentationProxy = new ActivityProxyInstrumentation(instrumentation);
//进行替换
field.set(activity, instrumentationProxy);
}
public class ActivityProxyInstrumentation extends Instrumentation {
private static final String TAG = “ActivityProxyInstrumentation”;
// ActivityThread中原始的对象, 保存起来
Instrumentation mBase;
public ActivityProxyInstrumentation(Instrumentation base) {
mBase = base;
}
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
// Hook之前, 可以输出你想要的!
Log.d(TAG,"xxxx: 执行了startActivity, 参数如下: " + “who = [” + who + "], " +
“contextThread = [” + contextThread + “], token = [” + token + "], " +
“target = [” + target + “], intent = [” + intent +
“], requestCode = [” + requestCode + “], options = [” + options + “]”);
// 开始调用原始的方法, 调不调用随你,但是不调用的话, 所有的startActivity都失效了.
// 由于这个方法是隐藏的,因此需要使用反射调用;首先找到这个方法
try {
Method execStartActivity = Instrumentation.class.getDeclaredMethod(
“execStartActivity”,
Context.class, IBinder.class, IBinder.class, Activity.class,
Intent.class, int.class, Bundle.class);
execStartActivity.setAccessible(true);
return (ActivityResult) execStartActivity.invoke(mBase, who,
contextThread, token, target, intent, requestCode, options);
} catch (Exception e) {
// rom修改了 需要手动适配
throw new RuntimeException(“do not support!!! pls adapt it”);
}
}
}
在 ActivityProxyInstrumentation 里面,我们打印相应的 log。
运行以下测试代码
try {
HookHelper.replaceInstrumentation(this);
} catch (Exception e) {
e.printStackTrace();
}
startActivity(new Intent(this,TestActivityStart.class));
将会看到输出以下 log
hook activity 的第二种方法
我们先来看一下 getApplicationContext startActivity 的调用关系
因此,这里我们要 hook 的是 ActivityThread 的 mInstrumentation
public static void attachContext() throws Exception {
Log.i(TAG, "attachContext: ");
// 先获取到当前的ActivityThread对象
Class<?> activityThreadClass = Class.forName(“android.app.ActivityThread”);
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod(“currentActivityThread”);
currentActivityThreadMethod.setAccessible(true);
//currentActivityThread是一个static函数所以可以直接invoke,不需要带实例参数
Object currentActivityThread = currentActivityThreadMethod.invoke(null);
// 拿到原始的 mInstrumentation字段
Field mInstrumentationField = activityThreadClass.getDeclaredField(“mInstrumentation”);
mInstrumentationField.setAccessible(true);
Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);
// 创建代理对象
Instrumentation evilInstrumentation = new ApplicationInstrumentation(mInstrumentation);
// 偷梁换柱
mInstrumentationField.set(currentActivityThread, evilInstrumentation);
}
public class ApplicationInstrumentation extends Instrumentation {
private static final String TAG = “ApplicationInstrumentation”;
// ActivityThread中原始的对象, 保存起来
Instrumentation mBase;
public ApplicationInstrumentation(Instrumentation base) {
mBase = base;
}
public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token,
Activity target, Intent intent, int requestCode,
Bundle options) {
// Hook之前, 可以输出你想要的!
Log.d(TAG, "xxxx: 执行了startActivity, 参数如下: " + “who = [” + who + "], " + "contextThread = " +
“” + “” + “[” + contextThread + “], token = [” + token + "], " + “target = [” +
target + “], intent = [” + intent + “], requestCode = [” + requestCode + "], " +
"options = " + “[” + options + “]”);
// 开始调用原始的方法, 调不调用随你,但是不调用的话, 所有的startActivity都失效了.
// 由于这个方法是隐藏的,因此需要使用反射调用;首先找到这个方法
try {
Method execStartActivity = Instrumentation.class.getDeclaredMethod
(“execStartActivity”, Context.class, IBinder.class, IBinder.class, Activity
.class, Intent.class, int.class, Bundle.class);
execStartActivity.setAccessible(true);
return (ActivityResult) execStartActivity.invoke(mBase, who, contextThread, token,
target, intent, requestCode, options);
} catch (Exception e) {
// rom修改了 需要手动适配
throw new RuntimeException(“do not support!!! pls adapt it”);
}
}
}
可以看到在 ApplicationInstrumentation 里面,我们只是打印出 startActivity 中各个方法参数的值。
运行以下测试代码
try {
HookHelper.attachContext();
} catch (Exception e) {
e.printStackTrace();
}
Intent intent = new Intent(this, TestActivityStart.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getApplicationContext().startActivity(intent);
将看到以下 log
上面 hook activity 的两种方法其实都有一定缺陷,比如,第一种方法,只能 hook 住通过 Activity startActivity 的 activity。第二种方法,只能 hook 住通过 getApplicationContext().startActivity 启动的 activity。那有没有一种方法能 hook 上述两种的,其实是有的,那就是 hook AMS。下面让我们一起来看一下。
上面 hook startActivity 其实都是 hook 相应的 mInstrumentation.execStartActivity 方法,因此,我们可以从这里下手,看 mInstrumentation.execStartActivity 里面有没有一些共性的东西,可以 hook。
我们先来 mInstrumentation.execStartActivity 方法
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
Uri referrer = target != null ? target.onProvideReferrer() : null;
if (referrer != null) {
intent.putExtra(Intent.EXTRA_REFERRER, referrer);
}
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
for (int i=0; i<N; i++) {
final ActivityMonitor am = mActivityMonitors.get(i);
ActivityResult result = null;
if (am.ignoreMatchingSpecificIntents()) {
result = am.onStartActivity(intent);
}
if (result != null) {
am.mHits++;
return result;
} else if (am.match(who, null, intent)) {
am.mHits++;
if (am.isBlocking()) {
return requestCode >= 0 ? am.getResult() : null;
}
break;
}
}
}
}
try {
intent.migrateExtraStreamToClipData();
intent.prepareToLeaveProcess(who);
int result = ActivityManager.getService()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException(“Failure from system”, e);
}
return null;
}
这里我们留意 ActivityManager.getService().startActivity 这个方法
public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}
private static final Singleton IActivityManagerSingleton =
new Singleton() {
@Override
protected IActivityManager create() {
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};
可以看到 IActivityManagerSingleton 是一个单例对象,因此,我们可以 hook 它。
public static void hookAMSAfter26() throws Exception {
// 第一步:获取 IActivityManagerSingleton
Class<?> aClass = Class.forName(“android.app.ActivityManager”);
Field declaredField = aClass.getDeclaredField(“IActivityManagerSingleton”);
declaredField.setAccessible(true);
Object value = declaredField.get(null);
Class<?> singletonClz = Class.forName(“android.util.Singleton”);
Field instanceField = singletonClz.getDeclaredField(“mInstance”);
instanceField.setAccessible(true);
Object iActivityManagerObject = instanceField.get(value);
// 第二步:获取我们的代理对象,这里因为 IActivityManager 是接口,我们使用动态代理的方式
Class<?> iActivity = Class.forName(“android.app.IActivityManager”);
InvocationHandler handler = new AMSInvocationHandler(iActivityManagerObject);
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new
Class<?>[]{iActivity}, handler);
// 第三步:偷梁换柱,将我们的 proxy 替换原来的对象
instanceField.set(value, proxy);
}
public class AMSInvocationHandler implements InvocationHandler {
private static final String TAG = “AMSInvocationHandler”;
Object iamObject;
public AMSInvocationHandler(Object iamObject) {
this.iamObject = iamObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Log.e(TAG, method.getName());
if (“startActivity”.equals(method.getName())) {
Log.i(TAG, “ready to startActivity”);
for (Object object : args) {
Log.d(TAG, “invoke: object=” + object);
}
}
return method.invoke(iamObject, args);
}
}
执行以下测试代码
try {
HookHelper.hookAMS();
} catch (Exception e) {
e.printStackTrace();
}
startActivity(new Intent(this,TestActivityStart.class));
将会看到以下 log
I/AMSInvocationHandler: ready to startActivity
接下来我们一起来看一下 API 25 Instrumentation 的代码(自 API 26 开始 ,Instrumentation execStartActivity 方法有所改变)
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
Uri referrer = target != null ? target.onProvideReferrer() : null;
if (referrer != null) {
intent.putExtra(Intent.EXTRA_REFERRER, referrer);
}
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
for (int i=0; i<N; i++) {
final ActivityMonitor am = mActivityMonitors.get(i);
if (am.match(who, null, intent)) {
am.mHits++;
if (am.isBlocking()) {
return requestCode >= 0 ? am.getResult() : null;
}
break;
}
}
}
}
try {
intent.migrateExtraStreamToClipData();
intent.prepareToLeaveProcess(who);
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException(“Failure from system”, e);
}
return null;
}
可以看到这里启动 activity 是调用 ActivityManagerNative.getDefault().startActivity 启动的。
public abstract class ActivityManagerNative extends Binder implements IActivityManager
{
/**
- Retrieve the system’s default/global activity manager.
*/
static public IActivityManager getDefault() {
return gDefault.get();
}
private static final Singleton gDefault = new Singleton() {
protected IActivityManager create() {
IBinder b = ServiceManager.getService(“activity”);
if (false) {
Log.v(“ActivityManager”, "default service binder = " + b);
}
IActivityManager am = asInterface(b);
if (false) {
Log.v(“ActivityManager”, "default service = " + am);
}
return am;
}
};
}
同理我们看到 ActivityManagerNative 的 gDefault 是一个静态变量,因此,我们可以尝试 hook gDefault.
public static void hookAmsBefore26() throws Exception {
// 第一步:获取 IActivityManagerSingleton
Class<?> forName = Class.forName(“android.app.ActivityManagerNative”);
Field defaultField = forName.getDeclaredField(“gDefault”);
defaultField.setAccessible(true);
Object defaultValue = defaultField.get(null);
Class<?> forName2 = Class.forName(“android.util.Singleton”);
Field instanceField = forName2.getDeclaredField(“mInstance”);
instanceField.setAccessible(true);
Object iActivityManagerObject = instanceField.get(defaultValue);
// 第二步:获取我们的代理对象,这里因为 IActivityManager 是接口,我们使用动态代理的方式
Class<?> iActivity = Class.forName(“android.app.IActivityManager”);
InvocationHandler handler = new AMSInvocationHandler(iActivityManagerObject);
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<?>[]{iActivity}, handler);
// 第三步:偷梁换柱,将我们的 proxy 替换原来的对象
instanceField.set(defaultValue, proxy);
}
到此,hook Activity 的三种方式已讲解完毕
启动一个没有在 AndroidManifest 声明的 Activity
我们知道,当我们启动一个没有在 AndroidManifest 中声明的 activity,会抛出 ActivityNotFoundException 异常。
Caused by: android.content.ActivityNotFoundException: Unable to find explicit activity class {com.xj.hookdemo/com.xj.hookdemo.activityhook.TargetAppCompatActivity}; have you declared this activity in your AndroidManifest.xml?
at android.app.Instrumentation.checkStartActivityResult(Instrumentation.java:2124)
at android.app.Instrumentation.execStartActivity(Instrumentation.java:1802)
at android.app.Activity.startActivityForResult(Activity.java:4514)
at android.support.v4.app.BaseFragmentActivityApi16.startActivityForResult(BaseFragmentActivityApi16.java:54)
at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:67)
at android.app.Activity.startActivityForResult(Activity.java:4472)
at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:720)
at android.app.Activity.startActivity(Activity.java:4833)
at android.app.Activity.startActivity(Activity.java:4801)
at com.xj.hookdemo.activityhook.TestStartActivityNoRegister.onB
从报错的堆栈中,我们非常定位到 Instrumentation.execStartActivity 方法
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, String resultWho,
Intent intent, int requestCode, Bundle options, UserHandle user) {
----- // 省略若干代码
try {
intent.migrateExtraStreamToClipData();
intent.prepareToLeaveProcess(who);
int result = ActivityManager.getService()
.startActivityAsUser(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, resultWho,
requestCode, 0, null, options, user.getIdentifier());
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException(“Failure from system”, e);
}
return null;
}
在该方法中,调用 startActivityAsUser 方法通过传入的 intent 获取 result,再通过 checkStartActivityResult 方法,判断 result 是否合法。
而我们知道我们启动的 activity 信息都储存在 intent 中,那么我们若想要 启动一个没有在 AndroidManifest 声明的 Activity,那我们只需要在 某个时机,即调用 startActivity 方法之前欺骗 AMS 我们的 activity 已经注册(即替换 intent),这样就不会抛出 ActivityNotFoundException 异常。
在前面的时候,我们已经讲解到如何 hook ams,这里我们不再具体讲述,主要步骤如下
-
第一步, API 26 以后,hook android.app.ActivityManager.IActivityManagerSingleton, API 25 以前,hook android.app.ActivityManagerNative.gDefault
-
第二步,获取我们的代理对象,这里因为是接口,所以我们使用动态代理的方式
-
第三步:设置为我们的代理对象
private static void hookAMS(Context context) throws ClassNotFoundException,
NoSuchFieldException, IllegalAccessException {
// 第一步, API 26 以后,hook android.app.ActivityManager.IActivityManagerSingleton,
// API 25 以前,hook android.app.ActivityManagerNative.gDefault
Field gDefaultField = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Class<?> activityManager = Class.forName(“android.app.ActivityManager”);
gDefaultField = activityManager.getDeclaredField(“IActivityManagerSingleton”);
} else {
Class<?> activityManagerNativeClass = Class.forName(“android.app.ActivityManagerNative”);
gDefaultField = activityManagerNativeClass.getDeclaredField(“gDefault”);
}
gDefaultField.setAccessible(true);
Object gDefaultObj = gDefaultField.get(null); //所有静态对象的反射可以通过传null获取。如果是实列必须传实例
Class<?> singletonClazz = Class.forName(“android.util.Singleton”);
Field amsField = singletonClazz.getDeclaredField(“mInstance”);
amsField.setAccessible(true);
Object amsObj = amsField.get(gDefaultObj);
//
String pmName = getPMName(context);
String hostClzName = getHostClzName(context, pmName);
// 第二步,获取我们的代理对象,这里因为是接口,所以我们使用动态代理的方式
amsObj = Proxy.newProxyInstance(context.getClass().getClassLoader(), amsObj.getClass()
.getInterfaces(), new AMSHookInvocationHandler(amsObj, pmName, hostClzName));
// 第三步:设置为我们的代理对象
amsField.set(gDefaultObj, amsObj);
}
接着,我们在动态代理对象中,当调用 startActivity 方法的时候,我们把 intent 信息替换,校验的时候就可以绕过系统对 activity 的校验,这样就不会跑出 ActivityNotFoundException 异常。
public class AMSHookInvocationHandler implements InvocationHandler {
public static final String ORIGINALLY_INTENT = “originallyIntent”;
private Object mAmsObj;
private String mPackageName;
private String cls;
public AMSHookInvocationHandler(Object amsObj, String packageName, String cls) {
this.mAmsObj = amsObj;
this.mPackageName = packageName;
this.cls = cls;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 对 startActivity进行Hook
if (method.getName().equals(“startActivity”)) {
int index = 0;
// 找到我们启动时的intent
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
index = i;
break;
}
}
// 取出在真实的Intent
Intent originallyIntent = (Intent) args[index];
Log.i(“AMSHookUtil”, “AMSHookInvocationHandler:” + originallyIntent.getComponent()
.getClassName());
// 自己伪造一个配置文件已注册过的Activity Intent
Intent proxyIntent = new Intent();
// 因为我们调用的Activity没有注册,所以这里我们先偷偷换成已注册。使用一个假的Intent
ComponentName componentName = new ComponentName(mPackageName, cls);
proxyIntent.setComponent(componentName);
// 在这里把未注册的Intent先存起来 一会儿我们需要在Handle里取出来用
proxyIntent.putExtra(ORIGINALLY_INTENT, originallyIntent);
args[index] = proxyIntent;
}
return method.invoke(mAmsObj, args);
}
}
但是,如果仅仅这样做,会存在一个问题,因为 intent 信息在校验的时候被我们替换了,但是我们并没有将其还原,这样,启动的 activity 就不是我们想要的 activity。
那么,我们要在哪个实际将 intent 信息还原呢?
我们回过头再来看一下 Activity 的 startActivityForResult 方法
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
@Nullable Bundle options) {
if (mParent == null) {
options = transferSpringboardActivityOptions(options);
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options);
if (ar != null) {
mMainThread.sendActivityResult(
mToken, mEmbeddedID, requestCode, ar.getResultCode(),
ar.getResultData());
}
if (requestCode >= 0) {
// If this start is requesting a result, we can avoid making
// the activity visible until the result is received. Setting
// this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
// activity hidden during this time, to avoid flickering.
// This can only be done when a result is requested because
// that guarantees we will get information back when the
// activity is finished, no matter what happens to it.
mStartedActivity = true;
}
cancelInputsAndStartExitTransition(options);
// TODO Consider clearing/flushing other event sources and events for child windows.
} else {
if (options != null) {
mParent.startActivityFromChild(this, intent, requestCode, options);
} else {
// Note we want to go through this method for compatibility with
// existing applications that may have overridden it.
mParent.startActivityFromChild(this, intent, requestCode);
}
}
}
该方法主要分为两个逻辑,当 mParent 为空的时候即不为空的时候
-
第一种情况,mParent 不为空的时候,调用到 mInstrumentation.execStartActivity 方法之后,会调用 mMainThread.sendActivityResult 方法
-
第二种情况,当 mParent 为空的时候,会调用 mParent.startActivityFromChild
public void startActivityFromChild(@NonNull Activity child, @RequiresPermission Intent intent,
int requestCode, @Nullable Bundle options) {
options = transferSpringboardActivityOptions(options);
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, child,
intent, requestCode, options);
if (ar != null) {
mMainThread.sendActivityResult(
mToken, child.mEmbeddedID, requestCode,
ar.getResultCode(), ar.getResultData());
}
cancelInputsAndStartExitTransition(options);
}
在 startActivityFromChild 方法里面,又会调用到 mMainThread.sendActivityResult 方法。因此,我们只需看一下该方法是怎样 send ActivityResult 的。
public final class ActivityThread {
public final void sendActivityResult(
IBinder token, String id, int requestCode,
int resultCode, Intent data) {
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
总结
**其实上面说了这么多,钱是永远赚不完的,在这个知识付费的时代,知识技能提升才是是根本!我作为一名8年的高级工程师,知识技能已经学习的差不多。**在看这篇文章的可能有刚刚入门,刚刚开始工作,或者大佬级人物。
像刚刚开始学Android开发小白想要快速提升自己,最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以这里分享一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。
这么重要的事情说三遍啦!点赞+点赞+点赞!
【Android高级架构师系统学习资料】高级架构师进阶必备——设计思想解读开源框架
第一章、热修复设计
第二章、插件化框架设计
第三章、组件化框架设计
第四章、图片加载框架
第五章、网络访问框架设计
第六章、RXJava 响应式编程框架设计
第七章、IOC 架构设计
第八章、Android 架构组件 Jetpack
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
inder token, String id, int requestCode,
int resultCode, Intent data) {
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-MdqTxdDO-1713080968625)]
[外链图片转存中…(img-wnQDycjr-1713080968626)]
[外链图片转存中…(img-qS3PuC0R-1713080968626)]
[外链图片转存中…(img-m65XqgcR-1713080968626)]
[外链图片转存中…(img-Ri43XXW1-1713080968627)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
总结
**其实上面说了这么多,钱是永远赚不完的,在这个知识付费的时代,知识技能提升才是是根本!我作为一名8年的高级工程师,知识技能已经学习的差不多。**在看这篇文章的可能有刚刚入门,刚刚开始工作,或者大佬级人物。
像刚刚开始学Android开发小白想要快速提升自己,最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以这里分享一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。
这么重要的事情说三遍啦!点赞+点赞+点赞!
[外链图片转存中…(img-p4WdIzgc-1713080968627)]
【Android高级架构师系统学习资料】高级架构师进阶必备——设计思想解读开源框架
第一章、热修复设计
第二章、插件化框架设计
第三章、组件化框架设计
第四章、图片加载框架
第五章、网络访问框架设计
第六章、RXJava 响应式编程框架设计
第七章、IOC 架构设计
第八章、Android 架构组件 Jetpack
[外链图片转存中…(img-dRkhSZwv-1713080968627)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!