어떻게 자원 의 열 복 구 를 진행 합 니까?

열 복구 에서 자원 파일 의 교체 와 관련 될 수 있 습 니 다. 두 가지 문제 가 있 습 니 다. 하 나 는 대체 할 수 있 습 니까?주 앱 의 자원 을 Patch apk 의 자원 으로 대체 하 는 것 입 니 다. 가능 합 니까?2. 어떻게 교체 합 니까? 자원 id 충돌 문제 가 있 습 니까?
본 고 는 이 두 가지 문 제 를 연구 하고 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

좋은 웹페이지 즐겨찾기