近日做了个android小例子,我是个初学者,新手上路,请多多指教。
一. 创建一个H5页面,UI使用easyui
创建assets目录,用来存放前端资源
下载easyui资源包,复制CSS、资源和js文件。
把easyui文件直接复制到目录下
这个路径就是 easyuif/……,下面这一步可以查看路径
这就是要引用的路径
参考easyui demo创建h5网页
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" type="text/css" href="easyuif/themes/default/easyui.css" />
<link rel="stylesheet" type="text/css" href="easyuif/themes/mobile.css" />
<link rel="stylesheet" type="text/css" href="easyuif/themes/icon.css" />
<link rel="stylesheet" type="text/css" href="easyuif/themes/color.css" />
<script type="text/javascript" src="easyuif/jquery.min.js"></script>
<script type="text/javascript" src="easyuif/jquery.easyui.min.js"></script>
<script type="text/javascript" src="easyuif/locale/easyui-lang-zh_CN.js"></script>
<style>
.nihong{
/* 要把阴影与大小配合好,一般来说大小都是偏大时采用 */
font-family: "Arial Rounded MT Bold", "Helvetica Rounded", Arial, sans-serif;
text-transform: uppercase;/* 全开大写 */
font-size: 24px;
color: orangered;
text-shadow: 0 8px 9px #c4b59d, 0px -2px 1px #fff;
font-weight: bold;
letter-spacing: -4px;
/*background: linear-gradient(to bottom, #ece4d9 0%,#e9dfd1 100%);*/
}
</style>
</head>
<body onload="iniatialize()">
<div style="margin:20px 0;">
<a class="nihong" style="">RCS参数下发_测试用例</a>
</div>
<div>
<ul class="m-list">
<li><span style="font-size:12px;color:gray">请求编号(RequestCode:string):</span><br><input id="RequestCode" class="easyui-textbox" prompt="...(选填)" style="width:100%;height:30px"></li>
<li><span style="font-size:12px;color:gray">任务类型(taskType:string):</span>
<br>
<select id="state" class="easyui-combobox" name="state" style="width:100%;height:30px">
<option value="AL">产线上线</option>
<option value="AK">立库出库</option>
<option value="AZ">产线下线</option>
</select>
</li>
<li><span style="font-size:12px;color:gray">任务号(taskNo:string):</span><br><input id="taskNo" class="easyui-textbox" prompt="..." style="width:100%;height:30px"></li>
<li><span style="font-size:12px;color:gray">状态(taskState:string):</span><br><input id="taskState" class="easyui-textbox" prompt="..." style="width:100%;height:30px"></li>
<li><span style="font-size:12px;color:gray">接收位置(begin_location:string):</span><br><input id="beginlocation" class="easyui-textbox" prompt="例如301-101" style="width:100%;height:30px"></li>
<li><span style="font-size:12px;color:gray">送达位置(target_location:string):</span><br><input id="targetlocation" class="easyui-textbox" prompt="例如301-103" style="width:100%;height:30px"></li>
<li><span style="font-size:12px;color:gray">下一工序产线编码(To_Line_Code:string):</span><br><input id="ToLineCode" class="easyui-textbox" prompt="..." style="width:100%;height:30px"></li>
<li><span style="font-size:12px;color:gray">请求操作时间(RequestTime:string)</span><br><input id="RequestTime" class="easyui-textbox" prompt="..." style="width:100%;height:30px"></li>
<li><span style="font-size:12px;color:gray">托盘号(PalletCode:string):</span><br><input id="PalletCode" class="easyui-textbox" prompt="例如:P2100056" style="width:100%;height:30px"></li>
<li><span style="font-size:12px;color:gray">部件总数量(TotalQuantity:int):</span><br><input id="TotalQuantity" class="easyui-textbox" prompt="请输入正整数" style="width:100%;height:30px"></li>
<li>
<span style="font-size:12px;color:gray">部件集合(PartSets:集合):(PartSets:ArrayList):</span>
<br>
<input id="PartSets" class="easyui-searchbox" data-options="prompt:'null(选填)',searcher:doSearch" style="width:100%;height:30px">
</li>
</ul>
<a href="javascript:quest();" class="easyui-linkbutton c3" style="width:100%;height:40px">提交</a>
</div>
<!--弹窗 -->
<div id="win" class="easyui-window" title="部件编辑" closed="true" style="top:40%;width:90%;height:40%;">
<form id="frm" style= "padding:1px 1px 1px 1px;">
<table id="dg" class="easyui-datagrid" style="width:100%;height:auto"
data-options="iconCls: 'icon-edit',singleSelect: true,toolbar: '#tb',onClickCell: onClickCell,onEndEdit: onEndEdit">
<thead>
<tr>
<th data-options="field:'SequenceNumber',width:40,align:'center',editor:'textbox'">顺序</th>
<th data-options="field:'ProductionBatchCode',width:80,align:'center',editor:'textbox'">批次号</th>
<th data-options="field:'PartSerialNumber',width:80,align:'center',editor:'textbox'">条码号</th>
<th data-options="field:'TaskNumber',width:80,align:'center',editor:'textbox'">任务单</th>
<th data-options="field:'PartMaterialCode',width:80,align:'center',editor:'textbox'">物料号</th>
<th data-options="field:'status',align:'middle',width:40,align:'center',editor:{type:'checkbox',options:{on:'P',off:''}}">状态</th>
</tr>
</thead>
</table>
<div id="tb" style="height:auto">
<a href="javascript:void(0)" class="easyui-linkbutton" data-options="iconCls:'icon-add',plain:true" onclick="append()">添加</a>
<a href="javascript:void(0)" class="easyui-linkbutton" data-options="iconCls:'icon-remove',plain:true" onclick="removeit()">删除</a>
<a href="javascript:void(0)" class="easyui-linkbutton" data-options="iconCls:'icon-clear',plain:true" onclick="removeall()">清空</a>
<a href="javascript:void(0)" class="easyui-linkbutton" data-options="iconCls:'icon-cancel',plain:true" onclick="closewin2()">关闭</a>
</div>
</form>
</div>
<!--弹窗 -->
<div id="win2" class="easyui-window" title="系统消息" closed="true" style="top:60%;width:90%;height:30%;">
<form id="frm2" style= "padding:10px 10px 10px 10px;">
<div style="width:100%;height:90%">
<div style="width:100%;height:5%">
<p style="font-size:14px"><label id="typename">sdfsdfsdfsdg</label></p>
</div>
<div style="width:100%;height:95%">
<p style="font-size:12px;color:darkgray;padding:5px 5px 5px 5px;">
<label id="msgtxt" style="word-break:break-all;">asdasdasd</label>
</p>
</div>
</div>
<div style="width:100%;top:90%;height:5%">
<p><a href="javascript:closewin()" class="easyui-linkbutton c4" style="left:60%;width:20%;height:40px">确定</a></p>
</div>
</form>
</div>
<script type="text/javascript">
function quest(){
var RequestCode_ =$("#RequestCode").textbox('getText');
var taskType_ =$("#state").combobox('getText');
var taskNo_ = $("#taskNo").textbox('getText');
var taskState_ = $("#taskState").textbox('getText');
var beginlocation_ = $("#beginlocation").textbox('getText');
var targetlocation_ = $("#targetlocation").textbox('getText');
var ToLineCode_ = $("#ToLineCode").textbox('getText');
var date = new Date();
var datm=dateTimeToString(date).toLocaleString();
$('#RequestTime').textbox('setValue', datm);
var RequestTime_ = $("#RequestTime").textbox('getText');
var PalletCode_ = $("#PalletCode").textbox('getText');
var TotalQuantity_ = parseInt($("#TotalQuantity").textbox('getText'));
var msginfo=getTable();
var PartSets_;
if(msginfo==''){
PartSets_ = null;
$('#PartSets').textbox('setValue', 'null');
}else{
PartSets_ = getTable();
var numcou=PartSets_.length;
$('#PartSets').textbox('setValue', 'data['+String(numcou)+']');
}
var datax={ RequestCode:RequestCode_,taskType:taskType_,taskNo:taskNo_,taskState:taskState_,begin_location:beginlocation_, target_location:targetlocation_,To_Line_Code:ToLineCode_,RequestTime:RequestTime_,PalletCode:PalletCode_,
TotalQuantity:TotalQuantity_,PartSets:PartSets_
};
android.showToast(JSON.stringify(datax));
}
function ShowMsg(str,datx){
document.getElementById('typename').innerHTML =datx;
document.getElementById('msgtxt').innerHTML = JSON.stringify(str);
openmsg();
}
function iniatialize(){
$('#RequestCode').textbox('setValue', 'testRCS_001');
$('#taskState').textbox('setValue', '下发');
$('#taskNo').textbox('setValue', 'Task001');
var date = new Date();
var datm=dateTimeToString(date).toLocaleString();
$('#RequestTime').textbox('setValue', datm);
$('#ToLineCode').textbox('setValue', '8A27A001004');
$('#TotalQuantity').textbox('setValue', '0');
$('#PartSets').textbox('setValue', 'null');
}
function dateTimeToString(date) {
var y = date.getFullYear();
var M = date.getMonth() + 1;
var d = date.getDate();
var H = date.getHours();
var m = date.getMinutes();
var s = date.getSeconds();
return y + '-' + (M < 10 ? ('0' + M) : M) + '-' + (d < 10 ? ('0' + d) : d) + " " + (H < 10 ? ('0' + H) : H) + ":" + (m < 10 ? ('0' + m) : m) + ":" + (s < 10 ? ('0' + s) : s);
}
function getTable(){
var list= [];
var checkedItems = $('#dg').datagrid('getRows');
$.each(checkedItems, function(index, item){
var _SequenceNumber=parseInt(item.SequenceNumber);
var _ProductionBatchCode=item.ProductionBatchCode==null?'':item.ProductionBatchCode;
var _PartSerialNumber=item.PartSerialNumber==null?'':item.PartSerialNumber;
var _TaskNumber=item.TaskNumber==null?'':item.TaskNumber;
var _PartMaterialCode=item.PartMaterialCode==null?'':item.PartMaterialCode;
var datax={SequenceNumber:_SequenceNumber,ProductionBatchCode:_ProductionBatchCode,PartSerialNumber:_PartSerialNumber,
TaskNumber:_TaskNumber,PartMaterialCode:_PartMaterialCode};
list.push(datax);
});
return list;
}
function doSearch(value){
openlogin();
}
var editIndex = undefined;
function endEditing(){
if (editIndex == undefined){return true}
if ($('#dg').datagrid('validateRow', editIndex)){
$('#dg').datagrid('endEdit', editIndex);
editIndex = undefined;
return true;
} else {
return false;
}
}
function onClickCell(index, field){
if (editIndex != index){
if (endEditing()){
$('#dg').datagrid('selectRow', index)
.datagrid('beginEdit', index);
var ed = $('#dg').datagrid('getEditor', {index:index,field:field});
if (ed){
($(ed.target).data('textbox') ? $(ed.target).textbox('textbox') : $(ed.target)).focus();
}
editIndex = index;
} else {
setTimeout(function(){
$('#dg').datagrid('selectRow', editIndex);
},0);
}
}
}
function onEndEdit(index, row){
var ed = $(this).datagrid('getEditor', {
index: index,
field: 'ProductionBatchCode'
});
row.ProductionBatchCode = $(ed.target).combobox('getText');
}
function append(){
if (endEditing()){
$('#dg').datagrid('appendRow',{status:'P'});
editIndex = $('#dg').datagrid('getRows').length-1;
$('#dg').datagrid('selectRow', editIndex)
.datagrid('beginEdit', editIndex);
}
}
function removeit(){
if (editIndex == undefined){return}
$('#dg').datagrid('cancelEdit', editIndex)
.datagrid('deleteRow', editIndex);
editIndex = undefined;
}
function removeall(){
$('#dg').datagrid('loadData',[]);
}
function accept(){
if (endEditing()){
$('#dg').datagrid('acceptChanges');
}
}
function reject(){
$('#dg').datagrid('rejectChanges');
editIndex = undefined;
}
function getChanges(){
var rows = $('#dg').datagrid('getChanges');
alert(rows.length+' rows are changed!');
}
function openlogin(){
$('#win').window('open');
}
function openmsg(){
$('#win2').window('open');
}
(function($){
function getParentMenu(rootMenu, menu){
return findParent(rootMenu);
function findParent(pmenu){
var p = undefined;
$(pmenu).find('.menu-item').each(function(){
if (!p && this.submenu){
if ($(this.submenu)[0] == $(menu)[0]){
p = pmenu;
} else {
p = findParent(this.submenu);
}
}
});
return p;
}
}
$.extend($.fn.menubutton.methods, {
enableNav: function(enabled){
var curr;
$(document).unbind('.menubutton');
if (enabled == undefined){enabled = true;}
if (enabled){
$(document).bind('keydown.menubutton', function(e){
var currButton = $(this).find('.m-btn-active,.m-btn-plain-active,.l-btn:focus');
if (!currButton.length){
return;
}
if (!curr || curr.button != currButton[0]){
curr = {
menu: currButton.data('menubutton') ? $(currButton.menubutton('options').menu) : $(),
button: currButton[0]
};
}
var item = curr.menu.find('.menu-active');
switch(e.keyCode){
case 13: // enter
item.trigger('click');
break;
case 27: // esc
currButton.trigger('mouseleave');
break;
case 38: // up
var prev = !item.length ? curr.menu.find('.menu-item:last') : item.prevAll('.menu-item:first');
prev.trigger('mouseenter');
return false;
case 40: // down
var next = !item.length ? curr.menu.find('.menu-item:first') : item.nextAll('.menu-item:first');
next.trigger('mouseenter');
return false;
case 37: // left
var pmenu = getParentMenu(currButton.data('menubutton') ? $(currButton.menubutton('options').menu) : $(), curr.menu);
if (pmenu){
curr.menu = pmenu;
item.triggerHandler('mouseleave');
} else {
var prev = currButton.prevAll('.l-btn:first');
if (prev.length){
currButton.trigger('mouseleave');
prev.focus();
}
}
return false;
case 39: // right
if (item.length && item[0].submenu){
curr.menu = $(item[0].submenu);
curr.button = currButton[0];
curr.menu.find('.menu-item:first').trigger('mouseenter');
} else {
var next = currButton.nextAll('.l-btn:first');
if (next.length){
currButton.trigger('mouseleave');
next.focus();
}
}
return false;
}
});
}
}
});
})(jQuery);
$(function(){
$.fn.menubutton.methods.enableNav();
$(document).keydown(function(e){
if (e.altKey && e.keyCode == 87){
$('#btn-home').focus();
}
})
});
//显示消息
let timer;
var inter=0;
function clock() {
if(inter<3) {
$("#msginfo").css("visibility","visible");
$("#msginfo").css("display","block");
inter++;
}else{
endclock();
$("#msginfo").css("visibility","hidden");
$("#msginfo").css("display","none");
}
}
function endclock() {
clearInterval(timer);
}
function closewin() {
$(".panel-tool-close").click();
}
function closewin2() {
$(".panel-tool-close").click();
var PartSets_ = getTable();
var numcou=PartSets_.length;
if(numcou==0){
$('#PartSets').textbox('setValue', 'null');
}else{
$('#PartSets').textbox('setValue', 'data['+String(numcou)+']');
}
}
</script>
</body>
</html>
前端网站至此创建完毕,我们继续配置android端后台。
二. 配置网络权限设置Layout布局
AnddoirdManifest文件里必须开通网络权限。
<!-- 开启网络访问权限 -->
<uses-permission android:name="android.permission.INTERNET"></uses-permission> <!-- 允许访问网络状态的权限 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <!-- 允许访问wifi状态的权限 -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <!-- 允许修改网络状态的权限 -->
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"></uses-permission> <!-- 允许修改wifi状态的权限 -->
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
在布局文件里修改手机UI布局。
拖放webview,给他一个id,布局需要设置Margin还需要指定布局大小。
我们做的是一个混合开发的APP,结合webview互操作前台与后台,原理与HbuilderX的nui和mui是一样的,也是现在开发的主流,网络api请求不指定平台,一般有idea和.net,soap+。前端其实一般前端制作工具编辑好设计好,再迁移到android studio,我用的是intellij idea,很方便测试。 编辑前端的时候我们再前端预留了2个js函数,方便android互操作。 前端按钮按下调用前端quest()。
操作android原生后台函数android.showToast(参数)。
ShowMsg是显示一个弹窗,当后台请求数据成功后,H5显示弹窗UI就没有违和感。
当提交按钮提交时,后台请求webapi,返回消息,后台控制webview加载弹窗,提示一个消息。
三. 后台代码
程序写三个类,MainActivity是主线程首先创建一个webapi请求的类requestApi,我们一直在纠结为什么要这么复杂使用后台,而不直接使用前台操作,js能一步到位。这个其实就是一个水很深的领域,是现存最大问题,前端有他的局限性,他会考虑跨域问题、内置浏览器端口限制问题,为了修复这个问题,需要配置浏览器还要配置API服务器支持浏览器,往往我们API来自不通领域,不仅仅是java开发的,浏览器访问需要考虑跨域问题,但是api是网络协议,不该有过多的限制,所以我们还是用后端做一些数据处理比较恰当。
package com.example.productlinetest;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
public class requestApi {
public String getSapData(String ptd) throws JSONException {
URL wsUrl;
int errCode=0;
JSONObject resultJson=new JSONObject();
String result="";
try {
wsUrl = new URL("http://xxx.xxx.xxx.xxx:6000/Mes/IssueTask?requestCommand=1");
HttpURLConnection conn = (HttpURLConnection) wsUrl.openConnection();
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json; charset=utf-8");
conn.setConnectTimeout(50000);
conn.setReadTimeout(50000);
OutputStream os = conn.getOutputStream();
//请求体
String soap = ptd.trim();
os.write(soap.getBytes());
InputStream is = conn.getInputStream();
byte[] b = new byte[1024];
int len = 0;
String s = "";
while((len = is.read(b)) != -1){
String ss = new String(b,0,len,"UTF-8");
s += ss+"\r\n";
}
result=s;
is.close();
os.close();
conn.disconnect();
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
System.out.println(e.toString());
errCode=1;
} catch (IOException e) {
// TODO Auto-generated catch block
System.out.println(e.toString());
errCode=2;
}
resultJson.put("errCode", errCode);
resultJson.put("data", result);
return resultJson.toString();
}
}
从上面就不难看出6000端口,那是Google限制端口,跟他内部冲突。
主线程MainActivity添加代码。
package com.example.productlinetest;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity{
public static Handler mHandler;
public WebView webview;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//实例化webview
webview=this.findViewById(R.id.webviewa);
//设置WebView属性,能够执行Javascript脚本
webview.getSettings().setJavaScriptEnabled(true);
//添加与js的交互接口,起的名称与js代码中的接口名称要一致
webview.addJavascriptInterface(new JavaScriptinterface(this), "android");
// 使WebView的网页跳转在WebView中进行,而非跳到浏览器
webview.setWebViewClient(new WebViewClient() {
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
});
//加载页面
try {
//设置打开的页面地址
webview.loadUrl("file:///android_asset/mhx.html");
} catch(Exception ex) {
ex.printStackTrace();
Toast toast=Toast.makeText(this,"加载服务器失败",Toast.LENGTH_SHORT);
toast.show();
}
//订阅静态传值对象
mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//msg.what对应子线程中msg的标签,在子线程中进行赋值
if(msg.what==1){
//操作js方法,显示消息
String datax=msg.obj.toString();
String[] datm=datax.split("》");
webview.loadUrl("javascript:ShowMsg('"+datm[1]+"','"+datm[0]+"');");
}
}
};
}
}
从上面代码来看,我们给webview注入了一个接口类变量。
给主窗口写了一个传值跨线程对象。
他是个静态对象,这里只是为了图方便,当然不建议用这种静态方法。
JavaScriptinterface这个类继承自主线程MainAcgtivity要调用静态handler传值。内部要写一个@JavascriptInterface需要接收前端传值。
package com.example.productlinetest;
import android.content.Context;
import android.os.Message;
import android.webkit.JavascriptInterface;
import org.json.JSONException;
public class JavaScriptinterface extends MainActivity{
Context context;
public JavaScriptinterface(Context contextpara) {
context= contextpara;
}
@JavascriptInterface
public void showToast(String ssss) {
requestApi req=new requestApi();
try {
String str=req.getSapData(ssss);
runOnUiThread(() -> {
Message msg = new Message();
msg.what = 1;
msg.obj="接口调用成功》"+str;
mHandler.sendMessage(msg);
});
} catch (JSONException e) {
runOnUiThread(() -> {
Message msg = new Message();
msg.what = 1;
msg.obj="接口请求异常:》erro unlinked";
mHandler.sendMessage(msg);
});
}
}
}
@JavascriptInterface是不能互操作后端的,需要写一个线程单独去刷新。 Android操作js也是要通过webview的。
webview是主线程控件,子线程访问需要刷新handler。