WMRouter使用记录

官方的说明还是不够简单直白,特别对于没有接触过路由的人来说

基础,也是所以外部URI路由跳转的根本所在

首先说下android系统路由, android:exported=”true”允许外部调用,再配置intent-filter,就可以在任意浏览器实现点击进入LoginActivity

<!-- 配置intent-filter -->
<activity android:name=".wmrouter.LoginActivity" android:exported="true" >
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <!-- 接收testwmrouter://wmrouter/loing的外部URI跳转 -->
                <data android:scheme="testwmrouter" android:host="wmrouter" android:path="/login"/>
            </intent-filter>
        </activity>

配置WMRouter

库地址:https://github.com/meituan/WMRouter
首先,根据官方文档一通配置接入
然后,在Application中初始化

//App.kt
import android.annotation.SuppressLint
import android.content.Context
import android.os.AsyncTask
import com.sankuai.waimai.router.Router
import com.sankuai.waimai.router.annotation.RouterProvider
import com.sankuai.waimai.router.annotation.RouterService
import com.sankuai.waimai.router.common.DefaultRootUriHandler
import com.sankuai.waimai.router.components.DefaultLogger
import com.sankuai.waimai.router.components.DefaultOnCompleteListener
import com.sankuai.waimai.router.core.Debugger.*

@RouterService(interfaces = [Context::class], key = ["/application"], singleton = true)
class App : Application() {
    companion object {
        lateinit var instance: App
            private set

        @RouterProvider
        fun provideApplication(): App {
            return instance
        }

        fun getContext(): Context {
            return instance
        }
    }

    override fun onCreate() {
        instance = this
        super.onCreate()
        initRouter(this)
    }

    @SuppressLint("StaticFieldLeak")
    private fun initRouter(context: Context) {
        // 自定义Logger
        val logger = object : DefaultLogger() {
            override fun handleError(t: Throwable) {
                super.handleError(t)
                // 此处上报Fatal级别的异常
                t.printStackTrace()
            }
        }

        // 设置Logger
        setLogger(logger)

        // Log开关,建议测试环境下开启,方便排查问题。
        setEnableLog(true)

        // 调试开关,建议测试环境下开启。调试模式下,严重问题直接抛异常,及时暴漏出来。
        setEnableDebug(true)

        // 创建RootHandler,可以在这里设置默认scheme和host
        val rootHandler = DefaultRootUriHandler(context, "testwmrouter", "wmrouter")

        // 设置全局跳转完成监听器,可用于跳转失败时统一弹Toast提示,做埋点统计等。
        rootHandler.globalOnCompleteListener = DefaultOnCompleteListener.INSTANCE

        // 初始化
        Router.init(rootHandler)

        // 懒加载后台初始化(可选)
        object : AsyncTask<Void, Void, Void>() {
            override fun doInBackground(vararg voids: Void): Void? {
                Router.lazyInit()
                return null
            }
        }.execute()
    }
}

接下来,需要一个中转页面UriProxyActivity

//UriProxyActivity.kt
import com.sankuai.waimai.router.core.UriRequest
import com.sankuai.waimai.router.core.OnCompleteListener
import com.sankuai.waimai.router.common.DefaultUriRequest
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity;

class UriProxyActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        DefaultUriRequest.startFromProxyActivity(this, object : OnCompleteListener {
            override fun onSuccess(request: UriRequest) {
                finish()
            }

            override fun onError(request: UriRequest, resultCode: Int) {
                finish()
            }
        })
    }
}
<!-- 配置UriProxyActivity -->
        <activity android:name=".UriProxyActivity" android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <!-- 接收所有scheme为testwmrouter的外部URI跳转,不区分host和path -->
                <data android:scheme="testwmrouter"/>
            </intent-filter>
        </activity>

再接下来,也是我使用路由的原因,拦截器(以登录为例)

