Java加密框架(JCA)简要说明

加密服务总是关联到一个特定的算法或类型,它既提供了密码操作(如Digital Signature或MessageDigest),生成或供应所需的加密材料(Key或Parameters)加密操作,也会以一个安全的方式生成数据对象(KeyStore或Certificate),封装(压缩)密钥(可以用于加密操作)。

Java Security API中,一个engine class就是定义了一种加密服务,不同的engine class提供不同的服务。下面就来看看有哪些engine class:

1)MessageDigest:对消息进行hash算法生成消息摘要(digest)。
2)Signature:对数据进行签名、验证数字签名。
3)KeyPairGenerator:根据指定的算法生成配对的公钥、私钥。
4)KeyFactory:根据Key说明(KeySpec)生成公钥或者私钥。
5)CertificateFactory:创建公钥证书和证书吊销列表(CRLs)。
6)KeyStore:keystore是一个keys的数据库。Keystore中的私钥会有一个相关联的证书链,证书用于鉴定对应的公钥。一个keystore也包含其它的信任的实体。
7)AlgorithmParameters:管理算法参数。KeyPairGenerator就是使用算法参数,进行算法相关的运算,生成KeyPair的。生成Signature时也会用到。
8)AlgorithmParametersGenerator:用于生成AlgorithmParameters。
9)SecureRandom:用于生成随机数或者伪随机数。
10)CertPathBuilder:用于构建证书链。
11)CertPathValidator:用于校验证书链。
12)CertStore:存储、获取证书链、CRLs到(从)CertStore中。

从上面这些engine class中,可以看出JCA(Java加密框架)中主要就是提供了4种服务:Digest、Key、Cert、Signature、Alogorithm。

1) 对消息内容使用某种hash算法就可以生成Digest。
2) 利用KeyFactory、KeyPairGenerator就可以生成公钥、私钥。
3) 证书中心使用公钥就可生成Cert。
4) 可以使用私钥和Digest就可以消息进行签名Signature。
5) 不论是Digest、Key、Cert、Signature,都要使用到算法Algorithm。

DynamicLoadApk 运行流程跟踪

一、解析插件Apk

DLPluginManager pluginManager = DLPluginManager.getInstance(BaseApplication.getInstance());

DLPluginPackage pluginPackage = pluginManager.loadApk(pluginApkPath);
DLIntent intent = new DLIntent(pluginPackage.packageName, pluginPackage.defaultActivity);

pluginManager.startPluginActivity(this, intent);

1、loadApk方法

public DLPluginPackage loadApk(final String dexPath, boolean hasSoLib){
    mFrom = DLConstants.From_external;
    
    int packageInfoFlags = PackageManager.Get_activities | PackageManager.Get_services;
    PackageInfo packageInfo = mContext.getPackageManager().getPackageArchiveInfo(dexPath, packageInfoFlags);
    if(packageInfo == null) return null;

    DLPluginPackage pluginPackage = preparePluginEnv(packageInfo, dexPath);
    if(hasSoLib){
        copySoLib(dexPath);
    }

    return pluginPackage;
}

2、preparePluginEnv方法

private DLPluginPackage preparePluginEnv(PackageInfo packageInfo, String dexPath){
    DLPluginPackage pluginPackage = mPackagesHolder.get(packageInfo.packageName);
    if(pluginPackage != null) return pluginPackage;

    DexClassLoader dexClassLoader = createDexClassLoader(dexPath);
    AssetManager assetManager = createAssetManager(dexPath);
    Resources resources = createResources(assetManager);

    // create PluginPackage
    pluginPackage = new DLPluginPackage(dexClassLoader, resources, packageInfo);
    mPackageHolder.put(packageInfo.packageName, pluginPackage);

    return pluginPackage;
}

private DexClassLoader createDexClassLoader(String dexPath){
    File dexOutputDir = mContext.getDir("dex", Context.Mode_private);
    dexOutputPath = dexOutputDir.getAbsolutePath();

    DexClassLoader loader = new DexClassLoader(
        dexPath,
        dexOutputPaht,
        mNativeLibDir,
        mContext.getClassLoader()
    );

    return loader;
}

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){}
}

private Resources createResources(AssetManager assetManager){
    Resources superRes = mContext.getResources();
    Resources resources = new Resources(
        assetManager,
        superRes.getDisplayMetrics(),
        superRes.getConfiguration()
    );

    return resources;
}

二、运行插件Apk

