web前端的小伙伴们大家好,说起APP混合开发,大家首先想到的可能就是类似Cordova的库,可以让我们不写一行安卓代码,就轻松地实现原生APP的一些常用功能,但是对于一些特别的"需求"我们就无能为力了。所以为了提高我们的知识储备,我觉得还是有必要学习一波安卓的知识的。下面就由我带着大家手把手撸一个安卓的壳子出来,由于本人也是第一次写安卓的东西,难免会有些不对的地方,希望各路大神见怪莫怪。
官网下载地址,开发安卓貌似就只有这一个编辑器可用了,这个IDE风格和webStorm的风格一模一样,应该是一家公司的产品,这个没有深究过。安装过程在此就不详细说明了,有需要的可以自行百度。
打开IDE,点击 Start a new Android Studio project 创建一个新项目
填写下面的信息(APP名称和域名建议和我填写一样,这样下面就不用改了^_^),点击next
至此,我的第一个APP已经创建完成,怎么运行它呢?有两种运行方式,一种是模拟器、一种真机。我建议使用真机,模拟器可能会出现一些意想不到的问题。
第一个代表模拟器,第二个是真机,这里我们选择真机 USB Device,点击OK
接下来把我们的手机通过USB连接电脑,注意手机要打开 USB调试 ,简单说一下手机怎么打开 USB调试,以华为畅玩9为例,首先我们要打开手机的开发者模式,依次点击 设置>系统>关于手机>连续点击版本号(考验手速的时候到了!) 直到出现 "打开手机开发者模式"字样的提示,然后我们再点击 设置>系统>开发者选项>USB调试开关打开。
然后点击下面的绿色三角,运行程序,开始运行的时候手机上可能会弹出来一次 允许调试的授权框 ,我们点击允许。
我们可以看到默认的APP图标和名称,作为一名优秀的前端开发,简直不能忍
Name的值不要改,Asset Type 选择 Image,path选择我们的图标路径,再通过 Resize 调整图片到合适大小,点击next。
修改名称:打开 app>res>values>strings.xml 修改其中的 app_name 的值
既然是混合开发,webview是必须的,理论上我们的Android架子只需要一个页面放置webview即可。
首先我们要允许webview联网,打开 app>manifests>AndroidManifest.xml,在 application 上面添加一行:
打开 app>res>layout>activity_main.xml 把默认的 TextView 控件删掉,加上webview控件,代码如下:
我们发现还有一个默认的标题,这个也是不需要的,打开 app>manifests>AndroidManifest.xml,找到android:theme="@style/AppTheme" 改成下面的
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
再次运行一下,标题栏没有了,但是白屏,那是因为我们还没有给webview链接。
*设置链接:
我们修改 MainActivity.java 文件中的MainActivity类,如下,(如果某个单词出现红色,说明没有引入包,把鼠标定位到红色单词上,按住 Alt + Enter 按照提示引入相应的包即可,之后不再重复说明此问题。)
myWebView.loadUrl( "https://zhengshaoguo.com/static/app/" ); 这行就是webview要加载的地址,我这里放的是一个服务器的地址,当然也可以放本地的,本地怎么放得这里就不做说明了。
public class MainActivity extends AppCompatActivity {
private WebView myWebView = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate( savedInstanceState );
setContentView( R.layout.activity_main );
myWebView = (WebView) findViewById( R.id.webview );
WebSettings webSettings = myWebView.getSettings();
myWebView.loadUrl( "https://zhengshaoguo.com/static/app/" );
}
}
服务器上对应的HTML文件如下:js相信大家都看的懂,就不解释了。
Carson
*启用JS:
我们重新运行APP,当点击按钮之后并没有出现文字,这是因为webview默认是禁用JS的,在 MainActivity.java 中
WebSettings webSettings = myWebView.getSettings(); 后面加一句
webSettings.setJavaScriptEnabled( true );
重新运行APP,一切正常。
*让所有的超链接都在webview中打开:
我们再HTML文件中加入一个超链接
点击去百度
重新运行APP,当我们点击 去百度 之后就会提示我们用默认浏览器打开这个网址,
对于混合开发APP,这样肯定是不行的,我们要让所有的链接都在webview中打开,在 MainActivity.java 中
webSettings.setJavaScriptEnabled( true ); 后面上如下内容
myWebView.setWebViewClient(new WebViewClient() {
public boolean shouldOverrideUrlLoading(WebView view, String url) {
return false;
}
});
*允许webview拨打电话:
前端的小伙伴们都知道,给一个a标签的href属性设置为 tel:18538328225 ,在微信中,点击这个标签就可以调起手机通讯录,便于快捷拨号,那我们在我们的APP中试一下。结果却很尴尬。居然毫无反应,不能忍,改!修改 setWebViewClient() 方法如下:
myWebView.setWebViewClient(new WebViewClient() {
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.startsWith("tel:")) {
Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(url));
startActivity(intent);
return true;
}
return false;
}
});
*开启Storage:
sessionStorage和localStorage在很多项目里都会用到,特别是移动端,webview默认是不允许js访问的需要我们手动开启
在 setWebViewClient() 方法下面加上下面内容
webSettings.setDomStorageEnabled( true );
webSettings.setAppCacheMaxSize( 1024 * 1024 * 8 );
String appCachePath = getApplicationContext().getCacheDir().getAbsolutePath();
webSettings.setAppCachePath( appCachePath );
webSettings.setAllowFileAccess( true );
webSettings.setAppCacheEnabled( true );
*返回键返回webview的历史记录:
当我们点击 去百度 就在本页面就打开了百度的链接,但当我们点击手机的物理返回键后,并没有返回我们的首页,而是直接退出了程序。这个也需要我们去做处理,在 onCreate() 的后面添加
public boolean
onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && myWebView.canGoBack()) {
myWebView.goBack();//返回上个页面
return true;
}
return super.onKeyDown(keyCode, event);//退出H5界面
}
这一块的内容还是比较多,也不是必须要开启的功能,所以单独拿出来说,在上传图片的时候,我们会用input标签 type=file 就能实现选择文件,并上传的功能,同样的,这也需要webview提供支持,在 private WebView myWebView = null; 后面加上
private ValueCallback uploadMessageAboveL;
然后在 setWebViewClient() 后面添加
webSettings.setAllowFileAccess(true);
myWebView.setWebChromeClient( new WebChromeClient(){
public boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback, WebChromeClient.FileChooserParams fileChooserParams ) {
if (uploadMessageAboveL != null) {
uploadMessageAboveL.onReceiveValue(null);
uploadMessageAboveL = null;
}
uploadMessageAboveL = filePathCallback;
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("image/*");
startActivityForResult( Intent.createChooser(i, "Image Chooser"), 2);
return true;
}
});
最后在 onKeyDown() 后面加上
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult( requestCode, resultCode, data );
if (requestCode == 2) {
if (null == uploadMessageAboveL) return;
Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
if (uploadMessageAboveL != null) {
onActivityResultAboveL(requestCode, resultCode, data);
}
}
}
//webview可以选择文件
private void onActivityResultAboveL(int requestCode, int resultCode, Intent data) {
System.out.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
if (requestCode != 2 || uploadMessageAboveL == null) {
return;
}
Uri[] results = null;
if (resultCode == Activity.RESULT_OK) {
if (data == null) {
} else {
String dataString = data.getDataString();
ClipData clipData = data.getClipData();
if (clipData != null) {
results = new Uri[clipData.getItemCount()];
for (int i = 0; i < clipData.getItemCount(); i++) {
ClipData.Item item = clipData.getItemAt(i);
results[i] = item.getUri();
}
}
if (dataString != null) {
results = new Uri[]{Uri.parse(dataString)};
}
}
}
uploadMessageAboveL.onReceiveValue(results);
uploadMessageAboveL = null;
return;
}
完美收工!至此一个完整的 webview 已经完成。谢谢阅读!
...
...
...
...
...
陈大海:你这个也太low了,还不如去用cordova,根本不用自己配置!
em....,看来只好放大招了!
js和android实现交互有很多种方式,这里我选择了最简单的一种,以供大家快速上手。
首先我们把 Android 的类对象(AndroidtoJs)和JS(test)的对象建立映射关系,在 myWebView.loadUrl() 上面加一行
myWebView.addJavascriptInterface( new AndroidtoJs(), "test" );
然后在 onKeyDown() 下面添加与JS映射的类并添加一个hello方法
public class AndroidtoJs extends Object {
@JavascriptInterface
public void hello(String msg) {
final String content = msg;
myWebView.post( new Runnable() {
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
@Override
public void run() {
myWebView.evaluateJavascript( "javascript:test.helloFn('hello,"+content+"')", new ValueCallback() {
@Override
public void onReceiveValue(String value) { }
} );
}
} );
}
}
编辑我们的JS文件
Carson
点击去百度
打电话
简单解释一下,因为 JS的 test 对象和Android的 AndroidtoJs 类进行了映射关系,所以当我用 js 调用 test 的 hello 方法,就相当于android 的 AndroidtoJs 去调用 hello 方法一样,反过来也是一样的。
既然js和android已经实现了交互,那么我们就来搞一波事情吧。
首先我们定义一个方法,实现和JS的交互,在 AndroidtoJs 类里添加
//获取当前地理位置
@JavascriptInterface
public void getPosition(String msg) {
showGPSContacts();
}
然后我们在 AndroidManifest.xml 文件中加上权限
最后在 MainActivity.java 文件中的 onActivityResult() 方法后面加上
//APP授权的回调
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if(requestCode == 100){
if (grantResults[0] == PERMISSION_GRANTED && grantResults.length > 0) {
getLocation();
} else {
showGPSContacts();
}
}
}
//获取地理位置
LocationManager lm;
Boolean positionNum=false;
public void showGPSContacts() {
lm = (LocationManager) MainActivity.this.getSystemService(MainActivity.this.LOCATION_SERVICE);
boolean ok = lm.isProviderEnabled(LocationManager.GPS_PROVIDER);
if (ok) {
if (Build.VERSION.SDK_INT >= 23) {
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) != PERMISSION_GRANTED) { // 没有权限,申请权限。
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.READ_PHONE_STATE}, 100);
} else {
getLocation();
}
} else {
getLocation();
}
} else {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
startActivityForResult(intent, 1315);
}
}
private void getLocation() {
LocationManager locationManager;
String serviceName = Context.LOCATION_SERVICE;
locationManager = (LocationManager) this.getSystemService(serviceName);
Criteria criteria = new Criteria();
criteria.setAccuracy(Criteria.ACCURACY_FINE);
criteria.setAltitudeRequired(false);
criteria.setBearingRequired(false);
criteria.setCostAllowed(true);
criteria.setPowerRequirement(Criteria.POWER_LOW);
String provider = locationManager.getBestProvider(criteria, true);
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PERMISSION_GRANTED) { return; }
Location location = locationManager.getLastKnownLocation(provider); // 通过GPS获取位置
updateLocation(location);
if(!positionNum){
positionNum=true;
lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 2000, 8, new LocationListener() {
@Override
public void onLocationChanged(Location location) {
updateLocation(location);
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@SuppressLint("MissingPermission")
@Override
public void onProviderEnabled(String provider) {
updateLocation(lm.getLastKnownLocation(provider));
}
@Override
public void onProviderDisabled(String provider) {
updateLocation(null);
}
});
}
}
private void updateLocation(Location location) {
if (location != null) {
final double latitude = location.getLatitude();
final double longitude = location.getLongitude();
System.out.println(latitude+":"+longitude);
myWebView.post( new Runnable() {
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
@Override
public void run() {
myWebView.evaluateJavascript( "javascript:test.getPositionFn('" + latitude+"','"+longitude+ "')", new ValueCallback() {
@Override
public void onReceiveValue(String value) { }
} );
}
} );
} else {
System.out.println("无法获取到位置信息");
}
}
修改JS文件
test.getPosition('');
test.getPositionFn=function(lat,lng){
document.getElementById("output").innerHTML = "lat:"+lat+",lng:"+lng;
}
说明,首先会去判断手机有没有打开GPS,没有打开跳到手机系统打开GPS的页面,打开了GPS的,再去向用户请求获取地理位置的权限,等用户授权之后,每两秒查询一次用户的位置,如果有更新,就调用 js 的方法,把最新的经纬度传过去。
这里我们使用一个第三方的包来实现此功能
引入包文件,打开下面文件
修改 其中的 allprojects 如下
allprojects {
repositories {
google()
jcenter()
maven { url 'https://jitpack.io' }
}
}
在 dependencies 中加入
implementation 'com.github.yuzhiqiang1993:zxing:2.2.1'
注意修改这两个文件之后窗口的顶部会出现如下提示,需要我们点击安装
然后我们在 AndroidManifest.xml 文件中加上权限
AndroidtoJs 类里定义一个和JS交互的方法
//启动扫描二维码
@JavascriptInterface
public void scanQRCode(String msg) {
if (ContextCompat.checkSelfPermission( MainActivity.this, android.Manifest.permission.CAMERA ) != PERMISSION_GRANTED) {
ActivityCompat.requestPermissions( MainActivity.this, new String[]{Manifest.permission.CAMERA}, 1 );
} else {
Intent intent = new Intent( MainActivity.this, CaptureActivity.class );
startActivityForResult( intent, 111 );
}
}
在 onRequestPermissionsResult() 中加一个打开摄像头的授权方法
//APP授权的回调
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if(requestCode == 100){
if (grantResults[0] == PERMISSION_GRANTED && grantResults.length > 0) {
getLocation();
} else {
showGPSContacts();
}
}else if(requestCode == 1){
if(grantResults.length > 0 && grantResults[0] == PERMISSION_GRANTED){
Intent intent = new Intent( MainActivity.this, CaptureActivity.class );
startActivityForResult( intent, 111 );
}
}
}
最后在 onActivityResult() 方法里加上扫描二维码的结果
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult( requestCode, resultCode, data );
if (requestCode == 2) {
if (null == uploadMessageAboveL) return;
Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
if (uploadMessageAboveL != null) {
onActivityResultAboveL(requestCode, resultCode, data);
}
}else if (requestCode == 111 && resultCode == RESULT_OK) {
if (data != null) {
final String content = data.getStringExtra( Constant.CODED_CONTENT );
myWebView.post( new Runnable() {
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
@Override
public void run() {
myWebView.evaluateJavascript( "javascript:test.scanQRCodeFn('" + content + "')", new ValueCallback() {
@Override
public void onReceiveValue(String value) { }
} );
}
} );
}
}
}
在修改一下JS的调用
function qrCode(){
test.scanQRCode('');
}
test.scanQRCodeFn=function(text){
document.getElementById("qrCode").innerHTML = "扫描二维码的结果是:"+text;
}
已经连续奋战3个小时了,今天就到这里了,存个档,有需要的朋友可以点这里下载全部代码