//LoginInterceptor.kt
import android.widget.Toast;
import androidx.annotation.NonNull;
import com.sankuai.waimai.router.core.UriCallback;
import com.sankuai.waimai.router.core.UriInterceptor;
import com.sankuai.waimai.router.core.UriRequest;
import com.sankuai.waimai.router.core.UriResult;

/** * Created by jzj on 2018/3/20. */

public class LoginInterceptor implements UriInterceptor {
    @Override
    public void intercept(@NonNull UriRequest request, @NonNull final UriCallback callback) {
        //注意:这里使用FakeAccountService会导致uriproxyactivity无法正常回调,不知道什么原因,暂不深究,官方文档给出的就是使用这个,下面也是官方的demo
        final IAccountService accountService = DemoServiceManager.getAccountService();
        if (accountService.isLogin()) {
            callback.onNext();
        } else {
            Toast.makeText(request.getContext(), "请先登录~", Toast.LENGTH_SHORT).show();
            accountService.registerObserver(new IAccountService.Observer() {
                @Override
                public void onLoginSuccess() {
                    accountService.unregisterObserver(this);
                    callback.onNext();
                }

                @Override
                public void onLoginCancel() {
                    accountService.unregisterObserver(this);
                    //-100为取消登录码,可自定义
                    callback.onComplete(-100);
                }

                @Override
                public void onLoginFailure() {
                    accountService.unregisterObserver(this);
                    //-101为登录失败码,可自定义
                    callback.onComplete(-101);
                }

                @Override
                public void onLogout() {
                    accountService.unregisterObserver(this);
                    callback.onComplete(UriResult.CODE_ERROR);
                }
            });
            accountService.startLogin(request.getContext());
        }
    }
}

拦截器相关代码

//IAccountService.kt
import android.content.Context;

/** * Created by jzj on 2018/4/19. */

public interface IAccountService {

    boolean isLogin();

    void startLogin(Context context);

    void registerObserver(Observer observer);

    void unregisterObserver(Observer observer);

    void notifyLoginSuccess();

    void notifyLoginCancel();

    void notifyLoginFailure();

    void notifyLogout();

    interface Observer {

        void onLoginSuccess();

        void onLoginCancel();

        void onLoginFailure();

        void onLogout();
    }
}
//FakeAccountService.kt

import android.content.Context;

import androidx.annotation.NonNull;

import com.example.testwmrouter.Constants;
import com.sankuai.waimai.router.Router;
import com.sankuai.waimai.router.annotation.RouterProvider;
import com.sankuai.waimai.router.annotation.RouterService;

import java.util.ArrayList;
import java.util.List;

/** * Created by jzj on 2018/3/26. */
@RouterService(interfaces = IAccountService.class, key = "/singleton", singleton = true)
public class FakeAccountService implements IAccountService {

    @RouterProvider
    public static FakeAccountService getInstance() {
        return new FakeAccountService(Router.getService(Context.class, "/application"));
    }

    private boolean mIsLogin = false;
    private final List<Observer> mObservers = new ArrayList<>();

    private FakeAccountService(Context context) {
        // ...
    }

    @Override
    public void startLogin(Context context) {
        Router.startUri(context, "testwmrouter://wmrouter/login");
    }

    @Override
    public boolean isLogin() {
        return mIsLogin;
    }

    @Override
    public void registerObserver(Observer observer) {
        if (observer != null && !mObservers.contains(observer)) {
            mObservers.add(observer);
        }
    }

    @Override
    public void unregisterObserver(Observer observer) {
        if (observer != null) {
            mObservers.remove(observer);
        }
    }

    @Override
    public void notifyLoginSuccess() {
        mIsLogin = true;
        Observer[] observers = getObservers();
        for (int i = observers.length - 1; i >= 0; --i) {
            observers[i].onLoginSuccess();
        }
    }

    @Override
    public void notifyLoginCancel() {
        Observer[] observers = getObservers();
        for (int i = observers.length - 1; i >= 0; --i) {
            observers[i].onLoginCancel();
        }
    }

