編輯:關於android開發
通過之前的例子例子,我們學習了如何尋找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中看下:
他調用了另外一個成員函數(這裡唠叨下,看到第一個參數沒?AMS通過他和我們的ui線程通信)
這裡的AMS代碼被重構了一遍,這裡是要進入到ActivityStackSupervisor這個類中去處理了。從名字上我們很容易看出,這裡就是進行之前我們說的——讓ActivityStack做一些准備工作
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);
切進去看下:
@Override
3027 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 query = queryIntentActivities(intent, resolvedType, flags, userId);
//選擇出最優的activity
3032 return chooseBestActivity(intent, resolvedType, flags, query, userId);
3033 }
queryIntentActivities:
3349 @Override
3350 public List 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 list = new ArrayList(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 // reader
3374 synchronized (mPackages) {
3375 final String pkgName = intent.getPackage();
3376 if (pkgName == null) {
3377 List 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 result = new ArrayList(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 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();
3406 }
3407 }
顯然是根據intent中提供的信息,檢索出最匹配的結果
之後調用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 recZ喎?http://www.Bkjia.com/kf/ware/vc/" target="_blank" class="keylink">vcmSjrLTmt8XU2rvutq/Vu8Dvw+ahozxiciAvPg0KztLDx7+0z8LP1Mq+xvS2r8qxSW50ZW50tcS5udTsuq/K/bDJo7o8YnIgLz4NCjxpbWcgYWx0PQ=="這裡寫圖片描述" src="http://www.bkjia.com/uploads/allimg/160417/0414292W6-14.png" title="\" />
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的!沒瑕疵
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:
使用方式:
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啟動就行了。具體請參考第二部分的博客,我還會介紹上述代碼的原理~
HTML5實現3D和2D可視化QuadTree四叉樹碰撞檢測QuadTree四叉樹顧名思義就是樹狀的數據結構,其每個節點有四個孩子節點,可將二維平面遞歸分割子區域。Qua
仿天天動聽5應用項目源碼,項目源碼這是一個高仿天天動聽5的android版音樂播放器,界面華麗功能完整,除了本地播放器應有的那些功能另外還添加了程序內直接在線匹配下載歌詞
如何寫一個簡易的文件系統(4):umount哈哈,時隔幾年,又從磁盤深處找出了原始代碼myfs.zip。---------------------------------
Android動畫解析(一)—— Frame Animation(幀動畫) 動畫在我們實際開發中占有很重要的地位,一個優秀的動畫能為我們的app應用增色很多,同時