700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > Android开发实战《手机安全卫士》——2.“设置中心”模块实现 自定义组件 Sp工

Android开发实战《手机安全卫士》——2.“设置中心”模块实现 自定义组件 Sp工

时间:2024-07-06 21:33:07

相关推荐

Android开发实战《手机安全卫士》——2.“设置中心”模块实现  自定义组件  Sp工

文章目录

1.home界面布局2.自定义获取焦点的TextView3.自定义控件回顾4.九宫格使用5.设置中心——条目布局结构6.设置中心——自定义组合控件构成布局结构7.设置中心——自定义组合控件中相关方法8.设置中心——选中SettingItemView条目状态切换9.设置中心——事件传递 & 相应规则10.设置中心——事件传递机制11.设置中心——sp工具类编写12.设置中心——sp存储更新状态13.设置中心——使用sp来判断客户端是否需要更新14.设置中心——自定义属性申明15.设置中心——构造方法中获取自定义属性值16.设置中心——给自定义组合控件内部控件赋值17.手机防盗——是否有密码区分对话框类型18.手机防盗——设置密码对话框19.手机防盗——对话初次设置密码验证过程20.手机防盗——确认密码对话框编写21.手机防盗——MD5加密过程

1.home界面布局

在上一篇博客中,我们完成了SplashActivity的全部编写,现在开始需要进行主界面HomeActivity的编写,在这之前,我们还需要优化一下SplashActivity,加入一段动画

修改SplashActivity,新增initAnimation(),作为初始化动画的方法,代码如下:

package com.example.mobilesafe.activity;import androidx.annotation.NonNull;import androidx.annotation.Nullable;import androidx.appcompat.app.AlertDialog;import androidx.appcompat.app.AppCompatActivity;import android.content.DialogInterface;import android.content.Intent;import android.content.pm.PackageInfo;import android.content.pm.PackageManager;import .Uri;import android.os.Bundle;import android.os.Environment;import android.os.Handler;import android.os.Message;import android.util.Log;import android.view.animation.AlphaAnimation;import android.widget.RelativeLayout;import android.widget.TextView;import com.example.mobilesafe.R;import com.example.mobilesafe.utils.StreamUtil;import com.example.mobilesafe.utils.ToastUtil;import com.lidroid.xutils.HttpUtils;import com.lidroid.xutils.exception.HttpException;import com.lidroid.xutils.http.ResponseInfo;import com.lidroid.xutils.http.callback.RequestCallBack;import org.json.JSONException;import org.json.JSONObject;import java.io.File;import java.io.IOException;import java.io.InputStream;import .HttpURLConnection;import .MalformedURLException;import .URL;public class SplashActivity extends AppCompatActivity {/*** 文本控件*/private TextView tv_version_name;/*** 根布局*/private RelativeLayout rl_root;/*** 本地版本号*/private int mLocalVersionCode;/*** 更新时描述信息*/private String mVersionDes;/*** 更新时的URL*/private String mDownloadUrl;private static final String tag = "SplashActivity";/*** Handler对象*/private Handler mHandler = new Handler(){@Overridepublic void handleMessage(@NonNull Message msg) {switch (msg.what){case UPDATE_VERSION:// 1.弹出对话框,提示用户更新showUpdateDialog();break;case ENTER_HOME:// 2.直接进入应用程序主界面enterHome();break;case URL_ERROR:// 3.弹出URL错误ToastUtil.show(SplashActivity.this,"url异常");enterHome();break;case IO_ERROR:// 4.弹出IO错误ToastUtil.show(SplashActivity.this,"IO异常");enterHome();break;case JSON_ERROR:// 5.弹出JSON错误ToastUtil.show(SplashActivity.this,"json异常");enterHome();break;default:break;}}};/*** 更新新版本的状态码*/private static final int UPDATE_VERSION = 100;/*** 进入应用程序主界面的状态码*/private static final int ENTER_HOME = 101;/*** URL地址出错的状态码*/private static final int URL_ERROR = 102;/*** IO操作出错的状态码*/private static final int IO_ERROR = 103;/*** JSON解析出错的状态码*/private static final int JSON_ERROR = 104;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_splash);// 初始化UIinitUI();// 初始化数据initData();// 初始化动画initAnimation();}/*** 1.初始化UI*/private void initUI() {tv_version_name = findViewById(R.id.tv_version_name);rl_root = findViewById(R.id.rl_root);}/*** 2.初始化数据*/private void initData() {// 1.获取应用版本名称String versionName = getVersionName();// 2.将应用版本名称设置到文本控件中tv_version_name.setText("版本名称:" + versionName);// 3.获取本地(客户端)版本号mLocalVersionCode = getVersionCode();// 4.获取服务端版本号(客户端发请求,服务端给响应(Json、Xml))/*Json中内容应该包括:1.更新版本的名称 versionName2.新版本的描述信息 versionDes3.服务器的版本号 versionCode4.新版本apk下载地址 downloadUrl*/checkVersion();}/*** 3.获取版本应用名称(在清单文件中)* @return 版本名称*/private String getVersionName() {// 1.获取包管理对象packageManager PackageManager pm = getPackageManager();// 2.从包管理对象中,获取指定包名的基本信息(版本名称,版本号),第二个参数传0代表获取基本信息try {PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0);// 3.获取并返回版本名称return packageInfo.versionName;} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}// 4.抛出异常return null;}/*** 4.获取版本号(在清单文件中)* @return 版本号*/private int getVersionCode() {// 1.获取包管理对象packageManagerPackageManager pm = getPackageManager();// 2.从包管理对象中,获取指定包名的基本信息(版本名称,版本号),第二个参数传0代表获取基本信息try {PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0);// 3.获取并返回版本编号return packageInfo.versionCode;} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}return 0;}/*** 5.获取服务端版本号*/private void checkVersion() {// 发送请求,获取数据,参数则为请求json的链接地址new Thread(){@Overridepublic void run() {// 0.获取message对象Message msg = Message.obtain();long startTime = System.currentTimeMillis();// 获取时间戳try {// 1.封装url地址URL url = new URL("http://10.0.2.2:8080/update74.json");// 2.开启一个链接HttpURLConnection connection = (HttpURLConnection) url.openConnection();// 3.设置常见请求参数(请求头)connection.setConnectTimeout(2000); // 请求超时connection.setReadTimeout(2000); // 读取超时//connection.setRequestMethod("GET"); // 请求方式,默认是get请求方式// 4.获取相应码,200为请求成功if (connection.getResponseCode() == 200){// 5.以流的形式将数据获取下来InputStream is = connection.getInputStream();// 6.将流转换成字符串(工具封装类)String json = StreamUtil.streamToString(is);// 7.json解析JSONObject jsonObject = new JSONObject(json);String versionName = jsonObject.getString("versionName");mVersionDes = jsonObject.getString("versionDes");String versionCode = jsonObject.getString("versionCode");mDownloadUrl = jsonObject.getString("downloadUrl");// 8.比对版本号(服务器版本号 > 本地版本号,提示用户更新)if (Integer.parseInt(versionCode) > mLocalVersionCode){// 9.提示用户更新,弹出对话框(UI),需要使用到消息机制msg.what = UPDATE_VERSION;}else {// 10.不需要更新,直接进入应用程序主界面msg.what = ENTER_HOME;}}}catch (MalformedURLException e) {e.printStackTrace();msg.what = URL_ERROR;}catch (IOException e) {e.printStackTrace();msg.what = IO_ERROR;}catch (JSONException e) {e.printStackTrace();msg.what = JSON_ERROR;}finally {// 11.指定睡眠时间,请求网络的时长超过4秒则不做处理,若小于4秒,则强制让其睡眠满4秒long endTime = System.currentTimeMillis();if (endTime - startTime < 4000){try {Thread.sleep(4000 - (endTime - startTime));} catch (Exception e) {e.printStackTrace();}}// 12.发送消息mHandler.sendMessage(msg);}}}.start();}/*** 6.进入应用程序的主界面*/private void enterHome() {Intent intent = new Intent(this, HomeActivity.class);startActivity(intent);finish(); // 开启新界面后,将导航界面销毁掉}/*** 7.弹出更新对话框*/private void showUpdateDialog() {final AlertDialog.Builder builder = new AlertDialog.Builder(this);builder.setIcon(R.drawable.ic_launcher); // 设置左上角图标builder.setTitle("版本更新"); // 设置标题builder.setMessage(mVersionDes); // 设置描述内容builder.setPositiveButton("立即更新", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {// 下载apk,需要apk的链接地址,即downloadUrldownloadApk();}});// 积极按钮,“是”builder.setNegativeButton("稍后再说", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {// 取消对话框,进入主界面enterHome();}});// 消极按钮,“否”builder.setOnCancelListener(new DialogInterface.OnCancelListener() {@Overridepublic void onCancel(DialogInterface dialog) {// 按下回退后,进入主界面,然后隐藏对话框enterHome();dialog.dismiss();}});// 回退按钮builder.show();}/*** 8.APK下载*/private void downloadApk() {// 需要apk下载链接地址,放置apk的所在路径// 1.判断sd卡是否可用,是否挂载if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){// 2.获取sd卡路径String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "app-release.apk";Log.i(tag,"路径为:" + path);// 3.发送请求,获取Apk,并且放置到指定路径(下载地址,下载应用的放置位置,回调方法)HttpUtils httpUtils = new HttpUtils();httpUtils.download(mDownloadUrl, path, new RequestCallBack<File>() {@Overridepublic void onSuccess(ResponseInfo<File> responseInfo) {// 下载成功(下载过后的放置在sd卡中apk)Log.i(tag,"下载成功!");File file = responseInfo.result;installApk(file);}@Overridepublic void onFailure(HttpException e, String s) {// 下载失败Log.i(tag,"下载失败!");e.printStackTrace();}@Overridepublic void onStart() {// 刚刚开始下载Log.i(tag,"刚刚开始下载!");super.onStart();}@Overridepublic void onLoading(long total, long current, boolean isUploading) {// 下载过程(下载文件大小,当前的下载位置,是否正在下载)Log.i(tag,"下载中......文件大小为" + total + "当前的下载位置为" + current);super.onLoading(total, current, isUploading);}});}}/*** 9.APK安装* @param file 安装文件*/private void installApk(File file) {// 系统应用界面,源码,安装apk入口Intent intent = new Intent("android.intent.action.VIEW");intent.addCategory("android.intent.category.DEFAULT");// 文件作为数据源// intent.setData(Uri.fromFile(file));// 设置安装的类型// intent.setType("application/vnd.android.package-archive");intent.setDataAndType(Uri.fromFile(file),"application/vnd.android.package-archive");startActivityForResult(intent,0);}/*** 10.开启一个Activity后,返回结果* @param requestCode* @param resultCode* @param data*/@Overrideprotected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {enterHome();super.onActivityResult(requestCode, resultCode, data);}/*** 11.初始化动画*/private void initAnimation() {AlphaAnimation alphaAnimation = new AlphaAnimation(0,1);alphaAnimation.setDuration(3000);rl_root.startAnimation(alphaAnimation);}}

修改activity_splash.xml,给RelativeLayout增加id,代码如下:

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="/apk/res/android"xmlns:tools="/tools"android:id="@+id/rl_root"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@drawable/launcher_bg"tools:context=".activity.SplashActivity"><!-- android:shadowRadius="5" 表示阴影所在范围 --><TextViewandroid:id="@+id/tv_version_name"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:shadowDx="1"android:shadowDy="1"android:shadowColor="#f00"android:shadowRadius="5"android:text="版本名称"/><ProgressBarandroid:layout_below="@id/tv_version_name"android:layout_centerHorizontal="true"android:layout_width="wrap_content"android:layout_height="wrap_content"/></RelativeLayout>

为了让一些样式得到复用,我们将一些样式抽取到style.xml中,代码如下:

<resources><!-- Base application theme. --><style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"><!-- Customize your theme here. --><item name="colorPrimary">@color/colorPrimary</item><item name="colorPrimaryDark">@color/colorPrimaryDark</item><item name="colorAccent">@color/colorAccent</item><item name="windowNoTitle">true</item></style><style name="TitleStyle"><item name="android:gravity">center</item><item name="android:textSize">20sp</item><item name="android:textColor">#000</item><item name="android:padding">10dp</item><item name="android:background">#0f0</item><item name="android:layout_width">match_parent</item><item name="android:layout_height">wrap_content</item></style></resources>

开始编写HomeActivity,首先编写activity_home.xml,添加相应控件,注意这里有一个具有跑马灯效果的TextView控件,代码如下:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="/apk/res/android"xmlns:tools="/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:context=".activity.HomeActivity"><!-- 将对应属性抽取到样式当中去 --><TextViewandroid:text="功能列表"style="@style/TitleStyle"/><!-- android:ellipsize="end" 添加省略点的所在位置 --><!-- 跑马灯 --><TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:text="欢迎使用本应用!作者赈川,欢迎共同探讨!QQ:545646733,测试测试测试测试"android:textColor="#000"android:singleLine="true"android:padding="5dp"android:ellipsize="marquee"/></LinearLayout>

2.自定义获取焦点的TextView

在上一节中,我们设置了具有跑马灯效果的TextView,然而,该控件并未实现该效果,实际上是因为焦点没有进行自定义获取的原因

修改activity_home.xml,对TextView控件进行相应修改,代码如下:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="/apk/res/android"xmlns:tools="/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:context=".activity.HomeActivity"><!-- 将对应属性抽取到样式当中去 --><TextViewandroid:text="功能列表"style="@style/TitleStyle"/><!-- 想要实现跑马灯效果,需实现下面三条属性 --><!-- 1.android:ellipsize="end" 添加省略点的所在位置 --><!-- 2.android:focus="true" 获取焦点 --><!-- 3.android:focusableInTouchMode="true" 触摸时仍然获取焦点 --><TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:text="欢迎使用本应用!作者赈川,欢迎共同探讨!QQ:545646733,测试测试测试测试"android:textColor="#000"android:singleLine="true"android:padding="5dp"android:focusable="true"android:focusableInTouchMode="true"android:marqueeRepeatLimit="marquee_forever"android:ellipsize="marquee"/></LinearLayout>

注意:经测试,在高版本的Android环境下,仅在xml中对原生控件进行修改已经达不到跑马灯效果了,如果想要实现这个功能的读者,可以尝试自定义View,及以下将要讲解的部分

现在想要实现一个具有跑马灯效果的自定义控件,在包下新建view包,存放自定义控件,并新建FocusTextView类,代码如下:

package com.example.mobilesafe.view;import android.content.Context;import android.util.AttributeSet;import android.widget.TextView;import androidx.annotation.Nullable;/*** 能够获取焦点的自定义TextView*/public class FocusTextView extends TextView {/*** 通过在Java代码来创建控件* @param context 上下文*/public FocusTextView(Context context) {super(context);}/*** 通过在xml代码来创建控件* @param context 上下文* @param attrs 属性*/public FocusTextView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);}/*** 通过在xml代码(结合Style)来创建控件* @param context 上下文* @param attrs 属性* @param defStyleAttr 样式*/public FocusTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}/*** 获取焦点的方法* @return*/@Overridepublic boolean isFocused() {return true;}}

修改activity_home.xml,使用自定义控件FocusTextView来替换原有的TextView,代码如下:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="/apk/res/android"xmlns:tools="/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:context=".activity.HomeActivity"><!-- 将对应属性抽取到样式当中去 --><TextViewandroid:text="功能列表"style="@style/TitleStyle"/><!-- 想要实现跑马灯效果,需实现下面三条属性 --><!-- 1.android:ellipsize="end" 添加省略点的所在位置2.android:focus="true" 获取焦点3.android:focusableInTouchMode="true" 触摸时仍然获取焦点(可选)4.android:marqueeRepeatLimit="marquee_forever" 永远滚动 --><!--<TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:text="欢迎使用本应用!作者赈川,欢迎共同探讨!QQ:545646733,测试测试测试测试"android:textColor="#000"android:singleLine="true"android:padding="5dp"android:focusable="true"android:focusableInTouchMode="true"android:marqueeRepeatLimit="marquee_forever"android:ellipsize="marquee"/>--><com.example.mobilesafe.view.FocusTextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:text="欢迎使用本应用!作者赈川,欢迎共同探讨!QQ:545646733,测试测试测试测试"android:textColor="#000"android:singleLine="true"android:padding="5dp"android:marqueeRepeatLimit="marquee_forever"android:ellipsize="marquee"/></LinearLayout>

3.自定义控件回顾

上一节我们写了一个简单的自定义控件,这里我们来回顾一下这个过程

创建一个类,继承至TextView重写其构造方法重写相应的属性方法在xml布局文件中使用(注意调用时需要全路径名称)

4.九宫格使用

由于主菜单采用九宫格样式,这里可以使用GridView来实现

修改activity_home.xml,添加gridview控件,并进行相应配置,代码如下:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="/apk/res/android"xmlns:tools="/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:context=".activity.HomeActivity"><!-- 将对应属性抽取到样式当中去 --><TextViewandroid:text="功能列表"style="@style/TitleStyle"/><!-- 想要实现跑马灯效果,需实现下面三条属性 --><!-- 1.android:ellipsize="end" 添加省略点的所在位置2.android:focus="true" 获取焦点3.android:focusableInTouchMode="true" 触摸时仍然获取焦点(可选)4.android:marqueeRepeatLimit="marquee_forever" 永远滚动 --><!--<TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:text="欢迎使用本应用!作者赈川,欢迎共同探讨!QQ:545646733,测试测试测试测试"android:textColor="#000"android:singleLine="true"android:padding="5dp"android:focusable="true"android:focusableInTouchMode="true"android:marqueeRepeatLimit="marquee_forever"android:ellipsize="marquee"/>--><com.example.mobilesafe.view.FocusTextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:text="欢迎使用本应用!作者赈川,欢迎共同探讨!QQ:545646733,测试测试测试测试"android:textColor="#000"android:singleLine="true"android:padding="5dp"android:marqueeRepeatLimit="marquee_forever"android:ellipsize="marquee"/><!-- android:numColumns 指定列数 --><GridViewandroid:id="@+id/gv_home"android:numColumns="3"android:layout_width="match_parent"android:layout_height="match_parent"/></LinearLayout>

修改HomeActivity,处理GridView相关逻辑,添加适配器内部类,代码如下:

package com.example.mobilesafe.activity;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;import android.view.View;import android.view.ViewGroup;import android.widget.BaseAdapter;import android.widget.GridView;import android.widget.ImageView;import android.widget.TextView;import com.example.mobilesafe.R;public class HomeActivity extends AppCompatActivity {/*** 存储标题*/private String[] mTitleStrs;/*** 存储图像*/private int[] mDrawableIds;/*** 网格对象*/private GridView gv_home;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_home);// 初始化UIinitUI();// 初始化数据initData();}/*** 1.初始化UI*/private void initUI() {gv_home = findViewById(R.id.gv_home);}/*** 2.初始化数据*/private void initData() {// 1.初始化每个图标的标题mTitleStrs = new String[]{"手机防盗","通信卫士","软件管理","进程管理","流量统计","手机杀毒","缓存清理","高级工具","设置中心"};// 2.初始化每个图标的图像mDrawableIds = new int[]{R.drawable.home_safe,R.drawable.home_callmsgsafe,R.drawable.home_apps,R.drawable.home_taskmanager,R.drawable.home_netmanager,R.drawable.home_trojan,R.drawable.home_sysoptimize,R.drawable.home_tools,R.drawable.home_settings};// 3.为GridView设置数据适配器gv_home.setAdapter(new MyAdapter());}/*** 3.自定义的数据适配器类*/class MyAdapter extends BaseAdapter{@Overridepublic int getCount() {// 统计条目的总数return mTitleStrs.length;}@Overridepublic Object getItem(int position) {// 根据索引获取对象return mTitleStrs[position];}@Overridepublic long getItemId(int position) {// 获取索引return position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {// 获取视图View view = View.inflate(getApplicationContext(), R.layout.gridview_item, null);TextView tv_title = view.findViewById(R.id.tv_title);ImageView iv_icon = view.findViewById(R.id.iv_icon);tv_title.setText(mTitleStrs[position]);iv_icon.setBackgroundResource(mDrawableIds[position]);return view;}}}

在res/layout下新建gridview_item.xml,作为子项的布局,代码如下:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="/apk/res/android"android:layout_width="wrap_content"android:layout_height="wrap_content"android:gravity="center"android:orientation="vertical"><ImageViewandroid:id="@+id/iv_icon"android:layout_width="wrap_content"android:layout_height="wrap_content"android:background="@drawable/ic_launcher"/><TextViewandroid:id="@+id/tv_title"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="模块标题"/></LinearLayout>

5.设置中心——条目布局结构

在上一节中,我们完成了主界面的编写,这一节我们开始编写主界面中九个条目(模块)的点击事件,这里我们首先编写九宫格中最后一个条目:设置中心

修改HomeActivity,修改initData()方法,为GridView注册相应的点击事件,代码如下:

package com.example.mobilesafe.activity;import androidx.appcompat.app.AppCompatActivity;import android.content.Intent;import android.os.Bundle;import android.view.View;import android.view.ViewGroup;import android.widget.AdapterView;import android.widget.BaseAdapter;import android.widget.GridView;import android.widget.ImageView;import android.widget.TextView;import com.example.mobilesafe.R;public class HomeActivity extends AppCompatActivity {/*** 存储标题*/private String[] mTitleStrs;/*** 存储图像*/private int[] mDrawableIds;/*** 网格对象*/private GridView gv_home;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_home);// 初始化UIinitUI();// 初始化数据initData();}/*** 1.初始化UI*/private void initUI() {gv_home = findViewById(R.id.gv_home);}/*** 2.初始化数据*/private void initData() {// 1.初始化每个图标的标题mTitleStrs = new String[]{"手机防盗","通信卫士","软件管理","进程管理","流量统计","手机杀毒","缓存清理","高级工具","设置中心"};// 2.初始化每个图标的图像mDrawableIds = new int[]{R.drawable.home_safe,R.drawable.home_callmsgsafe,R.drawable.home_apps,R.drawable.home_taskmanager,R.drawable.home_netmanager,R.drawable.home_trojan,R.drawable.home_sysoptimize,R.drawable.home_tools,R.drawable.home_settings};// 3.为GridView设置数据适配器gv_home.setAdapter(new MyAdapter());// 4.注册GridView中单个条目的点击事件gv_home.setOnItemClickListener(new AdapterView.OnItemClickListener() {@Overridepublic void onItemClick(AdapterView<?> parent, View view, int position, long id) {switch (position){case 8:Intent intent = new Intent(getApplicationContext(), SettingActivity.class);startActivity(intent);break;default:break;}}});}/*** 3.自定义的数据适配器类*/class MyAdapter extends BaseAdapter{@Overridepublic int getCount() {// 统计条目的总数return mTitleStrs.length;}@Overridepublic Object getItem(int position) {// 根据索引获取对象return mTitleStrs[position];}@Overridepublic long getItemId(int position) {// 获取索引return position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {// 获取视图View view = View.inflate(getApplicationContext(), R.layout.gridview_item, null);TextView tv_title = view.findViewById(R.id.tv_title);ImageView iv_icon = view.findViewById(R.id.iv_icon);tv_title.setText(mTitleStrs[position]);iv_icon.setBackgroundResource(mDrawableIds[position]);return view;}}}

在activity包下新建SettingActivity,作为设置中心的活动,代码模板套用EmptyActivity即可,然后修改activity_setting.xml,代码如下:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="/apk/res/android"xmlns:tools="/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".activity.SettingActivity"android:orientation="vertical"><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"><TextViewandroid:id="@+id/tv_title"android:text="自动更新设置"android:textColor="#000"android:textSize="18sp"android:layout_width="wrap_content"android:layout_height="wrap_content"/><TextViewandroid:id="@+id/tv_des"android:text="自动更新已关闭"android:textColor="#000"android:textSize="18sp"android:layout_below="@+id/tv_title"android:layout_width="wrap_content"android:layout_height="wrap_content"/><CheckBoxandroid:id="@+id/cb_box"android:layout_alignParentRight="true"android:layout_centerVertical="true"android:layout_width="wrap_content"android:layout_height="wrap_content"/><Viewandroid:background="#000"android:layout_below="@id/tv_des"android:layout_width="match_parent"android:layout_height="1dp"/></RelativeLayout></LinearLayout>

6.设置中心——自定义组合控件构成布局结构

在上一节中我们实现了一个条目,根据需求设置界面是由许多个这样的条目组成的,为了简化开发,我们可以使用自定义组合控件来封装这些组件,形成一个整体,如图所示:

组合控件的核心思想是:

将已经编写好的布局文件,抽取到一个类中去做管理,下次害需要使用此布局结构的时候,直接使用组合控件对应的对象即可将组合控件的布局,抽取到单独的一个xml中通过一个单独的类,去加载此段布局文件

下面开始编写代码

在res/layout目录下新建setting_item_view.xml,作为自定义组合控件的布局文件,代码如下:

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"><TextViewandroid:id="@+id/tv_title"android:text="自动更新设置"android:textColor="#000"android:textSize="18sp"android:layout_width="wrap_content"android:layout_height="wrap_content"/><TextViewandroid:id="@+id/tv_des"android:text="自动更新已关闭"android:textColor="#000"android:textSize="18sp"android:layout_below="@+id/tv_title"android:layout_width="wrap_content"android:layout_height="wrap_content"/><CheckBoxandroid:id="@+id/cb_box"android:layout_alignParentRight="true"android:layout_centerVertical="true"android:layout_width="wrap_content"android:layout_height="wrap_content"/><Viewandroid:background="#000"android:layout_below="@id/tv_des"android:layout_width="match_parent"android:layout_height="1dp"/></RelativeLayout></RelativeLayout>

在view下新建SettingItemView,作为组合控件的类,继承RelativeLayout,注意这里在调用inflate时第三个参数需要使用this来挂载到view上,原理如图所示:

代码如下:

package com.example.mobilesafe.view;import android.content.Context;import android.util.AttributeSet;import android.view.View;import android.widget.CheckBox;import android.widget.RelativeLayout;import android.widget.TextView;import com.example.mobilesafe.R;public class SettingItemView extends RelativeLayout {public SettingItemView(Context context) {this(context,null);}public SettingItemView(Context context, AttributeSet attrs) {this(context, attrs,0);}public SettingItemView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);// 1.将xml转换为view,即将设置界面的一个条目转换成view对象,并添加到了当前的类中View.inflate(context,R.layout.setting_item_view,this);// 2.获取自定义组合控件中的每个控件的实例TextView tv_title = findViewById(R.id.tv_title);TextView tv_des = findViewById(R.id.tv_des);CheckBox cb_box = findViewById(R.id.cb_box);}}

修改activity_setting.xml,调用刚刚编写好的自定义组合控件,代码如下:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="/apk/res/android"xmlns:tools="/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".activity.SettingActivity"android:orientation="vertical"><TextViewstyle="@style/TitleStyle"android:text="设置中心"/><com.example.mobilesafe.view.SettingItemViewandroid:layout_width="match_parent"android:layout_height="wrap_content"/></LinearLayout>

7.设置中心——自定义组合控件中相关方法

在上一节中,我们编写了简单的自定义组合控件,这一节需要完善相关的方法

修改SettingItemView,新增isCheck()和setCheck()方法,实现在点击CheckBox时,改变TextView的文本(未选中/已选中),代码如下:

package com.example.mobilesafe.view;import android.content.Context;import android.util.AttributeSet;import android.view.View;import android.widget.CheckBox;import android.widget.RelativeLayout;import android.widget.TextView;import com.example.mobilesafe.R;public class SettingItemView extends RelativeLayout {/*** CheckBox*/private CheckBox cb_box;/*** 文本描述控件*/private TextView tv_des;public SettingItemView(Context context) {this(context,null);}public SettingItemView(Context context, AttributeSet attrs) {this(context, attrs,0);}public SettingItemView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);// 1.将xml转换为view,即将设置界面的一个条目转换成view对象,并添加到了当前的类中View.inflate(context,R.layout.setting_item_view,this);// 2.获取自定义组合控件中的每个控件的实例TextView tv_title = findViewById(R.id.tv_title);tv_des = findViewById(R.id.tv_des);cb_box = findViewById(R.id.cb_box);}/*** 1.判断SettingItemView是否选中* @return SettingItemView是否选中状态*/public boolean isCheck() {// 有CheckBox的选中结果,决定当前条目是否开启return cb_box.isChecked();}/*** 2.设置SettingItemView的选中状态* @param isCheck 是否作为开启的变量,由点击过程中做传递*/public void setCheck(boolean isCheck){// 当前条目在选择的过程中,cb_box的选中状态也在跟随(isCheck)变化cb_box.setChecked(isCheck);if (isCheck){// 开启tv_des.setText("自动更新已开启");}else {// 关闭tv_des.setText("自动更新已关闭");}}}

8.设置中心——选中SettingItemView条目状态切换

上一节中我们完善了自定义组合控件的一些相关方法,现在需要进行进一步地完善

修改activity_setting.xml,给自定义控件加上id,代码如下:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="/apk/res/android"xmlns:tools="/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".activity.SettingActivity"android:orientation="vertical"><TextViewstyle="@style/TitleStyle"android:text="设置中心"/><com.example.mobilesafe.view.SettingItemViewandroid:id="@+id/siv_update"android:layout_width="match_parent"android:layout_height="wrap_content"/></LinearLayout>

修改SettingActivity,新增initUpdate(),作为修改CheckBox状态的方法,代码如下:

package com.example.mobilesafe.activity;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;import android.view.View;import com.example.mobilesafe.R;import com.example.mobilesafe.view.SettingItemView;public class SettingActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_setting);// 初始化更新initUpdate();}/*** 1.初始化"更新"条目的方法*/private void initUpdate() {final SettingItemView siv_update = findViewById(R.id.siv_update);siv_update.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {// 1.获取之前的选中状态boolean isCheck = siv_update.isCheck();// 2.取反选中状态siv_update.setCheck(!isCheck);}});}}