启动代理的Activity,分别可以:在DLPluginManager里调用startPluginActivityForResult(一般宿主使用),还可以在DLPluginActivity里使用startPluginActivity(在插件里使用)。

DLIntent intent = new DLIntent(getPackageName(), SecondActivity.class);
startPluginActivity(intent);

上面是调用Activity方法,再往下看:

public int startPluginActivity(DLIntent dlIntent){
    return startPluginActivityForResult(dlIntent, -1);
}

/** @return may be 
    {@link #Start_result_success}, 
    {@link #Start_result_no_pkg},  
    {@link #Start_result_no_class}, 
    {@link #Start_result_type_error} */
public int startPluginActivityForResult(DLIntent dlIntent, int requestCode){
    if(mFrom == DLConstants.From_external){
        if(dlIntent.getPluginPackage() == null){
            dlIntent.setPluginPackage(mPluginPackage.packageName);
        }
    }

    return mPluginManager.startPluginActivityForResult(that, dlIntent, requestCode);
}

之后交给PluginManager启动Activity:

public int startPluginActivityForResult(Context context, DLIntent dlIntent, int requestCode){
    if(mFrom == DLConstants.From_internal){
        dlIntent.setClassName(context, dlIntent.getPluginClass());
        preformStartActivityForResult(context, dlIntent, requestCode);
        return DLPluginManager.Start_result_success;
    }

    String packageName = dlIntent.getPluginPackage();
    if(TextUtils.isEmpty(packageName)){
        throw new NullPointerException("disallow null packageName.");
    }

    DLPluginPackage pluginPackage = mPackageHolder.get(packageName);
    if(pluginPackage == null){
        return Start_result_no_pkg;
    }

    final String className = getPluginActivityFullPath(dlIntent, pluginPackage);
    Class<?> clazz = loadPluginClass(pluginPackage.classLoader, className);
    if(clazz == null){
        return Start_result_no_class;
    }

    Class<? extends Activity> activityClass = getProxyActivityClass(clazz);
    if(activityClass == null){
        return Start_result_type_error;
    }

    dlIntent.putExtra(DLConstants.Extra_class, className);
    dlIntent.putExtra(DLConstants.Extra_package, packageName);
    dlIntent.setClass(mContext, activityClass);
    performStartActivityForResult(context, dlIntent, requestCode);
    return Start_result_success;
}

先进行一些判断,然后获取要启动的Activity的全称,然后通过函数loadPluginClass()利用反射获取插件实例activityClass

函数loadPluginClass:

private Class<?> loadPluginClass(ClassLoader classLoader, String className){
    Class<?> clazz = null;
    try{
        clazz = Class.forName(className, true, classLoader);
    }catch(ClassNotFoundException e){}
    return clazz;
}

通过函数getProxyActivityClass(),根据插件实例获取它对应在宿主里对应的代理类:

private Class<? extends Activity> getProxyActivityClass(Class<?> clazz){
    Class<? extends Activity> activityClass = null;
    if(DLBasePluginActivity.class.isAssignableFrom(clazz)){
        activityClass = DLProxyActivity.class;
    }else if(DLBasePluginFragmentActivity.class.isAssignableFrom(clazz)){
        activityClass = DLProxyFragmentActivity.class;
    }else if(Activity.class.isAssignableFrom(clazz)){
        activityClass = Activity.class;
    }
    return activityClass;
}

当这一步基本上完成偷天换柱的任务,表面上要启动的是插件类,其实启动的是一个宿主代理类。
在把插件包名和要启动的插件类名放到Intent中,传递一个代理类,最后直接启动代理Activity就好了。

private void performStartActivityForResult(Context context, DLIntent dlIntent, int requestCode){
    if(context instanceof Activity){
        ((Activity)context).startActivityForResult(dlIntent, requestCode);
    }else{
        context.startActivity(dlIntent);
    }
}

三、通过代理类运行插件类

在这里分析DLProxyActivity,经前面分析,已经启动了代理类,当然就执行到代理类的onCreate了:

protected DLPlugin mRemoteActivity;
private DLProxyImpl impl = new DLProxyImpl(this);

@Override
protected void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    impl.onCreate(getIntent());
}

接着流程就跑到DLProxyImpl类的onCreate()方法中:

public void onCreate(Intent intent){
    intent.setExtrasClassLoader(DLConfigs.sPluginClassLoader);

    mPackageName = intent.getStringExtra(DLConstants.Extra_package);
    mClass = intent.getStringExtra(DLConstants.Extra_class);

    mPluginManager = DLPluginManager.getInstance(mProxyActivity);
    mPluginPackage = mPluginManager.getPackage(mPackageName);
    mAssetManager = mPluginPackage.assetManager;
    mResources = mPluginPackage.resources;

    initializeActivityInfo();
    handleActivityInfo();
    launchTargetActivity();
}
1、在initializeActivityInfo()之前是获取intent传递过来的插件包名、插件类,并根据它们获取插件信息PluginPackage
2、PluginPackage里面存放了插件的resource和assetManager
3、initializeActivityInfo()和handleActivityInfo()主要是进行一些主题处理
4、重点在launchTargetActivity()方法,初始化插件类
protected void launchTargetActivity(){
    try{
        Class<?> localClass = getClassLoader().loadClass(mClass);
        Constructor<?> localConstructor = localClass.getConstructor(new Class[]{});
        Object instance = localConstructor.newInstance(new Object[]{});
        mPluginActivity = (DLPlugin)instance;

        ((DLAttachable)mProxyActivity).attach(mPluginActivity, mPluginManager);
        mPluginActivity.attach(mProxyActivity, mPluginPackage);

        Bundle bundle = new Bundle();
        bundle.putInt(DLConstants.From, DLConstants.From_external);
        mPluginActivity.onCreate(bundle);
    }catch(Exception e){}
}

创建好了代理类对象和插件类对象,就需要通过两个attach()方法将它们绑定在一起。

第一个是代理类的attach()方法:

public void attach(DLPlugin remoteActivity, DLPluginManager pluginManager){
    mRemoteActivity = remoteActivity;
}

第二个是插件类的attach()方法:

public void attach(Activity proxyActivity, DLPluginPackage pluginPackage){
    mProxyActivity = proxyActivity;
    that = mProxyActivity;
    mPluginPackage = pluginPackage;
}

可以看到插件类里的that被赋值了代理类对象,所以在编写插件代码的时候this的功能将被废弃,不能使用this,必须使用that

Android DynamicLoadApk 代码剖析

代码结构:

一、internal包


1、DLAttachable接口:

public void attach(DLPlugin proxyActivity, DLPluginManager pluginManager)

代理启动插件的Activity,DL将会调用attach方法将代理Activity与插件管理器连接到插件Activity中,插件Activity将会加载插件的资源,所以代理Activity是插件内部的Activity的一个资源托管。

2、DLIntent:

Intent的子类,内部包含两个变量:1) 需要启动的插件的包名以,2) 需要启动的插件类名

3、DLPluginManager:

主要功能:获取Dex,启动服务,停止服务,绑定服务,解绑服务,启动Activity,加载指定class名的class,拷贝so文件等

START_RESULT_SUCCESS:插件启动成功
START_RESULT_NO_PKG  :没找到指定的包名的插件
START_RESULT_NO_CLASS:没找到需要启动的插件类
START_RESULT_TYPE_ERROR:

4、DLPluginPackage:

指定一个插件Apk。在相同的apk中的一系列的Activities共享的AssetManager、Resources以及DexClassLoader。

包含:
1)插件的完整包名
2)默认的Activity的名称
3)Dex类加载器
4)AssetManager对象
5)资源Resources对象
6)包的相关描述对象PackageInfo对象

5、DLProxyImpl:

是一个插件Activity的代理,代理将会利用反射创建插件Activity,并同时调用插件Activity的attach以及onCreate的方法,此时插件Activity就处于运行的状态了。

1)String mClass:当前插件Activity的类名
2)String mPackageName:当前插件Activity的包名
3)DLPluginPackage mPluginPackage:标识当前的插件Apk的对象
4)DLPluginManage mPluginManager:插件管理器
5)AssetManager mAssetManager:Asset管理器
6)Resources mResources:资源管理器
7)Theme mTheme:主题对象
8)ActivityInfo mActivityInfo:描述当前的插件Activity的相关的信息对象
9)Activity mProxyActivity:代理Activity对象
10)DLPlugin mPluginActivity:插件的接口
11)ClassLoader mPluginClassLoader:类加载器

6、DLServiceAttachable:

与DLAttachable的功能是类似的,只不过一个是针对Activity,一个是针对Service。

7、DLServiceProxyImpl:

1)利用类加载器加载指定的插件内部的服务名称,实际上是利用反射创建对象
2) 将插件内部的Service对象绑定到代理Service对象上
3)直接调用插件Service的onCreate的方法

二、dynamicload包


1、DLBasePluginActivity、DLBasePluginFragmentActivity

