松原新闻头条,网站关键词百度自然排名优化,软件开发图片,wordpress 小米官网主题下载文章目录
一、开发环境搭建二、简单控件 1. 文本显示2. 按钮3. 常用布局 #xff08;1#xff09;线性布局LinearLayout#xff08;3#xff09;相对布局RelativeLayout#xff08;3#xff09;网格布局GridLayout 4. 图像显示 #xff08;1#xff09;图像的缩放问题…文章目录
一、开发环境搭建二、简单控件 1. 文本显示2. 按钮3. 常用布局 1线性布局LinearLayout3相对布局RelativeLayout3网格布局GridLayout 4. 图像显示 1图像的缩放问题2图像按钮ImageButton 三、Activity 1. Activity的启动和结束2. Activity的生命周期3. Activity的启动模式 1静态设置2动态设置 4. Activity之间传递信息 1显式Intent和隐式Intent 1. 显式Intent2. 隐式Intent 2向下一个Activity发送消息3向上一个Activity返回消息 5. Activity获取一些附加信息 1获取资源信息2获取元数据信息 四、数据存储 1. 共享参数SharedPreferences 1使用2应用实例记住密码功能 2. 数据库SQLite 1SQLiteDatabase2SQLiteOpenHelper3代码举例4优化记住密码 3. 存储卡 1私有空间和公有空间2在存储卡上读写文件3在存储卡上读写 图片文件 4. 应用组件Application5. 实战购物车 五、内容共享 1. 在应用之间共享数据2. 使用内容组件获取通讯信息 1运行时动态申请权限2使用ContentResolver读写联系人3使用ContentObserver监听短信 3. 在应用之间共享文件 1使用相册图片发送彩信2借助FileProvider发送彩信3借助FileProvider安装应用
一、开发环境搭建
安装android studio安装 sdk当前使用最新版33 手动下载gradle 更新弄完之后有时候没用可以再试试挂梯子换网络之类的 如果第一次启动AndroidStudio没有报错则无需设置这里是因为我启动完之后下载gradle报错 could not install gradle distribution from https://services.gradle.org/dist 可能是网络问题连接不到所以手动下载。 点击上面提示的链接下载压缩包然后解压到C:\Users\OYMN\.gradle\wrapper\dists\gradle-7.2-bin\2dnblmf4td7x66yl1d74lt32g 安装模拟器 使用androidstudio提供的模拟器或者自行下载第三方安卓模拟器雷电模拟器
二、简单控件
1. 文本显示
设置文本内容有两种方式
在 XML 文件中通过属性 android:text 设置文本在 Java 代码中调用文本视图对象的 setText 方法设置文本
引用字符串资源
在XML文件中引用string/xxx在Java代码中引用R.string.xxx
其余设置文本字体大小颜色等都是可以通过关键词代码提示很容易就能知道怎么写这里就不赘述。
2. 按钮
Button继承于TextView因此它们拥有的属性都是共通的。
除此之外Button最重要的是点击事件。 点击监听器通过setOnClickListener方法设置。按钮被按住少于500毫秒时会触发点击事件。 长按监听器通过setOnLongClickListener方法设置。按钮被按住超过500毫秒时会触发长按事件。
3. 常用布局
1线性布局LinearLayout
特点要不水平排列要不竖直排列通过orintation进行设置horiztal为水平vertical为竖直
权重属性通过layout_weight来设置在线性布局的直接下级进行设置表示该下级布局占据的宽高比例。
layout_width填0dp时layout_weight表示水平方向的宽度比例。layout_height填0dp时layout_weight表示垂直方向的高度比例。
3相对布局RelativeLayout
相对布局中的视图位置由两个因素所影响
与该视图平级的其他视图上级视图也就是它归属的RelativeLayout
相对位置的一些取值 3网格布局GridLayout
顾名思义该布局适用于表格类型的布局。
4. 图像显示
图片一般放在res/drawable目录下设置图像显示一般有两种方法
在XML文件中通过属性android:src设置图片资源属性值格式形如 drawable/不含扩展名的图片名称。在Java代码中调用setImageResource方法设置图片资源方法参数格式形如 R.drawable.不含扩展名的图片名称。
1图像的缩放问题
ImageView本身默认图片居中显示若要改变图片的显示方式可通过scaleType属性设定该属性的取值说明如下 2图像按钮ImageButton
ImageButton是显示图片的图像按钮但它继承自ImageView而非继承Button。
ImageButton和Button之间的区别有
Button既可显示文本也可显示图片ImageButton只能显示图片不能显示文本。ImageButton上的图像可按比例缩放而Button通过背景设置的图像会拉伸变形。Button只能靠背景显示一张图片而ImageButton可分别在前景和背景显示图片从而实现两张图片叠加的效果。
三、Activity
Activity是安卓开发四大组件之一非常重要。
1. Activity的启动和结束
Activity的启动这里指的是跳转从一个页面跳转到一个新的页面就相当于启动了一个新的页面。
示例
bt.setOnClickListener(new View.OnClickListener(){Overridepublic void onClick(View v) {Intent intent new Intent();intent.setClass(MainActivity.this, MainActivity2.class);startActivity(intent);}
});
结束Activity调用 finish()。
2. Activity的生命周期
onCreate此时将页面布局加载到内存中初始化页面。
onStart将页面展示在屏幕。
onResume此时页面能够和用户进行交互。
onPause页面进入暂停状态无法和用户进行交互。
onStop页面不在屏幕显示。
onDestory回收Activity占用的资源彻底销毁该Activity。
onRestartonStop状态可以转为onRestart状态。
onNewIntent重用已存在的活动实例。如果一个Activity已经启动了并且存在与当前栈而当前栈的启动模式为SingleTaskSingleInstanceSingleTop此时在任务栈顶端那么再次启动该Activity的话并不会重新进行onCreate而是会执行onNewIntent方法。 3. Activity的启动模式
Android允许在创建Activity时设置启动模式通过启动模式控制Activity的出入栈行为。
1静态设置
设置方式打开AndroidManifest.xml文件给activity添加属性android:launchMode。如以下表示该activity使用standard标准模式默认也是标准模式。
activity android:name.JumpFirstActivity android:launchModestandard /
launchMode的取值有 2动态设置
通过 Intent 动态设置 Activity启动模式
intent.setFlags();
4. Activity之间传递信息
Intent能够让Android各组件之间进行沟通。
Intent可以完成3部分工作
表明本次通信从哪里来往哪里走要怎么走。发送方可以携带消息给接收方接收方可以从收到的Intent解析数据。发送方如果想要知道接收方的处理结果接收方也可以通过Intent返回结果。
Intent的一些组成元素 1显式Intent和隐式Intent
1. 显式Intent
创建方式 在Intent的构造函数中指定 Intent intent new Intent(this, NextActivity.class); 调用setClass指定 Intent intent new Intent();
intent.setClass(this, NextActivity.class); 调用setComponent指定 Intent intent new Intent();
ComponentName component new ComponentName(this, NextActivity.class);
intent.setComponent(component);
2. 隐式Intent
没有明确指定所要跳转的页面而是通过一些动作字符串来让系统自动匹配。
通常是App不想向外暴露Activity的名称只给出一些定义好的字符串。这些字符串可以自己定义也有系统定义的。
常见的系统动作如下 下面以调用系统拨号页面举例
String phone 12345;
Intent intent new Intent();
//这里表示设置意图动作为准备拨号
intent.setAction(Intent.ACTION_DIAL);
intent.setData(Uri.parse(tel: phone));
startActivity(intent);
如果想要跳转到自己定义的activity
步骤一在AndroidManifest.xml找到该activity添加action和category标签同时设置exported为true表示允许被其他activity调用。 步骤二调用过程和上面一样
Intent intent new Intent();
intent.setAction(android.intent.action.activity2);
intent.addCategory(Intent.CATEGORY_DEFAULT);
startActivity(intent);
2向下一个Activity发送消息
Intent重载了很多putExtra方法用于传递各种类型的信息包括整数类型字符串等。但是显然通过调用putExtra方法会很不好管理因为数据都是零碎传递。所以Android引入了Bundle其内部是一个Map使用起来也和Map一样。 示例
Intent intent new Intent(this, NextActivity.class);
//通过bundle包装数据
Bundle bundle new Bundle();
bundle.putString(stringKey, stringValue);
intent.putExtras(bundle);
startActivity(intent);
然后下一个Activity就可以通过intent获取到所想要的数据了
Bundle bundle getIntent().getExtras();
String stringValue bundle.getString(stringKey);
3向上一个Activity返回消息
上一个页面跳转到下一个页面同时携带数据
private ActivityResultLauncherIntent register;Override
protected void onCreate(Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main2);findViewById(R.id.bt).setOnClickListener(this);//回调函数返回到这个页面时所执行的程序register registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallbackActivityResult() {//回调函数Overridepublic void onActivityResult(ActivityResult result) {if (result ! null) {Intent intent result.getData();if (intent ! null result.getResultCode() Activity.RESULT_OK) {//获取到返回的数据Bundle bundle intent.getExtras();//...}}}});
}Override
public void onClick(View v) {Intent intent new Intent(this, MainActivity3.class);//跳转下一页面register.launch(intent);}
下一个页面接受到数据处理之后返回结果给上一个页面
Bundle bundle getIntent().getExtras();
//...页面进行处理
//返回数据给上一个页面
Bundle bundle new Bundle();
bundle.putString(stringKey, stringValue);
intent.putExtras(bundle);
setResult(Activity.RESULT_OK, intent);
finish();
5. Activity获取一些附加信息
1获取资源信息
//获取strings.xml中的字符串资源
String text getString(R.string.text);
//获取color.xml中的颜色资源
int black getColor(R.color.black);
2获取元数据信息
try {//获取包管理器PackageManager pm getPackageManager();//获取当前的Activity信息ActivityInfo activityInfo pm.getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);Bundle bundle activityInfo.metaData;String text2 bundle.getString(text2);} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();
}
四、数据存储
1. 共享参数SharedPreferences
1使用
sharedPreferences是安卓的一个轻量级存储工具采用的方式是key-value以xml文件形式存在文件路径为/data/data/应用包名/shared_prefs/文件名.xml。
适合场景
简单且孤立的数据文本数据二进制数据则不合适需要持久化的数据也就是重启APP后数据仍然存在且有效。
实际开发中sharedPreferences经常用来存储的数据有APP的个性化配置信息用户使用APP的行为信息等。
sharedPreferences对数据的存储和读取类似Map提供put和set方法。
获取数据可以通过SharedPreferences对象获取
//第一个参数表示文件名第二个参数表示私有模式
SharedPreferences shared getSharedPreferences(fileName, MODE_PRIVATE);
String name shared.getString(name);
而存储数据则还需要借助Editor类
SharedPreferences.Editor editor shared.edit();
editor.putString(name, oymn);
editor.putInt(age, 20);
editor.commit();
2应用实例记住密码功能
声明一个共享参数对象并在onCreate中调用getSharedPreferences方法获取共享参数的实例。登录成功时如果用户勾选了“记住密码”就使用共享参数保存手机号码与密码。
所以在登录页面的onCreat方法中添加获取共享参数的代码
// 从share_login.xml获取共享参数对象
mShared getSharedPreferences(share_login, MODE_PRIVATE);
// 获取共享参数保存的手机号码
String phone mShared.getString(phone, );
// 获取共享参数保存的密码
String password mShared.getString(password, );
et_phone.setText(phone); // 往手机号码编辑框填写上次保存的手机号
et_password.setText(password); // 往密码编辑框填写上次保存的密码
接着在登录成功方法中添加保存功能
// 如果勾选了“记住密码”就把手机号码和密码都保存到共享参数中
if (isRemember) {SharedPreferences.Editor editor mShared.edit(); // 获得编辑器的对象editor.putString(phone, et_phone.getText().toString()); // 添加名叫phone的手机号码editor.putString(password, et_password.getText().toString()); // 添加名叫password的密码editor.commit(); // 提交编辑器中的修改
}
2. 数据库SQLite
SQLite是安卓的一种小巧的嵌入式数据库基本使用和思路和Mysql无异。
1SQLiteDatabase
java代码层面借助SQLiteDatabase来对SQLite进行操作。
//创建数据库text.db
SQLiteDatabase db openOrCreateDatabase(getFileDir() /test.db, Context.MODE_PRIVATE, null); 2SQLiteOpenHelper
由于SQLiteDatabase存在局限性一不小心就会重复打开数据库处理数据库的升级也不方便因此Android提供了数据库帮助器SQLiteOpenHelper帮助开发者合理使用SQLite。
SQLiteOpenHelper的具体使用步骤如下
步骤一新建一个继承自SQLiteOpenHelper的数据库操作类按提示重写onCreate和onUpgrade两个方法。其中onCreate方法只在第一次打开数据库时执行在此可以创建表结构而onUpgrade方法在数据库版本升高时执行在此可以根据新旧版本号变更表结构。步骤二为保证数据库安全使用需要封装几个必要方法包括获取单例对象、打开数据库连接、关闭数据库连接说明如下 获取单例对象确保在App运行过程中数据库只会打开一次避免重复打开引起错误。打开数据库连接SQLite有锁机制即读锁和写锁的处理故而数据库连接也分两种读连接可调用getReadableDatabase方法获得写连接可调用getWritableDatabase获得。关闭数据库连接数据库操作完毕调用数据库实例的close方法关闭连接。 步骤三 提供对表记录增加、删除、修改、查询的操作方法。能被SQLite直接使用的数据结构是ContentValues类它类似于映射Map也提供了put和get方法存取键值对。 区别之处在于ContentValues的键只能是字符串不能是其他类型。ContentValues主要用于增加记录和更新记录对应数据库的insert和update方法。记录的查询操作用到了游标类Cursor调用query和rawQuery方法返回的都是Cursor对象若要获取全部的查询结果则需根据游标的指示一条一条遍历结果集合。Cursor的常用方法可分为3类说明如下 3代码举例
public class UserDBHelper extends SQLiteOpenHelper {private static final String DB_NAME user.db; //数据库名称private static final int DB_VERSION 1; //数据库的版本号private static UserDBHelper helper null; //单例private SQLiteDatabase sdb null; //数据库实例public static final String TABLE_NAME user_info; //表名public UserDBHelper(Context context) {super(context, DB_NAME, null, DB_VERSION);}public UserDBHelper(Context context, int version) {super(context, DB_NAME, null, version);}//通过单例模式获取 UserDBHelper 的唯一实例public static synchronized UserDBHelper getInstance(Context context, int version) {if (version 0 helper null) {helper new UserDBHelper(context, version);} else if (helper null) {helper new UserDBHelper(context);}return helper;}//打开读连接public SQLiteDatabase openReadLink() {if (sdb null || !sdb.isOpen()) {sdb helper.getReadableDatabase();}return sdb;}//打开写连接public SQLiteDatabase openWriteLink() {if (sdb null || !sdb.isOpen()) {sdb helper.getWritableDatabase();}return sdb;}//关闭数据库连接public void closeLink() {if (sdb ! null sdb.isOpen()) {sdb.close();sdb null;}}//创建数据库执行建表语句Overridepublic void onCreate(SQLiteDatabase db) {//先删除已存在表String drop_sql drop table if exists TABLE_NAME ;;db.execSQL(drop_sql);//创建表String create_sql CREATE TABLE IF NOT EXISTS TABLE_NAME ( _id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR NOT NULL, age INTEGER NOT NULL, height INTEGER NOT NULL, weight FLOAT NOT NULL, married INTEGER NOT NULL, update_time VARCHAR NOT NULL//演示数据库升级时要先把下面这行注释 ,phone VARCHAR ,password VARCHAR );;db.execSQL(create_sql);}//修改表结构Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {if (newVersion 1) {//Android的ALTER命令不支持一次添加多列只能分多次添加String alter_sql ALTER TABLE TABLE_NAME ADD COLUMN phone VARCHAR;;db.execSQL(alter_sql);alter_sql ALTER TABLE TABLE_NAME ADD COLUMN password VARCHAR;;db.execSQL(alter_sql); // 执行完整的SQL语}}//根据指定条件删除记录public int delete(String condition) {return sdb.delete(TABLE_NAME, condition, null);}//删除全部记录public int deleteAll() {return sdb.delete(TABLE_NAME, 11, null);}//根据条件查询记录public ListUserInfo query(String condition) {String sql String.format(select rowid,_id,name,age,height,weight,married,update_time, phone,password from %s where %s;, TABLE_NAME, condition);//执行查询语句该语句返回结果集的游标Cursor cursor sdb.rawQuery(sql, null);ArrayListUserInfo userInfos new ArrayList();//循环取出游标指向的结果集while (cursor.moveToNext()) {UserInfo userInfo new UserInfo();userInfo.name cursor.getString(2);userInfo.age cursor.getInt(3);userInfos.add(userInfo);}cursor.close();return userInfos;}//往表里添加一条记录public long insert(UserInfo userinfo) {ArrayListUserInfo userInfos new ArrayList();userInfos.add(userinfo);return insert(userInfos);}//往表里添加多条记录public long insert(ListUserInfo userInfos) {long result -1;for (UserInfo userInfo : userInfos) {//如果名字相同则更新记录if (userInfo.name ! null userInfo.name.length() 0) {String condition String.format(name %s, userInfo.name);ListUserInfo dbUserInfoList query(condition);if (dbUserInfoList ! null dbUserInfoList.size() 0) {update(userInfo, condition);//返回其idresult dbUserInfoList.get(0).id;continue;}}//其余情况则说明记录不重复添加新纪录ContentValues cv new ContentValues();cv.put(name, userInfo.name);cv.put(age, userInfo.age);result sdb.insert(TABLE_NAME, , cv);if(result -1){return result;}}return result;}//根据指定条件更新表记录public int update(UserInfo userInfo, String condition) {ContentValues cv new ContentValues();cv.put(name, userInfo.name);cv.put(age, userInfo.age);return sdb.update(TABLE_NAME, cv, condition, null);}}
4优化记住密码
上面通过SharedPreferences存储密码的方式还是存在一定的局限性该方式只能记住一个用户的登录信息当下一个用户登录后上一个用户的信息将会被覆盖。正确的记住密码功能应该是输入手机号自动补充密码因此可以考虑使用数据库来进行存储。
主要的改造如下
声明一个数据库的helper对象在Activity的OnResume方法中获取数据库连接在OnPause方法中关闭数据库连接。
private UserDBHelper helper;Override
protected void onResume() {super.onResume();//获取数据库帮助器实例 (此处是单例所以不怕重复获取)helper UserDBHelper.getInstance(this, 1);//恢复页面时则获取连接helper.openWriteLink();
}Override
protected void onPause() {super.onPause();//暂停页面时就断开连接helper.closeLink();
}
登录成功后如果用户勾选了记住密码功能则保存到数据库。也就是在loginSuccess方法中添加如下
if (isRemember) {UserInfo info new UserInfo(); // 创建一个用户信息对象info.phone et_phone.getText().toString();info.password et_password.getText().toString();info.update_time DateUtil.getNowDateTime(yyyy-MM-dd HH:mm:ss);mHelper.insert(info); // 往用户数据库添加登录成功的用户信息
}
用户进行登录时根据输入手机号自动查找密码
// 根据手机号码查询指定记录
public UserInfo queryByPhone(String phone) {UserInfo info null;ListUserInfo infoList query(String.format(phone%s, phone));if (infoList.size() 0) { // 存在该号码的登录信息info infoList.get(0);}return info;
}
3. 存储卡
1私有空间和公有空间
为了更规范地管理手机存储空间Android从7.0开始将存储卡划分为私有存储和公共存储两大部分也就是分区存储方式系统给每个App都分配了默认的私有存储空间。App在私有空间上读写文件无须任何授权但是若想在公共空间读写文件则要在AndroidManifest.xml里面添加下述的权限配置。
!-- 存储卡读写 --
uses-permission android:nameandroid.permission.WRITE_EXTERNAL_STORAGE/
uses-permission android:nameandroid.permission.READ_EXTERNAL_STORAG/
但是即使App声明了完整的存储卡操作权限系统仍然默认禁止该App访问公共空间。打开手机的系统设置界面进入到具体应用的管理页面会发现该应用的存储访问权限被禁止了。
既然存储卡分为公共空间和私有空间两部分它们的空间路径获取也就有所不同。若想获取公共空间的存储路径调用的是Environment.getExternalStoragePublicDirectory方法若想获取应用私有空间的存储路径调用的是getExternalFilesDir方法。 //获取系统的公共存储路径
String publicPath Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString();//获取系统的私有存储路径
String privatePath getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString();boolean isLegacy true;
if(Build.VERSION.SDK_INT Build.VERSION_CODES.Q){//Android10的存储空间默认采用分区方式这里是判断是使用传统方式还是分区方式isLegacy Environment.isExternalStorageLegacy();
}
2在存储卡上读写文件
文本文件的读写借助IO流 FileOutputStream写文件和 FileInputStream读文件
// 把字符串保存到指定路径的文本文件
public static void saveText(String path, String txt) {// 根据指定的文件路径构建文件输出流对象try (FileOutputStream fos new FileOutputStream(path)) {fos.write(txt.getBytes()); // 把字符串写入文件输出流} catch (Exception e) {e.printStackTrace();}
}
// 从指定路径的文本文件中读取内容字符串
public static String openText(String path) {String readStr ;// 根据指定的文件路径构建文件输入流对象try (FileInputStream fis new FileInputStream(path)) {byte[] b new byte[fis.available()];fis.read(b); // 从文件输入流读取字节数组readStr new String(b); // 把字节数组转换为字符串} catch (Exception e) {e.printStackTrace();}return readStr; // 返回文本文件中的文本字符串
}
3在存储卡上读写 图片文件
文本文件可以转化为对字符串的读写而图像的读写就需要借助专门的位图工具Bitmap处理。不同图像来源获取Bitmap的方式不同有三种
从指定资源文件中获取decodeResource例如从资源文件img.png获取位图对象
Bitmap bitmap BitmapFactory.decodeResource(getResources(), R.drawable.img);
从指定路径下获取decodeFile但是要注意从Android10开始该方法只能获取私有空间下的图片公共空间下获取不了。
Bitmap bitmap BitmapFactory.decodeFile(C:\\Users\\OYMN\\Pictures\\onepunch.jpg);
从指定的输入流中获取比如使用IO流打开图片文件然后作为参数传入decodeStream
public static Bitmap openImage(String path) {Bitmap bitmap null; // 声明一个位图对象// 根据指定的文件路径构建文件输入流对象try (FileInputStream fis new FileInputStream(path)) {bitmap BitmapFactory.decodeStream(fis); // 从文件输入流中解码位图数据} catch (Exception e) {e.printStackTrace();}return bitmap; // 返回图片文件中的位图数据
}
获取到图片之后就可以通过ImageView的setImageBitmap进行设置了。
有多种读取图片的方式但是写图片只有一种方式。通过Bitmap的compress方法将位图数据压缩到文件输出流
public static void saveImage(String path, Bitmap bitmap){//根据文件路径构建文件输出流try(FileOutputStream fos new FileOutputStream()){//将位图数据压缩到文件输出流bitmap.compress(Bitmap.CompressFormat.JPEG, 80, fos);}catch(Exception e){e.printStackTrace();}
}
以下演示一下完整的文件读写操作
// 获取当前App的私有下载目录
String path getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString()
/;
// 从指定的资源文件中获取位图对象
Bitmap bitmap BitmapFactory.decodeResource(getResources(), R.drawable.huawei);
String file_path path DateUtil.getNowDateTime() .jpeg;
FileUtil.saveImage(file_path, bitmap); // 把位图对象保存为图片文件
tv_path.setText(图片文件的保存路径为\n file_path);
// 获取当前App的私有下载目录
mPath getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString() /;
// 获得指定目录下面的所有图片文件
mFilelist FileUtil.getFileList(mPath, new String[]{.jpeg});
if (mFilelist.size() 0) {
// 打开并显示选中的图片文件内容
String file_path mFilelist.get(0).getAbsolutePath();
tv_content.setText(找到最新的图片文件路径为file_path);
// 显示存储卡图片文件的第一种方式直接调用setImageURI方法
//iv_content.setImageURI(Uri.parse(file_path)); // 设置图像视图的路径对象
// 第二种方式先调用BitmapFactory.decodeFile获得位图再调用setImageBitmap方法
//Bitmap bitmap BitmapFactory.decodeFile(file_path);
//iv_content.setImageBitmap(bitmap); // 设置图像视图的位图对象
// 第三种方式先调用FileUtil.openImage获得位图再调用setImageBitmap方法
Bitmap bitmap FileUtil.openImage(file_path);
iv_content.setImageBitmap(bitmap); // 设置图像视图的位图对象
4. 应用组件Application
Application是Android的一大组件在App运行期间只有一个Application对象贯穿整个应用的生命周期。因此Application适合保存全局变量主要是以下三类数据 会频繁读取的信息如用户名手机号码等 不方便通过intent传递的数据如位图对象非字符串的集合对象等。 容易因频繁分配内存而导致内存泄漏的对象如Handler处理器实例等。
通过Application实现对全局内存的读写
先继承Application并获取唯一实例
public class MyApplication extends Application {private static MyApplication myApplication; //Application唯一实例public MapString, String map new HashMap(); //当作全局变量用来存储数据public static MyApplication getInstance(){return myApplication;}Overridepublic void onCreate() {super.onCreate();// 在打开应用时对静态的应用实例赋值myApplication this;}
}
在AndroidManifest.xml 通过name属性添加该Application 接下来就可以通过该Application在整个App中存取数据了
如在MainActivity6存储数据
Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main6);//存储数据MyApplication myApplication MyApplication.getInstance();myApplication.map.put(myKey, myValue);//跳转到MainActivity5View bt5 findViewById(R.id.bt5);bt5.setOnClickListener(new View.OnClickListener() {Overridepublic void onClick(View v) {Intent intent new Intent(MainActivity6.this, MainActivity5.class);startActivity(intent);}});}
在MainActivity5中获取数据
Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main5);TextView tv findViewById(R.id.tv);tv.setText(MyApplication.getInstance().map.get(myKey)); //成功获取到数据
}
5. 实战购物车
五、内容共享
1. 在应用之间共享数据
接下来将介绍Android的四大组件之一ContentProvider通过ContentProvider封装内部数据的外部访问接口实现不同应用能够互相传输数据。
和ContentProvider搭配使用的还有ContentResolver内容解析器ContentObserver内容观察器。
上面提到的SQLite可以操作自身的数据库而ContentProvider则是作为中间接口通过SQLiteOpenHelper和SQLiteDatabase间接操控数据库实现为其他应用提供数据的功能。 使用举例如下 创建一个UserInfoProvider用来提供用户信息给外界应用 在弹出的右键菜单中依次选择New→Other→Content Provider 此时会自动修改两处地方 1一是在AndroidManifest.xml中添加该Provider的配置信息 2二是创建的这个Provider会继承ContentProvider并重写了一些方法。
Server端代码
public class UserInfoProvider extends ContentProvider {//这里是上面实现的dbHelper用来操作本地数据库private UserDBHelper userDBHelper;//初始化Overridepublic boolean onCreate() {//初始化 dbHelperuserDBHelper UserDBHelper.getInstance(getContext());return true;}//插入//uri格式content://com.example.secondandroidapp.UserInfoProvider/userOverridepublic Uri insert(Uri uri, ContentValues values) {//使用sqlite插入数据SQLiteDatabase db userDBHelper.getWritableDatabase();db.insert(UserDBHelper.TABLE_NAME, null, values);return uri;}//查询Overridepublic Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) {SQLiteDatabase db userDBHelper.getReadableDatabase();return db.query(UserDBHelper.TABLE_NAME, projection, selection, selectionArgs, null, null, null);}//删除Overridepublic int delete(Uri uri, String selection, String[] selectionArgs) {int count 0;switch (uriMatcher.match(uri)) {//这种是uri不带参数content://com.example.secondandroidapp.UserInfoProvider/usercase USER:// 获取SQLite数据库的写连接SQLiteDatabase db userDBHelper.getWritableDatabase();// 执行SQLite的删除操作并返回删除记录的数目count db.delete(UserDBHelper.TABLE_NAME, selection,selectionArgs);db.close();break;//这种是uri带参数content://com.example.secondandroidapp.UserInfoProvider/user/2case USERS:String id uri.getLastPathSegment();SQLiteDatabase db2 userDBHelper.getWritableDatabase();count db2.delete(UserDBHelper.TABLE_NAME, id ?, new String[]{id});db2.close();break;}return count;}Overridepublic String getType(Uri uri) {// TODO: Implement this to handle requests for the MIME type of the data// at the given URI.throw new UnsupportedOperationException(Not yet implemented);}Overridepublic int update(Uri uri, ContentValues values, String selection,String[] selectionArgs) {// TODO: Implement this to handle requests to update one or more rows.throw new UnsupportedOperationException(Not yet implemented);}
} 利用ContentProvider只实现服务端App的数据封装如果客户端App想访问对方的内部数据就要通过内容解析器ContentResolver访问。
ContentProvider的Uri结构如下content://authority/data_path/id
Client的代码如下
public class MainActivity7 extends AppCompatActivity {private static Uri ContentUri Uri.parse(content://com.example.secondandroidapp.UserInfoProvider/user);Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main7);Button insertButton findViewById(R.id.insertButton);insertButton.setOnClickListener(new View.OnClickListener() {Overridepublic void onClick(View v) {ContentValues values new ContentValues();values.put(name, 陈鸿荣);values.put(age, 20);//获取到ContentResolver之后调用插入方法进行插入getContentResolver().insert(ContentUri, values);}});Button deleteButton findViewById(R.id.deleteButton);deleteButton.setOnClickListener(new View.OnClickListener() {Overridepublic void onClick(View v) {// content://com.example.secondandroidapp.UserInfoProvider/user/2Uri uri ContentUris.withAppendedId(ContentUri, 2);int count getContentResolver().delete(uri, null, null);}});}
}
出于安全考虑Android11需要事先声明需要访问的其他应用
在AndroidManifest.xml中添加如下
queries!--服务端应用包名 --package android:namecom.example.secondandroidapp/!--或者直接指定authorities--!-- provider android:authoritiescom.example.secondandroidapp.UserInfoProvider/ --
/queries
2. 使用内容组件获取通讯信息
1运行时动态申请权限
在上面讲公共存储空间与私有存储空间提到App若想访问存储卡的公共空间就要在AndroidManifest.xml里面添加下述的权限配置。
!-- 存储卡读写 --
uses-permission android:nameandroid.permission.WRITE_EXTERNAL_STORAGE/
uses-permission android:nameandroid.permission.READ_EXTERNAL_STORAG /
然而即使App声明了完整的存储卡操作权限从Android 7.0开始系统仍然默认禁止该App访问公共空间必须到设置界面手动开启应用的存储卡权限才行。尽管此举是为用户隐私着想可是人家咋知道要手工开权限呢就算用户知道去设置界面找到权限开关也颇费周折。为此Android支持在Java代码中处理权限处理过程分为3个步骤 检查App是否开启了指定权限 调用ContextCompat的checkSelfPermission方法 请求系统弹窗以便用户选择是否开启权限 调用ActivityCompat的requestPermissions方法即可命令系统自动弹出权限申请窗口。 判断用户的权限选择结果是开启还是拒绝 重写活动页面的权限请求回调方法onRequestPermissionsResult在该方法内部处理用户的权限选择结果
动态申请权限有两种方式饿汉式 和 懒汉式。
接下来通过获取通讯权限和短信权限来进行举例说明
首先是懒汉式当需要某种权限的时候再去申请
public class PermissionUtil {//检查权限返回true表示完全启用权限返回false则表示为完全启用所有权限public static boolean checkPermission(Activity activity, String[] permissions, int requestCode){//Android6.0之后采取动态权限管理if(Build.VERSION.SDK_INT Build.VERSION_CODES.M){int check PackageManager.PERMISSION_GRANTED; // 0for (String permission : permissions) {check ContextCompat.checkSelfPermission(activity, permission);if(check ! PackageManager.PERMISSION_GRANTED){break;}}//如果未开启该权限则请求系统弹窗好让用户选择是否开启权限if(check ! PackageManager.PERMISSION_GRANTED){//请求权限ActivityCompat.requestPermissions(activity, permissions, requestCode);return false;}return true;}return false;}//检查权限数组返回true表示都已经授权public static boolean checkGrant(int[] grantResults) {if(grantResults ! null){for (int grant : grantResults) {if(grant ! PackageManager.PERMISSION_GRANTED){return false;}}return true;}return false;}
}
通过两个按钮模拟分别获取权限
public class PermissionLazyActivity extends AppCompatActivity {//通讯录的读写权限private static final String[] PERMISSION_CONTACT {Manifest.permission.READ_CONTACTS,Manifest.permission.WRITE_CONTACTS};//短信的读写权限private static final String[] PERMISSION_SMS {Manifest.permission.SEND_SMS,Manifest.permission.RECEIVE_SMS};private static final int REQUEST_CODE_CONTACTS 1;private static final int REQUEST_CODE_SMS 2;Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_permission_lazy);//获取通讯录权限findViewById(R.id.btn_contact).setOnClickListener(new View.OnClickListener() {Overridepublic void onClick(View v) {PermissionUtil.checkPermission(PermissionLazyActivity.this, PERMISSION_CONTACT, REQUEST_CODE_CONTACTS);}});//获取短信权限findViewById(R.id.btn_sms).setOnClickListener(new View.OnClickListener() {Overridepublic void onClick(View v) {PermissionUtil.checkPermission(PermissionLazyActivity.this, PERMISSION_SMS, REQUEST_CODE_SMS);}});}// 用户选择权限结果后会调用该回调方法Overridepublic void onRequestPermissionsResult(int requestCode, NonNull String[] permissions, NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);switch (requestCode){case REQUEST_CODE_CONTACTS:if(PermissionUtil.checkGrant(grantResults)){Log.d(hhh, 通讯录获取成功);}else{Log.d(hhh, 通讯录获取失败);//跳转到设置界面jumpToSettings();}break;case REQUEST_CODE_SMS:if(PermissionUtil.checkGrant(grantResults)){Log.d(hhh, 短信权限获取成功);}else{Log.d(hhh, 短信权限获取失败);//跳转到设置界面jumpToSettings();}break;}}//跳转到设置界面private void jumpToSettings(){Intent intent new Intent();intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);intent.setData(Uri.fromParts(package, getPackageName(), null));intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);startActivity(intent);}
}
另外还需要在AndroidManifest.xml中配置在低版本中只需要配置这些信息即可高版本就需要上面的动态申请权限
!-- 开启通讯录权限--uses-permission android:nameandroid.permission.READ_CONTACTS/uses-permission android:nameandroid.permission.WRITE_CONTACTS/!-- 开启短信收发权限--uses-permission android:nameandroid.permission.SEND_SMS/uses-permission android:nameandroid.permission.RECEIVE_SMS/
效果如下 懒汉式在页面打开之后就一次性需要用户获取所有权限。
public class PermissionHungryActivity extends AppCompatActivity {//所需全部读写权限private static final String[] PERMISSIONS {Manifest.permission.READ_CONTACTS,Manifest.permission.WRITE_CONTACTS,Manifest.permission.SEND_SMS,Manifest.permission.RECEIVE_SMS};//private static final int REQUEST_CODE_ALL 0;Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_permission_lazy);//检查是否拥有所有所需权限PermissionUtil.checkPermission(this, PERMISSIONS, REQUEST_CODE_ALL);}// 用户选择权限结果后会调用该回调方法Overridepublic void onRequestPermissionsResult(int requestCode, NonNull String[] permissions, NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);switch (requestCode){case REQUEST_CODE_ALL:if(PermissionUtil.checkGrant(grantResults)){Log.d(hhh, 所有权限获取成功);}else{//部分权限获取失败for (int i 0; i grantResults.length; i) {if(grantResults[i] ! PackageManager.PERMISSION_GRANTED){//判断是什么权限获取失败switch (permissions[i]){case Manifest.permission.WRITE_CONTACTS:case Manifest.permission.READ_CONTACTS:Log.d(hhh, 通讯录获取失败);jumpToSettings();break;case Manifest.permission.SEND_SMS:case Manifest.permission.RECEIVE_SMS:Log.d(hhh, 短信权限获取失败);jumpToSettings();break;}}}}break;}}//跳转到设置界面private void jumpToSettings(){Intent intent new Intent();intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);intent.setData(Uri.fromParts(package, getPackageName(), null));intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);startActivity(intent);}
}
2使用ContentResolver读写联系人
手机中通讯录的主要表结构有
raw_contacts表 data表记录了用户的通讯录所有数据包括手机号显示名称等但是里面的mimetype_id表示不同的数据类型这与表mimetypes表中的id相对应raw_contact_id 与上面的 raw_contacts表中的 id 相对应。 mimetypes表 所以插入步骤如下
首先往raw_contacts表中插入一条数据得到id接着由于一个联系人有姓名电话号码邮箱因此需要分三次插入data表中将raw_contact_id和上面得到的id进行关联
下面是往通讯录插入和查询联系人的代码
public class ContactActivity extends AppCompatActivity implements View.OnClickListener {private EditText et_contact_name;private EditText et_contact_phone;private EditText et_contact_email;Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_contact);et_contact_name findViewById(R.id.et_contact_name);et_contact_phone findViewById(R.id.et_contact_phone);et_contact_email findViewById(R.id.et_contact_email);findViewById(R.id.btn_add_contact).setOnClickListener(this);findViewById(R.id.btn_read_contact).setOnClickListener(this);}Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.btn_add_contact:// 创建一个联系人对象Contact contact new Contact();contact.name et_contact_name.getText().toString().trim();contact.phone et_contact_phone.getText().toString().trim();contact.email et_contact_email.getText().toString().trim();// 方式一使用ContentResolver多次写入每次一个字段
// addContacts(getContentResolver(), contact);// 方式二批处理方式// 每一次操作都是一个 ContentProviderOperation构建一个操作集合然后一次性执行// 好处是要么全部成功要么全部失败保证了事务的一致性addFullContacts(getContentResolver(), contact);Toast.makeText(this, 添加联系人成功, Toast.LENGTH_SHORT).show();break;case R.id.btn_read_contact:readPhoneContacts(getContentResolver());break;}}//往通讯录添加一个联系人信息姓名号码邮箱private void addContacts(ContentResolver contentResolver, Contact contact) {//得到rawContentIdContentValues values new ContentValues();//插入记录得到idUri uri contentResolver.insert(ContactsContract.RawContacts.CONTENT_URI, values);long rawContentId ContentUris.parseId(uri);//插入名字ContentValues name new ContentValues();//关联上面得到的联系人idname.put(ContactsContract.Contacts.Data.RAW_CONTACT_ID, rawContentId);//关联联系人姓名的类型name.put(ContactsContract.Contacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);//关联联系人姓名name.put(ContactsContract.Data.DATA2, contact.name);contentResolver.insert(ContactsContract.Data.CONTENT_URI, name);//插入电话号码ContentValues phone new ContentValues();//关联上面得到的联系人idphone.put(ContactsContract.Contacts.Data.RAW_CONTACT_ID, rawContentId);//关联联系人电话号码的类型phone.put(ContactsContract.Contacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE);//关联联系人电话号码phone.put(ContactsContract.Data.DATA1, contact.phone);//指定该号码是家庭号码还是工作号码 (家庭)phone.put(ContactsContract.Data.DATA2, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE);contentResolver.insert(ContactsContract.Data.CONTENT_URI, phone);//插入邮箱ContentValues email new ContentValues();//关联上面得到的联系人idemail.put(ContactsContract.Contacts.Data.RAW_CONTACT_ID, rawContentId);//关联联系人邮箱的类型email.put(ContactsContract.Contacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE);//关联联系人邮箱email.put(ContactsContract.Data.DATA1, contact.email);//指定该号码是家庭邮箱还是工作邮箱email.put(ContactsContract.Data.DATA2, ContactsContract.CommonDataKinds.Phone.TYPE_WORK);contentResolver.insert(ContactsContract.Data.CONTENT_URI, email);}//事务操作四个插入操作一次性提交private void addFullContacts(ContentResolver contentResolver, Contact contact) {//创建一个插入联系人主记录的内容操作器ContentProviderOperation op_main ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)//没有实际意义不加这个会报错不加这个导致没有创建ContentValue导致报错.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null).build();//创建一个插入联系人姓名记录的内容操作器ContentProviderOperation op_name ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)//将第0个操作的id即raw_contacts中的id作为data表中的raw_contact_id.withValueBackReference(ContactsContract.Contacts.Data.RAW_CONTACT_ID, 0).withValue(ContactsContract.Contacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE).withValue(ContactsContract.Data.DATA2, contact.name).build();//创建一个插入联系人电话号码记录的内容操作器ContentProviderOperation op_phone ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)//将第0个操作的id即raw_contacts中的id作为data表中的raw_contact_id.withValueBackReference(ContactsContract.Contacts.Data.RAW_CONTACT_ID, 0).withValue(ContactsContract.Contacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE).withValue(ContactsContract.Data.DATA1, contact.phone).withValue(ContactsContract.Data.DATA2, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE).build();//创建一个插入联系人邮箱记录的内容操作器ContentProviderOperation op_email ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)//将第0个操作的id即raw_contacts中的id作为data表中的raw_contact_id.withValueBackReference(ContactsContract.Contacts.Data.RAW_CONTACT_ID, 0).withValue(ContactsContract.Contacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE).withValue(ContactsContract.Data.DATA1, contact.email).withValue(ContactsContract.Data.DATA2, ContactsContract.CommonDataKinds.Phone.TYPE_WORK).build();//全部放在集合中一次性提交ArrayListContentProviderOperation operations new ArrayList();operations.add(op_main);operations.add(op_name);operations.add(op_phone);operations.add(op_email);try {//批量提交四个操作contentResolver.applyBatch(ContactsContract.AUTHORITY, operations);} catch (OperationApplicationException e) {e.printStackTrace();} catch (RemoteException e) {e.printStackTrace();}}//读取联系人SuppressLint(Range)private void readPhoneContacts(ContentResolver contentResolver) {//先查询raw_contacts表再根据raw_contacts_id表 查询data表Cursor cursor contentResolver.query(ContactsContract.RawContacts.CONTENT_URI, new String[]{ContactsContract.RawContacts._ID}, null, null, null);while(cursor.moveToNext()){int rawContactId cursor.getInt(0);Uri uri Uri.parse(content://com.android.contacts/contacts/ rawContactId /data);Cursor dataCursor contentResolver.query(uri, new String[]{ContactsContract.Contacts.Data.MIMETYPE, ContactsContract.Contacts.Data.DATA1, ContactsContract.Contacts.Data.DATA2}, null, null, null);Contact contact new Contact();while (dataCursor.moveToNext()) {String data1 dataCursor.getString(dataCursor.getColumnIndex(ContactsContract.Contacts.Data.DATA1));String mimeType dataCursor.getString(dataCursor.getColumnIndex(ContactsContract.Contacts.Data.MIMETYPE));switch (mimeType) {//是姓名case ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE:contact.name data1;break;//邮箱case ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE:contact.email data1;break;//手机case ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE:contact.phone data1;break;}}dataCursor.close();// RawContacts 表中出现的 _id不一定在 Data 表中都会有对应记录if (contact.name ! null) {Log.d(hhh, contact.toString());}}cursor.close();}}
页面如下 3使用ContentObserver监听短信
ContentResolver获取数据采用的是主动查询方式有查询就有数据没查询就没数据。ContentResolver能够实时获取新增的数据最常见的业务场景是短信验证码。为了替用户省事App通常会监控手机刚收到的短信验证码并自动填写验证码输入框。这时就用到了内容观察器ContentObserver事先给目标内容注册一个观察器目标内容的数据一旦发生变化就马上触发观察器的监听事件从而执行开发者预先定义的代码。 示例代码如下记得在Manifest.xml中开启权限和动态开启权限
public class MonitorSmsActivity extends AppCompatActivity {private SmsGetObserver mObserver;Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_monitor_sms);// 给指定Uri注册内容观察器一旦发生数据变化就触发观察器的onChange方法Uri uri Uri.parse(content://sms);// notifyForDescendents// false 表示精确匹配即只匹配该Uritrue 表示可以同时匹配其派生的UrimObserver new SmsGetObserver(this);getContentResolver().registerContentObserver(uri, true, mObserver);}Overrideprotected void onDestroy() {super.onDestroy();//取消注册getContentResolver().unregisterContentObserver(mObserver);}private static class SmsGetObserver extends ContentObserver {private final Context mContext;public SmsGetObserver(Context context) {super(new Handler(Looper.getMainLooper()));this.mContext context;}//回调SuppressLint(Range)Overridepublic void onChange(boolean selfChange, Nullable Uri uri) {super.onChange(selfChange, uri);// onChange会多次调用收到一条短信会调用两次onChange// mUricontent://sms/raw/20// mUricontent://sms/inbox/20// 安卓7.0以上系统点击标记为已读也会调用一次// mUricontent://sms// 收到一条短信都是uri后面都会有确定的一个数字对应数据库的_id比如上面的20if (uri null) {return;}if (uri.toString().contains(content://sms/raw) ||uri.toString().equals(content://sms)) {return;}// 通过内容解析器获取符合条件的结果集游标Cursor cursor mContext.getContentResolver().query(uri, new String[]{address, body, date}, null, null, date DESC);if (cursor.moveToNext()) {// 短信的发送号码String sender cursor.getString(cursor.getColumnIndex(address));// 短信内容String content cursor.getString(cursor.getColumnIndex(body));Log.d(ning, String.format(sender:%s,content:%s, sender, content));}cursor.close();}}
}
3. 在应用之间共享文件
1使用相册图片发送彩信
2借助FileProvider发送彩信
3借助FileProvider安装应用
题外话
初入计算机行业的人或者大学计算机相关专业毕业生很多因缺少实战经验就业处处碰壁。下面我们来看两组数据
2023届全国高校毕业生预计达到1158万人就业形势严峻
国家网络安全宣传周公布的数据显示到2027年我国网络安全人员缺口将达327万。
一方面是每年应届毕业生就业形势严峻一方面是网络安全人才百万缺口。
6月9日麦可思研究2023年版就业蓝皮书包括《2023年中国本科生就业报告》《2023年中国高职生就业报告》正式发布。
2022届大学毕业生月收入较高的前10个专业
本科计算机类、高职自动化类专业月收入较高。2022届本科计算机类、高职自动化类专业月收入分别为6863元、5339元。其中本科计算机类专业起薪与2021届基本持平高职自动化类月收入增长明显2022届反超铁道运输类专业5295元排在第一位。
具体看专业2022届本科月收入较高的专业是信息安全7579元。对比2018届电子科学与技术、自动化等与人工智能相关的本科专业表现不俗较五年前起薪涨幅均达到了19%。数据科学与大数据技术虽是近年新增专业但表现亮眼已跻身2022届本科毕业生毕业半年后月收入较高专业前三。五年前唯一进入本科高薪榜前10的人文社科类专业——法语已退出前10之列。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ersR0CBX-1691798564539)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20230809162658551.png)]
“没有网络安全就没有国家安全”。当前网络安全已被提升到国家战略的高度成为影响国家安全、社会稳定至关重要的因素之一。
网络安全行业特点
1、就业薪资非常高涨薪快 2021年猎聘网发布网络安全行业就业薪资行业最高人均33.77万 2、人才缺口大就业机会多
2019年9月18日《中华人民共和国中央人民政府》官方网站发表我国网络空间安全人才 需求140万人而全国各大学校每年培养的人员不到1.5W人。猎聘网《2021年上半年网络安全报告》预测2027年网安人才需求300W现在从事网络安全行业的从业人员只有10W人。
行业发展空间大岗位非常多
网络安全行业产业以来随即新增加了几十个网络安全行业岗位︰网络安全专家、网络安全分析师、安全咨询师、网络安全工程师、安全架构师、安全运维工程师、渗透工程师、信息安全管理员、数据安全工程师、网络安全运营工程师、网络安全应急响应工程师、数据鉴定师、网络安全产品经理、网络安全服务工程师、网络安全培训师、网络安全审计员、威胁情报分析工程师、灾难恢复专业人员、实战攻防专业人员…
职业增值潜力大
网络安全专业具有很强的技术特性尤其是掌握工作中的核心网络架构、安全技术在职业发展上具有不可替代的竞争优势。
随着个人能力的不断提升所从事工作的职业价值也会随着自身经验的丰富以及项目运作的成熟升值空间一路看涨这也是为什么受大家欢迎的主要原因。
从某种程度来讲在网络安全领域跟医生职业一样越老越吃香因为技术愈加成熟自然工作会受到重视升职加薪则是水到渠成之事。
黑客网络安全如何学习
今天只要你给我的文章点赞我私藏的网安学习资料一样免费共享给你们来看看有哪些东西。
1.学习路线图
行业发展空间大岗位非常多
网络安全行业产业以来随即新增加了几十个网络安全行业岗位︰网络安全专家、网络安全分析师、安全咨询师、网络安全工程师、安全架构师、安全运维工程师、渗透工程师、信息安全管理员、数据安全工程师、网络安全运营工程师、网络安全应急响应工程师、数据鉴定师、网络安全产品经理、网络安全服务工程师、网络安全培训师、网络安全审计员、威胁情报分析工程师、灾难恢复专业人员、实战攻防专业人员…
职业增值潜力大
网络安全专业具有很强的技术特性尤其是掌握工作中的核心网络架构、安全技术在职业发展上具有不可替代的竞争优势。
随着个人能力的不断提升所从事工作的职业价值也会随着自身经验的丰富以及项目运作的成熟升值空间一路看涨这也是为什么受大家欢迎的主要原因。
从某种程度来讲在网络安全领域跟医生职业一样越老越吃香因为技术愈加成熟自然工作会受到重视升职加薪则是水到渠成之事。
黑客网络安全如何学习
今天只要你给我的文章点赞我私藏的网安学习资料一样免费共享给你们来看看有哪些东西。
1.学习路线图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XYUHLoI1-1691798564540)(C:\Users\Administrator\Desktop\网安思维导图\享学首创年薪40W网络安全工程师 青铜到王者技术成长路线V4.0.png)]
攻击和防守要学的东西也不少具体要学的东西我都写在了上面的路线图如果你能学完它们你去就业和接私活完全没有问题。
2.视频教程
网上虽然也有很多的学习资源但基本上都残缺不全的这是我自己录的网安视频教程上面路线图的每一个知识点我都有配套的视频讲解。
内容涵盖了网络安全法学习、网络安全运营等保测评、渗透测试基础、漏洞详解、计算机基础知识等都是网络安全入门必知必会的学习内容。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sTOqMvrt-1691798564541)(C:\Users\Administrator\Desktop\网安资料截图\视频课件.jpeg)]
都打包成一块的了不能一一展开总共300多集
因篇幅有限仅展示部分资料需要点击下方链接即可前往获取
如果你对网络安全入门感兴趣那么你需要的话可以点击这里网络安全重磅福利入门进阶全套282G学习资源包免费分享
3.技术文档和电子书
技术文档也是我自己整理的包括我参加大型网安行动、CTF和挖SRC漏洞的经验和技术要点电子书也有200多本由于内容的敏感性我就不一一展示了。 因篇幅有限仅展示部分资料需要点击下方链接即可前往获取
如果你对网络安全入门感兴趣那么你需要的话可以点击这里网络安全重磅福利入门进阶全套282G学习资源包免费分享
4.工具包、面试题和源码
“工欲善其事必先利其器”我为大家总结出了最受欢迎的几十款款黑客工具。涉及范围主要集中在 信息收集、Android黑客工具、自动化工具、网络钓鱼等感兴趣的同学不容错过。
还有我视频里讲的案例源码和对应的工具包需要的话也可以拿走。
因篇幅有限仅展示部分资料需要点击下方链接即可前往获取 如果你对网络安全入门感兴趣那么你需要的话可以点击这里网络安全重磅福利入门进阶全套282G学习资源包免费分享
最后就是我这几年整理的网安方面的面试题如果你是要找网安方面的工作它们绝对能帮你大忙。
这些题目都是大家在面试深信服、奇安信、腾讯或者其它大厂面试时经常遇到的如果大家有好的题目或者好的见解欢迎分享。
参考解析深信服官网、奇安信官网、Freebuf、csdn等
内容特点条理清晰含图像化表示更加易懂。
内容概要包括 内网、操作系统、协议、渗透测试、安服、漏洞、注入、XSS、CSRF、SSRF、文件上传、文件下载、文件包含、XXE、逻辑漏洞、工具、SQLmap、NMAP、BP、MSF… 因篇幅有限仅展示部分资料需要点击下方链接即可前往获取
如果你对网络安全入门感兴趣那么你需要的话可以点击这里网络安全重磅福利入门进阶全套282G学习资源包免费分享