9.设置中心——事件传递 & 相应规则

上一节中我们完成了CheckBox的切换,但是逻辑中还存在一个涉及到事件传递的小Bug,这里我们来修复一下

先来复盘一下这个bug的产生原因:

SettingActivity对于布局文件的根布局,获取点击事件,此事件传递给SettingImageView

若点击在SettingitemView非CheckBox区域,事件就由SettingImageView去做响应若点击在SettingitemViewCheckBox区域,事件就由SettingImageView传递给CheckBox,然后由CheckBox去做响应,则SettingItemView就不能再去响应此事件,则相应的点击事件则不会执行

为了解决这个问题,这里使用一个最简单粗暴的方法:禁止CheckBox可以被点击并且禁用其焦点,这样CheckBox的点击事件就无法消费,便会传回给上一级控件,也就是SettingImageView,原理图如下:

代码如下:

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"><TextViewandroid:id="@+id/tv_title"android:text="自动更新设置"android:textColor="#000"android:textSize="18sp"android:layout_width="wrap_content"android:layout_height="wrap_content"/><TextViewandroid:id="@+id/tv_des"android:text="自动更新已关闭"android:textColor="#000"android:textSize="18sp"android:layout_below="@+id/tv_title"android:layout_width="wrap_content"android:layout_height="wrap_content"/><CheckBoxandroid:id="@+id/cb_box"android:clickable="false"android:focusable="false"android:focusableInTouchMode="false"android:layout_alignParentRight="true"android:layout_centerVertical="true"android:layout_width="wrap_content"android:layout_height="wrap_content"/><Viewandroid:background="#000"android:layout_below="@id/tv_des"android:layout_width="match_parent"android:layout_height="1dp"/></RelativeLayout></RelativeLayout>

