ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Android之组件化路由器的实现

2022-01-25 22:04:45  阅读:197  来源: 互联网

标签:redirect moduleName String getInstance params context 组件 Android 路由器


一、背景

随着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&params2=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());
                }

这样就可以达到传递大的对象的目的。

三、源码地址

标签:redirect,moduleName,String,getInstance,params,context,组件,Android,路由器
来源: https://blog.csdn.net/u012636632/article/details/122692327

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有