1、Activity mProxyActivity:Activity代理是真正运行中的Activity
2、Activity that:等同mProxyActivity
3、DLPluginManager mPluginManager:插件管理器
4、DLPluginPackage mPluginPackage:插件的包名

职责:
1、代理Activity的赋值
2、指向插件Apk对象的本身
3、类似onCreate、setContentView等Activity相关方法,判断是内部启动还是外部启动,如果是内部启动,完全可以不需要代理Activity的处理
4、凡是调用了onAttach,均是属于外部的启动

2、DLPlugin:

这是一个插件开发的接口:

public void onCreate(Bundle savedInstanceState);
public void onStart();
public void onRestart();
public void onActivityResult(int requestCode, int resultCode, Intent data);
public void onResume();
public void onPause();
public void onStop();
public void onDestroy();
public void attach(Activity proxyActivity, DLPluginPackage pluginPackage);
public void onSaveInstanceState(Bundle outState);
public void onNewIntent(Intent intent);
public void onRestoreInstanceState(Bundle savedInstanceState);
public boolean onTouchEvent(MotionEvent event);
public boolean onKeyUp(int keyCode, KeyEvent event);
public void onWindowAttributesChanged(LayoutParams params);
public void onWindowFocusChanged(boolean hasFocus);
public void onBackPressed();
public boolean onCreateOptionsMenu(Menu menu);
public boolean onOptionsItemSelected(MenuItem item);

3、DLProxyActivity、DLProxyFragmentActivity:

插件Activity的代理类。其中的主要成员变量:

1、DLPlugin:指向插件对象
2、DLProxyImpl:指向代理的实现对象

4、DLProxyService

Service代理服务

5、DLServicePlugin

一个接口,包括Service的关键性的方法。

三、util包


1、DLConfigs:

存储与获取so的修改时间

2、DLConstants:

在这个常量类中,主要配置下面几大类常量:

1、内部启动还是外部启动
2、Dex路径以及相关的包名,存储的Tag
3、Activity的相关类型
4、CPU相关的体系结构

3、DLUtils:

这个工具类包含以下功能:

1、获取在SDcard目录下指定插件Apk文件的包名getPackageInfo
2、获取在SDcard目录下指定插件Apk文件的应用图标getAppIcon
3、获取在SDcard目录下指定插件Apk文件的应用名称getAppLabel
4、利用反射机制获取指定的插件Activity中的类型

4、SoLibManager

1、获取CPU相关信息
2、在线程中将插件的so文件拷贝到当前工程目录的so文件的目录文件夹中(根据当前的cpu体系结构,拷贝合适的so文件)

Android Studio利用Gradle删除没有使用到的资源和代码文件

一、打包时忽略无用资源

  我们在打包的时候默认会把没有用到的资源(比如图片)也打包成app,徒增了应用的大小。现在我们可以利用Gradle来优雅的去除没有用到的资源文件了!

就是在gradle中配置shrinkResources true。这个东西依赖于minifyEnabled,所以minifyEnabled也要为true才行。

官方推荐在正式版中这么写:

android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
}
}
}

如果你觉得debug包也太大,可以按照下面的方式写:

buildTypes {
debug {
minifyEnabled true
shrinkResources true
}
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}

我通过测试确实可以大大减少应用的大小,但也发现了一个问题,很多没有用到的类还是会被打包进去。比如你用了lib,lib里有很多很多类,你可能就用到了一个类,但却把lib中的所有类都打包了进去,很不合理。而且因为去除无效代码的功能要依赖于混淆,混淆又是一个耗时的操作,还是没有彻底解决打包慢的问题。

二、打包时忽略无用的代码
我目前的需求是很多工具类都在一个lib中,但我平常只用到lib中的几个类,所以希望生成apk的时候自动拍出lib中没有用到的类。于是我想到了下面的办法:

1.我首先在debug模式下配置一下,在debug时也进行混淆,并且自定义了混淆的配置文件debug-proguard-rules.pro

buildTypes {
debug {
minifyEnabled true // 是否混淆
shrinkResources true // 是否去除无效的资源文件
proguardFiles getDefaultProguardFile('proguard-android.txt'),'debug-proguard-rules.pro'
}
release {
minifyEnabled true // 是否混淆
shrinkResources true // 是否去除无效的资源文件
// 混淆的配置文件
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}

这样在debug的时候也会进行混淆,但我仅仅需要混淆lib包中的代码,而不混淆我app项目下的代码。这样就可以删除掉lib中无用的代码了。测试下来可以保证只有用到的东西才会被打包进来,其余的东西都没有被打包,效果不错!