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'])
........
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.详细代码如下:
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();
}
}
}
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() {
}
}
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;
}
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);
}
}
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();
}
}
|