10.设置中心——事件传递机制

在上面的这些小节中,我们大致完成了“设置中心”模块的编写,现在让我们以一张通俗易懂的图来复习一下前面因为遇到bug而面对的事件传递机制:

11.设置中心——sp工具类编写

解决了第一个bug,现在需要面对第二个bug:需要保存条目选中/未选中的状态,不然会导致从当前Activity回退再进来时状态的不一致,这里可以选中使用SharedPreference来实现,这里就编写一个工具类来方便以后在代码中进行相应调用

在utils包下新建SharedPreferencesUtil,作为工具类,其中主要的api和读取和写入,代码如下:

package com.example.mobilesafe.utils;import android.content.Context;import android.content.SharedPreferences;public class SharedPreferencesUtil {private static SharedPreferences sp;/*** 1.写入(boolean)* @param ctx 上下文* @param key 键* @param value 值*/public static void putBoolean(Context ctx,String key,boolean value){if (sp == null){sp = ctx.getSharedPreferences("config", Context.MODE_PRIVATE);}sp.edit().putBoolean(key,value).commit();}/*** 2.读取(boolean)* @param ctx 上下文* @param key 键* @param defValue (默认)值* @return 默认值或者相应结果*/public static boolean getBoolean(Context ctx,String key,boolean defValue){if (sp == null){sp = ctx.getSharedPreferences("config", Context.MODE_PRIVATE);}return sp.getBoolean(key,defValue);}}

12.设置中心——sp存储更新状态

上一节中我们完成了sharedpreferences的工具类编写,现在就需要使用它来存储更新状态

修改SettingActivity,修改initUpdate()方法,完善相应逻辑,代码如下:

package com.example.mobilesafe.activity;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;import android.view.View;import com.example.mobilesafe.R;import com.example.mobilesafe.constant.ConstantValue;import com.example.mobilesafe.utils.SharedPreferencesUtil;import com.example.mobilesafe.view.SettingItemView;public class SettingActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_setting);// 初始化更新initUpdate();}/*** 1.初始化"更新"条目的方法*/private void initUpdate() {final SettingItemView siv_update = findViewById(R.id.siv_update);// 0.从sp中获取已有的开关状态,然后根据这一次存储的结果去做决定boolean open_update = SharedPreferencesUtil.getBoolean(this, ConstantValue.OPEN_UPDATE, false);siv_update.setCheck(open_update);siv_update.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {// 1.获取之前的选中状态boolean isCheck = siv_update.isCheck();// 2.取反选中状态siv_update.setCheck(!isCheck);// 3.将该状态存储到sp中SharedPreferencesUtil.putBoolean(getApplicationContext(),ConstantValue.OPEN_UPDATE,!isCheck);}});}}

在包下新建constant包,然后在包下新建ConstantValue,作为存放一些静态常量的类,方便之后调用,代码如下:

package com.example.mobilesafe.constant;public class ConstantValue {/*** 记录更新的状态*/public static final String OPEN_UPDATE = "open_update";}

13.设置中心——使用sp来判断客户端是否需要更新

前面的小节我们使用了sp存储了更新状态,为了让该设置(即自动更新的功能)生效,这里我们再延伸一下,用于SplashActivity中的更新功能

修改SplashActivity,修改initData()方法,使用Sharepreferences(记录了更新状态,选择自动更新就会进入更新逻辑,若没有选择自动更新则直接进入主界面)来判断是否需要更新,代码如下:

