IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android SDK 模块化处理 -> 正文阅读

[移动开发]Android SDK 模块化处理

Android SDK 模块化处理

随着sdk业务的增多,android底层代码越来越臃肿,维护起来越来越麻烦,于是决定把底层的sdk全部进行模块化处理。既然下了决心,就要干出一些成绩出来。经过细细的思量,觉得还是有可行性的。经过进一步规划,确定方案是使用“独立Module+开关控制+自动化管理”来实现。方案思路是:
1.sdk模块独立管理自身代码
2.通过开关控制sdk是否启用
3.编译时自动生成开关,达到自动化管理

实现的最终目的是让sdk能独立管理自身,与外界的调用分割开来,达到广大程序猿同胞经常说的“解耦合”,“低耦合高内聚”的目标。虽然有很多人用这个词来装X, 但真正理解和使用的人并不多。既然目标和思路清晰了,怎么实现就需要好好规划一下。经过整整一周的埋头折腾,基础框架搭建起来了,总体运行良好,试了几个SDK的实现,发现这个框架真的适合多业务的管理,后期维护起来得心应手。 接下来,与大家分享一下整体的实现思路。

一、sdk模块独立管理自身代码

1.创建sdk模块

首先创建一个sdk模块,在androidstudio中,使用 File->new ->new Module-> Android Library 创建一个模块。定义一个包名,这个包名不是app的包名,只是sdk的包名,后期会使用此包名作为入口进行调用。

(1)创建模块,库名为testSdk, 效果图如下:
在这里插入图片描述
(2)上面已创建一个testsdk模块,在AndroidStudio左侧打开该模块。模块包括独立的AndroidManifest.xml文件和build.gradle文件,权限和类的声明都可以在AndroidManifest件定义,独立给当前模块声明,同样build.gradle的编译规则也独立给当前模块使用。

下一步是搭建框架,为了方便测试,先新建一个入口文件 TestSdk.java, 那么完整的类名入口是com.mzc.testsdk.TestSdk,注意后面会用到。为了方便测试,实现的方法只是打印日志,用于测试流程是否通了,但真正的sdk代码是比较复杂的,入口确定下来,实现具体业务就通过入口来调用就好了。我们先定义几个常用的方法,代码如下:
在这里插入图片描述
TestSdk 的代码如下:

public class TestSdk extends SDKBase {
    private static final String TAG = "[TestSdk]";
    @Override
    public void onCreate() {
        Log.i(TAG, "onCreate()");
 
    }
    @Override
    public void onResume(){
        Log.i(TAG, "onResume()");
    }
    @Override
    public void onDestroy(){
        Log.i(TAG, "onDestroy()");
    }
    public void doTest(){
        Log.i(TAG, "doTest()");
    }
}

(3)上面的代码包括安卓平台常用的生命周期方法,onCreate, onResume, onDestroy等,doTest是提供给应用层调用的方法(例如cococreate,unity等平台可通过类名和入口方法来调用到此函数)。TestSdk的父类是SDKBase , 该方法包括基本的生命周期方法。

(4)sdk创建好了,下一步该怎么管理它们呢?当然是需要一个管理类。定义管理类为SDKManager.
实现思路:系统启动时读取根目录下的一个文件project.json,该文件定义了所有模块的入口包名,读取完后,就可以通过该包名和反射机制加载sdk的类到内存,并保存到列表,用于后续的生命周期方法的调用。

2.加载sdk的入口包名

首先看下如何加载sdk的入口包名,系统启动时,首先读取"project.json"文件,然后解析sdkClassPath数组,然后循环把数组中的sdk入口包名通过cls.newInstance()实例化,并保存到this.sdkList列表中。

我们新建一个project.json文件, 格式如下:

{
    "sdkClassPath": [
        "com.mzc.testsdk.TestSdk"
    ]
}

在android的app/build.gradle添加到输出编译目录中,路径根据项目情况定义

