1, Background
With the increasing scale and volume of App development, the development and compilation time is longer and longer. At the same time, the coupling of code may become higher. Now it is more necessary to re plan the development of App by component framework. In the process of component development, a router is needed to jump the page and transmit parameters. Among them, Alibaba's ARouter is the most popular. Today, I'd like to introduce a relatively simple router framework.
2, Use
1. Register the corresponding ModuleName in the AndroidManifest
<?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>
Each registered activity corresponds to a meta data, where key is "moduleName" and value is "test" (for example),
Under each Application, a meta data is registered as the product name of the project.
2. Implement Router class
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("Jump class","=="+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); } }
Initialize the Router in the Application.
Router.getInstance().initialize(getApplicationContext());
Each time the app starts, it initializes, gets all the activity nodes in the AndroidManifest, performs a traversal process, finds the meta data attribute corresponding to each activity, and saves it in a HashMap. The key is the value corresponding to the moduleName, and the value contains the class attribute corresponding to the activity, If the procedure contains an object with the same moduleName, an exception is thrown. Among them, moduleName can correspond to multiple names, separated by "|", that is, the same activity can correspond to moduleName with multiple names, because multiple scenes may correspond to one activity. In the "launchModule" method, take the class object of the corresponding activity from the HashMap just mentioned, and open the activity through Intent startup.
Some usage rules:
Register in AndroidManifest:
<activity android:name=".TestActivity"> <meta-data android:name="moduleName" android:value="test|testAlias" /> </activity>
TestActivity has two corresponding modulenames: test and 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. Complete address jump format:
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);
adopt
String host = getIntent().getData().getHost();
You can judge whether the incoming host in the router is test or testaias, so that we can distinguish which scenario it is.
String params1 = getIntent().getData().getQueryParameter("params1"); String params2 = getIntent().getData().getQueryParameter("params2");
In this way, you can get the parameters passed in.
4. Logged in account
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);
Judge whether to jump to the login page through the transparent page of LoginCheckActivity with moduleName "lc".
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. If there is no login account, you need to jump to LoginActivity first, and then jump to the specified page after logging in the account successfully
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, "Login completed", Toast.LENGTH_SHORT).show(); redirectNextPage(); finish(); }, 4000); } private void redirectNextPage(){ Router.getInstance().redirect(LoginActivity.this, redirectUrl); } }
6. If you need to pass large objects
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);
We can achieve this through the tool class of 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; } }
By passing the hashcode of the object to the next interface, save the current object to the ModelStore,
Then get the current object on the next page.
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()); }
In this way, the purpose of passing large objects can be achieved.