package com.example.mobilesafe.activity;import androidx.annotation.NonNull;import androidx.annotation.Nullable;import androidx.appcompat.app.AlertDialog;import androidx.appcompat.app.AppCompatActivity;import android.content.DialogInterface;import android.content.Intent;import android.content.pm.PackageInfo;import android.content.pm.PackageManager;import .Uri;import android.os.Bundle;import android.os.Environment;import android.os.Handler;import android.os.Message;import android.util.Log;import android.view.animation.AlphaAnimation;import android.widget.RelativeLayout;import android.widget.TextView;import com.example.mobilesafe.R;import com.example.mobilesafe.constant.ConstantValue;import com.example.mobilesafe.utils.SharedPreferencesUtil;import com.example.mobilesafe.utils.StreamUtil;import com.example.mobilesafe.utils.ToastUtil;import com.lidroid.xutils.HttpUtils;import com.lidroid.xutils.exception.HttpException;import com.lidroid.xutils.http.ResponseInfo;import com.lidroid.xutils.http.callback.RequestCallBack;import org.json.JSONException;import org.json.JSONObject;import java.io.File;import java.io.IOException;import java.io.InputStream;import .HttpURLConnection;import .MalformedURLException;import .URL;public class SplashActivity extends AppCompatActivity {/*** 文本控件*/private TextView tv_version_name;/*** 根布局*/private RelativeLayout rl_root;/*** 本地版本号*/private int mLocalVersionCode;/*** 更新时描述信息*/private String mVersionDes;/*** 更新时的URL*/private String mDownloadUrl;private static final String tag = "SplashActivity";/*** Handler对象*/private Handler mHandler = new Handler(){@Overridepublic void handleMessage(@NonNull Message msg) {switch (msg.what){case UPDATE_VERSION:// 1.弹出对话框,提示用户更新showUpdateDialog();break;case ENTER_HOME:// 2.直接进入应用程序主界面enterHome();break;case URL_ERROR:// 3.弹出URL错误ToastUtil.show(SplashActivity.this,"url异常");enterHome();break;case IO_ERROR:// 4.弹出IO错误ToastUtil.show(SplashActivity.this,"IO异常");enterHome();break;case JSON_ERROR:// 5.弹出JSON错误ToastUtil.show(SplashActivity.this,"json异常");enterHome();break;default:break;}}};/*** 更新新版本的状态码*/private static final int UPDATE_VERSION = 100;/*** 进入应用程序主界面的状态码*/private static final int ENTER_HOME = 101;/*** URL地址出错的状态码*/private static final int URL_ERROR = 102;/*** IO操作出错的状态码*/private static final int IO_ERROR = 103;/*** JSON解析出错的状态码*/private static final int JSON_ERROR = 104;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_splash);// 初始化UIinitUI();// 初始化数据initData();// 初始化动画initAnimation();}/*** 1.初始化UI*/private void initUI() {tv_version_name = findViewById(R.id.tv_version_name);rl_root = findViewById(R.id.rl_root);}/*** 2.初始化数据*/private void initData() {// 1.获取应用版本名称String versionName = getVersionName();// 2.将应用版本名称设置到文本控件中tv_version_name.setText("版本名称:" + versionName);// 3.获取本地(客户端)版本号mLocalVersionCode = getVersionCode();// 4.获取服务端版本号(客户端发请求,服务端给响应(Json、Xml))/*Json中内容应该包括:1.更新版本的名称 versionName2.新版本的描述信息 versionDes3.服务器的版本号 versionCode4.新版本apk下载地址 downloadUrl*/if (SharedPreferencesUtil.getBoolean(this, ConstantValue.OPEN_UPDATE,false)){checkVersion();}else {// 这里不调用enterHome(),是因为直接调用会很快进入主界面(HomeActivity),跳过闪屏页面(SplashActivity)// 也不选择Thread.sleep(4000)后再enterHome(),是因为在主线程阻塞4秒风险太大,若到达7秒则会ANR// 所以选择发送消息的形式来实现在没有选择“自动更新”的情况下直接进入主界面,即时发送消息后,延时4秒钟,否则会太快进入主界面mHandler.sendEmptyMessageDelayed(ENTER_HOME,4000);}}/*** 3.获取版本应用名称(在清单文件中)* @return 版本名称*/private String getVersionName() {// 1.获取包管理对象packageManager PackageManager pm = getPackageManager();// 2.从包管理对象中,获取指定包名的基本信息(版本名称,版本号),第二个参数传0代表获取基本信息try {PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0);// 3.获取并返回版本名称return packageInfo.versionName;} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}// 4.抛出异常return null;}/*** 4.获取版本号(在清单文件中)* @return 版本号*/private int getVersionCode() {// 1.获取包管理对象packageManagerPackageManager pm = getPackageManager();// 2.从包管理对象中,获取指定包名的基本信息(版本名称,版本号),第二个参数传0代表获取基本信息try {PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0);// 3.获取并返回版本编号return packageInfo.versionCode;} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}return 0;}/*** 5.获取服务端版本号*/private void checkVersion() {// 发送请求,获取数据,参数则为请求json的链接地址new Thread(){@Overridepublic void run() {// 0.获取message对象Message msg = Message.obtain();long startTime = System.currentTimeMillis();// 获取时间戳try {// 1.封装url地址URL url = new URL("http://10.0.2.2:8080/update74.json");// 2.开启一个链接HttpURLConnection connection = (HttpURLConnection) url.openConnection();// 3.设置常见请求参数(请求头)connection.setConnectTimeout(2000); // 请求超时connection.setReadTimeout(2000); // 读取超时//connection.setRequestMethod("GET"); // 请求方式,默认是get请求方式// 4.获取相应码,200为请求成功if (connection.getResponseCode() == 200){// 5.以流的形式将数据获取下来InputStream is = connection.getInputStream();// 6.将流转换成字符串(工具封装类)String json = StreamUtil.streamToString(is);// 7.json解析JSONObject jsonObject = new JSONObject(json);String versionName = jsonObject.getString("versionName");mVersionDes = jsonObject.getString("versionDes");String versionCode = jsonObject.getString("versionCode");mDownloadUrl = jsonObject.getString("downloadUrl");// 8.比对版本号(服务器版本号 > 本地版本号,提示用户更新)if (Integer.parseInt(versionCode) > mLocalVersionCode){// 9.提示用户更新,弹出对话框(UI),需要使用到消息机制msg.what = UPDATE_VERSION;}else {// 10.不需要更新,直接进入应用程序主界面msg.what = ENTER_HOME;}}}catch (MalformedURLException e) {e.printStackTrace();msg.what = URL_ERROR;}catch (IOException e) {e.printStackTrace();msg.what = IO_ERROR;}catch (JSONException e) {e.printStackTrace();msg.what = JSON_ERROR;}finally {// 11.指定睡眠时间,请求网络的时长超过4秒则不做处理,若小于4秒,则强制让其睡眠满4秒long endTime = System.currentTimeMillis();if (endTime - startTime < 4000){try {Thread.sleep(4000 - (endTime - startTime));} catch (Exception e) {e.printStackTrace();}}// 12.发送消息mHandler.sendMessage(msg);}}}.start();}/*** 6.进入应用程序的主界面*/private void enterHome() {Intent intent = new Intent(this, HomeActivity.class);startActivity(intent);finish(); // 开启新界面后,将导航界面销毁掉}/*** 7.弹出更新对话框*/private void showUpdateDialog() {final AlertDialog.Builder builder = new AlertDialog.Builder(this);builder.setIcon(R.drawable.ic_launcher); // 设置左上角图标builder.setTitle("版本更新"); // 设置标题builder.setMessage(mVersionDes); // 设置描述内容builder.setPositiveButton("立即更新", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {// 下载apk,需要apk的链接地址,即downloadUrldownloadApk();}});// 积极按钮,“是”builder.setNegativeButton("稍后再说", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {// 取消对话框,进入主界面enterHome();}});// 消极按钮,“否”builder.setOnCancelListener(new DialogInterface.OnCancelListener() {@Overridepublic void onCancel(DialogInterface dialog) {// 按下回退后,进入主界面,然后隐藏对话框enterHome();dialog.dismiss();}});// 回退按钮builder.show();}/*** 8.APK下载*/private void downloadApk() {// 需要apk下载链接地址,放置apk的所在路径// 1.判断sd卡是否可用,是否挂载if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){// 2.获取sd卡路径// String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "app-release.apk";String path = (SplashActivity.this).getExternalFilesDir(null) + File.separator + "app-release.apk";Log.i(tag,"路径为:" + path);// 3.发送请求,获取Apk,并且放置到指定路径(下载地址,下载应用的放置位置,回调方法)HttpUtils httpUtils = new HttpUtils();httpUtils.download(mDownloadUrl, path, new RequestCallBack<File>() {@Overridepublic void onSuccess(ResponseInfo<File> responseInfo) {// 下载成功(下载过后的放置在sd卡中apk)Log.i(tag,"下载成功!");File file = responseInfo.result;installApk(file);}@Overridepublic void onFailure(HttpException e, String s) {// 下载失败Log.i(tag,"下载失败!");e.printStackTrace();}@Overridepublic void onStart() {// 刚刚开始下载Log.i(tag,"刚刚开始下载!");super.onStart();}@Overridepublic void onLoading(long total, long current, boolean isUploading) {// 下载过程(下载文件大小,当前的下载位置,是否正在下载)Log.i(tag,"下载中......文件大小为" + total + "当前的下载位置为" + current);super.onLoading(total, current, isUploading);}});}}/*** 9.APK安装* @param file 安装文件*/private void installApk(File file) {// 系统应用界面,源码,安装apk入口Intent intent = new Intent("android.intent.action.VIEW");intent.addCategory("android.intent.category.DEFAULT");// 文件作为数据源// intent.setData(Uri.fromFile(file));// 设置安装的类型// intent.setType("application/vnd.android.package-archive");intent.setDataAndType(Uri.fromFile(file),"application/vnd.android.package-archive");startActivityForResult(intent,0);}/*** 10.开启一个Activity后,返回结果* @param requestCode* @param resultCode* @param data*/@Overrideprotected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {enterHome();super.onActivityResult(requestCode, resultCode, data);}/*** 11.初始化动画*/private void initAnimation() {AlphaAnimation alphaAnimation = new AlphaAnimation(0,1);alphaAnimation.setDuration(3000);rl_root.startAnimation(alphaAnimation);}}

注意:

1.在else分支中,这里不调用enterHome(),是因为直接调用会很快进入主界面(HomeActivity),跳过闪屏页面(SplashActivity)

2.也不选择Thread.sleep(4000)后再enterHome(),是因为在主线程阻塞4秒风险太大,若到达7秒则会ANR

3.所以选择发送消息的形式来实现在没有选择“自动更新”的情况下直接进入主界面,即时发送消息后,延时4秒钟,否则会太快进入主界面

14.设置中心——自定义属性申明

现在我们完成了设置中心中“自动更新”的功能,如图所示:

为了增加其他的功能,需要增加其他的条目(即复用之前的自定义组合控件),为了让每个条目都有所区别,这里就需要运用到一些自定义属性

参照源码,在res/value下新建attrs.xml,存放一些用到的自定义属性,代码如下:

<?xml version="1.0" encoding="utf-8"?><resources><declare-styleable name="com.example.mobilesafe.view.SettingItemView"><!-- 标题内容 --><attr name="destitle" format="string"/><!-- 单选框关闭时的文本内容 --><attr name="desoff" format="string"/><!-- 单选框开启时的文本内容 --><attr name="deson" format="string"/></declare-styleable></resources>

15.设置中心——构造方法中获取自定义属性值

上一节中我们申明了自定义属性,这一节需要我们在构造方法中使用这些属性,大概分为这几步:

定义名空间namespace:类似于xmlns:mobilesafe="/apk/res/com.example.mobilesafe"在控件中调用相关属性:类似于mobilesafe:destitle="自动更新设置"需要在view类中获取相应属性值

现在开始编写代码

修改activity_setting.xml,调用自定义属性,代码如下:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="/apk/res/android"xmlns:tools="/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".activity.SettingActivity"android:orientation="vertical"><TextViewstyle="@style/TitleStyle"android:text="设置中心"/><!-- 自动更新 --><com.example.mobilesafe.view.SettingItemViewxmlns:mobilesafe="/apk/res/com.example.mobilesafe"android:id="@+id/siv_update"android:layout_width="match_parent"android:layout_height="wrap_content"mobilesafe:destitle="自动更新设置"mobilesafe:desoff="自动更新已关闭"mobilesafe:deson="自动更新已开启"/></LinearLayout>

修改SettingItemView,添加initAttrs()方法,用于修改自定义属性集合,代码如下:

package com.example.mobilesafe.view;import android.content.Context;import android.util.AttributeSet;import android.view.View;import android.widget.CheckBox;import android.widget.RelativeLayout;import android.widget.TextView;import com.example.mobilesafe.R;public class SettingItemView extends RelativeLayout {/*** CheckBox*/private CheckBox cb_box;/*** 文本描述控件*/private TextView tv_des;public SettingItemView(Context context) {this(context,null);}public SettingItemView(Context context, AttributeSet attrs) {this(context, attrs,0);}public SettingItemView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);// 1.将xml转换为view,即将设置界面的一个条目转换成view对象,并添加到了当前的类中View.inflate(context,R.layout.setting_item_view,this);// 2.获取自定义组合控件中的每个控件的实例TextView tv_title = findViewById(R.id.tv_title);tv_des = findViewById(R.id.tv_des);cb_box = findViewById(R.id.cb_box);// 3.获取自定义以及原生属性,从AttributeSet attrs参数中获取initAttrs(attrs);}/*** 1.判断SettingItemView是否选中* @return SettingItemView是否选中状态*/public boolean isCheck() {// 有CheckBox的选中结果,决定当前条目是否开启return cb_box.isChecked();}/*** 2.设置SettingItemView的选中状态* @param isCheck 是否作为开启的变量,由点击过程中做传递*/public void setCheck(boolean isCheck){// 当前条目在选择的过程中,cb_box的选中状态也在跟随(isCheck)变化cb_box.setChecked(isCheck);if (isCheck){// 开启tv_des.setText("自动更新已开启");}else {// 关闭tv_des.setText("自动更新已关闭");}}/*** 3.初始化属性* @param attrs 维护好的属性集合*/private void initAttrs(AttributeSet attrs) {}}

16.设置中心——给自定义组合控件内部控件赋值

上一节中我们定义了initAttrs()方法,现在来完善它的具体实现

修改SettingItemView,修改initAttrs()方法,完善相应逻辑,代码如下:

package com.example.mobilesafe.view;import android.content.Context;import android.util.AttributeSet;import android.view.View;import android.widget.CheckBox;import android.widget.RelativeLayout;import android.widget.TextView;import com.example.mobilesafe.R;public class SettingItemView extends RelativeLayout {/*** 自定义的命名空间*/private static final String NAMESPACE = "/apk/res/com.example.mobilesafe";/*** CheckBox*/private CheckBox cb_box;/*** 文本描述控件*/private TextView tv_des;/*** 自定义命名空间:标题*/private String mDestitle;/*** 自定义命名空间:关闭按钮后文本*/private String mDesoff;/*** 自定义命名空间:开启按钮后文本*/private String mDeson;public SettingItemView(Context context) {this(context,null);}public SettingItemView(Context context, AttributeSet attrs) {this(context, attrs,0);}public SettingItemView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);// 1.将xml转换为view,即将设置界面的一个条目转换成view对象,并添加到了当前的类中View.inflate(context,R.layout.setting_item_view,this);// 2.获取自定义组合控件中的每个控件的实例TextView tv_title = findViewById(R.id.tv_title);tv_des = findViewById(R.id.tv_des);cb_box = findViewById(R.id.cb_box);// 3.获取自定义以及原生属性,从AttributeSet attrs参数中获取initAttrs(attrs);// 4.为控件赋值tv_title.setText(mDestitle);}/*** 1.判断SettingItemView是否选中* @return SettingItemView是否选中状态*/public boolean isCheck() {// 有CheckBox的选中结果,决定当前条目是否开启return cb_box.isChecked();}/*** 2.设置SettingItemView的选中状态* @param isCheck 是否作为开启的变量,由点击过程中做传递*/public void setCheck(boolean isCheck){// 当前条目在选择的过程中,cb_box的选中状态也在跟随(isCheck)变化cb_box.setChecked(isCheck);if (isCheck){// 开启tv_des.setText(mDeson);}else {// 关闭tv_des.setText(mDesoff);}}/*** 3.初始化属性* @param attrs 维护好的属性集合*/private void initAttrs(AttributeSet attrs) {mDestitle = attrs.getAttributeValue(NAMESPACE, "destitle");mDesoff = attrs.getAttributeValue(NAMESPACE, "desoff");mDeson = attrs.getAttributeValue(NAMESPACE, "deson");}}

修改setting_item_view.xml,将写死的文本删除,便于之后代码进行复用,代码如下:

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"><TextViewandroid:id="@+id/tv_title"android:textColor="#000"android:textSize="18sp"android:layout_width="wrap_content"android:layout_height="wrap_content"/><TextViewandroid:id="@+id/tv_des"android:textColor="#000"android:textSize="18sp"android:layout_below="@+id/tv_title"android:layout_width="wrap_content"android:layout_height="wrap_content"/><CheckBoxandroid:id="@+id/cb_box"android:clickable="false"android:focusable="false"android:focusableInTouchMode="false"android:layout_alignParentRight="true"android:layout_centerVertical="true"android:layout_width="wrap_content"android:layout_height="wrap_content"/><Viewandroid:background="#000"android:layout_below="@id/tv_des"android:layout_width="match_parent"android:layout_height="1dp"/></RelativeLayout></RelativeLayout>

17.手机防盗——是否有密码区分对话框类型

设置中心的第一个功能——自动更新可以先告一段落了,现在我们先编写九宫格中的第二个模块——手机防盗,其中的一个功能就是设置密码,如图所示:

在首次使用该功能时,需要设置一个初始密码,之后才能进入,后面再从主界面进入时,需要输入初始密码,才能使用该功能,如图所示:

现在我们先来实现这个对话框的逻辑

修改SharedPreferencesUtil,添加读写字符串的方法,用作存储密码,代码如下:

package com.example.mobilesafe.utils;import android.content.Context;import android.content.SharedPreferences;public class SharedPreferencesUtil {private static SharedPreferences sp;/*** 1.写入(boolean)* @param ctx 上下文* @param key 键* @param value 值*/public static void putBoolean(Context ctx,String key,boolean value){if (sp == null){sp = ctx.getSharedPreferences("config", Context.MODE_PRIVATE);}sp.edit().putBoolean(key,value).commit();}/*** 2.读取(boolean)* @param ctx 上下文* @param key 键* @param defValue (默认)值* @return 默认值或者相应结果*/public static boolean getBoolean(Context ctx,String key,boolean defValue){if (sp == null){sp = ctx.getSharedPreferences("config", Context.MODE_PRIVATE);}return sp.getBoolean(key,defValue);}/*** 3.写入(string)* @param ctx 上下文* @param key 键* @param value 值*/public static void putString(Context ctx,String key,String value){if (sp == null){sp = ctx.getSharedPreferences("config", Context.MODE_PRIVATE);}sp.edit().putString(key,value).commit();}/*** 4.读取(string)* @param ctx 上下文* @param key 键* @param defValue (默认)值* @return 默认值或者相应结果*/public static String getString(Context ctx,String key,String defValue){if (sp == null){sp = ctx.getSharedPreferences("config", Context.MODE_PRIVATE);}return sp.getString(key,defValue);}}

修改ConstantValue,新增一个代表密码的标识符,代码如下:

package com.example.mobilesafe.constant;public class ConstantValue {/*** 记录更新的状态*/public static final String OPEN_UPDATE = "open_update";/*** 手机防盗——设置密码的状态*/public static final String MOBILE_SAFE_PASSWORD = "mobile_safe_password";}

修改HomeActivity,添加showDialog(),作为弹出密码编辑的对话框的方法,代码如下:

package com.example.mobilesafe.activity;import androidx.appcompat.app.AppCompatActivity;import android.content.Intent;import android.os.Bundle;import android.text.TextUtils;import android.view.View;import android.view.ViewGroup;import android.widget.AdapterView;import android.widget.BaseAdapter;import android.widget.GridView;import android.widget.ImageView;import android.widget.TextView;import com.example.mobilesafe.R;import com.example.mobilesafe.constant.ConstantValue;import com.example.mobilesafe.utils.SharedPreferencesUtil;public class HomeActivity extends AppCompatActivity {/*** 存储标题*/private String[] mTitleStrs;/*** 存储图像*/private int[] mDrawableIds;/*** 网格对象*/private GridView gv_home;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_home);// 初始化UIinitUI();// 初始化数据initData();}/*** 1.初始化UI*/private void initUI() {gv_home = findViewById(R.id.gv_home);}/*** 2.初始化数据*/private void initData() {// 1.初始化每个图标的标题mTitleStrs = new String[]{"手机防盗","通信卫士","软件管理","进程管理","流量统计","手机杀毒","缓存清理","高级工具","设置中心"};// 2.初始化每个图标的图像mDrawableIds = new int[]{R.drawable.home_safe,R.drawable.home_callmsgsafe,R.drawable.home_apps,R.drawable.home_taskmanager,R.drawable.home_netmanager,R.drawable.home_trojan,R.drawable.home_sysoptimize,R.drawable.home_tools,R.drawable.home_settings};// 3.为GridView设置数据适配器gv_home.setAdapter(new MyAdapter());// 4.注册GridView中单个条目的点击事件gv_home.setOnItemClickListener(new AdapterView.OnItemClickListener() {@Overridepublic void onItemClick(AdapterView<?> parent, View view, int position, long id) {switch (position){case 0:// 手机防盗showDialog();case 8:// 设置中心Intent intent = new Intent(getApplicationContext(), SettingActivity.class);startActivity(intent);break;default:break;}}});}/*** 3.自定义的数据适配器类*/class MyAdapter extends BaseAdapter{@Overridepublic int getCount() {// 统计条目的总数return mTitleStrs.length;}@Overridepublic Object getItem(int position) {// 根据索引获取对象return mTitleStrs[position];}@Overridepublic long getItemId(int position) {// 获取索引return position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {// 获取视图View view = View.inflate(getApplicationContext(), R.layout.gridview_item, null);TextView tv_title = view.findViewById(R.id.tv_title);ImageView iv_icon = view.findViewById(R.id.iv_icon);tv_title.setText(mTitleStrs[position]);iv_icon.setBackgroundResource(mDrawableIds[position]);return view;}}/*** 4.手机防盗——密码对话框*/private void showDialog() {// 1.通过判断本地是否有存储密码来确定显示哪个对话框(sp)String password = SharedPreferencesUtil.getString(this, ConstantValue.MOBILE_SAFE_PASSWORD, "");if (TextUtils.isEmpty(password)){// 2.初始设置密码对话框showSetPasswordDialog();}else {// 3.确认密码对话框showConfirmPasswordDialog();}}/*** 5.初次设置密码对话框*/private void showSetPasswordDialog() {}/*** 6.再次设置密码对话框*/private void showConfirmPasswordDialog() {}}

18.手机防盗——设置密码对话框

上一节中我们完成了密码对话框的骨架,现在来进一步完善编写的两个方法

修改HomeActivity,完善showSetPasswordDialog()方法,代码如下:

package com.example.mobilesafe.activity;import androidx.appcompat.app.AlertDialog;import androidx.appcompat.app.AppCompatActivity;import android.content.Intent;import android.os.Bundle;import android.text.TextUtils;import android.view.View;import android.view.ViewGroup;import android.widget.AdapterView;import android.widget.BaseAdapter;import android.widget.GridView;import android.widget.ImageView;import android.widget.TextView;import com.example.mobilesafe.R;import com.example.mobilesafe.constant.ConstantValue;import com.example.mobilesafe.utils.SharedPreferencesUtil;public class HomeActivity extends AppCompatActivity {/*** 存储标题*/private String[] mTitleStrs;/*** 存储图像*/private int[] mDrawableIds;/*** 网格对象*/private GridView gv_home;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_home);// 初始化UIinitUI();// 初始化数据initData();}/*** 1.初始化UI*/private void initUI() {gv_home = findViewById(R.id.gv_home);}/*** 2.初始化数据*/private void initData() {// 1.初始化每个图标的标题mTitleStrs = new String[]{"手机防盗","通信卫士","软件管理","进程管理","流量统计","手机杀毒","缓存清理","高级工具","设置中心"};// 2.初始化每个图标的图像mDrawableIds = new int[]{R.drawable.home_safe,R.drawable.home_callmsgsafe,R.drawable.home_apps,R.drawable.home_taskmanager,R.drawable.home_netmanager,R.drawable.home_trojan,R.drawable.home_sysoptimize,R.drawable.home_tools,R.drawable.home_settings};// 3.为GridView设置数据适配器gv_home.setAdapter(new MyAdapter());// 4.注册GridView中单个条目的点击事件gv_home.setOnItemClickListener(new AdapterView.OnItemClickListener() {@Overridepublic void onItemClick(AdapterView<?> parent, View view, int position, long id) {switch (position){case 0:// 手机防盗showDialog();break;case 8:// 设置中心Intent intent = new Intent(getApplicationContext(), SettingActivity.class);startActivity(intent);break;default:break;}}});}/*** 3.自定义的数据适配器类*/class MyAdapter extends BaseAdapter{@Overridepublic int getCount() {// 统计条目的总数return mTitleStrs.length;}@Overridepublic Object getItem(int position) {// 根据索引获取对象return mTitleStrs[position];}@Overridepublic long getItemId(int position) {// 获取索引return position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {// 获取视图View view = View.inflate(getApplicationContext(), R.layout.gridview_item, null);TextView tv_title = view.findViewById(R.id.tv_title);ImageView iv_icon = view.findViewById(R.id.iv_icon);tv_title.setText(mTitleStrs[position]);iv_icon.setBackgroundResource(mDrawableIds[position]);return view;}}/*** 4.手机防盗——密码对话框*/private void showDialog() {// 1.通过判断本地是否有存储密码来确定显示哪个对话框(sp)String password = SharedPreferencesUtil.getString(this, ConstantValue.MOBILE_SAFE_PASSWORD, "");if (TextUtils.isEmpty(password)){// 2.初始设置密码对话框showSetPasswordDialog();}else {// 3.确认密码对话框showConfirmPasswordDialog();}}/*** 5.初次设置密码对话框*/private void showSetPasswordDialog() {AlertDialog.Builder builder = new AlertDialog.Builder(this);AlertDialog dialog = builder.create();View view = View.inflate(this, R.layout.dialog_set_password, null);dialog.setView(view);dialog.show();}/*** 6.再次设置密码对话框*/private void showConfirmPasswordDialog() {}}

在res/layout下新建dialog_set_password,作为初始化密码的对话框的布局,代码如下:

<?xml version="1.0" encoding="utf-8"?><LinearLayoutxmlns:android="/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><TextViewstyle="@style/TitleStyle"android:text="设置密码"android:background="#f00"/><EditTextandroid:id="@+id/et_set_password"android:layout_width="match_parent"android:layout_height="wrap_content"android:hint="设置密码"/><EditTextandroid:id="@+id/et_confirm_password"android:layout_width="match_parent"android:layout_height="wrap_content"android:hint="确认密码"/><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"><Buttonandroid:id="@+id/btn_submit"android:text="确认"android:layout_width="0dp"android:layout_weight="1"android:layout_height="wrap_content"/><Buttonandroid:id="@+id/btn_cancel"android:text="取消"android:layout_width="0dp"android:layout_weight="1"android:layout_height="wrap_content"/></LinearLayout></LinearLayout>

19.手机防盗——对话初次设置密码验证过程

上一节中我们完成了初次设置密码的对话框的布局,现在需要完善相应的逻辑

修改HomeActivity,修改showSetPasswordDialog()方法,新建一个套用Empty Activity模板的TestActivity作为测试Activity,代码如下:

package com.example.mobilesafe.activity;import androidx.appcompat.app.AlertDialog;import androidx.appcompat.app.AppCompatActivity;import android.content.Intent;import android.os.Bundle;import android.text.TextUtils;import android.view.View;import android.view.ViewGroup;import android.widget.AdapterView;import android.widget.BaseAdapter;import android.widget.Button;import android.widget.EditText;import android.widget.GridView;import android.widget.ImageView;import android.widget.TextView;import com.example.mobilesafe.R;import com.example.mobilesafe.constant.ConstantValue;import com.example.mobilesafe.utils.SharedPreferencesUtil;import com.example.mobilesafe.utils.ToastUtil;public class HomeActivity extends AppCompatActivity {/*** 存储标题*/private String[] mTitleStrs;/*** 存储图像*/private int[] mDrawableIds;/*** 网格对象*/private GridView gv_home;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_home);// 初始化UIinitUI();// 初始化数据initData();}/*** 1.初始化UI*/private void initUI() {gv_home = findViewById(R.id.gv_home);}/*** 2.初始化数据*/private void initData() {// 1.初始化每个图标的标题mTitleStrs = new String[]{"手机防盗","通信卫士","软件管理","进程管理","流量统计","手机杀毒","缓存清理","高级工具","设置中心"};// 2.初始化每个图标的图像mDrawableIds = new int[]{R.drawable.home_safe,R.drawable.home_callmsgsafe,R.drawable.home_apps,R.drawable.home_taskmanager,R.drawable.home_netmanager,R.drawable.home_trojan,R.drawable.home_sysoptimize,R.drawable.home_tools,R.drawable.home_settings};// 3.为GridView设置数据适配器gv_home.setAdapter(new MyAdapter());// 4.注册GridView中单个条目的点击事件gv_home.setOnItemClickListener(new AdapterView.OnItemClickListener() {@Overridepublic void onItemClick(AdapterView<?> parent, View view, int position, long id) {switch (position){case 0:// 手机防盗showDialog();break;case 8:// 设置中心Intent intent = new Intent(getApplicationContext(), SettingActivity.class);startActivity(intent);break;default:break;}}});}/*** 3.自定义的数据适配器类*/class MyAdapter extends BaseAdapter{@Overridepublic int getCount() {// 统计条目的总数return mTitleStrs.length;}@Overridepublic Object getItem(int position) {// 根据索引获取对象return mTitleStrs[position];}@Overridepublic long getItemId(int position) {// 获取索引return position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {// 获取视图View view = View.inflate(getApplicationContext(), R.layout.gridview_item, null);TextView tv_title = view.findViewById(R.id.tv_title);ImageView iv_icon = view.findViewById(R.id.iv_icon);tv_title.setText(mTitleStrs[position]);iv_icon.setBackgroundResource(mDrawableIds[position]);return view;}}/*** 4.手机防盗——密码对话框*/private void showDialog() {// 1.通过判断本地是否有存储密码来确定显示哪个对话框(sp)String password = SharedPreferencesUtil.getString(this, ConstantValue.MOBILE_SAFE_PASSWORD, "");if (TextUtils.isEmpty(password)){// 2.初始设置密码对话框showSetPasswordDialog();}else {// 3.确认密码对话框showConfirmPasswordDialog();}}/*** 5.初次设置密码对话框*/private void showSetPasswordDialog() {AlertDialog.Builder builder = new AlertDialog.Builder(this);final AlertDialog dialog = builder.create();final View view = View.inflate(this, R.layout.dialog_set_password, null);dialog.setView(view);dialog.show();Button btn_submit = view.findViewById(R.id.btn_submit);Button btn_cancel = view.findViewById(R.id.btn_cancel);btn_submit.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {EditText et_set_password = view.findViewById(R.id.et_set_password);EditText et_confirm_password = view.findViewById(R.id.et_confirm_password);String password = et_set_password.getText().toString();String confirmpassword = et_confirm_password.getText().toString();if (!TextUtils.isEmpty(password) && !TextUtils.isEmpty(confirmpassword)){if(password.equals(confirmpassword)){// 进入手机防盗模块Intent intent = new Intent(getApplicationContext(), TestActivity.class);startActivity(intent);dialog.dismiss();}else {// 提示用户确认密码有误ToastUtil.show(getApplicationContext(),"确认密码错误");}}else {// 提示用户密码输入有为空ToastUtil.show(getApplicationContext(),"请输入密码");}}});btn_cancel.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {dialog.dismiss();}});}/*** 6.再次设置密码对话框*/private void showConfirmPasswordDialog() {}}

修改dialog_set_password.xml,为编辑框添加密码属性,代码如下:

<?xml version="1.0" encoding="utf-8"?><LinearLayoutxmlns:android="/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><TextViewstyle="@style/TitleStyle"android:text="设置密码"android:background="#f00"/><EditTextandroid:id="@+id/et_set_password"android:inputType="textPassword"android:layout_width="match_parent"android:layout_height="wrap_content"android:hint="设置密码"/><EditTextandroid:id="@+id/et_confirm_password"android:inputType="textPassword"android:layout_width="match_parent"android:layout_height="wrap_content"android:hint="确认密码"/><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"><Buttonandroid:id="@+id/btn_submit"android:text="确认"android:layout_width="0dp"android:layout_weight="1"android:layout_height="wrap_content"/><Buttonandroid:id="@+id/btn_cancel"android:text="取消"android:layout_width="0dp"android:layout_weight="1"android:layout_height="wrap_content"/></LinearLayout></LinearLayout>

20.手机防盗——确认密码对话框编写

前面我们完成了初次设置密码对话框的逻辑,现在需要编写确认密码对话框的逻辑

在res/layout下新建dialog_confirm_password.xml,作为确认密码对话框的视图,代码如下:

<?xml version="1.0" encoding="utf-8"?><LinearLayoutxmlns:android="/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><TextViewstyle="@style/TitleStyle"android:text="确认密码"android:background="#f00"/><EditTextandroid:id="@+id/et_confirm_password"android:inputType="textPassword"android:layout_width="match_parent"android:layout_height="wrap_content"android:hint="确认密码"/><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"><Buttonandroid:id="@+id/btn_submit"android:text="确认"android:layout_width="0dp"android:layout_weight="1"android:layout_height="wrap_content"/><Buttonandroid:id="@+id/btn_cancel"android:text="取消"android:layout_width="0dp"android:layout_weight="1"android:layout_height="wrap_content"/></LinearLayout></LinearLayout>

修改HomeActivity,修改showConfirmPasswordDialog()方法,完善相应逻辑,代码如下:

package com.example.mobilesafe.activity;import androidx.appcompat.app.AlertDialog;import androidx.appcompat.app.AppCompatActivity;import android.content.Intent;import android.os.Bundle;import android.text.TextUtils;import android.view.View;import android.view.ViewGroup;import android.widget.AdapterView;import android.widget.BaseAdapter;import android.widget.Button;import android.widget.EditText;import android.widget.GridView;import android.widget.ImageView;import android.widget.TextView;import com.example.mobilesafe.R;import com.example.mobilesafe.constant.ConstantValue;import com.example.mobilesafe.utils.SharedPreferencesUtil;import com.example.mobilesafe.utils.ToastUtil;public class HomeActivity extends AppCompatActivity {/*** 存储标题*/private String[] mTitleStrs;/*** 存储图像*/private int[] mDrawableIds;/*** 网格对象*/private GridView gv_home;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_home);// 初始化UIinitUI();// 初始化数据initData();}/*** 1.初始化UI*/private void initUI() {gv_home = findViewById(R.id.gv_home);}/*** 2.初始化数据*/private void initData() {// 1.初始化每个图标的标题mTitleStrs = new String[]{"手机防盗","通信卫士","软件管理","进程管理","流量统计","手机杀毒","缓存清理","高级工具","设置中心"};// 2.初始化每个图标的图像mDrawableIds = new int[]{R.drawable.home_safe,R.drawable.home_callmsgsafe,R.drawable.home_apps,R.drawable.home_taskmanager,R.drawable.home_netmanager,R.drawable.home_trojan,R.drawable.home_sysoptimize,R.drawable.home_tools,R.drawable.home_settings};// 3.为GridView设置数据适配器gv_home.setAdapter(new MyAdapter());// 4.注册GridView中单个条目的点击事件gv_home.setOnItemClickListener(new AdapterView.OnItemClickListener() {@Overridepublic void onItemClick(AdapterView<?> parent, View view, int position, long id) {switch (position){case 0:// 手机防盗showDialog();break;case 8:// 设置中心Intent intent = new Intent(getApplicationContext(), SettingActivity.class);startActivity(intent);break;default:break;}}});}/*** 3.自定义的数据适配器类*/class MyAdapter extends BaseAdapter{@Overridepublic int getCount() {// 统计条目的总数return mTitleStrs.length;}@Overridepublic Object getItem(int position) {// 根据索引获取对象return mTitleStrs[position];}@Overridepublic long getItemId(int position) {// 获取索引return position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {// 获取视图View view = View.inflate(getApplicationContext(), R.layout.gridview_item, null);TextView tv_title = view.findViewById(R.id.tv_title);ImageView iv_icon = view.findViewById(R.id.iv_icon);tv_title.setText(mTitleStrs[position]);iv_icon.setBackgroundResource(mDrawableIds[position]);return view;}}/*** 4.手机防盗——密码对话框*/private void showDialog() {// 1.通过判断本地是否有存储密码来确定显示哪个对话框(sp)String password = SharedPreferencesUtil.getString(this, ConstantValue.MOBILE_SAFE_PASSWORD, "");if (TextUtils.isEmpty(password)){// 2.初始设置密码对话框showSetPasswordDialog();}else {// 3.确认密码对话框showConfirmPasswordDialog();}}/*** 5.初次设置密码对话框*/private void showSetPasswordDialog() {AlertDialog.Builder builder = new AlertDialog.Builder(this);final AlertDialog dialog = builder.create();final View view = View.inflate(this, R.layout.dialog_set_password, null);dialog.setView(view);dialog.show();Button btn_submit = view.findViewById(R.id.btn_submit);Button btn_cancel = view.findViewById(R.id.btn_cancel);btn_submit.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {EditText et_set_password = view.findViewById(R.id.et_set_password);EditText et_confirm_password = view.findViewById(R.id.et_confirm_password);String password = et_set_password.getText().toString();String confirmpassword = et_confirm_password.getText().toString();if (!TextUtils.isEmpty(password) && !TextUtils.isEmpty(confirmpassword)){if(password.equals(confirmpassword)){// 进入手机防盗模块Intent intent = new Intent(getApplicationContext(), TestActivity.class);startActivity(intent);dialog.dismiss();// 将密码存储到sp中SharedPreferencesUtil.putString(getApplicationContext(),ConstantValue.MOBILE_SAFE_PASSWORD,password);}else {// 提示用户确认密码有误ToastUtil.show(getApplicationContext(),"确认密码错误");}}else {// 提示用户密码输入有为空ToastUtil.show(getApplicationContext(),"请输入密码");}}});btn_cancel.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {dialog.dismiss();}});}/*** 6.再次设置密码对话框*/private void showConfirmPasswordDialog() {AlertDialog.Builder builder = new AlertDialog.Builder(this);final AlertDialog dialog = builder.create();final View view = View.inflate(this, R.layout.dialog_confirm_password, null);dialog.setView(view);dialog.show();Button btn_submit = view.findViewById(R.id.btn_submit);Button btn_cancel = view.findViewById(R.id.btn_cancel);btn_submit.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {EditText et_confirm_password = view.findViewById(R.id.et_confirm_password);String confirmpassword = et_confirm_password.getText().toString();if (!TextUtils.isEmpty(confirmpassword)){// 从sp中获取密码String password = SharedPreferencesUtil.getString(getApplicationContext(), ConstantValue.MOBILE_SAFE_PASSWORD, "");if(password.equals(confirmpassword)){// 进入手机防盗模块Intent intent = new Intent(getApplicationContext(), TestActivity.class);startActivity(intent);dialog.dismiss();}else {// 提示用户确认密码有误ToastUtil.show(getApplicationContext(),"确认密码错误");}}else {// 提示用户密码输入有为空ToastUtil.show(getApplicationContext(),"请输入密码");}}});btn_cancel.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {dialog.dismiss();}});}}

21.手机防盗——MD5加密过程

之前的小节中,我们完成了密码的设置和存储,但是密码是用明文存储在sp中的,为了应用安全,所以需要使用MD5加密算法进行一个加密

MD5:将字符串转换成32位的字符串(16进制),不可逆

在utils包下新建MD5Util,作为MD5加密算法的工具类,代码如下:

package com.example.mobilesafe.utils;import java.security.MessageDigest;import java.security.NoSuchAlgorithmException;public class MD5Util {/*** 给指定字符串按照MD5算法进行加密* @param password 待加密的字符型*/public static void encoder(String password){try {// 1.指定加密算法类型MessageDigest digest = MessageDigest.getInstance("MD5");// 2.将需要加密的字符串中转换成byte类型的数组,然后进行随机的哈希过程byte[] bs = digest.digest(password.getBytes());// 3.循环遍历数组,然后让其生成32位字符串,固定写法StringBuffer stringBuffer = new StringBuffer();for (byte b : bs) {int i = b & 0xff;// 4.将int类型的i转换成16进制字符String hexString = Integer.toHexString(i);if (hexString.length() < 2){hexString = "0" + hexString;}// 5.字符串拼接stringBuffer.append(hexString);}} catch (NoSuchAlgorithmException e) {e.printStackTrace();}}}

Android开发实战《手机安全卫士》——2.“设置中心”模块实现 自定义组件 Sp工具类 MD5加密

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。