編輯:關於Android編程
如何動態啟動插件中的Activity呢?我們首先分析,啟動插件中的Activity需要做那些准備?
1.插件中Activity類的加載我們使用之前我們改造的AssetsMultiDexLoader,來加載assets目錄下的apk。由於之前的博客已經說明了問題,再次不在贅述。
這件事情需要在打包時處理,也就是說,我們需要改造我們的打包工具,在打包時,將各個插件的AndroidManifest文件合並到宿主AndroidManifest文件中。
我們需要在編譯過程和運行過程分別做處理:
我們首先回顧一下Android打包的過程:
1.生成R.java文件
比如:
aapt package -f -m -J ./gen -S res -M AndroidManifest.xml -I D:\android_sdk_for_studio\platforms\android-22\android.jar
2.清空bin目錄
清空上次生成的文件
3.編譯java文件和jar包
javac -encoding GBK -target 1.5 -bootclasspath D:\android_sdk_for_studio\platforms\android-22\android.jar -d bin src\net\mobctrl\normal\apk\*.java gen\net\mobctrl\normal\apk\R.java -classpath libs\*.jar
4.使用dx工具打包成classes.dex
dx --dex --output=C:\Users\mochuan.zhb\newworkspace\BundleApk5\bin\classes.dex C:\Users\mochuan.zhb\newworkspace\BundleApk5\bin\
5.編譯成資源文件
aapt package -f -M AndroidManifest.xml -S res -I D:\android_sdk_for_studio\platforms\android-22\android.jar -F bin\resources.ap_ --non-constant-id
6.使用sdklib.jar工具生成未簽名的apk
java -cp D:\android_sdk_for_studio\tools\lib\sdklib.jar com.android.sdklib.build.ApkBuilderMain bin\MyCommond.apk -v -u -z bin\resources.ap_ -f bin\classes.dex -rf C:\Users\mochuan.zhb\newworkspace\BundleApk5\src
7.使用jarsigner對apk進行簽名
jarsigner -verbose -keystore C:\test.keystore -storepass 123456 -keypass 123456 -signedjar C:\projectdemo-signed.apk C:\test.apk test
為了解決插件與插件之間以及宿主之間的資源沖突問題,我們需要對插件進行編號,修改R文件的生成過程。我們知道,R文件中的ID是一個int類型,總共32位。那麼這32位分別代表什麼含義呢?
1.前8位代表插件的packageId,其中兩個特殊的Id:Host是0x7f,android系統自帶的是以0x01開頭.
2.緊跟著的8位是區分資源類型的,比如layout,id,string,dimen等
3.後面16位是資源的編號
為了解決資源的命名沖突,一般由以下2中方法:
團隊在開發時,對資源的命名進行約定,比如各業務線按照一定的規則命名,大家准守規則,避免重復。
然後在打包時,我們對各個插件的資源進行合並,統一生成R文件,所有插件和宿主的R文件內容都是完全一樣的,資源都保存在宿主項目的資源中。
說明:Google使用的Android打包過程,就是這樣的。比如主project依賴lib_project1,lib_project2等,在編譯主project的時候,它就將各個lib項目的資源都復制到主project中,然後使用aapt進行統一生成R文件,生成多個不同包名的R文件,但是R文件的內容是完全一樣的。
為了從機制上避免資源名稱重復的問題,我們可以通過修改aapt的源碼,讓其可以根據不同的packageId生成不同的id。也就是說,R文件中的id由以下32位組成:
[packageId(8)][resourceType(8)][resourcesSeq(16)]
我們為每一個插件分配一個packageId,范圍是(1,127).
修改aapt的源碼之後,我們需要改動打包過程中的第一步和第五步,在生成R文件和編譯資源的時候,使用我們改造後的aapt。這樣,最終生成的apk,其R文件就是按照我們分配的方式。
我們可以通過反編譯,查看test.apk中的R文件,如下圖所示,我們設定的packageId是5:
【test.apk的反編譯圖】
反編譯之後的id需要轉化為16進制顯示。
注:
反編譯步驟:
1.解壓apk文件
2.使用d2j-dex2jar工具,將dex轉化為jar
3.使用Java-Decompiler反編譯jar
核心代碼:
/**
* 修改AssetManager
*
* @param assetManager
* @param apkPaths
* @return
*/
private static AssetManager modifyAssetManager(AssetManager assetManager,
List apkPaths) {
if (apkPaths == null || apkPaths.size() == 0) {
return null;
}
try {
for (String apkPath : apkPaths) {
try {
AssetManager.class.getDeclaredMethod("addAssetPath",
String.class).invoke(assetManager, apkPath);
} catch (Throwable th) {
System.out.println("debug:createAssetManager :"
+ th.getMessage());
th.printStackTrace();
}
}
return assetManager;
} catch (Throwable th) {
System.out.println("debug:createAssetManager :" + th.getMessage());
th.printStackTrace();
}
return null;
}
/**
* 獲取整個App的資源管理器中的資源
*
* @param context
* @param apkPath
* @return
*/
public static Resources getAppResource(Context context) {
System.out.println("debug:getAppResource ...");
AssetsManager.copyAllAssetsApk(context);
// 獲取dex文件列表
File dexDir = context.getDir(AssetsManager.APK_DIR,
Context.MODE_PRIVATE);
File[] szFiles = dexDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String filename) {
return filename.endsWith(AssetsManager.FILE_FILTER);
}
});
if (szFiles == null || szFiles.length == 0) {
return context.getResources();
}
System.out.println("debug:getAppResource szFiles = "+szFiles.length);
List apkPaths = new ArrayList();
for (File f : szFiles) {
Log.i(TAG, "load file:" + f.getName());
apkPaths.add(f.getAbsolutePath());
System.out.println("debug:apkPath = " + f.getAbsolutePath());
}
AssetManager assetManager = modifyAssetManager(context.getAssets(),
apkPaths);
AppResource resources = new AppResource(
assetManager, context.getResources().getDisplayMetrics(),
context.getResources().getConfiguration());
return resources;
}
核心代碼
public class HostApplication extends Application {
private Resources mAppResources = null;
private Resources mOldResources = null;
@Override
public void onCreate() {
super.onCreate();
mOldResources = super.getResources();
AssetsMultiDexLoader.install(this);// 加載assets中的apk
installResource();
}
@Override
public Resources getResources() {
if(mAppResources == null){
return mOldResources;
}
return this.mAppResources;
}
private void installResource() {
if (mAppResources == null) {
mAppResources = BundlerResourceLoader.getAppResource(this);// 加載assets中的資源對象
}
}
@Override
public AssetManager getAssets() {
if (this.mAppResources == null) {
return super.getAssets();
}
return this.mAppResources.getAssets();
}
}
以BundleActivity為例:
public class BundleActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.bundle_layout);
findViewById(R.id.text_view).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(getApplicationContext(), "Hello", Toast.LENGTH_LONG).show();
}
});
}
@Override
public Resources getResources() {
return getApplication().getResources();
}
}
最關鍵的是,重寫getResources()方法,使用Application的Resource替換當前的Resource方法。
1.Activity的生命周期1)多個Activity組成Activity棧,當前活動位於棧頂。我們先來看看各種Activity基類的類圖:當Activity類定義出來之
Android應用開發-小巫CSDN博客客戶端Jsoup篇 距上一篇博客已經過去了兩個星期,小巫也覺得非常抱歉,因為在忙著做另外一個項目,幾乎抽不出空來,這不小巫會把剩下
拍照——裁剪,或者是選擇圖片——裁剪,是我們設置頭像或上傳圖片時經常需要的一組操作。上篇講了Camera的使用,這篇講一下
前面兩篇文章,我們分析了Activity的布局文件加載、繪制流程,算是對整個Android系統中界面的顯示流程有了一個大概的了解,其實Android系統中所有的顯示控件(