android.applicationVariants.all { variant ->
    ...
    
    variant.mergeAssetsProvider.get().doLast {
        def sourceDir = "${buildDir}/../../../../.."
        ....
        copy {
            from "${sourceDir}/main.js"
            from "${sourceDir}/project.json"
            into outDir
        }
    }
}

加载sdk类:

public void loadSDKClass() {
        try {
            String json = this.getJson(this.mActivity, "project.json");
            JSONObject jsonObject = new JSONObject(json);
            JSONArray serviceClassPath = jsonObject.getJSONArray("sdkClassPath");
            if (serviceClassPath == null) return;
 
            int length = serviceClassPath.length();
            for (int i = 0; i < length; i++) {
                String classPath = serviceClassPath.getString(i);
                if(classPath != null){
                    Class cls = null;
                    try {
                        cls = Class.forName(classPath);
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                    if(cls != null){
                        SDKBase sdk = (SDKBase) cls.newInstance();
                        if(sdk != null){
                            this.sdkList.add(sdk);
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

3.调用生命周期方法

(1)系统启动时,实例化sdk的入口类已保存在列表中。我们就可以根据列表类,在合适的场景循环调用自身定义的生命周期的方法. 什么是合适的场景,当然在Acivity中生命周期方法中调用SDKManager对应的方法 。

//以下是SDKManager 循环调用sdk的onCreate方法:

public void onCreate() {
    for (SDKBase sdk : this.sdkList) {
        sdk.onCreate();
    }
}

(2)在Acivity中生命周期方法中调用SDKManager,下面只是简单写了onCreate方法,详细的代码会后续列出。

public class AppActivity extends Cocos2dxActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        SDKManager.getInstance().onCreate();
    }
}

(3)其它的生命周期方法也是按照同样的方法来实现,sdk的独立管理自身就初步完成,入口有了,具体业务就通过自身的入口来调用,这样就与其它模块分割了,达到解耦合的目的。总体的调用流程如下:
Acivity -> SDKManager -> SDK入口 -> SDK业务

4.实现具体业务

(1)下面具体业务以facebook的sdk为案例。Module有自身的build.gradle文件和AndroidManifest文件. 我们可以独立的在这两个文件添加具体的业务。新增编译库时,我们在模块中的build.gradle添加独立的编译规则,如

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
 
    ........
    // facebook
    implementation 'com.facebook.android:facebook-core:9.0.0'
    implementation 'com.facebook.android:facebook-login:9.0.0'
}

(2)在模块中的AndroidManifest申明权限和Activity等

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.mzc.libfacebook">
 
    <uses-permission android:name="android.permission.INTERNET" />
 
    <application>
        <activity
            android:name="com.facebook.FacebookActivity"
            android:configChanges="keyboard|keyboardHidden|screenLayout|screenSize|orientation"
            android:label="@string/app_name" />
    </application>
    ......
</manifest>

(3)这里简单提了一个FBSdk 的具体业务
onCreate完成sdk的初始化,login方法完成登录操作,提供给上层调用。
代码如下:

public class FBSdk extends SDKBase {
    private static final String TAG = "[FBSdk]";
 
    @Override
    public void onCreate() {
        Log.i(TAG, "onCreate()");
        // 注册回调
        LoginManager.getInstance().registerCallback(mCallbackManager,
            //省略。。。
            });
    }
 
    // 登录
    public static void login(String type) {
        final AccessToken accessToken = AccessToken.getCurrentAccessToken();
        boolean isLoggedIn = accessToken != null && accessToken.isCurrentAccessTokenActive();
        if (isLoggedIn) {
            getActivity().runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    FaceBookSdk.loginCallbackWithToken(accessToken);
                }
            });
        } else {
            getActivity().runOnGLThread(new Runnable() {
                @Override
                public void run() {
                    LoginManager.getInstance().logInWithReadPermissions(getActivity(), Arrays.asList("public_profile"));
                }
            });
        }
    }
}

5.特殊情况