    @Override
    public void notifyLoginFailure() {
        Observer[] observers = getObservers();
        for (int i = observers.length - 1; i >= 0; --i) {
            observers[i].onLoginFailure();
        }
    }

    @Override
    public void notifyLogout() {
        mIsLogin = false;
        Observer[] observers = getObservers();
        for (int i = observers.length - 1; i >= 0; --i) {
            observers[i].onLogout();
        }
    }

    @NonNull
    private Observer[] getObservers() {
        return mObservers.toArray(new Observer[mObservers.size()]);
    }
}
//DemoServiceManager.kt
import com.sankuai.waimai.router.Router;

/** * Created by jzj on 2018/4/19. */

public class DemoServiceManager {
    public static IAccountService getAccountService() {
        return Router.getService(IAccountService.class, "/singleton");
    }
}

下面是登录界面

//LoginActivity.kt
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import androidx.annotation.Nullable;
import com.sankuai.waimai.router.annotation.RouterUri;
import androidx.appcompat.app.AppCompatActivity;

/** * 登录页 * * Created by jzj on 2018/3/19. */
@RouterUri(path = "/login",exported = true)
public class LoginActivity extends AppCompatActivity{

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.show(this,"登录成功",Toast.LENGTH_SHORT).show();
             	DemoServiceManager.getAccountService().notifyLoginSuccess();
                finish();
            }
        });
    }

    @Override
    public void onBackPressed() {
        Toast.show(this,"登录取消",Toast.LENGTH_SHORT).show();
        DemoServiceManager.getAccountService().notifyLoginCancel();
        super.onBackPressed();
    }
}
<!-- activity_login.xml -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:ignore="HardcodedText">

    <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="text" android:text="UserName"/>

    <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="text" android:text="Password"/>

    <Button android:id="@+id/button" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:text="登录"/>

</LinearLayout>

目标页面


@RouterUri(path = ["main"], exported = true, interceptors = [LoginInterceptor::class])
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

}
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity">

    <TextView android:id="@+id/textview" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

然后,你需要一个html

<html><head><title>测试WMRouter</title><head><body><br><br><h1><a href='testwmrouter://wmrouter/main'>测试路由testwmrouter://wmrouter/main</a></h1><br><br><br><h1 color='red' id='test'>测试js路由testwmrouter://wmrouter/main</h1><script>document.getElementById('test').onclick = function(){window.location='testwmrouter://wmrouter/main'};</script></body></html>

如果觉得创建一个html,然后在手机上访问不太方便,可以使用webview,下面是一个简单的WebView实现

//WebActivity.kt

import android.annotation.SuppressLint
import android.os.Build
import android.os.Bundle
import android.webkit.*
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import com.sankuai.waimai.router.Router
import com.viz.tools.Toast
import kotlinx.android.synthetic.main.activity_web.*

class WebActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_web)

        initWebSettings()
        initWebClient()
        initWebChromeClient()
        val url = Constants.schema + "://" + Constants.host + "/user/member/join"
        textInputEditText_url.setText(url)
        materialButton_go.setOnClickListener {
            val url = textInputEditText_url.text.toString()
            if (url.isNotEmpty()) {
// webView.loadUrl(url)//直接加载会无法拦截
                //下面两种方式实现,原理都是一样的,按需选择吧
// webView.loadUrl("javascript:window.location='$url';")
                webView.loadData("<html><head><title>测试WMRouter</title><head><body><br><br><h1><a href='$url'>测试路由$url</a></h1><br><br><br><h1 color='red' id='test'>测试js路由$url</h1><script>document.getElementById('test').onclick = function(){window.location='$url'};</script></body></html>","text/html","utf-8")
            } else {
                Toast.show(this, "请输入网址")
            }
        }
    }

    private fun initWebChromeClient() {
        webView.webChromeClient = object : WebChromeClient() {
            override fun onProgressChanged(view: WebView, newProgress: Int) {
                super.onProgressChanged(view, newProgress)
                //newProgress 当前的加载进度
            }

            override fun onReceivedTitle(view: WebView, title: String) {
                super.onReceivedTitle(view, title)
                //获取网页中的标题
            }

            override fun onJsAlert(
                view: WebView,
                url: String,
                message: String,
                result: JsResult
            ): Boolean {
                return super.onJsAlert(view, url, message, result)
                //支持js中的警告框,自己定义弹出框 返回true
            }

            override fun onJsConfirm(
                view: WebView,
                url: String,
                message: String,
                result: JsResult
            ): Boolean {
                return super.onJsConfirm(view, url, message, result)
                //支持js中的确认框,自己定义弹出框 返回true
            }

            override fun onJsPrompt(
                view: WebView,
                url: String,
                message: String,
                defaultValue: String,
                result: JsPromptResult
            ): Boolean {
                return super.onJsPrompt(view, url, message, defaultValue, result)
                //支持js中的输入框,自己定义弹出框 返回true
            }
        }
    }

    private fun initWebClient() {
        webView.webViewClient = object : WebViewClient() {
            override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
                if (url != null) {
                    if(url.startsWith("http://") && !url.startsWith("https://")) {
                        Router.startUri(this@WebActivity, url)
                        return true
                    }
                }
                return super.shouldOverrideUrlLoading(view, url)
            }
            @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
            override fun shouldOverrideUrlLoading(
                view: WebView?,
                request: WebResourceRequest?
            ): Boolean {
                if (request != null) {
                    //这里设置除http/https请求外使用路由,也可以指定特定的scheme
                    if (request.url.scheme != "http" && request.url.scheme != "https") {
                        //这里路由可以分为两种
                        //第一种由WMRouter控制
                        Router.startUri(this@WebActivity, request.url.toString())
                        //第二种由系统控制
// val intent = Intent()
// intent.data = request.url
// startActivity(intent)
                        return true
                    }
                }
                return super.shouldOverrideUrlLoading(view, request)
            }
        }
    }

    @SuppressLint("SetJavaScriptEnabled")
    private fun initWebSettings() {

        //声明WebSettings子类
        val webSettings = webView.settings

        //如果访问的页面中要与Javascript交互,则webview必须设置支持Javascript
        webSettings.javaScriptEnabled = true
        // 若加载的 html 里有JS 在执行动画等操作,会造成资源浪费(CPU、电量)
        // 在 onStop 和 onResume 里分别把 setJavaScriptEnabled() 给设置成 false 和 true 即可
        //支持插件
// webSettings.setPluginsEnabled(true)
        //设置自适应屏幕,两者合用
        webSettings.useWideViewPort = true //将图片调整到适合webview的大小
        webSettings.loadWithOverviewMode = true // 缩放至屏幕的大小
        //缩放操作
        webSettings.setSupportZoom(true) //支持缩放,默认为true。是下面那个的前提。
        webSettings.builtInZoomControls = true //设置内置的缩放控件。若为false,则该WebView不可缩放
        webSettings.displayZoomControls = false //隐藏原生的缩放控件
        //其他细节操作
        webSettings.cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK
        //缓存模式如下:
        //LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据
        //LOAD_DEFAULT: (默认)根据cache-control决定是否从网络上取数据。
        //LOAD_NO_CACHE: 不使用缓存,只从网络获取数据.
        //LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。
        //关闭webview中缓存
        webSettings.allowFileAccess = true //设置可以访问文件
        webSettings.javaScriptCanOpenWindowsAutomatically = true //支持通过JS打开新窗口
        webSettings.loadsImagesAutomatically = true //支持自动加载图片
        webSettings.defaultTextEncodingName = "utf-8"//设置编码格式

        //5.1以上默认http和https不混合处理
        //开启
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            webView.settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
        }
    }
}

到这里WMRouter算是可以满足我的需求了

点赞