Detailed Explanation of Design and Implementation of Android scheme Jump
- 2021-12-04 11:24:56
- OfStack
Origin
With the growth of App, we will inevitably encounter the following requirements:
H5 Hop Native Interface Notification click to adjust the relevant interface Jump interface according to the data returned from the background, for example, jump to different interfaces after successful login or jump to different interfaces according to operation requirements Realize the jump of AppLink
In order to solve these problems, App 1 generally customizes an scheme jump protocol, which is implemented by many terminals to solve various operational requirements. Let's analyze the latest version of QMUI today
QMUISchemeHandler
Design and implementation of.
The format of an scheme looks like this:
schemeName://action?param1=value1¶m2=value2
For example:
qmui://home?tab=2
From a technical point of view, it is not very difficult to realize the jump of scheme, that is, the following two steps:
Analysis of scheme Jump to the specified interface according to the analysis resultBut if you don't design it when you write code, it is easy to be if else of heap 1 heap. For example:
if(action=="action1"){
doAction1(params)
}else if(action=="action2"){
doAction2(params)
}else {
...
}
Whenever a new scheme is added, add an if until it gradually becomes a huge long rotten code, which can't be changed. Therefore, we should think diligently, reconstruct more, and free our hands by designing an excellent framework as soon as possible.
For refactoring such as if else, a basic way is to put all the conditions and the behaviors to be executed in an map by looking up the table, and then get the behaviors to be executed by querying this map when using it. However, we can build this map by annotation and code generation, thus reducing the amount of code we write. In addition, we also need to consider various functional requirements:
You can set the interceptor interceptor, for example, skip some interfaces. If you are in a non-login state, you may need to jump to the login interface Parameter can specify 1 base type, scheme carries the parameter value is string, but we hope it can be easily converted to the base type we need The same action can have different jump behaviors according to different parameters, for example, all jump book details, and the interface to jump for comic books and ordinary books may not be the same If the current interface is already the target interface, you can choose to refresh the current interface or start a new interface For QMUI, both Activity and Fragment are supported, so scheme should also support both You can customize the instantiation method of the new interfaceInterface design
For the development of any one library, in order to make the business users comfortable enough, it is necessary to ensure that the functions of the library are powerful enough and the use is convenient. QMUI Scheme is mainly
QMUISchemeHandler
This entry class, and
ActivityScheme
And
FragmentScheme
Two annotations.
QMUISchemeHandler
QMUISchemeHandler
Instantiated by Builder mode:
// Settings schemeName
val instance = QMUISchemeHandler.Builder("qmui://")
// Prevents short-time classes from triggering the same scheme Jump
.blockSameSchemeTimeout(1000)
// scheme Parameter decode
.addInterpolator(new QMUISchemeParamValueDecoder())
.addInterpolator(...)
// Default fragment Instantiation factory
.defaultFragmentFactory(...)
// Default activity Instantiation factory
.defaultIntentFactory(...)
// Default scheme Matcher
.defaultSchemeMatcher(...)
.build();
if(!instance.handle("qmui://xxx")){
// scheme Not by handle Logging?
}
In most scenarios,
QMUISchemeHandler
Use singleton mode. It can set multiple interceptors, set the default instantiation plant of fragment and activity, and the default matcher. Instance factories and matchers both provide default implementations, and most scenarios do not require callers to care. Moreover, only the global default values are set here. At the level 1 of scheme annotation, different values can be specified for each scheme to meet possible customization requirements.
ActivityScheme and FragmentScheme Notes
These two annotations are very similar, but because Fragment has 1 more configuration items, it is independent.
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface ActivityScheme {
// scheme action Name
String name();
// Required parameter list for supporting the same 1 A action Corresponding to multiple scheme The scene, every 1 Items can be "type=4" To specify a value, or just pass "type" To match any value
String[] required() default {};
// If the current interface is scheme For the target value of jump, you can choose to refresh the current interface. Of course, the current interface must be implemented ActivitySchemeRefreshable
boolean useRefreshIfCurrentMatched() default false;
// Customize the current scheme The matching implementation method of, Passed value is QMUISchemeMatcher Implementation of
Class<?> customMatcher() default void.class;
// Customize the current Activity Instance factory, passing value is QMUISchemeIntentFactory
Class<?> customFactory() default void.class;
// Specifies the type of parameter, supports int/bool/long/float/double These underlying types, if not specified, are string Type
String[] keysWithIntValue() default {};
String[] keysWithBoolValue() default {};
String[] keysWithLongValue() default {};
String[] keysWithFloatValue() default {};
String[] keysWithDoubleValue() default {};
}
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface FragmentScheme {
// These parameters are the same ActivityScheme
String name();
String[] required() default {};
Class<?> customMatcher() default void.class;
String[] keysWithIntValue() default {};
String[] keysWithBoolValue() default {};
String[] keysWithLongValue() default {};
String[] keysWithFloatValue() default {};
String[] keysWithDoubleValue() default {};
// Same as ActivityScheme But currently UI Must be realized FragmentSchemeRefreshable
boolean useRefreshIfCurrentMatched() default false;
// Same as ActivityScheme, But the passing value is QMUISchemeFragmentFactory Implementation class of
Class<?> customFactory() default void.class;
// You can host the target Fragment Adj. activity List, if the current activity Is not in the list, use the activities The first part of 1 Item to start a new activity
Class<?>[] activities();
// Whether to force the start of a new Activity
boolean forceNewActivity() default false;
// It can be passed through scheme Control whether to force a new Activity
String forceNewActivityKey() default "";
}
It can be seen that all the requirements listed above are reflected in SchemeHandler and two scheme.
Use
For business users, we only need to set the
Activity
Or
Fragment
Add notes to it.
QMUISchemeHandler
By default, the parameters are parsed and placed in the
Activity
In intent or
Fragment
In arguments, so we can find the
onCreate
Take out the values we care about in:
@ActivityScheme(name="activity1")
class Activity1: QMUIActivity{
override fun onCreate(...){
...
if(isStartedByScheme()){
// Pass intent extra Gets the value of the parameter
val param1 = getIntent().getStringExtra(paramName)
}
}
}
@FragmentScheme(name="activity1", activities = {QDMainActivity.class})
class Fragment1: QMUIFragment{
override fun onCreate(...){
...
if(isStartedByScheme()){
// Pass arguments Gets the value of the parameter
val param1 = getArguments().getString(paramName)
}
}
}
This method of value transfer is in line with the official design of Android, which also requires
Fragment
Follow the way parameterless constructors are used.
For WebView, we can rewrite the
WebViewClient#shouldOverrideUrlLoading
To handle scheme jumps:
class MyWebViewClient: WebViewClient{
override fun shouldOverrideUrlLoading(view: WebView, url: String){
if(schemeHandler.handle(url)){
return true;
}
return super.shouldOverrideUrlLoading(view, url);
}
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest){
if(schemeHandler.handle(request.getUrl().toString())){
return true;
}
return super.shouldOverrideUrlLoading(view, request);
}
}
Realization
QMUISchemeHandler
Adopt the method of code generation, and generate 1 at compile time
SchemeMapImpl
Class, which implements the
SchemeMap
Class
public interface SchemeMap {
// Pass action And parameter search SchemeItem
SchemeItem findScheme(QMUISchemeHandler handler, String schemeAction, Map<String, String> params);
// Judge schemeAction Does it exist
boolean exists(QMUISchemeHandler handler, String schemeAction);
}
And the annotation of each scheme corresponds to 1
SchemeItem
:
ActivityScheme
Corresponding instantiation of 1
ActivitySchemeItem
Class and added to map
FragmentScheme
Corresponding instantiation of 1
FragmentSchemeItem
Class and added to map
At compile time through
SchemeProcessor
Generated
SchemeMapImpl
It probably looks like this:
public class SchemeMapImpl implements SchemeMap {
private Map<String, List<SchemeItem>> mSchemeMap;
public SchemeMapImpl() {
mSchemeMap = new HashMap<>();
List<SchemeItem> elements;
ArrayMap<String, String> required = null;
elements = new ArrayList<>();
required =null;
elements.add(new FragmentSchemeItem(QDSliderFragment.class,false,new Class[]{QDMainActivity.class},null,false,"",required,null,null,null,null,null,SliderSchemeMatcher.class));
mSchemeMap.put("slider", elements);
elements = new ArrayList<>();
required = new ArrayMap<>();
required.put("aa", null);
required.put("bb", "3");
elements.add(new ActivitySchemeItem(ArchTestActivity.class,true,null,required,null,new String[]{"aa"},null,null,null,null));
mSchemeMap.put("arch", elements);
}
@Override
public SchemeItem findScheme(QMUISchemeHandler arg0, String arg1, Map<String, String> arg2) {
List<SchemeItem> list = mSchemeMap.get(arg1);
if(list == null || list.isEmpty()) {
return null;
}
for (int i = 0; i < list.size(); i++) {
SchemeItem item = list.get(i);
if(item.match(arg0, arg2)) {
return item;
}
}
return null;
}
@Override
public boolean exists(QMUISchemeHandler arg0, String arg1) {
return mSchemeMap.containsKey(arg1);
}
}
This is the overall design and implementation idea, and the rest is all kinds of coding details. Those who are interested can pass
QMUISchemeHandler#handle()
Track it, or see
SchemeProcessor
How to do code generation. This function looks simple, but it also includes the application of design patterns such as Builder pattern, responsibility chain pattern and factory method, and the application of object-oriented interface, inheritance and polymorphism such as SchemeMatcher and SchemeItem. Read 1 Read may enlighten you, and maybe you can help me discover some potential Bug.
Summarize