(1)特殊情况1,有时候多个sdk需要调用公用的代码,此时项目需要一个公共的组件,提供给各个SDK进行调用。例如我们创建一个新的公共模块是libcommon,其它sdk调用它的话,只要在build.gradle的dependencies 声明把libcommon编译到当前模块就行,当前sdk就可以调用libcommon的类和方法了。声明如下:

implementation project(path: ':libcommon')

(2)特殊情况2,有时候sdk之间相互调用,怎么实现。我们可以在Activity类中创建一个handle, 子模块通过handle消息发给activity,然后 activity再通过SDKManager 分发给各个子模块来处理。这个具体看业务,这里只提供思路。

6.详细代码如下:

//SDKManager 详细代码如下:
public class SDKManager {
    private Activity mActivity = null;
    private static SDKManager mInstace = null;
    private List<SDKBase> sdkList;
 
    private SDKManager() {
        this.sdkList = new ArrayList<SDKBase>();
    }
 
    public static SDKManager getInstance() {
        if (null == mInstace) {
            mInstace = new SDKManager();
        }
        return mInstace;
    }
 
    public void init(Activity context) {
        this.mActivity = context;
    }
 
    /**
     * 默认的处理类
     */
    public void initClass() {
        
    }
 
    public Activity getActivity() {
        return this.mActivity;
    }
 
    public void onCreate() {
        for (SDKBase sdk : this.sdkList) {
            sdk.onCreate();
        }
    }
 
