APK動態加載框架解析(一)

既然項目中剛好用到了動態加載框架DL,我就索性研究了一下DL的實現,現在已有小成,否則也不敢出來寫博客,那麼今天我們就看一下DL最簡單的實現,直接上代碼,代碼中有足夠清楚的解釋,這段代碼只能做到啓動Plugin,暫時還不能獲取plugin中的各種資源
先看一下宿主程序的Manifest配置
權限:

 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

Activity節點:

 <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <activity android:name=".ProxyActivity">
        </activity>

宿主程序的啓動頁:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private String mAbsolutePath;
    private TextView mTv_go;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        initSD();
    }
    //不再贅述
    private void initView() {
        mTv_go = (TextView) findViewById(R.id.tv_go);
        mTv_go.setOnClickListener(this);
    }
    //Android 6.0 以後提高了安全性,對SD的讀寫加上這段代碼就行了
    private void initSD() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            if (!ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);
            }
        }
    }

    @Override
    public void onClick(View v) {
        PackageInfo packageInfo;
        //獲取外部存儲路徑,注意:需要提前在手機SD卡中新建DL文件夾,將打包後的插件APK放到這個文件夾中
        String pluginFolder = Environment.getExternalStorageDirectory() + "/DL";
        File pluginFile = new File(pluginFolder);
        //如果 pluginFile這個文件不存在,就新建
        if (!pluginFile.exists()) {
            pluginFile.mkdir();
        }
        //獲取pluginFile文件夾下的所有文件,建議只放一個APK文件,否則還要自己加代碼進去
        File[] files = pluginFile.listFiles();
        //如果pluginFile中沒有插件,顯示未發現
        if (files.length == 0) {
            mTv_go.setText("沒有發現插件");
            return;
        }
        //如果有,遍歷 也就只有一個
        for (File file : files) {
            //APK所在的絕對路徑
            mAbsolutePath = file.getAbsolutePath();
            //獲取 PackageManager
            PackageManager packageManager = this.getPackageManager();
            try {
                //獲取包信息 PackageInfo
                packageInfo = packageManager.getPackageArchiveInfo(mAbsolutePath, PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES);
                if (packageInfo.activities != null && packageInfo.activities.length > 0) {
                    //獲取啓動Activity的name
                    String name = packageInfo.activities[0].name;
                    mTv_go.setText("name:" + name + ";path:" + mAbsolutePath);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        //
        Intent intent = new Intent(this, ProxyActivity.class);
        intent.putExtra(ProxyActivity.EXTRA_DEX_PATH, mAbsolutePath);
        startActivity(intent);
   }
}        

宿主程序的代理Activity

/**
 * 這裏雖然寫的是proxy,但是現在還不算是proxy,根據原著的想法是通過代理模式來託管插件的生命週期
 * 以及獲取插件的相關資源,後期再說,現在先不管。
 */
public class ProxyActivity extends AppCompatActivity {
    public static final String EXTRA_DEX_PATH = "extra.dex.path";
    public static final String FROM = "extra.from";
    public static final int FROM_EXTERNAL = 0;
    private String mDexPath;
    private String mClass;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //接收的值,這裏只傳遞了路徑
        mDexPath = getIntent().getStringExtra(EXTRA_DEX_PATH);
        mClass = getIntent().getStringExtra(EXTRA_CLASS);
        if (mClass == null) {
            launchTargetActivity();
        } else {
            launchTargetActivity(mClass);
        }
    }
    protected void launchTargetActivity() {
        PackageInfo packageInfo = getPackageManager().getPackageArchiveInfo(
                mDexPath, PackageManager.GET_ACTIVITIES);
        if ((packageInfo.activities != null)
                && (packageInfo.activities.length > 0)) {
            mClass = packageInfo.activities[0].name;
            launchTargetActivity(mClass);
        }
    }
    //這裏是主要的邏輯,基本實現就是Java的反射、類裝載以及Android的DexClassLoader
    protected void launchTargetActivity(final String className) {
        Log.i("aaa","start launchTargetActivity, className=" + className);
        //創建一個dex差分包的路徑
        File dexOutputDir = this.getDir("dex", Context.MODE_PRIVATE);
        //獲取dex的絕對路徑
        final String dexOutputPath = dexOutputDir.getAbsolutePath();
        //獲取系統的類裝載器
        ClassLoader localClassLoader = ClassLoader.getSystemClassLoader();
        //APK進行處理,生成dex差分文件
        DexClassLoader dexClassLoader = new DexClassLoader(mDexPath,
                dexOutputPath, null, localClassLoader);
        try {
            //裝載插件的啓動頁Activity,並返回啓動頁類對象,className爲插件APK啓動頁Activity所在的絕對路徑,
            Class<?> localClass = dexClassLoader.loadClass(className);
            //利用反射獲取構造方法
            Constructor<?> localConstructor = localClass
                    .getConstructor();
            //實例化啓動頁對象
            Object instance = localConstructor.newInstance();
            //利用反射獲取setProxy方法
            Method setProxy = localClass.getMethod("setProxy",
                    Activity.class);
            setProxy.setAccessible(true);
            //調用setProxy方法
            setProxy.invoke(instance, this);
            //利用發射獲取protected方法
            Method onCreate = localClass.getDeclaredMethod("onCreate",
                    Bundle.class);
            onCreate.setAccessible(true);
            Bundle bundle = new Bundle();
            bundle.putInt(FROM, FROM_EXTERNAL);
            //調用onCreate方法,並傳遞值
            onCreate.invoke(instance, bundle);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

插件程序的代碼:

public class BaseActivity extends AppCompatActivity {
    public static final String PROXY_VIEW_ACTION = "com.cy.tplugin.VIEW";
    public static final String DEX_PATH = "/storage/emulated/0/DL/app-debug.apk";
    public static final String EXTRA_DEX_PATH = "extra.dex.path";
    public static final String FROM = "extra.from";
    public static final int FROM_EXTERNAL = 0;
    public static final int FROM_INTERNAL = 1;
    protected int mFrom = FROM_INTERNAL;
    protected Activity mProxyActivity;

    public void setProxy(Activity proxyActivity) {
        mProxyActivity = proxyActivity;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        //這裏做了一個判斷,如果是通過插件啓動,則用mProxyActivity.setContentView(generateContentView(mProxyActivity));
        //這個佈局,如果不是則直接調用原始的R.layout.
        if (savedInstanceState != null) {
            mFrom = savedInstanceState.getInt(FROM, FROM_INTERNAL);
        }
        if (mFrom == FROM_INTERNAL) {
            super.onCreate(savedInstanceState);
            mProxyActivity = this;
        }
        Log.i("aaa","onCreate: from= " + mFrom);
        if (mFrom==FROM_EXTERNAL){
            mProxyActivity.setContentView(generateContentView(mProxyActivity));
        }else {
            setContentView(R.layout.main);
        }
    }
    //由於我們只是僅僅實驗啓動插件程序,並沒有去想辦法獲取其資源文件,所以我們不能用XML,只能通過代碼來設置佈局
    private View generateContentView(final Context context) {
        LinearLayout layout = new LinearLayout(context);
        layout.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT));
        layout.setBackgroundColor(Color.parseColor("#ef563a"));
        Button button = new Button(context);
        button.setText("button");
        layout.addView(button, ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.WRAP_CONTENT);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(context, "you clicked button",
                        Toast.LENGTH_SHORT).show();
            }
        });
        return layout;
    }

    protected void startActivityByProxy(String className) {
        if (mProxyActivity == this) {
            Intent intent = new Intent();
            intent.setClassName(this, className);
            this.startActivity(intent);
        } else {
            Intent intent = new Intent(PROXY_VIEW_ACTION);
            intent.putExtra(EXTRA_DEX_PATH, DEX_PATH);
            intent.putExtra(EXTRA_CLASS, className);
            mProxyActivity.startActivity(intent);
        }
    }
}

最後一定要尊重原著

http://blog.csdn.net/singwhatiwanna/article/details/39937639/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章