在我们公司的一个项目中使用到了QQ微信微博等三方登陆,但是由于网站已经已成了这些登陆方式,为了简化app端的工作量以及最快的方式让app拥有三方登陆的功能,我们决定在app里使用web登陆的方式。
早在之前我写过一篇文章介绍了一些简单的WebView控件的使用,不过WebView确实是很复杂的一个控件,这里说的复杂并不是说它有多难用,而是很多的状态的不确定性,在不同的手机上可能渲染的效果不一样等等,不过如果api level>=19问题就会相对好得多,所以如果webview需要大量用的话,不妨将api level设置大于19,或者使用Crosswalk,不过比较坑的是,Crosswalk已经不再维护了...
不过眼下解决的问题是,app许多网页和本地代码交互的界面,如果保证不用放置太多的包含WebView的Activity才是关键,这里写了个简单的库 WebFrame,也许是重复造的轮子。
用法:
allprojects {
repositories {
...
maven { url 'https://www.jitpack.io' }
}
}
dependencies {
compile 'com.github.yahch:WebFrame:2017.09.27.2'
}
如果有需要在网页上调用的本地代码,需要继承WebFrameScriptInterface 这个抽象类。
WebFrameSettings 是一个单例,用于对webview 的 activity 进行设置,它的成员如下:
private String url;
private HashMap<String, WebFrameScriptInterface> objs;
private boolean noActionBar;
第一个表示需要导航到的url,第二个表示这个页面和本地代码调用的对象类的集合,第三个设置是否需要ActionBar。
在我的项目中,我点击 QQ 登录的图标后,导航至我们网站的 QQ 登录授权链接,对应在 Android 中的操作如下:
Intent intentForQQLogin = new Intent(LoginActivity.this, WebFrameActivity.class);
WebFrameSettings.instance.reset();
WebFrameSettings.instance.setUrl("http://my.域名.com/oauth/qq?client=android");
WebFrameSettings.instance.addObject("app", new WebInterface());
WebFrameSettings.instance.setNoActionBar(true);
startActivity(intentForQQLogin);
可以看到所有交互传入的对象是 WebInterface 的实例,来看看 WebInterface 的内容。
class WebInterface extends WebFrameScriptInterface {
@JavascriptInterface
public void oauth(String uid) {
if (getWebFrameActivity() != null) getWebFrameActivity().finish();
}
}
因为在我们网站授权登录后的回调时 app.oauth(qqopenid),所以方法签名一定要对应好。
这里有个黑魔法是在调用方能关闭授权窗口的Activity,也就是能通过 getWebFrameActivity() 获取到打开的窗口的 “句柄”,然后进行操作。
WebFrameScriptInterface 的设计如下:
public abstract class WebFrameScriptInterface {
private WebFrameActivity webFrameActivity;
public WebFrameScriptInterface() {
}
public WebFrameActivity getWebFrameActivity() {
return this.webFrameActivity;
}
public void setWebFrameActivity(WebFrameActivity webFrameActivity) {
this.webFrameActivity = webFrameActivity;
}
}
WebFrameScriptInterface 是一个抽象类,其实不写成抽象类也可以,因为也不需要子类重写或实现什么。它唯一作用就是保存着一个 WebFrame 对象实例,供 Java/JS 调用接口层使用。
在 WebFrameScriptInterface 的 onCreate 方法中,会将当前的实例传递给抽象类的实现者。
@SuppressLint("JavascriptInterface")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
HashMap<String, WebFrameScriptInterface> invokeObjects = WebFrameSettings.instance.getObjs();
if (invokeObjects.size() > 0) {
for (Map.Entry<String, WebFrameScriptInterface> entry : invokeObjects.entrySet()) {
WebFrameScriptInterface ws = entry.getValue();
ws.setWebFrameActivity(WebFrameActivity.this);
webViewWebFrameModule.addJavascriptInterface(ws, entry.getKey());
}
}
...
}
到这里这个组件算是介绍完了,我是沿着我的思路是实现的,也可能网络上有更好的第三方库,写了多年的 C# 也可能还是保留有 C# 的某些语法习惯 😂