    public void loadSDKClass() {
        try {
            String json = this.getJson(this.mActivity, "project.json");
            JSONObject jsonObject = new JSONObject(json);
            JSONArray serviceClassPath = jsonObject.getJSONArray("sdkClassPath");
            if (serviceClassPath == null) return;
 
            int length = serviceClassPath.length();
            for (int i = 0; i < length; i++) {
                String classPath = serviceClassPath.getString(i);
                if(classPath != null){
                    Class cls = null;
                    try {
                        cls = Class.forName(classPath);
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                    if(cls != null){
                        SDKBase sdk = (SDKBase) cls.newInstance();
                        if(sdk != null){
                            this.sdkList.add(sdk);
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
    private String getJson(Context mContext, String fileName) {
        StringBuilder sb = new StringBuilder();
        AssetManager am = mContext.getAssets();
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(am.open(fileName)));
            String next = "";
            while (null != (next = br.readLine())) {
                sb.append(next);
            }
        } catch (IOException e) {
            e.printStackTrace();
            sb.delete(0, sb.length());
        }
        return sb.toString().trim();
    }
 
    public void setGLSurfaceView(GLSurfaceView view, Cocos2dxActivity context) {
        this.mActivity = context;
        this.initClass();
        this.loadSDKClass();
        for (SDKBase sdk : this.sdkList) {
            sdk.setGLSurfaceView(view);
        }
    }
 
    public void onResume() {
        for (SDKBase sdk : this.sdkList) {
            sdk.onResume();
        }
    }
 
    public void onPause() {
        for (SDKBase sdk : this.sdkList) {
            sdk.onPause();
        }
    }
 
    public void onDestroy() {
        for (SDKBase sdk : this.sdkList) {
            sdk.onDestroy();
        }
    }
 
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        for (SDKBase sdk : this.sdkList) {
            sdk.onActivityResult(requestCode, resultCode, data);
        }
    }
 
    public void onNewIntent(Intent intent) {
        for (SDKBase sdk : this.sdkList) {
            sdk.onNewIntent(intent);
        }
    }
 
    public void onRestart() {
        for (SDKBase sdk : this.sdkList) {
            sdk.onRestart();
        }
    }
 
    public void onStop() {
        for (SDKBase sdk : this.sdkList) {
            sdk.onStop();
        }
    }
 
    public void onBackPressed() {
        for (SDKBase sdk : this.sdkList) {
            sdk.onBackPressed();
        }
    }
 
    public void onConfigurationChanged(Configuration newConfig) {
        for (SDKBase sdk : this.sdkList) {
            sdk.onConfigurationChanged(newConfig);
        }
    }
 
    public void onRestoreInstanceState(Bundle savedInstanceState) {
        for (SDKBase sdk : this.sdkList) {
            sdk.onRestoreInstanceState(savedInstanceState);
        }
    }
 
    public void onSaveInstanceState(Bundle outState) {
        for (SDKBase sdk : this.sdkList) {
            sdk.onSaveInstanceState(outState);
        }
    }
 
    public void onStart() {
        for (SDKBase sdk : this.sdkList) {
            sdk.onStart();
        }
    }
}
 
//SDKBase 代码
public class SDKBase {
    public static Activity getActivity() {
        return SDKManager.getInstance().getActivity();
    }
 
    public static Class<?> getActivityClass() {
        return SDKManager.getInstance().getActivity().getClass();
    }
 
    public void onCreate() {
    }
 
    public void setGLSurfaceView(GLSurfaceView view) {
    }
 
    public void onResume() {
    }
 
    public void onPause() {
    }
 
    public void onDestroy() {
    }
 
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
    }
 
    public void onNewIntent(Intent intent) {
    }
 
    public void onRestart() {
    }
 
    public void onStop() {
    }
 
    public void onBackPressed() {
    }
 
    public void onConfigurationChanged(Configuration newConfig) {
    }
 
    public void onRestoreInstanceState(Bundle savedInstanceState) {
    }
 
    public void onSaveInstanceState(Bundle outState) {
    }
 
    public void onStart() {
    }
}
 
 
//AppActivity 代码
public class AppActivity extends Cocos2dxActivity {
 
    @Override
    public void init() {
        super.init();
        SDKManager.getInstance().init(this);
    }
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
 
        SDKManager.getInstance().onCreate();
    }
 
    @Override
    public Cocos2dxGLSurfaceView onCreateView() {
        SDKManager.getInstance().setGLSurfaceView(glSurfaceView, this);
 
        return glSurfaceView;
    }
 
    @Override
    protected void onResume() {
        super.onResume();
 
        SDKManager.getInstance().onResume();
    }
 
    @Override
    protected void onPause() {
        super.onPause();
 
        SDKManager.getInstance().onPause();
    }
 
    @Override
    protected void onDestroy() {
        super.onDestroy();
        ...
 
        SDKManager.getInstance().onDestroy();
    }
 
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        SDKManager.getInstance().onActivityResult(requestCode, resultCode, data);
    }
 
    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
 
        SDKManager.getInstance().onNewIntent(intent);
    }
 
    @Override
    protected void onRestart() {
        super.onRestart();
 
        SDKManager.getInstance().onRestart();
    }
 
    @Override
    protected void onStop() {
        super.onStop();
        SDKManager.getInstance().onStop();
    }
 
    @Override
    public void onBackPressed() {
        SDKManager.getInstance().onBackPressed();
        super.onBackPressed();
    }
 
    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        SDKManager.getInstance().onConfigurationChanged(newConfig);
        super.onConfigurationChanged(newConfig);
    }
 
    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        SDKManager.getInstance().onRestoreInstanceState(savedInstanceState);
        super.onRestoreInstanceState(savedInstanceState);
    }
 
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        SDKManager.getInstance().onSaveInstanceState(outState);
        super.onSaveInstanceState(outState);
    }
 
    @Override
    protected void onStart() {
        SDKManager.getInstance().onStart();
        super.onStart();
    }
}

二、实现sdk的开关控制

(1)新建sdkconfig.gradle文件,用于定义sdk的开关,放在android 的根目录下。
内容如下:

ext{
    TEST_SDK_ENABLE = true
    FACEBOOK_ENABLE = true
}

(2)在app/build.gradle添加sdk的编译规则,使用开关控制;注意sdkconfig.gradle需要添加apply ,开关才会生效。

apply from: "../sdkconfig.gradle"
 
dependencies {
    implementation fileTree(dir: '../libs', include: ['*.jar','*.aar'])
 
    //......
 
    if (this.hasProperty("TEST_SDK_ENABLE") && (TEST_SDK_ENABLE == true)) {
        println 'build libtestsdk'
        implementation project(':libtestsdk')
    }
}

(3)在settings.gradle中添加编译规则。同样需要apply配置文件sdkconfig.gradle

apply from: "sdkconfig.gradle"
 
//......
 
if(this.hasProperty("TEST_SDK_ENABLE") && TEST_SDK_ENABLE == true){
    include ':libtestsdk'
}

三、实现sdk自动化管理

如果只是单纯的android项目,无须自动化管理,请忽略下面的内容。上面已创建独立的模块和开关控制,但在cocos, unity等平台,修改开关和project.json都需要转到底层处理,显得不太友好和方便。所以我们定义一套规则,在编译上层应用时,自动生成sdkconfig.gradle和project.json, 这样就完成sdk自动化管理。下面只针对cocos平台的自动化处理,仅供参考:

1.创建扩展组件,最终生成packages的目录下,可以通过cococreator ,扩展->创建新扩展插件 来新建插件。

2.在onBuildNativeFinish中添加编译规则, 启动编译时自动完成写入。

function onBuildNativeFinish(options, callback) {
    Editor.log("onBuildNativeFinish()");
    if (options.platform !== "android" && options.platform !== "ios") {
        callback();
        return;
    }
    onConfigSDK(options, callback);
}

3.编译规则如下:
我们在上层应用定义sdk-config.json,把sdk的类名和开关都定义在该文件。通过读取该文件,最终生成sdkconfig.gradle和project.json

{
    "sdkClassPath": [
        "com.mzc.testsdk.TestSdk",
        "com.mzc.libfacebook.FBSdk"
    ],
 
    "sdkconfig":{
        "TEST_SDK_ENABLE": true,
        "FACEBOOK_ENABLE": true
    }
}

4.以下通过js脚本生成sdkconfig.gradle和project.json文件。

function onConfigSDK(options, callback) {
    var basePath = path.normalize(options.dest);
    var projPath = path.join(basePath, "project.json");
    var proj_json = null;
    var config_json = null;
 
    try {
        var proj_content = fs.readFileSync(projPath);
        proj_json = JSON.parse(proj_content);
    } catch (error) {
        Editor.error(error);
        return
    }
 
    try {
        let file_content = fs.readFileSync(path.join(Editor.assetdb.cwd, "./sdk-config.json"));
        config_json = JSON.parse(file_content);
    } catch (error) {
        Editor.error(error);
        return;
    }
    // 把包名写入到project.json, sdkClassPath
    if (proj_json && config_json && config_json.sdkClassPath) {
        proj_json.sdkClassPath = [];
 
        config_json.sdkClassPath.forEach(item => {
            if (proj_json.sdkClassPath.indexOf(item) == -1) {
                proj_json.sdkClassPath.push(item);
            }
        });
        // 写入数据
        try {
            let out = JSON.stringify(proj_json, null, 4)
            fs.writeFileSync(projPath, out);
        } catch (error) {
            Editor.error(error);
        }
    }
    // 把sdk开关写入到config.gradle,最后需要同步操作复制到原生项目
    if (config_json != null && config_json.sdkconfig != null) {
        let sdkconfig = config_json.sdkconfig;
        let studioPath = path.join(basePath, "/frameworks/runtime-src/proj.android-studio/config.gradle");
 
        let space = "    "
        let out = "ext{\n"
        for (let key in sdkconfig) {
            let value = sdkconfig[key];
            let str = space + key + " = " + `${value}` + "\n";
            out += str;
        }
        out += "}\n";
        fs.writeFileSync(studioPath, out);
 
    }
 
    if (callback) {
        callback();
    }
}
  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-08-26 12:13:18  更:2021-08-26 12:15:38 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年5日历 -2025/5/1 9:47:20-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码