一、背景
随着App开发的规模体积越来越大,开发编译的时间越来越久,同时,代码的耦合性可能变高,现在更需要组件化框架重新规划App的开发。在组件化开发的过程中,需要路由器来进行页面的跳转和传参数。其中,以阿里的ARouter最为火热,今天,给大家介绍一个实现比较简单的路由器框架。
二、使用
1、在AndroidManifest里面注册对应的ModuleName
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.syy.router">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:name=".MyApplication"
android:supportsRtl="true"
android:theme="@style/Theme.Router">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".TestActivity">
<meta-data
android:name="moduleName"
android:value="test|testAlias" />
</activity>
<activity android:name=".TestActivity1">
<meta-data
android:name="moduleName"
android:value="test1" />
</activity>
<meta-data
android:name="productName"
android:value="router" />
</application>
</manifest>
每个注册activity对应一个meta-data的,其中key为“moduleName”,value是“test”(举例),
每个Application下面注册一个meta-data的,作为这个工程的productName。
2、实现Router类
package com.syy.router;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import com.syy.router.bean.ModuleInfo;
import com.syy.router.provider.ModuleInfoProvider;
import com.syy.router.utils.HttpHelper;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
public class Router {
public static final String TAG = "Router";
private static Router mRouter;
private Router() {
}
public synchronized static Router getInstance() {
if (mRouter == null) {
mRouter = new Router();
}
return mRouter;
}
public void initialize(Context context) throws Exception {
String product = "";
Bundle appMeta = context.getPackageManager().getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA).metaData;
if (appMeta != null) {
product = appMeta.getString("productName");
}
PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES);
for (ActivityInfo activityInfo : packageInfo.activities) {
ActivityInfo metaInfo = context.getPackageManager().getActivityInfo(new ComponentName(activityInfo.packageName, activityInfo.name),
PackageManager.GET_META_DATA);
if (metaInfo.metaData != null) {
String moduleName = metaInfo.metaData.getString("moduleName");
String target = metaInfo.metaData.getString("target");
if (StringUtils.isEmpty(moduleName)) {
continue;
}
if (StringUtils.isNotEmpty(target)) {
if (StringUtils.isNotEmpty(product)) {
if (target.contains("!")) {
if (target.replace("!", "").equals(product)) {
continue;
}
} else {
List<String> targets = Arrays.asList(target.split("\\|"));
if (!targets.contains(product)) {
continue;
}
}
} else {
continue;
}
}
ModuleInfo moduleInfo = new ModuleInfo();
moduleInfo.className = activityInfo.name;
List<String> moduleNames = Arrays.asList(moduleName.split("\\|"));
for (String moduleKey : moduleNames) {
if (!StringUtils.isEmpty(moduleKey)) {
if (ModuleInfoProvider.getInstance().isModuleExist(moduleKey)) {
throw new Exception("module name: " + moduleKey + " duplicated! Rename or add target is optional.");
}
ModuleInfoProvider.getInstance().addModuleInfo(moduleKey.trim(), moduleInfo);
}
}
}
}
}
private void launchModule(Context context, String uriString, Bundle bundle, List<Integer> flags) {
Log.d(TAG, "launchModule: uriString = " + uriString);
try {
if (TextUtils.isEmpty(uriString)) {
return;
}
Uri uri = Uri.parse(uriString);
String schema = uri.getScheme();
String moduleName = uri.getHost();
ModuleInfo targetModule = ModuleInfoProvider.getInstance().getModuleInfo(moduleName);
if (targetModule != null) {
Log.w("跳转的类","=="+targetModule.className);
Intent intent = new Intent();
intent.setClassName(context.getPackageName(), targetModule.className);
intent.setData(uri);
if (bundle != null) {
intent.putExtras(bundle);
}
if (flags != null && !flags.isEmpty()) {
for (Integer flag : flags) {
intent.addFlags(flag);
}
}
context.startActivity(intent);
}
} catch (Exception e) {
Log.e(TAG, "launchModule: exception=" + e.toString());
}
}
@Deprecated
public void redirect(Context context, String uriString) {
launchModule(context, uriString, null, null);
}
@Deprecated
public void redirect(Context context, String uriString, Bundle bundle) {
launchModule(context, uriString, bundle, null);
}
public void redirect(Context context, String moduleName, Map<String, String> params) {
redirect(context, moduleName, params, null, null);
}
public void redirect(Context context, String moduleName, Map<String, String> params, int flag) {
List<Integer> flags = new ArrayList<Integer>();
flags.add(flag);
redirect(context, moduleName, params, null, flags);
}
public void redirect(Context context, String moduleName, Map<String, String> params, List<Integer> flags) {
redirect(context, moduleName, params, null, flags);
}
public void redirect(Context context, String moduleName, Map<String, String> params, Bundle bundle, List<Integer> flags) {
String url;
Uri uri = Uri.parse(HttpHelper.getAppSchema(context) + "://" + moduleName);
if (params != null) {
Uri.Builder builder = uri.buildUpon();
for (Map.Entry<String, String> entry : params.entrySet()) {
builder.appendQueryParameter(entry.getKey(), HttpHelper.getNotNullParam(entry.getValue()));
}
url = builder.build().toString();
} else {
url = uri.toString();
}
launchModule(context, url, bundle, flags);
}
}
在Application里面进行Router的初始化。
Router.getInstance().initialize(getApplicationContext());
每次app启动的时候,进行初始化,获取到所有AndroidManifest里面所有的Activity节点,进行一个遍历过程,找到每个activity对应的meta-data属性,将其放在一个hashMap里面进行保存,key为moduleName对应的值,value是包含了这个activity对应的class属性,如果过程包含了同样的moduleName的对象,则抛出异常。其中,moduleName可以对应多个名字,用"|"进行隔开,就是说同一个activity可以对应多个名称的moduleName,因为可能会有多个场景对应一个activity的情况出现。在“launchModule“的方法里面,从刚刚说的那个hashmap里面取出对应的activity的class对象,通过Intent启动的方式打开activity。
一些使用规则:
AndroidManifest里面注册:
<activity android:name=".TestActivity">
<meta-data
android:name="moduleName"
android:value="test|testAlias" />
</activity>
TestActivity有两个对应的moduleName:test和testAlias
1、
HashMap<String, String> params = new HashMap<String, String>();
params.put("params1", "1");
params.put("params2", "2");
Router.getInstance().redirect(MainActivity.this, "test", params);
2、完整的地址跳转格式:
Router.getInstance().redirect(MainActivity.this, "router://test?params1=1¶ms2=2");
3、
HashMap<String, String> params = new HashMap<String, String>();
params.put("params1", "1");
params.put("params2", "2");
Router.getInstance().redirect(MainActivity.this, "testAlias", params);
通过
String host = getIntent().getData().getHost();
可以判断路由器中传入的host是test还是testAlias,这样我们可以区分是哪一个场景。
String params1 = getIntent().getData().getQueryParameter("params1");
String params2 = getIntent().getData().getQueryParameter("params2");
通过这种方式可以得到传入的参数是什么。
4、已经登入账号的情况
UserInfoProvider.getInstance().setUserId("1111111");
HashMap<String, String> params = new HashMap<String, String>();
params.put("params1", "1");
params.put("params2", "2");
Router.getInstance().redirect(MainActivity.this, "lc/test", params);
通过moduleName为“lc”的LoginCheckActivity的透明页面去判断是否要去跳转到登入页面。
public class LoginCheckActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login_check);
try {
String schema = HttpHelper.getAppSchema(this);
String redirectUrl = schema + ":/" + getIntent().getData().getPath() + "?" + getIntent().getData().getEncodedQuery();
if (getIntent().getData() != null) {
if (StringUtils.isEmpty(UserInfoProvider.getInstance().getUserId())) {
String url = schema + "://login?redirect=" + Uri.encode(redirectUrl);
Router.getInstance().redirect(LoginCheckActivity.this, url);
} else {
Router.getInstance().redirect(LoginCheckActivity.this, redirectUrl);
}
}
} catch (Exception e) {
}
finish();
}
}
5、如果没有登入账号,则需要先跳转到LoginActivity中,登入账号成功后再跳转到指定的页面
public class LoginActivity extends AppCompatActivity {
private String redirectUrl;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_login);
if (getIntent().getData() != null) {
redirectUrl = getIntent().getData().getQueryParameter("redirect");
}
new Handler().postDelayed(() -> {
Toast.makeText(LoginActivity.this, "已经登入完成", Toast.LENGTH_SHORT).show();
redirectNextPage();
finish();
}, 4000);
}
private void redirectNextPage(){
Router.getInstance().redirect(LoginActivity.this, redirectUrl);
}
}
6、如果需要传递大的对象时
HashMap<String, String> params = new HashMap<String, String>();
params.put("params1", "1");
params.put("params2", "2");
BigBean bigBean = new BigBean();
bigBean.setName("name");
params.put("info", String.valueOf(ModelStore.getInstance().put(bigBean)));
Router.getInstance().redirect(MainActivity.this, "test", params);
我们可以通过ModelStore的工具类来达到目的
public class ModelStore {
private static ModelStore instance;
private SparseArray<Object> models = new SparseArray<>();
public synchronized static ModelStore getInstance() {
if (instance == null) {
instance = new ModelStore();
}
return instance;
}
public synchronized int put(Object obj) {
if (obj == null) {
return 0;
}
int key = obj.hashCode();
models.put(key, obj);
return key;
}
public synchronized Object pop(int key) {
Object obj = models.get(key);
models.remove(key);
return obj;
}
public synchronized boolean fetch(int key) {
return models.get(key) != null;
}
}
通过传递该对象的hashcode到下一个界面,保存当前的对象到ModelStore里面去,
再在下一个页面去获取当前的对象。
params.put("info", String.valueOf(ModelStore.getInstance().put(bigBean)));
String infoIdValue = getIntent().getData().getQueryParameter("info");
if (StringUtils.isNotEmpty(infoIdValue)) {
Integer infoId = Integer.parseInt(infoIdValue);
BigBean bigBean = (BigBean) ModelStore.getInstance().pop(infoId);
Log.d(TAG, "onCreate: " +bigBean.getName());
}
这样就可以达到传递大的对象的目的。