어떻게 자원 의 열 복 구 를 진행 합 니까?
본 고 는 이 두 가지 문 제 를 연구 하고 Demo 를 통 해 자원 을 어떻게 교체 하 는 지 보 여줄 것 이다.
패 치 apk 를 불 러 올 때 플러그 인 을 불 러 오 는 것 과 유사 합 니 다. 이전 두 편의 글 을 참고 하 십시오.
http://blog.csdn.net/dingjikerbo/article/details/47757511 http://blog.csdn.net/dingjikerbo/article/details/47783411
아래 와 같다
DexClassLoader dexClassLoader = createDexClassLoader(pluginId, packageId, packagePath);
AssetManager assetManager = createAssetManager(packagePath);
Resources resources = createResources(assetManager);
private AssetManager createAssetManager(String dexPath) {
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod(
"addAssetPath", String.class);
addAssetPath.invoke(assetManager, dexPath);
return assetManager;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
private Resources createResources(AssetManager assetManager) {
Resources superRes = mContext.getResources();
Resources resources = new Resources(assetManager,
superRes.getDisplayMetrics(), superRes.getConfiguration());
return resources;
}
패 치 apk 를 위 한 독립 된 AssetManager 를 만 들 고 이 apk 의 경 로 를 AssetManager 의 AssetPath 경로 집합 에 추가 한 것 을 알 수 있 습 니 다.
여기에 두 가지 문제 가 있 습 니 다. 1. 메 인 앱 과 Patch 가 자원 을 불 러 오 는 데 사용 하 는 것 은 서로 다른 AssetManager 와 Resources 입 니 다. 만약 에 메 인 앱 과 Patch APK 의 자원 id 가 충돌 하면 자원 을 불 러 올 때 연결 되 지 않 습 니까?2. 같은 AssetManager 에 여러 개의 자원 팩 을 추가 할 수 있 습 니 다. 만약 에 이 자원 팩 사이 에 같은 자원 id 가 존재 한다 면 자원 을 불 러 올 때 연결 되 지 않 습 니까?
이 두 가지 문 제 를 연구 하려 면 우 리 는 먼저 AssetManager 의 실현 과 자원 로드 체 제 를 알 아야 한다.
모든 AssetManager 에 여러 개의 자원 패 키 지 를 추가 할 수 있 고 자원 을 찾 을 때 빠 르 기 위해 서 는 이 자원 패 키 지 를 먼저 분석 한 다음 에 색인 을 생 성하 여 나중에 자원 을 불 러 올 수 있 습 니 다.이 색인 의 key 는 자원 의 id 가 자원 의 package id, 자원 type id 와 자원 의 entry id 를 포함 해 야 합 니 다.키 의 유일 성 을 확보 하기 위해 서 이 세 가 지 는 동시에 충돌 할 수 없다.자원 type id 는 보통 고정 적 인 것 을 제외 하고 자원 의 package id 와 entry id 는 apk 를 포장 할 때 생 성 되 어야 합 니 다. 따라서 apk 를 포장 할 때 자원 의 package id 와 entry id 를 어떻게 생 성 하 는 지 확인 하 는 데 중점 을 두 어야 합 니 다.그러나 만약 에 두 개의 자원 가방 에 있 는 자원 이 매우 많 고 모든 자원 entry id 를 채 울 수 있 을 정도 로 많다 면 반드시 entry id 의 충돌 이 있 을 것 이 라 고 상상 할 수 있 습 니 다. 그러면 key 의 유일 성 을 확보 하기 위 한 마지막 관문 은 자원 의 package id 만 남 았 습 니 다.그러나 실제 적 으로 컴 파일 할 때 생 성 된 R. 자바 의 모든 자원 은 0x7F 로 시작 합 니 다. 이것 이 바로 package id 입 니 다. 이 를 통 해 포장 할 때 기본 적 인 package id 는 0x7F 임 을 알 수 있 습 니 다. aapt 을 가방 마다 다른 package id 로 지정 하지 않 으 면 자원 충돌 이 발생 할 수 있 습 니 다.
물론 서로 다른 AssetManager 를 사용 하여 자원 가방 에 있 는 자원 을 불 러 오 면 이런 문제 가 없 을 것 입 니 다. 색인 표 가 다 르 기 때문에 key 와 도 상관 이 없습니다.
그 다음 에 우 리 는 AssetManager 의 코드 를 살 펴 보 겠 습 니 다. 여 기 는 자원 로드 의 대체적인 절 차 를 알 기 위해 서 입 니 다. 그래서 세부 사항 을 생략 할 것 입 니 다. 더 깊이 알 고 싶 으 면 다음 과 같은 몇 편의 글 을 참고 하 십시오. 안 드 로 이 드 응용 프로그램 자원 의 컴 파일 과 포장 과정 에서 안 드 로 이 드 응용 프로그램 자원 관리자 (Asset Manager) 를 분석 할 수 있 습 니 다.생 성 프로 세 스 분석 Android 응용 프로그램 자원 검색 프로 세 스 분석
public AssetManager() {
init();
}
private native final void init();
네 이 티 브 init 함 수 를 직접 호출 하여 초기 화 한 것 을 알 수 있 습 니 다.
static void android_content_AssetManager_init(JNIEnv* env, jobject clazz)
{
AssetManager* am = new AssetManager();
am->addDefaultAssets();
env->SetIntField(clazz, gAssetManagerOffsets.mObject, (jint)am);
}
이 init 는 주로 세 가지 일 을 했 습 니 다. 먼저 Native 층 에 AssetManager 를 새로 만 든 다음 에 addDefaultAssets 를 향 해 마지막 으로 이 AssetManager 의 지침 을 자바 의 AssetManager 류 mObject 에 설정 합 니 다.먼저 Asset Manager 의 구조 함 수 를 살 펴 보 자.
class AssetManager : public AAssetManager {
public:
..........
Vector<asset_path> mAssetPaths;
mutable ResTable* mResources;
ResTable_config* mConfig;
CacheMode mCacheMode; // is the cache enabled?
SortedVector<AssetDir::FileInfo> mCache;
AssetManager(CacheMode cacheMode = CACHE_OFF);
..........
}
이 AssetManager 에서 비교적 중요 한 것 은 mAssetPaths 와 mResources 이다.mAssetPaths 에 저 장 된 것 은 이 AssetManager 가 유지 하 는 모든 자원 apk 의 경로 입 니 다.mResources 는 자원 색인 표 에 해당 하 는 리 소스 테이블 입 니 다.addDefaultAssets 다시 보기:
bool AssetManager::addDefaultAssets()
{
const char* root = getenv("ANDROID_ROOT");
String8 path(root);
path.appendPath(kSystemAssets);
return addAssetPath(path, NULL);
}
static const char* kSystemAssets = "framework/framework-res.apk";
이 를 통 해 알 수 있 듯 이 default Assets 는 ${ANDROID ROOT}/framework/framework - res. apk 입 니 다. 시스템 자원 경로 입 니 다.
다음은 이 addAssetPath 함 수 를 중점적으로 설명 합 니 다. 이 함수 코드 를 분석 하기 전에 우 리 는 먼저 그곳 에서 그것 을 호출 한 것 을 보 겠 습 니 다.
1. addDefaultAssets 에서 addAssetPath (path, NULL) 를 호출 했 습 니 다.
2. 자바 층 의 AssetManager 가 addAssetPath 를 호출 할 때 native 층 의 android 로 바 뀌 었 습 니 다.content_AssetManager_addAssetPath 는 다음 과 같 습 니 다.
static jint android_content_AssetManager_addAssetPath(JNIEnv* env, jobject clazz,
jstring path)
{
ScopedUtfChars path8(env, path);
AssetManager* am = assetManagerForJavaObject(env, clazz);
void* cookie;
bool res = am->addAssetPath(String8(path8.c_str()), &cookie);
return (res) ? (jint)cookie : 0;
}
이 두 곳 의 호출 방식 은 약간 다 릅 니 다. 전자 두 번 째 매개 변 수 는 NULL 로 전 달 됩 니 다. 후 자 는 사용자 정의 path 를 추가 할 때 & cookie 로 전 달 됩 니 다. void * 입 니 다. 지침 을 되 돌려 야 할 것 같 습 니 다. 이 지침 이 무엇 을 하 는 지 우 리 는 addAssetPath 코드 를 본 후에 야 알 수 있 습 니 다.
bool AssetManager::addAssetPath(const String8& path, void** cookie) {
for (size_t i=0; i < mAssetPaths.size(); i++) {
if (mAssetPaths[i].path == path) {
if (cookie) {
*cookie = (void*)(i+1);
}
return true;
}
}
mAssetPaths.add(ap);
if (cookie) {
*cookie = (void*)mAssetPaths.size();
}
return true;
}
코드 에서 알 수 있 듯 이 현재 추가 할 path 가 AssetManager 의 mAssetPaths 에 존재 한다 면 바로 돌아 갑 니 다. 그렇지 않 으 면 path 를 mAssetPaths 끝 에 추가 합 니 다.쿠키 의 역할 은 바로 이 path 가 mAssetPaths 에 있 는 index 를 표시 하 는 것 입 니 다.
자, 자원 apk 의 경 로 를 AssetManager 에 추가 하 였 습 니 다. 그러면 자원 을 어떻게 불 러 옵 니까?우 리 는 리 소스. getDrawable 을 입구 로 삼 을 것 이다.
public Drawable getDrawable(int id) {
TypedValue value;
synchronized (mAccessLock) {
value = mTmpValue;
if (value == null) {
value = new TypedValue();
} else {
mTmpValue = null;
}
getValue(id, value, true);
}
final Drawable res = loadDrawable(value, id, theme);
synchronized (mAccessLock) {
if (mTmpValue == null) {
mTmpValue = value;
}
}
return res;
}
이 안 에는 getValue 가 자원 id 에 들 어 와 Typed Value 를 되 돌려 주 고 이 자원 id 와 Typed Value 를 가지 고 loadDrawable 로 갑 니 다.getValue 가 뭐 하 는 지 먼저 볼 까요?
public void getValue(int id, TypedValue outValue, boolean resolveRefs) {
boolean found = mAssets.getResourceValue(id, 0, outValue, resolveRefs);
if (found) {
return;
}
throw new NotFoundException("Resource ID #0x"
+ Integer.toHexString(id));
}
이 안 에는 AssetManager 의 getResource Value 가 호출 되 었 습 니 다. 다음 과 같 습 니 다.
boolean getResourceValue(int ident, int density, TypedValue outValue, boolean resolveRefs)
{
loadResourceValue(ident, (short) density, outValue, resolveRefs);
.............
}
loadResourceValue 에 이 outValue 가 설 정 될 것 같 습 니 다.
private native final int loadResourceValue(int ident, short density, TypedValue outValue,
boolean resolve);
이 함 수 는 native 입 니 다. 다음 과 같이 실 현 됩 니 다.
static jint android_content_AssetManager_loadResourceValue(JNIEnv* env, jobject clazz,
jint ident,
jshort density,
jobject outValue,
jboolean resolve)
{
AssetManager* am = assetManagerForJavaObject(env, clazz);
const ResTable& res(am->getResources());
Res_value value;
ResTable_config config;
uint32_t typeSpecFlags;
ssize_t block = res.getResource(ident, &value, false, density, &typeSpecFlags, &config);
copyValue(env, outValue, &res, value, ident, block, typeSpecFlags, &config);
}
보기 에는 좀 복잡 한 것 같 지만, 많은 함 수 를 조정 한 것 같 지만, 괜찮아, 우 리 는 인내심 을 가지 고 분석 하 자.이 함 수 는 주로 네 가지 일 을 했 습 니 다. 1. assetManager ForJavaObject 를 통 해 AssetManager 대상 을 얻 었 습 니 다.2. am -> getResources () 를 통 해 AssetManager 의 ResTable 을 가 져 옵 니 다.3. ResTable 의 getResource 함 수 를 호출 하여 자원 과 관련 된 색인 과 설정 정 보 를 가 져 옵 니 다.4. copyValue 를 호출 하여 일부 자원 속성 을 TypedValue 에 복사 합 니 다.
먼저 am -> getResources () 의 실현 은 다음 과 같다.
const ResTable& AssetManager::getResources(bool required) const
{
const ResTable* rt = getResTable(required);
return *rt;
}
const ResTable* AssetManager::getResTable(bool required) const
{
ResTable* rt = mResources;
if (rt) {
return rt;
}
if (mResources != NULL) {
return mResources;
}
const size_t N = mAssetPaths.size();
for (size_t i=0; i < N; i++) {
Asset* ass = NULL;
ResTable* sharedRes = NULL;
..........
if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) {
if (rt == NULL) {
mResources = rt = new ResTable();
}
if (sharedRes != NULL) {
rt->add(sharedRes);
} else {
rt->add(ass, (void*)(i+1), !shared, idmap);
}
}
}
if (!rt) {
mResources = rt = new ResTable();
}
return rt;
}
이곳 에 서 는 많은 코드 를 생략 하 였 으 나, 우리 가 대체적인 절 차 를 파악 하 는 데 영향 을 주지 않 는 다.먼저 자원 색인 표 가 생 성 되 었 는 지 여 부 를 판단 하고 생 성 되 었 으 면 바로 되 돌아 갑 니 다. 그렇지 않 으 면 색인 표를 생 성 해 야 합 니 다.mAssetPaths 를 옮 겨 다 니 며 자원 파일 을 순서대로 분석 하고 생 성 된 데이터 구조 와 색인 을 전체 mResources 에 통합 합 니 다.
자, 리 소스 테이블 이 준비 되 었 습 니 다. 리 소스 테이블 에서 getResource 를 어떻게 하 는 지 보 겠 습 니 다.
ssize_t ResTable::getResource(uint32_t resID, Res_value* outValue, bool mayBeBag, uint16_t density,
uint32_t* outSpecFlags, ResTable_config* outConfig) const
{
const ssize_t p = getResourcePackageIndex(resID);
const int t = Res_GETTYPE(resID);
const int e = Res_GETENTRY(resID);
const Res_value* bestValue = NULL;
const Package* bestPackage = NULL;
ResTable_config bestItem;
const PackageGroup* const grp = mPackageGroups[p];
const ResTable_config* desiredConfig = &mParams;
ResTable_config* overrideConfig = NULL;
if (density > 0) {
overrideConfig = (ResTable_config*) malloc(sizeof(ResTable_config));
memcpy(overrideConfig, &mParams, sizeof(ResTable_config));
overrideConfig->density = density;
desiredConfig = overrideConfig;
}
ssize_t rc = BAD_VALUE;
size_t ip = grp->packages.size();
while (ip > 0) {
ip--;
int T = t;
int E = e;
const Package* const package = grp->packages[ip];
const ResTable_type* type;
const ResTable_entry* entry;
const Type* typeClass;
ssize_t offset = getEntry(package, T, E, desiredConfig, &type, &entry, &typeClass);
..........
bestItem = thisConfig;
bestValue = item;
bestPackage = package;
}
if (bestValue) {
outValue->size = dtohs(bestValue->size);
outValue->res0 = bestValue->res0;
outValue->dataType = bestValue->dataType;
outValue->data = dtohl(bestValue->data);
if (outConfig != NULL) {
*outConfig = bestItem;
}
rc = bestPackage->header->index;
goto out;
}
out:
if (overrideConfig != NULL) {
free(overrideConfig);
}
return rc;
}
이 함 수 는 좀 길 지만 주로 몇 가지 일 을 했 는 지 알 수 있 습 니 다. 먼저 자원 ID 를 통 해 자원 의 package id, 유형 id, entry id 를 얻 을 수 있 습 니 다.그리고 패키지 id 를 통 해 해당 하 는 패키지 그룹 을 가 져 오고 패키지 그룹 에 있 는 모든 패키지 에서 자원 을 찾 아 가장 일치 하 는 자원 항목 을 찾 아 되 돌려 줍 니 다.가장 적합 한 자원 항목 을 어떻게 찾 는 지 에 대해 서 는 우리 가 주목 하 는 중점 이 아니 므 로 군말 하지 않 겠 습 니 다.
마지막 으로 이 copyValue 를 살 펴 보 겠 습 니 다. 주로 이 자바 에서 전해 내 려 오 는 outValue 대상 을 설정 합 니 다. 이 outValue 는 자바 의 TypedValue 에 대응 합 니 다.
jint copyValue(JNIEnv* env, jobject outValue, const ResTable* table,
const Res_value& value, uint32_t ref, ssize_t block,
uint32_t typeSpecFlags, ResTable_config* config)
{
env->SetIntField(outValue, gTypedValueOffsets.mType, value.dataType);
env->SetIntField(outValue, gTypedValueOffsets.mAssetCookie,
(jint)table->getTableCookie(block));
env->SetIntField(outValue, gTypedValueOffsets.mData, value.data);
env->SetObjectField(outValue, gTypedValueOffsets.mString, NULL);
env->SetIntField(outValue, gTypedValueOffsets.mResourceId, ref);
env->SetIntField(outValue, gTypedValueOffsets.mChangingConfigurations,
typeSpecFlags);
if (config != NULL) {
env->SetIntField(outValue, gTypedValueOffsets.mDensity, config->density);
}
return block;
}
마지막 으로 Resources 의 loadDrawable 이 어떻게 실현 되 었 는 지 살 펴 보 겠 습 니 다.
Drawable loadDrawable(TypedValue value, int id)
throws NotFoundException {
boolean isColorDrawable = false;
if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
isColorDrawable = true;
}
final long key = isColorDrawable ? value.data :
(((long) value.assetCookie) << 32) | value.data;
Drawable dr = getCachedDrawable(isColorDrawable ? mColorDrawableCache : mDrawableCache, key);
if (dr != null) {
return dr;
}
Drawable.ConstantState cs;
if (isColorDrawable) {
cs = sPreloadedColorDrawables.get(key);
} else {
cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
}
if (cs != null) {
dr = cs.newDrawable(this);
} else {
if (isColorDrawable) {
dr = new ColorDrawable(value.data);
}
if (dr == null) {
String file = value.string.toString();
if (file.endsWith(".xml")) {
XmlResourceParser rp = loadXmlResourceParser(
file, id, value.assetCookie, "drawable");
dr = Drawable.createFromXml(this, rp);
rp.close();
} else {
InputStream is = mAssets.openNonAsset(
value.assetCookie, file, AssetManager.ACCESS_STREAMING);
dr = Drawable.createFromResourceStream(this, value, is,
file, null);
is.close();
}
}
}
.........
return dr;
}
이 함 수 는 먼저 캐 시 에서 자원 을 찾 습 니 다. 찾 으 면 되 돌아 갑 니 다. 찾 지 못 하면 자원 파일 을 열 어 자원 을 읽 고 읽 은 후에 캐 시 에 넣 습 니 다. 논 리 는 간단 합 니 다. 서로 다른 자원 처리 방식 이 다 를 뿐 입 니 다.
전체 자원 을 불 러 오 는 절 차 를 정리 합 니 다. 첫째, 자원 을 가 져 올 때 캐 시 에 있 는 지 없 는 지 먼저 보고 있 으 면 바로 돌아 갑 니 다. 없 으 면 자원 을 불 러 옵 니 다. 둘째, 자원 을 불 러 올 때 자원 의 색인 표 가 만 들 어 졌 는 지 먼저 봅 니 다. 만 들 었 다 면 색인 과 배치 정보 에 따라 자원 을 불 러 옵 니 다. 그렇지 않 으 면 자원 색인 을 분석 하고 만 듭 니 다.
주의해 야 할 것 은 첫째, 자원 인덱스 의 구축 은 AssetManager 단위 입 니 다. 같은 AssetManager 에 여러 개의 자원 패키지 가 있 으 면 이 패키지 들 사이 에 id 충돌 이 없 도록 해 야 합 니 다. 포장 할 때 각 패키지 에 서로 다른 패키지 id 를 설정 하 는 방법 을 고려 해 야 합 니 다.그 러 고 싶 지 않 으 면 가방 마다 AssetManager 를 하나씩 부여 하고 id 충돌 문 제 를 걱정 하지 않 아 도 됩 니 다.
다음은 자원 의 열 복 구 를 어떻게 하 는 지 보 여 주 는 데 사용 되 는 데 모 를 드 리 겠 습 니 다.
먼저 메 인 앱 프로젝트 를 만 듭 니 다. 다음 과 같 습 니 다.
public class MainActivity extends Activity {
private Button mBtnPatch;
private Button mBtnShow;
private TextView mTvText;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBtnPatch = (Button) findViewById(R.id.patch);
mBtnShow = (Button) findViewById(R.id.show);
mTvText = (TextView) findViewById(R.id.text);
mBtnPatch.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
patch();
}
});
mBtnShow.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showName();
}
});
}
private void patch() {
File dir = getExternalCacheDir();
if (!dir.exists() && !dir.mkdirs()) {
return;
}
File patch = new File(getExternalCacheDir(), "Patch1.apk");
if (patch.exists() && patch.isFile()) {
PatchMain.load(this, patch.getAbsolutePath(), null);
}
}
public void showName() {
mTvText.setText(R.string.host);
}
}
여기에 두 개의 단 추 를 추 가 했 습 니 다. 한 단 추 는 Patch 를 터치 하고 한 단 추 는 특정한 함 수 를 호출 합 니 다. 이 함 수 는 우리 가 나중에 Hook 을 원 하 는 것 입 니 다.
패 치 프로젝트 를 다시 만 듭 니 다. 다음 과 같 습 니 다.
public class Patch1 extends Patch {
@Override
public void handlePatch(PatchParam arg0) throws Throwable {
// TODO Auto-generated method stub
final Class<?> clazz = arg0.context.getClassLoader().loadClass(
"com.example.hookresearch.MainActivity");
final Context context = arg0.context;
DexposedBridge.findAndHookMethod(clazz, "showName", new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam arg0)
throws Throwable {
// TODO Auto-generated method stub
Field field = FieldUtils.getDeclaredField(clazz, "mTvText", true);
TextView textView = (TextView) field.get(context);
String text = getPatchString(R.string.patch_world);
Log.i("bush", "patch string is " + text);
textView.setText(text);
Log.i("bush", "host string is " + getHostString("host"));
return null;
}
});
}
}
이 패 치 apk 에는 패 치 패 치 패 치 류 가 하나 밖 에 없 으 며 패 치 류 를 계승 합 니 다.MainActivity 에서 mTvText 에 표 시 된 문자열 을 복원 하기 위해 서 입 니 다. 주 App 에 있 는 다른 문자열 을 표시 할 수도 있 고 Patch apk 에 있 는 문자열 을 표시 할 수도 있 습 니 다.여기 서 먼저 목표 TextView 를 얻 으 려 면 반 사 를 통 해 이 루어 져 야 합 니 다. 이 반 사 는 apache comon lang 의 오픈 소스 라 이브 러 리 를 사용 합 니 다.TextView 를 가 져 오 면 String 자원 을 가 져 옵 니 다. Patch 류 는 Host apk 의 자원 과 Patch apk 의 자원 을 불 러 오 는 인 터 페 이 스 를 제공 합 니 다.Patch apk 에 있 는 자원 을 불 러 오 면 자원 id 를 직접 지정 할 수 있 지만 Host apk 에 있 는 자원 을 불 러 오 려 면 자원 의 name 을 지정 해 야 합 니 다.
다음은 이 Patch 프레임 워 크 의 실현 을 살 펴 보 겠 습 니 다. Host apk 에서 패 치 를 불 러 올 때 PatchMain. load 함 수 를 호출 합 니 다. 다음 과 같 습 니 다.
public static PatchResult load(Context context, String apkPath, HashMap<String, Object> contentMap) {
PatchResult result = loadAllCallbacks(context, apkPath, context.getClassLoader());
PatchParam lpparam = new PatchParam(loadedPatchCallbacks);
lpparam.context = context;
lpparam.contentMap = contentMap;
return PatchCallback.callAll(lpparam);
}
보기 에는 간단 합 니 다. 패 치 apk 를 분석 하고 Patch Result 를 생 성 한 다음 에 파 라 메 터 를 입력 하여 모든 패 치 를 호출 하 는 것 입 니 다.다음은 이 loadAllCallbacks 가 어떻게 실현 되 었 는 지 보 겠 습 니 다.
private static PatchResult loadAllCallbacks(Context context, String apkPath, ClassLoader cl) {
try {
File e = new File(apkPath + "odex");
if(e.exists()) {
e.delete();
}
DexClassLoader mcl = null;
PatchContext patchContext = null;
try {
mcl = new DexClassLoader(apkPath, context.getFilesDir().getAbsolutePath(), (String)null, cl);
AssetManager assetManager = createAssetManager(apkPath);
Resources resources = createResources(context, assetManager);
patchContext = new PatchContext(mcl, assetManager, resources);
} catch (Throwable var11) {
return new PatchResult(false, PatchResult.FOUND_PATCH_CLASS_EXCEPTION, "Find patch class exception ", var11);
}
DexFile dexFile = DexFile.loadDex(apkPath, apkPath + "odex", 0);
Enumeration entrys = dexFile.entries();
ReadWriteSet entry = loadedPatchCallbacks;
synchronized(loadedPatchCallbacks) {
loadedPatchCallbacks.clear();
}
while(entrys.hasMoreElements()) {
String entry1 = (String)entrys.nextElement();
Class entryClass = null;
try {
entryClass = mcl.loadClass(entry1);
} catch (ClassNotFoundException var12) {
var12.printStackTrace();
break;
}
if (Patch.class.isAssignableFrom(entryClass)) {
Patch module = (Patch) entryClass.newInstance();
module.context = context;
module.patch = patchContext;
hookLoadPatch(new PatchCallback(module));
}
}
} catch (Exception var13) {
return new PatchResult(false, PatchResult.FOUND_PATCH_CLASS_EXCEPTION, "Find patch class exception ", var13);
}
return new PatchResult(true, PatchResult.NO_ERROR, "");
}
private static void hookLoadPatch(PatchCallback callback) {
ReadWriteSet var1 = loadedPatchCallbacks;
synchronized(loadedPatchCallbacks) {
loadedPatchCallbacks.add(callback);
}
}
이 코드 는 조금 번 거 롭 지만 주로 두 가지 일 을 했 습 니 다. 첫째, DexClassLoader 로 patch apk 를 불 러 옵 니 다. 대응 하 는 Resources 를 생 성하 면 patch apk 의 자원 을 불 러 옵 니 다. 둘째, patch apk 의 모든 패 치 류 를 분석 하고 패 치 류 는 모두 Patch 류 에서 계승 합 니 다.모든 패 치 류 는 하나의 집합 에 추 가 됩 니 다.
다음은 모든 패 치 류 를 옮 겨 다 니 며 handle Patch 인 터 페 이 스 를 순서대로 호출 하여 패 치 를 하 는 것 입 니 다.
마지막 으로 패 치 의 실현 을 살 펴 보 자.
public abstract class Patch implements IPatch {
public Context context;
public PatchContext patch;
public String getHostString(String name) {
Resources resources = getHostResources();
if (resources != null) {
int resId = resources.getIdentifier(name, "string", getHostPackageName());
if (resId > 0) {
return resources.getString(resId);
}
}
return "";
}
public Drawable getHostDrawable(String name) {
Resources resources = getHostResources();
if (resources != null) {
int resId = resources.getIdentifier(name, "drawable", getHostPackageName());
if (resId > 0) {
return resources.getDrawable(resId);
}
}
return null;
}
public String getPatchString(int resId) {
Resources resources = getPatchResources();
if (resources != null && resId > 0) {
return resources.getString(resId);
}
return "";
}
public Drawable getPatchDrawable(int resId) {
Resources resources = getPatchResources();
if (resources != null && resId > 0) {
return resources.getDrawable(resId);
}
return null;
}
public Resources getHostResources() {
return context != null ? context.getResources() : null;
}
public Resources getPatchResources() {
return patch != null ? patch.resources : null;
}
public String getHostPackageName() {
return context != null ? context.getPackageName() : "";
}
}
전체 프로젝트 파일 링크 는 다음 과 같 습 니 다.https://github.com/dingjikerbo/Techs-Report/blob/master/files/hotfix.7z
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Flutter kakao sdk apk 실행 안될때카카오톡로그인 을 구현하는 도중에 릴리즈와 디버그 키를 모두 넣어주었는데도 apk파일을 배포할때 키값이 다르다면서 실행이 전혀 안되었다.. 난 릴리즈와 디버그 키를 다 넣어주었는데도..실행이 안되다 보니까 이게 뭐지...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.