做了一个这样的功能:
Android 调用AJAX进行登录
遇到的问题有:
跨域访问题:
*出现的错误信息是:Origin null is not allowed by Access-Control-Allow-Origin.
原因是:XMLHttpRequest2 进行跨域访问时需要服务器许可,不是任何域都接受跨域请求的,
网上有说是在HTML中加上
<meta http-equiv="Access-Control-Allow-Origin" content="*">
或者是在ajax中把dataType: “json”改为dataType: “jsonp”,
第一种情况没有变化,第二种改为jsonp的话会变为
Console: Uncaught SyntaxError: Unexpected token :错误
如果是在远程服务器里ajax()请求外域服务器里的页面,即使通过服务器环境运行也会报跨域的错误,此时需要通过JSONP的形式!
什么是JSONP?
JSONP(JSON with Padding)是JSON的一种“使用模式”,可用于解决主流浏览器的跨域数据访问的问题。
虽然可以解决这个跨越的问题,但是我返回的数据格式就不对了
类似跨越问题是需要服务器端进行配置的,APP端设置是不起作用。
主要的原因是WebKit和Chromium这两个内核
Android 4.4之后出现的Chromium,关于这两个内核的区别和各自的相关信息可参考 理解WebKit和Chromium
本案例整体思路:
Android用户的账号和密码均由EditText输入,点击Button 调用Ajax请求访问,将name和password传入,等Ajax请求完毕后,将信息回调给Android,同时把获得到的token存储到本地。
登录成功后,显示主题界面,主题界面需要token,然后在html加载的时候,把我们存储在APP中的token加载到JS中。
前言:
1.我使用的是腾讯TBS浏览器服务
2.官网腾讯TBS
环境配置步骤:(android studio 2.3)
1.导入jar包和jniLibs下的动态库文件(.so文件)
2.defaultConfig中添加如下信息:
ndk {
abiFilters "armeabi", "armeabi-v7a", "x86_64", 'armeabi-v8a', 'x86', "mips"
}
3.初始化在Application中
public class App extends Application {
private static App myApplaction;
public static String ID = null;
public static String TOKEN = null;
@Override
public void onCreate() {
super.onCreate();
myApplaction=this;
preinitX5WebCore();
//预加载x5内核
Intent intent = new Intent(this, AdvanceLoadX5Service.class);
startService(intent);
}
public static App getIntance(){
return myApplaction;
}
private void preinitX5WebCore() {
if (!QbSdk.isTbsCoreInited()) {
QbSdk.preInit(getApplicationContext(), null);// 设置X5初始化完成的回调接口
}
}
// x5 init service
public class AdvanceLoadX5Service extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
initX5();
}
private void initX5() {
// 预加载X5内核
QbSdk.initX5Environment(getApplicationContext(), cb);
}
QbSdk.PreInitCallback cb = new QbSdk.PreInitCallback() {
@Override
public void onViewInitFinished(boolean arg0) {
// TODO Auto-generated method stub
//初始化完成回调
}
@Override
public void onCoreInitFinished() {
// TODO Auto-generated method stub
}
};
}
}
4.封装一个BaseWebAcitity(我封装的不完美,还在改进中)
/**
* Created by adminZPH on 2017/4/24.
*/
public class BaseWebViewActivty extends Activity{
private final String TAG="BaseWebViewActivty";
private View headview;
private WebView webView;
private String url;
@Override
public void onCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) {
super.onCreate(savedInstanceState, persistentState);
getWindow().setFormat(PixelFormat.TRANSLUCENT);
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE | WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
}
public void setHeadview(View headview){
this.headview=headview;
// setstatusbarcolor();
}
public void SetWebView(WebView webView,String url){
this.webView=webView;
this.url=url;
Setting();
}
public void ShowWebView(){
webView.loadUrl(url);
webView.setWebViewClient(new MyWebViewClient());
webView.setWebChromeClient(new MyWebChromeClient());
}
private void Setting(){
WebSettings settings = webView.getSettings();
settings.setSupportZoom(true);
settings.setJavaScriptEnabled(true);
settings.setJavaScriptCanOpenWindowsAutomatically(true);
settings.setBuiltInZoomControls(true);
settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);
settings.setBuiltInZoomControls(false);
webView.setHorizontalScrollBarEnabled(false);
webView.setVerticalScrollBarEnabled(false);
/**
* 设置本地缓存策略
* */
webView.getSettings().setDomStorageEnabled(true);
webView.getSettings().setAppCacheMaxSize(1024*1024*8);
String appCachePath = getApplicationContext().getCacheDir().getAbsolutePath();
webView.getSettings().setAppCachePath(appCachePath);
webView.getSettings().setAllowFileAccess(true);
webView.getSettings().setAppCacheEnabled(true);
//下面方法去掉线条
IX5WebViewExtension ix5 = webView.getX5WebViewExtension();
if (null != ix5) {
ix5.setScrollBarFadingEnabled(false);
}
}
/**
* 自定义的MyWebViewClient
* */
public class MyWebViewClient extends com.tencent.smtt.sdk.WebViewClient{
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// 返回值是true的时候控制去WebView打开,为false调用系统浏览器或第三方浏览器
view.loadUrl(url);
return true;
}
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
// 重写此方法可以让webview处理https请求。
super.onReceivedSslError(view, handler, error);
handler.proceed();
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
// 在页面加载开始时调用。
super.onPageStarted(view, url, favicon);
Log.i(TAG,"onPageStarted");
}
@Override
public void onPageFinished(WebView view, String url) {
// 在页面加载结束时调用。
super.onPageFinished(view, url);
Log.i(TAG,"onPageFinished");
webView.setVisibility(View.VISIBLE);
}
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
// 网络不通,加载失败
super.onReceivedError(view, errorCode, description, failingUrl);
view.loadData("网络连接失败,请稍后重试!", "text/html; charset=utf-8", "utf-8");
}
@Override
public void onLoadResource(WebView view, String url) {
// 在加载页面资源时会调用,每一个资源(比如图片)的加载都会调用一次。
super.onLoadResource(view, url);
}
}
public class MyWebChromeClient extends com.tencent.smtt.sdk.WebChromeClient {
@Override
public void onProgressChanged(WebView view, int newProgress) {
if (newProgress == 100) {
} else {
}
super.onProgressChanged(view, newProgress);
}
@Override
public void onReceivedTitle(WebView view, String title) {
super.onReceivedTitle(view, title);
}
}
@Override
public void onBackPressed() {
// finish();
if (null!=webView&&webView.canGoBack()){
webView.goBack();
// return true;
}
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode==KeyEvent.KEYCODE_BACK){
if (null!=webView&&webView.canGoBack()){
webView.goBack();
return true;
}
setResult(Activity.RESULT_OK);
}
return super.onKeyDown(keyCode, event);
}
}
在Src/java目录下建立assets文件夹,写一个html文件如下:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>登录</title>
<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css" />
<style type="text/css">
body {
padding-top: 40px;
padding-bottom: 40px;
background: none;
}
.form-signin {
max-width: 300px;
padding: 19px 29px 29px;
margin: 0 auto 20px;
background-color: #fff;
border: 1px solid #e5e5e5;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
-moz-box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
}
.form-signin .form-signin-heading,
.form-signin .checkbox {
margin-bottom: 10px;
}
.form-signin input[type="text"],
.form-signin input[type="password"] {
font-size: 16px;
height: auto;
margin-bottom: 15px;
padding: 7px 9px;
}
</style>
</head>
<body>
<div class="container-fluid">
<div class="form-signin" style="text-align: center;">
<h2 class="form-signin-heading">登录</h2>
<input type="text" id="username" name="username" class="input-block-level" placeholder="账号" style="width: 100%!important;">
<br/>
<input type="password" id="password" name="password" class="input-block-level" placeholder="密码" style="width: 100%!important;">
<p><button class="btn btn-large btn-success" id="login" type="submit">登录</button></p>
</div>
</div>
</body>
<script type="text/javascript" src="js/jquery.min.js"></script>
<script type="text/javascript">
function loginclick(username,password) {
console.log(username);
$.ajax({
url: "http://XXX.XXX.X.XX:8080/XXXX/XXXX.go?method=XXXXXX",
data: { "username": username, "password": password },
type: "post",
dataType: "json",
success: function (result) {
console.log(result.state)
if (result.state == "OK") {
window.android.ShowMessage(result.msg);
}
if(result.state=="ERROR"){
window.android.ShowMessage("1");
}
},
error: function () {
window.android.ShowMessage("0");
}
});
}
</script>
</html>
其中有一个function loginclick(),就是我们要调用的ajax方法,
对于Android和JS互调,需要实体类中方法做中间引领人,
这里我封装一个接口如下:
public interface BaseJsImp {
/**
* 显示一些提示信息的方法
* */
@JavascriptInterface
public void ShowMessage(String T);
/**
* Js调用Android的方法
* */
@JavascriptInterface
public void JsToAndroid(Object... T);
/**
* Android调用JS的方法
* */
@JavascriptInterface
public void AndroidToJs(Object... T);
/**
* Js调用调用android方法,带有返回值的
* */
@JavascriptInterface
public Object GetDateFromAndroid();
/**
* Js调用调用android方法,带有返回值的和参数的
* */
@JavascriptInterface
public Object GetDateFromAndroid(Object... T);
}
接下来到了我们的登录功能中:
在xml文件中,edittext和button随便写
关键是加上
<com.tencent.smtt.sdk.WebView
android:layout_width="0dp"
android:id="@+id/login_webview"
android:layout_height="0dp">
</com.tencent.smtt.sdk.WebView>
在LoginActivity中(部分方法可能只有我有)
代码如下
public class LoginActivity extends BaseWebViewActivty implements View.OnClickListener {
private Button login;
private ImageView weixin,qq;
private MaterialEditText name,password;
private LoadingDialog loadingDialog;
private WebView webView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
initHeadView();
//加载资源文件
SetWebView(webView,"file:///android_asset/static/login.html");
ShowWebView();
//初始化回传信息接口
webView.addJavascriptInterface(new LoginClass(), "android");
}
private void initHeadView() {
login= (Button) findViewById(R.id.login_btn);
name= (MaterialEditText) findViewById(R.id.login_username);
password= (MaterialEditText) findViewById(R.id.login_password);
weixin= (ImageView) findViewById(R.id.weixin_login);
qq= (ImageView) findViewById(R.id.qq_login);
login.setOnClickListener(this);
weixin.setOnClickListener(this);
qq.setOnClickListener(this);
webView= (WebView) findViewById(R.id.login_webview);
setstatusbarcolor();
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.login_btn:
if(name.getText().toString().equals("")){
toast("账号不能为空");
return;
}
if(password.getText().toString().equals("")){
toast("密码不能为空");
return;
}
Login(name.getText().toString(),password.getText().toString());
break;
case R.id.weixin_login:
break;
case R.id.qq_login:
break;
}
}
private void Login(String name,String psd) {
loadingDialog=new LoadingDialog(this,"登录中");
loadingDialog.show();
//把我们的信息传入JS中
webView.loadUrl("javascript:loginclick('"+name+"','"+psd+"')");
}
@Override
protected void onDestroy() {
super.onDestroy();
}
public class LoginClass implements BaseJsImp {
@Override
public void ShowMessage(String message) {
if(loadingDialog!=null){
if(loadingDialog.isShowing()){
loadingDialog.dismiss();
}
}
if(message.equals("0") ||message.equals("1")) {
toast("登录失败!");
Log.i("TAG","登录失败");
}
else {
Log.i("TAG","登录成功"+message);
toast("登录成功!"+message);
String[] a = message.split(",");
App.ID=a[0];
App.TOKEN=a[1];
Intent intent=new Intent(LoginActivity.this,TestActivity.class);
intent.putExtra("url",message);
startActivity(intent);
// AddDB();
}
}
@Override
public void JsToAndroid(Object... T) {
}
@Override
public void AndroidToJs(Object... T) {
}
@Override
public Object GetDateFromAndroid() {
return null;
}
@Override
public Object GetDateFromAndroid(Object... T) {
return null;
}
}
@Override
public void onBackPressed() {
super.onBackPressed();
this.finish();
}
private void AddDB() {
User u=new User();
u.setLoginState(true);
u.setUsername(name.getText().toString());
u.setPassword(password.getText().toString());
DBUtil.initDataBase(u);
this.finish();
}
}
上述实例中,可以看出JS和Android互调用法
Android中方法
webView.addJavascriptInterface(new LoginClass(), “android”);
Js中(JS将信息回传给ANdroid)
window.android.ShowMessage(result.msg);
接下来,如何在JS加载中拿到保存在Android中所需要的token呢?
一个html页面中需要很多js,每个js都可能是一个单独的文件,我们解决在不同JS中调一个Android中的数据时候的思路是:
在主HTML中设置全局变量,然后每一个js都调用这个,对于token的话就需要在初始化数据的时候再ajax请求中添加headers,然后把我们android中的保存token加载过去。
实例如下:
主html文件:
在head中:
<script>
var _access_tocken = window.android.GetDateFromAndroid();
</script>
如果我们是在一个新的Activity中加载的话,在新的Activity中需要
webView.addJavascriptInterface(new NewClass(), “android”);
NewClass继承我们的BaseJsImp,只要将重写的GetDateFromAndroid()返回我们所需的token
如下:
@Override
public Object GetDateFromAndroid() {
if(App.TOKEN!=null)
return App.TOKEN;
else
return null;
}
然后在我们的主html中数据初始化的js中添加headers
如下
//自定义的方法
function myDefultAction() {
$.ajax({
url: baseurl+"getAgenda",
headers: {
access_tocken: _access_tocken
},
//data :{"_date":_date,"title":password},
type: "post",
dataType: "json",
// async: false,
通过以上方式我们就可以将动态的添加token到js中
这片海
2017-04-26