这篇文章主要围绕androidapp应用后台用什么技术实现的和安卓应用后端展开,旨在为您提供一份详细的参考资料。我们将全面介绍androidapp应用后台用什么技术实现的的优缺点,解答安卓应用后端的相
这篇文章主要围绕android app 应用后台用什么技术实现的和安卓应用后端展开,旨在为您提供一份详细的参考资料。我们将全面介绍android app 应用后台用什么技术实现的的优缺点,解答安卓应用后端的相关问题,同时也会为您带来Android 6.0 7.0 8.0 一个简单的 app 内更新版本 - okgo app 版本更新、Android App 增量更新实例(Smart App Updates)、Android APP 打开 flutter app 并传递参数、Android APP 检测之自动化检测实战:五大 APP 安全在线检测平台对比的实用方法。
本文目录一览:- android app 应用后台用什么技术实现的(安卓应用后端)
- Android 6.0 7.0 8.0 一个简单的 app 内更新版本 - okgo app 版本更新
- Android App 增量更新实例(Smart App Updates)
- Android APP 打开 flutter app 并传递参数
- Android APP 检测之自动化检测实战:五大 APP 安全在线检测平台对比
android app 应用后台用什么技术实现的(安卓应用后端)
android app 应用后台用什么技术实现的Android 6.0 7.0 8.0 一个简单的 app 内更新版本 - okgo app 版本更新
登陆时 splash 初始页调用接口检查 app 版本。如有更新,使用 okGo 的文件下载,保存到指定位置,调用 Android 安装 apk。
<!-- Android 8.0 (Android O)为了针对一些流氓软件引导用户安装其他无关应用。在应用权限设置的“特殊访问权限”中,加入了“安装其他应用”的设置 -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
package com.test.activity.sys;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AlertDialog;
import android.text.format.Formatter;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.widget.TextView;
import android.widget.Toast;
import com.lzy.okgo.OkGo;
import com.lzy.okgo.callback.FileCallback;
import com.lzy.okgo.callback.StringCallback;
import com.lzy.okgo.convert.FileConvert;
import com.lzy.okgo.model.Progress;
import com.lzy.okgo.model.Response;
import com.test.BuildConfig;
import com.test.Constant;
import com.test.R;
import com.test.activity.MainActivity;
import com.test.activity.sys.LoginActivity;
import com.test.util.DataUtils;
import com.test.view.NumberProgressBar;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.text.NumberFormat;
/**
* @author cralor
* @date 2018/8/8
* 启动页 Activity
*/
public class SplashActivity extends Activity{
public static final int INSTALL_APK_REQUESTCODE = 3;
public static final int GET_UNKNOWN_APP_SOURCES = 7;
private File apkFile;
/**
* 可以额外指定文件的下载目录和下载完成后的文件名
*/
private String destFileDir = Environment.getExternalStorageDirectory() + FileConvert.DM_TARGET_FOLDER + "myAppDownload" + File.separator;
private String destFileName = "myApp.apk";
private Handler handler;
private Context content;
private TextView tvDownloadSize;
private TextView tvProgress;
private TextView tvNetSpeed;
private NumberProgressBar pbProgress;
private NumberFormat numberFormat;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
//去掉标题栏
requestWindowFeature(Window.FEATURE_NO_TITLE);
// getWindow().setFlags(WindowManager.LayoutParams. FLAG_FULLSCREEN ,WindowManager.LayoutParams. FLAG_FULLSCREEN);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
content = this;
handler = new Handler(Looper.getMainLooper());
numberFormat = NumberFormat.getPercentInstance();
numberFormat.setMinimumFractionDigits(2);
initView();
}
/**
* 打开程序时判断是否已经登陆过,且token有效
*/
private void initView() {
// ...
handler.postDelayed(new Runnable() {
@Override
public void run() {
OkGo.<String>post(Constant.GET_VERSION)
.tag(1)
.execute(new StringCallback() {
@Override
public void onSuccess(Response<String> response) {
String data = response.body();
String code = "";
String sysVersion = "";
try {
JSONObject jsonObject = new JSONObject(data);
code = jsonObject.get("code").toString();
sysVersion = jsonObject.get("data").toString();
} catch (JSONException e) {
e.printStackTrace();
}
Log.v("data",""+code);
if(Constant.SUCCESS_CODE.equals(code)){
// Android studio 中 build.gradle 中的版本号
// 获取PackageManager的实例
PackageManager packageManager = getPackageManager();
// getPackageName()是你当前类的包名,0代表是获取版本信息
PackageInfo packInfo = null;
String appVersion = "";
try {
packInfo = packageManager.getPackageInfo(getPackageName(), 0);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
if (packInfo != null) {
appVersion = packInfo.versionName;
}
if(appVersion.equals(sysVersion)){
Intent intent = new Intent(SplashActivity.this, MainActivity.class);
startActivity(intent);
finish();
}else{
Log.e("SplashActivity","需要更新版本到---"+sysVersion);
Toast.makeText(content, "有新版本需要更新", Toast.LENGTH_SHORT).show();
getVersion();
}
}
}
@Override
public void onError(Response<String> response) {
Toast.makeText(content, "请求失败", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(SplashActivity.this, LoginActivity.class);
startActivity(intent);
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
finish();
}
});
}
}, 3);
}
/**
* 更新弹出框
*/
public void getVersion(){
//退出的确认弹出框
new AlertDialog.Builder(content)
.setTitle("提示")
.setMessage("请更新最新版本")
.setCancelable(false)
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int whichButton) {
Toast.makeText(content, "dianjixiazai ", Toast.LENGTH_SHORT).show();
fileDownload();
View view1 = getLayoutInflater().inflate(R.layout.update_app_progress, null);
tvDownloadSize = view1.findViewById(R.id.downloadSize);
tvProgress = view1.findViewById(R.id.tvProgress);
tvNetSpeed = view1.findViewById(R.id.netSpeed);
pbProgress = view1.findViewById(R.id.pbProgress);
AlertDialog.Builder builder = new AlertDialog.Builder(content);
builder.setTitle("正在下载");
builder.setView(view1);
builder.setCancelable(false);
builder.setNegativeButton("取消", new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
});
builder.create().show();
}
})
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int whichButton) {
finish();
}
})
.show();
}
/**
* 获取服务器apk
*/
public void fileDownload() {
//检查是否获得写入权限,未获得则向用户请求。Android 7.0 以上需要动态获取权限
if (ActivityCompat.checkSelfPermission(SplashActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
//未获得,向用户请求
ActivityCompat.requestPermissions(SplashActivity.this, new String[]
{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 200);
}
OkGo.<File>get("https://app地址/app.apk")
.tag(1)
.headers("header1", "headerValue1")
.params("param1", "paramValue1")
/*
* 文件目录如果不指定,默认下载的目录为 sdcard/download/,文件名如果不指定,则按照以下规则命名:
1.首先检查用户是否传入了文件名,如果传入,将以用户传入的文件名命名
2.如果没有传入,那么将会检查服务端返回的响应头是否含有Content-Disposition=attachment;filename=FileName.txt该种形式的响应头,如果有,则按照该响应头中指定的文件名命名文件,如FileName.txt
3.如果上述响应头不存在,则检查下载的文件url,例如:http://image.baidu.com/abc.jpg,那么将会自动以abc.jpg命名文件
4.如果url也把文件名解析不出来,那么最终将以"unknownfile_" + System.currentTimeMillis()命名文件
*/
.execute(new FileCallback(""+destFileDir, ""+destFileName) {
@Override
public void onSuccess(Response<File> response) {
String absolutePath = response.body().getAbsolutePath();
apkFile = response.body().getAbsoluteFile();
Log.e("appInstall---", ""+absolutePath);
//Android8.0 版本更新无法自动安装问题解决
if (Build.VERSION.SDK_INT >= 26) {
//来判断应用是否有权限安装apk
Log.e("appInstall---", "android版本8.0以上");
boolean installAllowed= getPackageManager().canRequestPackageInstalls();
//有权限
if (installAllowed) {
//安装apk
Log.e("appInstall---", "有权限安装apk");
installApk(apkFile);
} else {
//无权限 申请权限
Log.e("appInstall---", "无权限 申请权限");
ActivityCompat.requestPermissions(SplashActivity.this, new String[]{Manifest.permission.REQUEST_INSTALL_PACKAGES}, INSTALL_APK_REQUESTCODE);
}
} else {
Log.e("appInstall---", "android版本低于8.0");
installApk(apkFile);
}
}
@Override
public void onError(Response<File> response) {
Throwable exception = response.getException();
exception.printStackTrace();
Log.e("eeeee", exception.getMessage());
}
@Override
public void downloadProgress(Progress progress) {
System.out.println(progress);
String downloadLength = Formatter.formatFileSize(getApplicationContext(), progress.currentSize);
String totalLength = Formatter.formatFileSize(getApplicationContext(), progress.totalSize);
tvDownloadSize.setText(downloadLength + "/" + totalLength);
String speed = Formatter.formatFileSize(getApplicationContext(), progress.speed);
tvNetSpeed.setText(String.format("%s/s", speed));
tvProgress.setText(numberFormat.format(progress.fraction));
pbProgress.setMax(10000);
pbProgress.setProgress((int) (progress.fraction * 10000));
}
});
}
/**
* 权限申请回调
* @param requestCode requestCode
* @param permissions permissions
* @param grantResults grantResults
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case INSTALL_APK_REQUESTCODE:
//有注册权限且用户允许安装
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.e("appInstall---", "有注册权限且用户允许安装");
installApk(apkFile);
} else {
//将用户引导至安装未知应用界面。
Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
startActivityForResult(intent, GET_UNKNOWN_APP_SOURCES);
Log.e("appInstall---", "将用户引导至安装未知应用界面");
}
break;
default:break;
}
}
/**
* 将用户引导至安装未知应用界面,允许安装未知应用后,回到当前activity继续安装应用
* @param requestCode requestCode
* @param resultCode resultCode
* @param data data
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Log.e("appInstall---", "将用户引导至安装未知应用界面,允许安装未知应用后,回到当前activity继续安装应用");
installApk(apkFile);
}
/**
* 安装APK
* @param apkFile apkFile
*/
public void installApk(File apkFile){
//判读版本是否在7.0以上
if(Build.VERSION.SDK_INT >= 24) {
Log.e("appInstall---", "android版本7.");
//在AndroidManifest中的android:authorities的值, BuildConfig.APPLICATION_ID = com.test
Uri apkUri = FileProvider.getUriForFile(content, BuildConfig.APPLICATION_ID+".provider", apkFile);
Intent install = new Intent(Intent.ACTION_VIEW);
install.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
install.setDataAndType(apkUri, "application/vnd.android.package-archive");
startActivity(install);
} else {
Log.e("appInstall---", "android版本低于7.0");
Intent install = new Intent(Intent.ACTION_VIEW);
install.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
install.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(install);
}
finish();
}
/**
* 调用默认浏览器下载apk
*/
public void getApk(){
Uri uri = Uri.parse("https://lighttruck.com.cn:8086/app.apk");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
}
}
activity_splash.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"
android:gravity="center"
tools:context="com.test.activity.sys.SplashActivity">
</LinearLayout>
初始页面自定义主题
<!-- 加载页面 -->
<activity
android:name=".activity.sys.SplashActivity"
android:theme="@style/SplashTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<style name="SplashTheme" parent="AppBaseTheme">
<!-- 欢迎页背景引用刚才写好的 -->
<item name="android:windowBackground">@drawable/splash</item>
<item name="android:windowFullscreen">true</item>
<!-- <item name="android:windowIsTranslucent">true</item> --> <!-- 透明背景 -->
</style>
update_app_progress.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/update_app_progress"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@color/bg_gray"
android:orientation="vertical" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="20dp"
android:layout_marginTop="10dp">
<TextView
android:id="@+id/downloadSize"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:text="--M/--M"
android:textSize="10sp"/>
<TextView
android:id="@+id/netSpeed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="10dp"
android:text="---K/s"
android:textSize="10sp"/>
<TextView
android:id="@+id/tvProgress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:text="--.--%"
android:textSize="10sp"/>
</RelativeLayout>
<com.test.view.NumberProgressBar
android:id="@+id/pbProgress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:progress_reached_bar_height="1.5dp"
app:progress_reached_color="#3498DB"
app:progress_text_color="#3498DB"
app:progress_text_size="10sp"
app:progress_unreached_bar_height="0.75dp"
app:progress_unreached_color="#CCCCCC"/>
</LinearLayout>
NumberProgressBar 为okGo中的view
/*
* Copyright 2016 jeasonlzy(廖子尧)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.test.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.View;
import com.test.R;
/**
* ================================================
* 作 者:jeasonlzy(廖子尧)Github地址:https://github.com/jeasonlzy
* 版 本:1.0
* 创建日期:16/9/11
* 描 述:
* 修订历史:
* ================================================
*/
public class NumberProgressBar extends View {
public interface OnProgressBarListener {
void onProgressChange(int current, int max);
}
private int mMaxProgress = 100;
/**
* Current progress, can not exceed the max progress.
*/
private int mCurrentProgress = 0;
/**
* The progress area bar color.
*/
private int mReachedBarColor;
/**
* The bar unreached area color.
*/
private int mUnreachedBarColor;
/**
* The progress text color.
*/
private int mTextColor;
/**
* The progress text size.
*/
private float mTextSize;
/**
* The height of the reached area.
*/
private float mReachedBarHeight;
/**
* The height of the unreached area.
*/
private float mUnreachedBarHeight;
/**
* The suffix of the number.
*/
private String mSuffix = "%";
/**
* The prefix.
*/
private String mPrefix = "";
private final int default_text_color = Color.rgb(66, 145, 241);
private final int default_reached_color = Color.rgb(66, 145, 241);
private final int default_unreached_color = Color.rgb(204, 204, 204);
private final float default_progress_text_offset;
private final float default_text_size;
private final float default_reached_bar_height;
private final float default_unreached_bar_height;
/**
* For save and restore instance of progressbar.
*/
private static final String INSTANCE_STATE = "saved_instance";
private static final String INSTANCE_TEXT_COLOR = "text_color";
private static final String INSTANCE_TEXT_SIZE = "text_size";
private static final String INSTANCE_REACHED_BAR_HEIGHT = "reached_bar_height";
private static final String INSTANCE_REACHED_BAR_COLOR = "reached_bar_color";
private static final String INSTANCE_UNREACHED_BAR_HEIGHT = "unreached_bar_height";
private static final String INSTANCE_UNREACHED_BAR_COLOR = "unreached_bar_color";
private static final String INSTANCE_MAX = "max";
private static final String INSTANCE_PROGRESS = "progress";
private static final String INSTANCE_SUFFIX = "suffix";
private static final String INSTANCE_PREFIX = "prefix";
private static final String INSTANCE_TEXT_VISIBILITY = "text_visibility";
private static final int PROGRESS_TEXT_VISIBLE = 0;
/**
* The width of the text that to be drawn.
*/
private float mDrawTextWidth;
/**
* The drawn text start.
*/
private float mDrawTextStart;
/**
* The drawn text end.
*/
private float mDrawTextEnd;
/**
* The text that to be drawn in onDraw().
*/
private String mCurrentDrawText;
/**
* The Paint of the reached area.
*/
private Paint mReachedBarPaint;
/**
* The Paint of the unreached area.
*/
private Paint mUnreachedBarPaint;
/**
* The Paint of the progress text.
*/
private Paint mTextPaint;
/**
* Unreached bar area to draw rect.
*/
private RectF mUnreachedRectF = new RectF(0, 0, 0, 0);
/**
* Reached bar area rect.
*/
private RectF mReachedRectF = new RectF(0, 0, 0, 0);
/**
* The progress text offset.
*/
private float mOffset;
/**
* Determine if need to draw unreached area.
*/
private boolean mDrawUnreachedBar = true;
private boolean mDrawReachedBar = true;
private boolean mIfDrawText = true;
/**
* Listener
*/
private OnProgressBarListener mListener;
public enum ProgressTextVisibility {
Visible, Invisible
}
public NumberProgressBar(Context context) {
this(context, null);
}
public NumberProgressBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public NumberProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
default_reached_bar_height = dp2px(1.5f);
default_unreached_bar_height = dp2px(1.0f);
default_text_size = sp2px(10);
default_progress_text_offset = dp2px(3.0f);
//load styled attributes.
final TypedArray attributes = context.getTheme().obtainStyledAttributes(attrs, R.styleable.NumberProgressBar, defStyleAttr, 0);
mReachedBarColor = attributes.getColor(R.styleable.NumberProgressBar_progress_reached_color, default_reached_color);
mUnreachedBarColor = attributes.getColor(R.styleable.NumberProgressBar_progress_unreached_color, default_unreached_color);
mTextColor = attributes.getColor(R.styleable.NumberProgressBar_progress_text_color, default_text_color);
mTextSize = attributes.getDimension(R.styleable.NumberProgressBar_progress_text_size, default_text_size);
mReachedBarHeight = attributes.getDimension(R.styleable.NumberProgressBar_progress_reached_bar_height, default_reached_bar_height);
mUnreachedBarHeight = attributes.getDimension(R.styleable.NumberProgressBar_progress_unreached_bar_height, default_unreached_bar_height);
mOffset = attributes.getDimension(R.styleable.NumberProgressBar_progress_text_offset, default_progress_text_offset);
int textVisible = attributes.getInt(R.styleable.NumberProgressBar_progress_text_visibility, PROGRESS_TEXT_VISIBLE);
if (textVisible != PROGRESS_TEXT_VISIBLE) {
mIfDrawText = false;
}
setProgress(attributes.getInt(R.styleable.NumberProgressBar_progress_current, 0));
setMax(attributes.getInt(R.styleable.NumberProgressBar_progress_max, 100));
attributes.recycle();
initializePainters();
}
@Override
protected int getSuggestedMinimumWidth() {
return (int) mTextSize;
}
@Override
protected int getSuggestedMinimumHeight() {
return Math.max((int) mTextSize, Math.max((int) mReachedBarHeight, (int) mUnreachedBarHeight));
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measure(widthMeasureSpec, true), measure(heightMeasureSpec, false));
}
private int measure(int measureSpec, boolean isWidth) {
int result;
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
int padding = isWidth ? getPaddingLeft() + getPaddingRight() : getPaddingTop() + getPaddingBottom();
if (mode == MeasureSpec.EXACTLY) {
result = size;
} else {
result = isWidth ? getSuggestedMinimumWidth() : getSuggestedMinimumHeight();
result += padding;
if (mode == MeasureSpec.AT_MOST) {
if (isWidth) {
result = Math.max(result, size);
} else {
result = Math.min(result, size);
}
}
}
return result;
}
@Override
protected void onDraw(Canvas canvas) {
if (mIfDrawText) {
calculateDrawRectF();
} else {
calculateDrawRectFWithoutProgressText();
}
if (mDrawReachedBar) {
canvas.drawRect(mReachedRectF, mReachedBarPaint);
}
if (mDrawUnreachedBar) {
canvas.drawRect(mUnreachedRectF, mUnreachedBarPaint);
}
if (mIfDrawText) canvas.drawText(mCurrentDrawText, mDrawTextStart, mDrawTextEnd, mTextPaint);
}
private void initializePainters() {
mReachedBarPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mReachedBarPaint.setColor(mReachedBarColor);
mUnreachedBarPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mUnreachedBarPaint.setColor(mUnreachedBarColor);
mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTextPaint.setColor(mTextColor);
mTextPaint.setTextSize(mTextSize);
}
private void calculateDrawRectFWithoutProgressText() {
mReachedRectF.left = getPaddingLeft();
mReachedRectF.top = getHeight() / 2.0f - mReachedBarHeight / 2.0f;
mReachedRectF.right = (getWidth() - getPaddingLeft() - getPaddingRight()) / (getMax() * 1.0f) * getProgress() + getPaddingLeft();
mReachedRectF.bottom = getHeight() / 2.0f + mReachedBarHeight / 2.0f;
mUnreachedRectF.left = mReachedRectF.right;
mUnreachedRectF.right = getWidth() - getPaddingRight();
mUnreachedRectF.top = getHeight() / 2.0f + -mUnreachedBarHeight / 2.0f;
mUnreachedRectF.bottom = getHeight() / 2.0f + mUnreachedBarHeight / 2.0f;
}
private void calculateDrawRectF() {
mCurrentDrawText = String.format("%d", getProgress() * 100 / getMax());
mCurrentDrawText = mPrefix + mCurrentDrawText + mSuffix;
mDrawTextWidth = mTextPaint.measureText(mCurrentDrawText);
if (getProgress() == 0) {
mDrawReachedBar = false;
mDrawTextStart = getPaddingLeft();
} else {
mDrawReachedBar = true;
mReachedRectF.left = getPaddingLeft();
mReachedRectF.top = getHeight() / 2.0f - mReachedBarHeight / 2.0f;
mReachedRectF.right = (getWidth() - getPaddingLeft() - getPaddingRight()) / (getMax() * 1.0f) * getProgress() - mOffset + getPaddingLeft();
mReachedRectF.bottom = getHeight() / 2.0f + mReachedBarHeight / 2.0f;
mDrawTextStart = (mReachedRectF.right + mOffset);
}
mDrawTextEnd = (int) ((getHeight() / 2.0f) - ((mTextPaint.descent() + mTextPaint.ascent()) / 2.0f));
if ((mDrawTextStart + mDrawTextWidth) >= getWidth() - getPaddingRight()) {
mDrawTextStart = getWidth() - getPaddingRight() - mDrawTextWidth;
mReachedRectF.right = mDrawTextStart - mOffset;
}
float unreachedBarStart = mDrawTextStart + mDrawTextWidth + mOffset;
if (unreachedBarStart >= getWidth() - getPaddingRight()) {
mDrawUnreachedBar = false;
} else {
mDrawUnreachedBar = true;
mUnreachedRectF.left = unreachedBarStart;
mUnreachedRectF.right = getWidth() - getPaddingRight();
mUnreachedRectF.top = getHeight() / 2.0f + -mUnreachedBarHeight / 2.0f;
mUnreachedRectF.bottom = getHeight() / 2.0f + mUnreachedBarHeight / 2.0f;
}
}
/**
* Get progress text color.
*
* @return progress text color.
*/
public int getTextColor() {
return mTextColor;
}
/**
* Get progress text size.
*
* @return progress text size.
*/
public float getProgressTextSize() {
return mTextSize;
}
public int getUnreachedBarColor() {
return mUnreachedBarColor;
}
public int getReachedBarColor() {
return mReachedBarColor;
}
public int getProgress() {
return mCurrentProgress;
}
public int getMax() {
return mMaxProgress;
}
public float getReachedBarHeight() {
return mReachedBarHeight;
}
public float getUnreachedBarHeight() {
return mUnreachedBarHeight;
}
public void setProgressTextSize(float textSize) {
this.mTextSize = textSize;
mTextPaint.setTextSize(mTextSize);
invalidate();
}
public void setProgressTextColor(int textColor) {
this.mTextColor = textColor;
mTextPaint.setColor(mTextColor);
invalidate();
}
public void setUnreachedBarColor(int barColor) {
this.mUnreachedBarColor = barColor;
mUnreachedBarPaint.setColor(mUnreachedBarColor);
invalidate();
}
public void setReachedBarColor(int progressColor) {
this.mReachedBarColor = progressColor;
mReachedBarPaint.setColor(mReachedBarColor);
invalidate();
}
public void setReachedBarHeight(float height) {
mReachedBarHeight = height;
}
public void setUnreachedBarHeight(float height) {
mUnreachedBarHeight = height;
}
public void setMax(int maxProgress) {
if (maxProgress > 0) {
this.mMaxProgress = maxProgress;
invalidate();
}
}
public void setSuffix(String suffix) {
if (suffix == null) {
mSuffix = "";
} else {
mSuffix = suffix;
}
}
public String getSuffix() {
return mSuffix;
}
public void setPrefix(String prefix) {
if (prefix == null) mPrefix = "";
else {
mPrefix = prefix;
}
}
public String getPrefix() {
return mPrefix;
}
public void incrementProgressBy(int by) {
if (by > 0) {
setProgress(getProgress() + by);
}
if (mListener != null) {
mListener.onProgressChange(getProgress(), getMax());
}
}
public void setProgress(int progress) {
if (progress <= getMax() && progress >= 0) {
this.mCurrentProgress = progress;
invalidate();
}
}
@Override
protected Parcelable onSaveInstanceState() {
final Bundle bundle = new Bundle();
bundle.putParcelable(INSTANCE_STATE, super.onSaveInstanceState());
bundle.putInt(INSTANCE_TEXT_COLOR, getTextColor());
bundle.putFloat(INSTANCE_TEXT_SIZE, getProgressTextSize());
bundle.putFloat(INSTANCE_REACHED_BAR_HEIGHT, getReachedBarHeight());
bundle.putFloat(INSTANCE_UNREACHED_BAR_HEIGHT, getUnreachedBarHeight());
bundle.putInt(INSTANCE_REACHED_BAR_COLOR, getReachedBarColor());
bundle.putInt(INSTANCE_UNREACHED_BAR_COLOR, getUnreachedBarColor());
bundle.putInt(INSTANCE_MAX, getMax());
bundle.putInt(INSTANCE_PROGRESS, getProgress());
bundle.putString(INSTANCE_SUFFIX, getSuffix());
bundle.putString(INSTANCE_PREFIX, getPrefix());
bundle.putBoolean(INSTANCE_TEXT_VISIBILITY, getProgressTextVisibility());
return bundle;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
final Bundle bundle = (Bundle) state;
mTextColor = bundle.getInt(INSTANCE_TEXT_COLOR);
mTextSize = bundle.getFloat(INSTANCE_TEXT_SIZE);
mReachedBarHeight = bundle.getFloat(INSTANCE_REACHED_BAR_HEIGHT);
mUnreachedBarHeight = bundle.getFloat(INSTANCE_UNREACHED_BAR_HEIGHT);
mReachedBarColor = bundle.getInt(INSTANCE_REACHED_BAR_COLOR);
mUnreachedBarColor = bundle.getInt(INSTANCE_UNREACHED_BAR_COLOR);
initializePainters();
setMax(bundle.getInt(INSTANCE_MAX));
setProgress(bundle.getInt(INSTANCE_PROGRESS));
setPrefix(bundle.getString(INSTANCE_PREFIX));
setSuffix(bundle.getString(INSTANCE_SUFFIX));
setProgressTextVisibility(bundle.getBoolean(INSTANCE_TEXT_VISIBILITY) ? ProgressTextVisibility.Visible : ProgressTextVisibility.Invisible);
super.onRestoreInstanceState(bundle.getParcelable(INSTANCE_STATE));
return;
}
super.onRestoreInstanceState(state);
}
public float dp2px(float dp) {
final float scale = getResources().getDisplayMetrics().density;
return dp * scale + 0.5f;
}
public float sp2px(float sp) {
final float scale = getResources().getDisplayMetrics().scaledDensity;
return sp * scale;
}
public void setProgressTextVisibility(ProgressTextVisibility visibility) {
mIfDrawText = visibility == ProgressTextVisibility.Visible;
invalidate();
}
public boolean getProgressTextVisibility() {
return mIfDrawText;
}
public void setOnProgressBarListener(OnProgressBarListener listener) {
mListener = listener;
}
}
Android App 增量更新实例(Smart App Updates)
Android应用增量更新库(Smart App Updates)
介绍
你所看到的,是一个用于Android应用程序增量更新的开源库。
包括客户端、服务端两部分代码。
原理
自从 Android 4.1 开始,Google引入了应用程序的增量更新。
Link: http://developer.android.com/about/versions/jelly-bean.html
Smart app updates is a new feature of Google Play that introduces a better way of delivering app updates to devices. When developers publish an update, Google Play now delivers only the bits that have changed to devices, rather than the entire APK. This makes the updates much lighter-weight in most cases, so they are faster to download, save the device’s battery, and conserve bandwidth usage on users’ mobile data plan. On average, a smart app update is about 1/3 the sizeof a full APK update.
增量更新的原理非常简单,就是将手机上已安装apk与服务器端最新apk进行二进制对比,并得到差分包,用户更新程序时,只需要下载差分包,并在本地使用差分包与已安装apk,合成新版apk。
例如,当前手机中已安装微博V1,大小为12.8MB,现在微博发布了最新版V2,大小为15.4MB,我们对两个版本的apk文件查分比对之后,发现差异只有3M,那么用户就只需要要下载一个3M的差分包,使用旧版apk与这个差分包,合成得到一个新版本apk,提醒用户安装即可,不需要整包下载15.4M的微博V2版apk。
apk文件的差分、合成,可以通过开源的二进制比较工具bsdiff来实现(Link:http://www.daemonology.net/bsdiff/)
因为bsdiff依赖bzip2,所以我们还需要用到bzip2(Link:http://www.bzip.org/downloads.html)
bsdiff中,bsdiff.c用于生成查分包,bspatch.c用于合成文件。
接下来,我们分开说,需要做3件事。
1.在服务器端,生成这两个版本微博的差分包;
2.在手机客户端,使用已安装的旧版apk与这个差分包,合成为一个新版微博apk;
3.校验新合成的微博客户端文件是否完成,签名时候和已安装客户端一致,如一致,提示用户安装;
过程分析
1 生成差分包
这一步需要在服务器端来实现,一般来说,每当apk有新版本需要提示用户升级,都需要运营人员在后台管理端上传新apk,上传时就应该由程序生成之前所有旧版本们与最新版的差分包。
例如: 你的apk已经发布了3个版,V1.0、V2.0、V3.0,这时候你要在后台发布V4.0,那么,当你在服务器上传最新的V4.0包时,服务器端就应该立即生成以下差分包:
V1.0 ——> V4.0的差分包;
V2.0 ——> V4.0的差分包;
V3.0 ——> V4.0的差分包;
ApkPatchLibraryServer工程即为Java语言实现的服务器端查分程序。
下面对ApkPatchLibraryServer做一些简单说明:
1.1 C部分
ApkPatchLibraryServer/jni 中,除了以下4个:
com_cundong_utils_DiffUtils.c com_cundong_utils_DiffUtils.h com_cundong_utils_PatchUtils.c com_cundong_utils_PatchUtils.h
全部来自bzip。
com_cundong_utils_DiffUtils.c com_cundong_utils_DiffUtils.h
用于生成差分包。
com_cundong_utils_PatchUtils.c com_cundong_utils_PatchUtils.h
用于合成新apk文件。
其中,com_cundong_utils_DiffUtils.c修改自 bsdiff/bsdiff.c,com_cundong_utils_PatchUtils.c修改自bsdiff/bspatch.c。
我们在需要将jni中的C文件,build输出为动态链接库,以供Java调用(Window环境下生成的文件名为libApkPatchLibraryServer.dll,Unix-like系统下为libApkPatchLibraryServer.so,OSX下为libApkPatchLibraryServer.dylib)。
Build成功后,将该动态链接库文件,加入环境变量,供Java语言调用。
1.2 Java部分
com.cundong.utils包,为调用C语言的Java实现; com.cundong.apkdiff包,为apk查分程序的Demo; com.cundong.apkpatch包,为apk合并程序的Demo;
调用,com.cundong.utils.DiffUtils中genDiff()方法,可以通过传入的新旧apk路径,得到差分包。
/**
* 类说明: apk diff 工具类
*
* @author Cundong
* @date 2013-9-6
* @version 1.0
*/
public class DiffUtils {
/**
* 本地方法 比较路径为oldPath的apk与newPath的apk之间差异,并生成patch包,存储于patchPath
*
* @param oldPath
* @param newPath
* @param patchPath
* @return
*/
public static native int genDiff(String oldApkPath, String newApkPath, String patchPath);
}
调用,com.cundong.utils.PatchUtils中patch()方法,可以通过旧apk与差分包,合成为新apk。
/**
* 类说明: APK Patch工具类
*
* @author Cundong
* @date 2013-9-6
* @version 1.0
*/
public class PatchUtils {
/**
* native方法
* 使用路径为oldApkPath的apk与路径为patchPath的补丁包,合成新的apk,并存储于newApkPath
* @param oldApkPath
* @param newApkPath
* @param patchPath
* @return
*/
public static native int patch(String oldApkPath, String newApkPath,
String patchPath);
}
2.使用旧版apk与差分包,在客户端合成新apk
需要在手机客户端实现,ApkPatchLibrary工程封装了这个过程。
2.1 C部分
ApkPatchLibrary/jni/bzip2目录中所有文件都来自bzip2项目。
ApkPatchLibrary/jni/com_cundong_utils_PatchUtils.c、ApkPatchLibrary/jni/com_cundong_utils_PatchUtils.c实现文件的合并过程,其中com_cundong_utils_PatchUtils.c修改自bsdiff/bspatch.c。
我们需要用NDK编译出一个libApkPatchLibrary.so文件,生成的so文件位于libs/armeabi/ 下,其他 Android 工程便可以使用该libApkPatchLibrary.so文件来合成apk。
2.2 Java部分
com.cundong.utils包,为调用C语言的Java实现;
调用,com.cundong.utils.PatchUtils中patch()方法,可以通过旧apk与差分包,合成为新apk。
/**
* 类说明: APK Patch工具类
*
* @author Cundong
* @date 2013-9-6
* @version 1.0
*/
public class PatchUtils {
/**
* native方法
* 使用路径为oldApkPath的apk与路径为patchPath的补丁包,合成新的apk,并存储于newApkPath
* @param oldApkPath
* @param newApkPath
* @param patchPath
* @return
*/
public static native int patch(String oldApkPath, String newApkPath,
String patchPath);
}
3.校验新合成的apk文件
新包和成之后,还需要对客户端合成的apk包与最新版本apk包进行MD5或SHA1校验,如果校验码不一致,说明合成过程有问题,新合成的包将不能被安装。
注意事项
增量更新的前提条件,是在手机客户端能让我们读取到当前应用程序安装后的源apk,如果获取不到源apk,那么就无法进行增量更新了。
另外,如果你的应用程序不是很大,比如只有2、3M,那么完全没有必要使用增量更新,增量更新适用于apk包比较大的情况,比如游戏客户端。
GitHub地址
GitHub:https://github.com/cundong/SmartAppUpdates
一些说明
源码中,包含以下文件:
1.ApkPatchLibraryServer:Java语言实现的,服务器端生成差分包工程;
2.ApkPatchLibrary:客户端使用的apk合成库;
3.ApkPatchLibraryDemo:引用ApkPatchLibrary Library 的Demo,以新浪微博客户端的升级为例,假设手机上安装的是V4.5.0,最新版是V4.5.5,用户需要从V4.5.0升级到V4.5.5。
4.TestApk:用于测试的,旧版本的微博客户端,以及使用ApkPatchLibraryServer生成的新旧新浪微博差分包;
关于我
Blog: http://my.oschina.net/liucundong/blog
Mail: cundong.liu#gmail.com
Update
1.目前的做法只是提供了一个例子,并没有做成开源库,打算这几天改进一下,做成一个开源库,push到GitHub上,开发ing..(2014年,8月31日)
2.已经大幅度重构原代码,并将原来的Demo程序提取成为开源库,欢迎所有人Watch、Star、Fork。(2014年,9月2日)
3.修改ReadMe.md,更加清晰的说明开源库的使用,同时进一步重构代码。(2014年,10月4日晚)
License
Copyright 2014 Cundong
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Android APP 打开 flutter app 并传递参数
1 flutter Android 端配置
1.1 manifest 配置修改
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<!-- 添加如下代码 -->
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
1.2 在 Android 源代码的 mainActivity 为如下代码
package com.swan.flutter_framework
import android.os.Bundle
import io.flutter.plugin.common.MethodChannel
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
//FlutterMain.startInitialization(this);
super.onCreate(savedInstanceState)
// GeneratedPluginRegistrant.registerWith(FlutterEngine(this)) // here is the error: Type mismatch. Required: FlutterEngine! Found: MainActivity
//接收第三方app调运参数并且传递给flutter
MethodChannel(flutterEngine?.dartExecutor, CHANNEL).setMethodCallHandler { call, result ->
if (call.method == InvokeMethod) {
val greetings = successNativeCode()
result.success(greetings)
}
}
}
private fun successNativeCode(): String {
return "我是android原生跑过来的数据=>${intent.getStringExtra("test")}"
}
companion object {
private const val CHANNEL = "com.swan.shareData"
private const val InvokeMethod = "shareData"
}
}
1.3 在要接受回传内容的 dart 文件中这样写
getSharedText() async {
const String CHANNEL = "com.swan.shareData"; //这儿要与MethodChannel(flutterEngine?.dartExecutor, CHANNEL)中CHANNEL名称一致
const String invokeMethod = "shareData"; //这儿要与 call.method == invokeMethod中invokeMethod名称一致
var channel = const MethodChannel(CHANNEL);
var result = await channel.invokeMethod(invokeMethod);
ToastUtils.showToast("message=>$result");
print(''message=>$result'');
}
1.4 源代码
- android 端
package com.swan.flutter_framework
import android.os.Bundle
import io.flutter.plugin.common.MethodChannel
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
//FlutterMain.startInitialization(this);
super.onCreate(savedInstanceState)
// GeneratedPluginRegistrant.registerWith(FlutterEngine(this)) // here is the error: Type mismatch. Required: FlutterEngine! Found: MainActivity
//接收第三方app调运参数并且传递给flutter
MethodChannel(flutterEngine?.dartExecutor, CHANNEL).setMethodCallHandler { call, result ->
if (call.method == InvokeMethod) {
val greetings = successNativeCode()
result.success(greetings)
}
}
}
private fun successNativeCode(): String {
return "我是android原生跑过来的数据=>${intent.getStringExtra("test")}"
}
companion object {
private const val CHANNEL = "com.swan.shareData"
private const val InvokeMethod = "shareData"
}
}
- dart
import ''package:flutter/material.dart'';
import ''package:flutter/services.dart'';
import ''package:flutter_framework/app/navigator/my_bottom_app_bar.dart'';
import ''package:flutter_framework/app/pages/first_guild_page.dart'';
/// @Description 应用启动闪屏页
/// @Author swan
/// @Date 2021/12/30 9:37 上午
///
class IndexPage extends StatefulWidget {
const IndexPage({Key? key}) : super(key: key);
@override
_IndexPageState createState() => _IndexPageState();
}
class _IndexPageState extends State<IndexPage> with ProtocolModel,CheckUpdateUtil{
getSharedText() async {
const String CHANNEL = "com.swan.shareData"; //这儿要与MethodChannel(flutterEngine?.dartExecutor, CHANNEL)中CHANNEL名称一致
const String invokeMethod = "shareData"; //这儿要与 call.method == invokeMethod中invokeMethod名称一致
var channel = const MethodChannel(CHANNEL);
var result = await channel.invokeMethod(invokeMethod);
ToastUtils.showToast("message=>$result");
print(''message=>$result'');
}
// 页面的初始化函数
@override
void initState() {
super.initState();
// 1 检查权限
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
// 2 用户权限和隐私政策
initDataNext();
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Image.asset(Res.welcome),
),
);
}
/// 初始化工具类
Future<void> initDataNext() async {
getSharedText();
}
}
2 打开 APP 调用代码
Intent intent = new Intent();
//参数:包名,要打开的类名
intent.setClassName("com.swan.flutter_framework","com.swan.fluter_framework.MainActivity");
intent.putExtra("test","test");
startActivity(intent);
Android APP 检测之自动化检测实战:五大 APP 安全在线检测平台对比
Android APP 本文作者:ice@DMZLab
最近一直在研究的检测,写了一个系列的文章 —— 手工检测,自动化检测,常见漏洞分析。今天给大家带来的是自动化检测。本篇没有深入的讲解每一个漏洞的详情,仅作测试结果对比和自己的体验心得。
0×01 五大在线检测平台
腾讯的金刚审计系统 http://service.security.tencent.com/kingkong
360 的捉虫猎手 http://appscan.360.cn/
阿里巴巴的聚安全 http://jaq.alibaba.com/gc/appsec/index.htm
百度的移动云测试中心 http://mtc.baidu.com/
梆梆加固测试平台(http://dev.bangcle.com/apps/index)
效果对比
这里选用墨迹天气 app 的测试结果
百度移动测试中心
漏洞名称 | 风险级别 | 说明 | 修复建议 | 详情 |
---|---|---|---|---|
组件暴露 ——Activity | 中危 | 当应用程序的组件被导出后,导出的组件可以被第三方 app 任意调用,从而导致敏感信息泄露,而且恶意攻击者也可以通过精心构造数据来达到攻击目标应用的的目的。 | 如果组件不需要与其他应用共享数据或进行交互,则在 AndroidManifest.xml 文件中设置该组件为 exported = “false”,反之,则需要对导出的组件进行权限控制并且严格校验传入的参数。 | com.moji.mjweather.activity.main.MainActivity com.moji.mjweather.CSplashScreen com.moji.mjweather.activity.share.ManualShareActivity com.moji.mjweather.activity.skinshop.SkinSelectorActivity com.kepler.jd.login.AuthSuccessActivity com.moji.mjweather.activity.liveview.MessageDetailActivity com.moji.mjweather.activity.liveview.OwnerMessageCenterActivity com.moji.mjweather.activity.account.SnsLoginActivity com.moji.mjweather.activity.liveview.HomePageActivity com.moji.mjweather.activity.voiceclock.AlarmAlertActivity com.moji.mjweather.activity.voiceclock.AlarmAlertFullScreenActivity com.moji.mjweather.activity.share.SharePlatformDialog com.tencent.tauth.AuthActivity com.moji.mjweather.activity.liveview.LauncherCameraActivity com.moji.mjweather.activity.bindapp.InstallAppActivity com.moji.mjweather.activity.settings.WidgetConfigureActivity com.igexin.sdk.GActivity com.moji.mjweather.wxapi.WXPayEntryActivity com.moji.mjweather.wxapi.WXEntryActivity com.moji.mjweather.activity.forum.TopicActivity com.moji.mjweather.x5webview.BrowserActivity 共:21 个。 |
组件暴露 ——Service | 中危 | 当应用程序的组件被导出后,导出的组件可以被第三方 app 任意调用,从而导致敏感信息泄露,而且恶意攻击者也可以通过精心构造数据来达到攻击目标应用的的目的。 | 如果组件不需要与其他应用共享数据或进行交互,则在 AndroidManifest.xml 文件中设置该组件为 exported = “false”,反之,则需要对导出的组件进行权限控制并且严格校验传入的参数。 | com.moji.mjweather.service.ScreenService com.igexin.sdk.PushService com.igexin.sdk.PushServiceUser com.moji.mjweather.authaccount.AuthenticationService com.moji.mjweather.authaccount.SyncService 共:5 个。 |
组件暴露 ——BroadcastReceiver | 中危 | 当应用程序的组件被导出后,导出的组件可以被第三方 app 任意调用,从而导致敏感信息泄露,而且恶意攻击者也可以通过精心构造数据来达到攻击目标应用的的目的。 | 如果组件不需要与其他应用共享数据或进行交互,则在 AndroidManifest.xml 文件中设置该组件为 exported = “false”,反之,则需要对导出的组件进行权限控制并且严格校验传入的参数。 | com.moji.mjweather.receiver.PackageReceiver com.moji.mjweather.receiver.MojiReceiver com.moji.mjweather.CMojiWidget4x1 com.moji.mjweather.CMojiWidget4x2 com.moji.mjweather.CMojiWidget5x1 com.moji.mjweather.CMojiWidget5x2 com.igexin.sdk.PushReceiver com.igexin.download.DownloadReceiver com.baidu.bottom.service.BottomReceiver com.zk.drivermonitor.reciever.SystemStartReceiver 共:10 个。 |
应用数据任意备份风险 | 中危 | 当 AndroidManifest.xml 配置文件中没有有设置 allowBackup 标志 (默认为 true) 或将 allowBackup 标志设置为 true 时,应用程序的数据可以被任意备份和恢复,恶意攻击者可以通过 adb 工具备份复制应用程序的数据。 | 在 AndroidManifest.xml 文件中设置 application 的属性 android:allowBackup=”false” | |
权限滥用风险 | 中危 | 自定义权限的保护级别过低,导致任意应用程序都可以使用此权限,无法起到保护作用。 | 如非必要,自定义权限的保护级别至少要设置为:signature。 |
漏洞名称 | 风险级别 | 说明 | 修复建议 | 详情 |
---|---|---|---|---|
WebView 组件系统隐藏接口未移除漏洞 | 高危 | 使用 Android WebView 组件时,没有移除其中内置的 searchBoxJavaBridge_,accessibility 和 accessibilityTraversal 等导出接口,可能导致远程代码任意执行 | 使用 Android WebView 组件时,通过调用 removeJavascriptInterface 方法移除 searchBoxJavaBridge_, accessibility 和 accessibilityTraversal 等导出接口,防止被恶意利用 | 源文件: 类:com.baidu.mobad.feeds.remote.BaiduActivity 方法:a 行数:-1 |
WebView 组件系统隐藏接口未移除漏洞 | 高危 | 使用 Android WebView 组件时,没有移除其中内置的 searchBoxJavaBridge_,accessibility 和 accessibilityTraversal 等导出接口,可能导致远程代码任意执行 | 使用 Android WebView 组件时,通过调用 removeJavascriptInterface 方法移除 searchBoxJavaBridge_, accessibility 和 accessibilityTraversal 等导出接口,防止被恶意利用 | 源文件: 类:com.qq.e.comm.plugin.j.a 方法:onAfterCreate 行数:-1 |
Dex 文件动态加载风险 | 中危 | Android 提供的 DexClassLoader 动态加载方法,并没有对 DEX 文件和路径进行安全校验,可能导致加载文件或者优化文件被恶意替换 | 使用 DexClassLoader 方法动态加载 DEX 文件时,对 DEX 文件进行安全校验,并保证加载路径和优化路径的安全 | 源文件: 类:com.baidu.mobad.feeds.remote.AdManager 方法:getPatchClassLoader 行数:-1 |
SSL 证书验证不当漏洞 | 中危 | 应用忽略证书校验错误或信任任意证书,会导致中间人攻击,造成隐私泄露 | 禁止使用 ALLOW_ALL_HOSTNAME_VERIFIER;禁止使用 X509TrustManager.checkServerTrusted 方法来忽略证书验证错误;在使用 HostnameVerifier 时 verify 合理处理,禁止直接返回 true | 源文件: 类:com.qq.e.comm.plugin.k.d$a$1 方法:verify 行数:-1 |
WebView 密码明文保存漏洞 | 低危 | 在默认情况下,如果用户选择保存在 WebView 中输入的用户名和密码,则会被明文保存到应用数据目录的 databases/webview.db 文件中,存在密码被泄露的风险 | 使用 WebView.getSettings ().setSavePassword (false) 来禁止保存密码 | 源文件: 类:com.qq.e.comm.plugin.m.f 方法:a 行数:-1 |
WebView 密码明文保存漏洞 | 低危 | 在默认情况下,如果用户选择保存在 WebView 中输入的用户名和密码,则会被明文保存到应用数据目录的 databases/webview.db 文件中,存在密码被泄露的风险 | 使用 WebView.getSettings ().setSavePassword (false) 来禁止保存密码 | 源文件: 类:com.qq.e.comm.plugin.m.f 方法:b 行数:-1 |
WebView 密码明文保存漏洞 | 低危 | 在默认情况下,如果用户选择保存在 WebView 中输入的用户名和密码,则会被明文保存到应用数据目录的 databases/webview.db 文件中,存在密码被泄露的风险 | 使用 WebView.getSettings ().setSavePassword (false) 来禁止保存密码 | 源文件: 类:com.qq.e.comm.plugin.j.a 方法:onAfterCreate 行数:-1 |
WebView 密码明文保存漏洞 | 低危 | 在默认情况下,如果用户选择保存在 WebView 中输入的用户名和密码,则会被明文保存到应用数据目录的 databases/webview.db 文件中,存在密码被泄露的风险 | 使用 WebView.getSettings ().setSavePassword (false) 来禁止保存密码 | 源文件: 类:com.baidu.mobad.feeds.remote.BaiduActivity 方法:a 行数:-1 |
WebView 密码明文保存漏洞 | 低危 | 在默认情况下,如果用户选择保存在 WebView 中输入的用户名和密码,则会被明文保存到应用数据目录的 databases/webview.db 文件中,存在密码被泄露的风险 | 使用 WebView.getSettings ().setSavePassword (false) 来禁止保存密码 | 源文件: 类:com.baidu.mobad.feeds.remote.BaiduActivity$1 方法:shouldOverrideUrlLoading 行数:-1 |
WebView 密码明文保存漏洞 | 低危 | 在默认情况下,如果用户选择保存在 WebView 中输入的用户名和密码,则会被明文保存到应用数据目录的 databases/webview.db 文件中,存在密码被泄露的风险 | 使用 WebView.getSettings ().setSavePassword (false) 来禁止保存密码 | 源文件: 类:com.baidu.mobad.feeds.remote.DownloaderTask 方法:a 行数:-1 |
PendingIntent 包含隐式 Intent 风险 | 低危 | PendingIntent 以其发送方应用的权限使用该 PendingIntent 包含的 Intent,如果该 Intent 为隐式的,可能造成隐私泄露和权限泄露 | 使用 PendingIntent 时,建议使用显示 Intent | 源文件: 类:com.baidu.mobad.feeds.remote.download.e 方法:run 行数:-1 |
PendingIntent 包含隐式 Intent 风险 | 低危 | PendingIntent 以其发送方应用的权限使用该 PendingIntent 包含的 Intent,如果该 Intent 为隐式的,可能造成隐私泄露和权限泄露 | 使用 PendingIntent 时,建议使用显示 Intent | 源文件: 类:com.qq.e.comm.plugin.a.b.c 方法:d 行数:-1 |
PendingIntent 包含隐式 Intent 风险 | 低危 | PendingIntent 以其发送方应用的权限使用该 PendingIntent 包含的 Intent,如果该 Intent 为隐式的,可能造成隐私泄露和权限泄露 | 使用 PendingIntent 时,建议使用显示 Intent | 源文件: 类:com.qq.e.comm.plugin.a.i 方法:b 行数:-1 |
WebView 密码明文保存漏洞 | 低危 | 在默认情况下,如果用户选择保存在 WebView 中输入的用户名和密码,则会被明文保存到应用数据目录的 databases/webview.db 文件中,存在密码被泄露的风险 | 使用 WebView.getSettings ().setSavePassword (false) 来禁止保存密码 | 源文件: 类:com.qq.e.comm.plugin.m.c 方法:d 行数:-1 |
日志泄露隐私风险 | 低危 | 调试输出接口未关闭可能导致敏感信息泄露 | 关闭调试接口,禁止输出敏感信息 | 源文件: 类:com.baidu.mobads.location.BDLocManager 方法:a 行数:-1 |
日志泄露隐私风险 | 低危 | 调试输出接口未关闭可能导致敏感信息泄露 | 关闭调试接口,禁止输出敏感信息 | 源文件: 类:com.qq.e.comm.plugin.g.a 方法:a 行数:-1 |
日志泄露隐私风险 | 低危 | 调试输出接口未关闭可能导致敏感信息泄露 | 关闭调试接口,禁止输出敏感信息 | 源文件:src/com/qihoo/util/StubApplication.java 类:com.qihoo.util.StubApplication 方法:initCrashReport 行数:93 |
梆梆加固测试结果:
内网测试信息残留漏洞 | |
---|---|
评估项 | 内网测试信息残留漏洞 |
漏洞描述 | 检测程序代码内部是否包含残留测试信息,例如内网 url 地址等。 |
漏洞影响 | 低 |
评估方案 | 通过检测是否包含内网 URl 地址,判断是否发布包中是否包含测试数据。残留的测试数据,例如 URL 地址,测试账号,密码,可能会被盗取并恶意利用在正式服务器上进行攻击,例如账号重试,攻击安全薄弱的测试服务器以获取服务器安全漏洞或者逻辑漏洞。 |
评估结果 | 安全 |
漏洞分析 | 该 App 应用中未包含测试数据信息。 |
漏洞详情 | N/A |
解决方案 | N/A |
下载任意 apk 漏洞 | |
---|---|
评估项 | 下载任意 apk 漏洞 |
漏洞描述 | 检测应用中是否存在下载任意 apk 的漏洞。 |
漏洞影响 | 中 |
评估方案 | 具有下载 apk 功能的组件存在导出漏洞,并且未对组件调用者进行校验。攻击者可利用导出组件的手段下载攻击者指定的任意 apk 文件,并且在下载过程中伪装 apk 文件的下载信息,例如图标、描述等,导致用户被诱导下载安装恶意应用。 |
评估结果 | 安全 |
漏洞分析 | 该 App 应用中不存在可被导出的具有下载 apk 功能的组件。 |
漏洞详情 | N/A |
解决方案 | N/A |
HTTPS 未校验服务器证书漏洞 | |
---|---|
评估项 | HTTPS 未校验服务器证书漏洞 |
漏洞描述 | 检测 App 程序在使用 HTTPS 协议传输数据时是否对服务器证书进行完整校验。 |
漏洞影响 | 中 |
评估方案 | 使用 HTTPS 协议时,客户端必须对服务器证书进行完整校验,以验证服务器是真实合法的目标服务器。如果没有校验,客户端可能与仿冒的服务 器建立通信链接,即 “中间人攻击”。仿冒的中间人可以冒充服务器与银行客户端进行交互,同时冒充银行客户端与银行服务器进行交互,在充当中间人转发信息的 时候,窃取手机号,账号,密码等敏感信息。 |
评估结果 | 存在漏洞 |
漏洞分析 | 该 App 应用在使用 HTTPS 进行数据传输时未校验服务器证书或者未校验主机名。 |
漏洞详情 | ["com.moji.mjweather.util.log.InstalledAppTrackerSDK.a:(ILjava/lang/String;Lorg/apache/http/client/methods/HttpPost;)Ljava/lang/String;"] |
解决方案 | 在使用 https 时对服务器证书进行校验,并且使用 STRICT_HOSTNAME_VERIFIER 严格校验主机名。 |
Webview 远程代码执行漏洞 | |
---|---|
评估项 | Webview 远程代码执行漏洞 |
漏洞描述 | 检测 app 应用的 webview 组件中是否存在远程代码执行漏洞。 |
漏洞影响 | 高 |
评估方案 | Webview 是 Android 用于浏览网页的组件,其包含的接口函数 addJavascriptInterface 可以将 Java 类或方 法导出以供 JavaScript 调用,实现网页 JS 与本地 JAVA 的交互。由于系统没有限制已注册 JAVA 类的方法调用,因此未注册的其它任何 JAVA 类 也可以被反射机制调用,这样可能导致被篡改的 URL 中存在的恶意代码被执行,用户手机被安装木马程序,发送扣费短信,通信录或者短信被窃取,甚至手机被远 程控制。 |
评估结果 | 存在漏洞 |
漏洞分析 | 该 App 应用中可能存在被 addJavascriptInterface 接口导出的未注册 Java 类函数。 |
漏洞详情 | ["com.tencent.bugly.crashreport.CrashReport.setJavascriptMonitor:(Landroid/webkit/WebView;ZZ)Z"] |
解决方案 | 取消使用 addJavascriptInterface 接口,以其他 Java 与 JavaScript 互通方案代替;若必须使用,则应对访问的 url 进行过滤限制或对 html 页面进行完整性校验,同时显示移除对指定的 javascript 接口的调用: removeJavascriptInterface (searchBoxJavaBridge_) emoveJavascriptInterface (accessibility);removeJavascriptInterface (accessibilityTraversal);。 |
Webview 绕过证书校验漏洞 | |
---|---|
评估项 | Webview 绕过证书校验漏洞 |
漏洞描述 | 检测 App 应用的 webview 组件是否在发现 https 网页证书错误后继续加载页面。 |
漏洞影响 | 低 |
评估方案 | 客户端的 Webview 组件访问使用 HTTPS 协议加密的 url 时,如果服务器证书校验错误,客户端应该拒绝继续加载页面。但如果重载 WebView 的 onReceivedSslError () 函数并在其中执行 handler.proceed (),客户端可以绕过证书校验错误继续访问此 非法 URL。这样将会导致 “中间人攻击”,攻击者冒充服务器与银行客户端进行交互,同时冒充银行客户端与银行服务器进行交互,在充当中间人转发信息的时 候,窃取手机号,账号,密码等敏感信息。 |
评估结果 | 存在漏洞 |
漏洞分析 | 该 App 应用的 webview 组件中存在忽略证书校验错误的漏洞。 |
漏洞详情 | [ "com.alipay.sdk.app.b.onReceivedSslError:(Landroid/webkit/WebView;Landroid/webkit/SslErrorHandler;Landroid/net/http/SslError;)V", "com.alipay.sdk.app.d.onClick:(Landroid/content/DialogInterface;I)V", "com.alipay.sdk.auth.AuthActivity.b.onReceivedSslError:(Landroid/webkit/WebView;Landroid/webkit/SslErrorHandler;Landroid/net/http/SslError;)V", "com.alipay.sdk.auth.f.onClick:(Landroid/content/DialogInterface;I)V", "com.tencent.smtt.sdk.aw.b.proceed:()V"] |
解决方案 | 取消在 Webveiw 组件中对 onReceivedSslError () 函数的重载。 |
360 捉虫猎手检测结果:
因结果扫了很久还没出来,就直接来张其他 app 的扫描结果吧
阿里聚对墨迹天气的安全检测结果:
漏洞详情 | 风险等级 | 修复建议 |
---|---|---|
webview 远程代码执行漏洞(2 个)addJavascriptInterface 存在高危远程代码执行漏洞,应尽量避免使用,API 17 中用 @JavascriptInterface 代替 addjavascriptInterface; 移除系统 webkit 内置的危险接口 searchBoxJavaBridge_,accessibility,accessibilityTraversal [了解更多] [实际案例] | 高危触及安全红线
|
应尽量避免使用,API 17 中用 @JavascriptInterface 代替 addjavascriptInterface; 移除系统 webkit 内置的危险接口 searchBoxJavaBridge_,accessibility,accessibilityTraversal |
WebView 不校验证书漏洞(1 个)调用了 android/webkit/SslErrorHandler 类的 proceed 方法,可能导致 WebView 忽略校验证书的步骤 | 高危触及安全红线 | 不要调用 android.webkit.SslErrorHandler 的 proceed 方法 |
中间人攻击漏洞(1 个)HTTPS 禁止使用 ALLOW_ALL_HOSTNAME_VERIFIER,因为这样会存在中间人攻击的风险 [了解更多] | 高危触及安全红线 | 必须使用 STRIC_HOSTNAME_VERIFIER 并校验证书 |
备份标识配置风险(1 个)当这个标志被设置为 true 或不设置该标志时应用程序数据可以备份和恢复,adb 调试备份允许恶意攻击者复制应用程序数据。 [了解更多] [实际案例] | 中危触及安全红线 | 在 AndroidManifest.xml 中设置 android:allowBackup=”false” |
拒绝服务漏洞(22 个)不校验导出组件(Activity,Service 等)的传递参数,导致拒绝服务,需注意空值判定以及类型转换判断。 [了解更多] [实际案例] | 中危触及安全红线 | 请严格校验输入参数,注意空值判定和类型转换判断,防止由于异常输入导致的应用崩溃. |
SharedPrefs 任意读写漏洞(2 个)存在内容被替换的风险,SharedPreference 禁止使用 MODE_WORLD_READABLE 和 MODE_WORLD_WRITABLE [了解更多] | 中危触及安全红线 | 不要使用 MODE_WORLD_READABLE 和 MODE_WORLD_WRITABLE。 |
主机名弱效验(3 个)在实现的 HostnameVerifier 子类中未对主机名做效验,这样会导致恶意程序利用中间人攻击绕过主机名效验。利用 HostnameVerifier 子类中的 verify 函数效验服务器主机名的合法性。 | 中危触及安全红线 | 在实现的 HostnameVerifier 子类 verify 函数中校验主机名的合法性。 |
证书弱校验(3 个)在实现的 HostnameVerifier 子类中未对主机名做效验,这样会导致恶意程序利用中间人攻击绕过主机名效验。利用 HostnameVerifier 子类中的 verify 函数效验服务器主机名的合法性。 | 中危触及安全红线 | 在实现的 X509TrustManager 子类中 checkServerTrusted 函数效验服务器端证书的合法性。 |
File 任意读写漏洞(7 个)存在内容被替换的风险,openFileOutput 禁止使用 MODE_WORLD_READABLE 和 MODE_WORLD_WRITABLE [了解更多] | 中危触及安全红线 | 不要使用 MODE_WORLD_READABLE 和 MODE_WORLD_WRITABLE。 |
随机数生成函数使用错误(1 个)使用 SecureRandom 时不要使用 SecureRandom (byte [] seed) 这个构造函数,会造成生成的随机数不随机。 [了解更多] | 高危 | 建议通过 /dev/urandom 或者 /dev/random 获取的熵值来初始化伪随机数生成器 PRNG |
AES/DES 弱加密风险(19 个)使用 AES/DES 加密算法时,应显式指定使用 CBC 或 CFB 模式。否则容易受到选择明文攻击 (CPA) 的风险,造成信息泄露。 | 高危 | 使用 AES/DES 加密算法时应使用 CBC 或 CFB 模式。或者使用安全组件的安全加密接口 SecurityCipher 进行加密。 |
Native 动态调试(1 个)so 文件存在被调试的风险,攻击者可以利用此风险对应用进行动态调试,造成核心逻辑和敏感数据等信息泄漏。 | 高危 | 聚安全安全组件通过监控进程的多种调试状态特征,给应用提供全方位的反调试保护。 |
密钥硬编码风险(9 个)本地存储密钥存在被攻击者利用并通过密钥构造伪数据的风险。 [实际案例] | 高危 | 1、禁止把密钥写死在程序中,2、使用聚安全提供的安全加密组件。 |
初始化 IvParameterSpec 函数错误(7 个)使用固定初始化向量,结果密码文本可预测性会高得多,容易受到字典式攻击。修复建议:1、禁止使用常量初始化矢量参数构建 IvParameterSpec,2、推荐使用聚安全提供的安全组件。 [实际案例] | 中危 | 修复建议:1、禁止使用常量初始化矢量参数构建 IvParameterSpec,2、推荐使用聚安全提供的安全组件。 |
未进行安全加固风险(1 个)应用没有被安全加固,攻击者可以利用重打包等手段修改程序的原始逻辑和内容,并上传仿冒 app 到第三方应用市场,欺骗用户。 [实际案例] | 中危 | 建议使用聚安全的应用加固方案,聚安全应用加固提供对 dex、so 等文件的保护以及混淆。 |
PendingIntent 误用风险(5 个)使用 PendingIntent 的时候,如果使用了一个空 Intent,会导致恶意用户劫持修改 Intent 的内容。禁止使用一个空 Intent 去构造 PendingIntent,构造 PendingIntent 的 Intent 一定要设置 ComponentName 或者 action。 | 中危 | 禁止使用一个空 Intent 去构造 PendingIntent,构造 PendingIntent 的 Intent 一定要设置 ComponentName 或者 action。 |
Webview 明文存储密码漏洞(5 个)使用 Webview 时需要关闭 webview 的自动保存密码功能,防止用户密码被 webview 明文存储。 | 中危 | 显示设置 webView.getSetting ().setSavePassword (false) |
未移除有风险的 Webview 系统隐藏接口(17 个)android webview 组件包含 3 个隐藏的系统接口:searchBoxJavaBridge_, accessibilityTraversal 以及 accessibility,恶意程序可以利用它们实现远程代码执行。请通过显示调用 removeJavascriptInterface 移除这三个系统隐藏接口。 [实际案例] | 中危 | 请通过显示调用 removeJavascriptInterface 移除这三个系统隐藏接口。 |
数据弱保护(1 个)数据安全保护级别较低,攻击者可以通过逆向分析等手段,较容易得获取应用的关键数据,比如签名算法、加密密钥、加密数据等。 | 中危 | 推荐使用安全组件的数据加签和安全存储功能,提高应用的安全保护级别。 |
日志泄漏风险(20 个)使用 System.out.print 等标准输出打印日志信息或转存日志信息,容易泄漏敏感信息。建议删除所有使用 System.out.print 等标准输出打印日志或转存日志信息的代码 [实际案例] | 低危 | 建议删除所有使用 System.out.print 等标准输出打印日志或转存日志信息的代码 |
关于阿里巴巴的聚安全,聚安全会给代码详情打码,如下图(4.1 号以后的新规则,需要验证 app 的签名,会给你一个 demo, 你需要把 keystore 签到阿里官方给的 demo 中,验证应用开发者,然后才能看到详情)如下图:
有些朋友说,出现了这个 “为保护应用隐私,查看详情漏洞位置请先申请应用所有权认证”,
如何签名
如何签名:
jarsigner -verbose -keystore [keystorePath] -signedjar [apkOut] [apkin] [alias]
命令格式及参数意义:
-verbose -> 输出签名过程的详细信息
-keystore [keystorePath] -> 密钥的库的位置
-signedjar [apkOut] -> 签名后的输出文件名
[apkin] -> 待签名的文件名
[alias] -> 证书别名
示例:
D:\>jarsigner -verbose -keystore demo.keystore -signedjar jaq_demo_signed.apk jaq_demo.apk demo.keystore
下面讲下如何对 app 的应用开发者进行认证:
那么如何签名呢
创建一个 keystore,用来存放签名 app 时要用的:
keytool -genkey -v -keystore relax.keystore -alias rela -keyalg RSA 生成私钥
用私钥对 apk 进行重新签名
root@kali:~/Desktop# jarsigner -verbose -sigalg MD5withRSA --digestalg SHA1 -keystore /root/Desktop/relax.keystore jaq_demo_1460103308355.apk rela
就是说,使用开发者的 keystore 对聚安全的那个 demo.apk 进行签名,然后就完成了认证
聚安全的一些其他看点
聚安全结合乌云,里面有很多实例,有很多常用漏洞的集合,是新手快速解决问题的好去处。
聚安全还有一个仿冒监测:(这里说下为什么会出现仿冒软件,因为 app 没有加固,导致被反编译,被打包后,植入而已代码后又在其他地方上线,所以这里忠告一下,下 app,一定要去官方网站上下载,能提供验证 MD5,尽量要验证一下)
检测结果总结:
阿里聚安全问题汇总:
webview 远程代码执行漏洞
WebView 不校验证书漏洞
中间人攻击漏洞
备份标识配置风险
拒绝服务漏洞
SharedPrefs 任意读写漏洞
主机名弱效验
证书弱校验
File 任意读写漏洞
随机数生成函数使用错误
AES/DES 弱加密风险
Native 动态调试
密钥硬编码风险
初始化 IvParameterSpec 函数错误
未进行安全加固风险
PendingIntent 误用风险
Webview 明文存储密码漏洞
未移除有风险的 Webview 系统隐藏接口
数据弱保护
日志泄露隐私风险(logcat 日志输出)
百度移动测试中心问题汇总:
组件暴露 ——Activity
组件暴露 ——Service
组件暴露 ——BroadcastReceiver
应用数据任意备份风险
权限滥用风险
WebView 组件系统隐藏接口未移除漏洞
Dex 文件动态加载风险
SSL 证书验证不当漏洞
WebView 密码明文保存漏洞
PendingIntent 包含隐式 Intent 风险
日志泄露隐私风险
最后看下梆梆的检测结果:
Java 代码保护风险
组件导出风险
敏感函数调用风险
调试日志函数调用风险
应用数据任意备份风险
明文数字证书风险
未使用 HTTPS 协议的数据传输风险
Webview 明文存储密码风险
HTTPS 未校验服务器证书漏洞
Webview 远程代码执行漏洞
Webview 绕过证书校验漏洞
梆梆的新鲜的亮点: 到一处 直接打印出 app 里涉及到的 url 列表地址了,是不是涉及到很多新鲜的子域名和 url。
总结和一些争议
评估一下 APP 的安全性可以综合参考以上的检测,然后综合性的评估,阿里的需要验证开发者权限,百度那个要花钱的,还不错,梆梆也可以(很方便渗透额),360 怎么一直扫描不出报告。开发不一定能改第三方的包的安全问题,所以本包的问题能改的尽量改,咱们能做的就是给 app 进行加固。
加固前后效果对比:
加固后,可以看到数据备份还是没有打钩,因为,我没对 apk 中的(在 AndroidManifest.xml 中设置 android:allowBackup=”false”)这个设置项进行更改。
后记
本次在线检测实战旨在帮助开发者更快的评估自己的 android 问题,作为一个菜鸟 app 检测人员,希望带给大家的是让自己的 app 更加安全,当然安全从开发开始构思时,就该考虑是否使用第三方包,这样,对 app 的安全更加可控。
阅读拓展
http://www.droidsec.cn/ 安卓安全中文站
http://blog.nsfocus.net/mobile-app-security-security-test/ 移动 app 检测要点
http://zbc.baijia.baidu.com/article/365622 10 大移动安全威胁
* 本文作者:ice@DMZLab,转载须注明来自 FreeBuf 黑客与极客(FreeBuf.COM)
今天关于android app 应用后台用什么技术实现的和安卓应用后端的讲解已经结束,谢谢您的阅读,如果想了解更多关于Android 6.0 7.0 8.0 一个简单的 app 内更新版本 - okgo app 版本更新、Android App 增量更新实例(Smart App Updates)、Android APP 打开 flutter app 并传递参数、Android APP 检测之自动化检测实战:五大 APP 安全在线检测平台对比的相关知识,请在本站搜索。
本文标签: