Android13系统源码内置App并通过AIDL调用获取内置存储卡的真实大小

本文主要是介绍Android13系统源码内置App并通过AIDL调用获取内置存储卡的真实大小,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

jix 进行从事Android系统源码开发不得不在原有的系统上内置自己的App。通过内置App一般都需要调用些系统才能访问的系统级App。App的部署和调试需要依赖源码系统。通过命令 : mm 来实现。

第三方App想调用内置的app需要通过跨进程调用。

这里通过AIDL来实现跨进程调用。

首先声明AIDL文件,

Android源码工程的文件构成和格式和标准的app完全不一样。

为了方便调试,先在标准的App中调试通过。

再copy标准工程到源码App工程里。

声明的AIDL文件:

Callback.aidl

package com.android.kvservice;interface Callback {oneway void onMessageReceived(int type, String value);
}

KvInterface.aidl

package com.android.kvservice;import com.android.kvservice.Callback;interface KvInterface {void registerCallback(Callback callback);void unregisterCallback(Callback callback);void sendMessage(int type, String value);
}

AIDL的文件夹放的位置

注意在build.gradle 里声明: aidl true

plugins {alias(libs.plugins.androidApplication)alias(libs.plugins.jetbrainsKotlinAndroid)
}android {namespace 'com.yyy.xxx.service'compileSdk 34defaultConfig {applicationId "com.yyy.xxx.kvservice"minSdk 29targetSdk 34versionCode 1versionName "1.0"testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"vectorDrawables {useSupportLibrary true}}buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'}}compileOptions {sourceCompatibility JavaVersion.VERSION_1_8targetCompatibility JavaVersion.VERSION_1_8}kotlinOptions {jvmTarget = '1.8'}buildFeatures {compose trueviewBinding trueaidl true}composeOptions {kotlinCompilerExtensionVersion '1.5.1'}packaging {resources {excludes += '/META-INF/{AL2.0,LGPL2.1}'}}}dependencies {implementation libs.androidx.core.ktximplementation libs.androidx.lifecycle.runtime.ktximplementation libs.androidx.activity.composeimplementation platform(libs.androidx.compose.bom)implementation libs.androidx.uiimplementation libs.androidx.ui.graphicsimplementation libs.androidx.ui.tooling.previewimplementation libs.androidx.material3testImplementation libs.junitandroidTestImplementation libs.androidx.junitandroidTestImplementation libs.androidx.espresso.coreandroidTestImplementation platform(libs.androidx.compose.bom)androidTestImplementation libs.androidx.ui.test.junit4debugImplementation libs.androidx.ui.toolingdebugImplementation libs.androidx.ui.test.manifest
}

实现AIDL接口的地方

import android.content.Context;
import android.os.Binder;
import android.os.Build;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.StatFs;
import android.os.storage.StorageVolume;
import android.util.Log;
import android.os.storage.DiskInfo;
import android.os.storage.VolumeInfo;
import android.os.storage.StorageManager;
import android.app.usage.StorageStatsManager;import com.android.kvservice.Callback;
import com.android.kvservice.KvInterface;
import com.android.server.kvservice.storage.StorageEntry;
import com.android.server.kvservice.storage.StorageUtils;import java.util.List;public class KVService extends KvInterface.Stub {/*** 获取系统全部内存大小,包括隐藏的内存*/
//    public static final int GET_SYSTEM_STORGE_TOTAL = 1;public static final int GET_ALL_STORGE = 1;/****/public static final int GET_SYSTEM_STORGE_REMAIN = 2;public static final int GET_SDCARD_TOTAL = 3;public static final int GET_SDCARD_REMAIN = 4;private static final String TAG = KVService.class.getSimpleName();private RemoteCallbackList<Callback> mCallbackList = new RemoteCallbackList<>();private Context mContext;private Object lack = new Object();private StorageManager storageManager = null;public KVService(Context context) {this.mContext = context;storageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);Log.d(TAG, "henryservice init");}@Overridepublic void registerCallback(Callback callback) {boolean result = mCallbackList.register(callback);Log.d(TAG, "register pid:" + Binder.getCallingPid()+ " uid:" + Binder.getCallingUid() + " result:" + result);}@Overridepublic void unregisterCallback(Callback callback) {boolean result = mCallbackList.unregister(callback);Log.d(TAG, "unregister pid:" + Binder.getCallingPid()+ " uid:" + Binder.getCallingUid() + " result:" + result);}@Overridepublic void sendMessage(int type, String value) throws RemoteException {String result = new String("no-data");if (type == GET_ALL_STORGE) {
//           ....}Log.e(TAG, "rev the type : =========== " + type);Log.e(TAG, "rev the value : =========== " + value);sendEventToRemote(type, result);}public void sendEventToRemote(int type, String value) {synchronized (lack) {int count = mCallbackList.getRegisteredCallbackCount();Log.d(TAG, "remote callback count:" + count);if (count > 0) {// 注意: 遍历过程如果存在多线程操作, 需要加锁, 不然可能会抛出异常final int size = mCallbackList.beginBroadcast();for (int i = 0; i < size; i++) {Callback cb = mCallbackList.getBroadcastItem(i);try {if (cb != null) {cb.onMessageReceived(type, value);}} catch (RemoteException e) {e.printStackTrace();Log.d(TAG, "remote exception:" + e.getMessage());}}mCallbackList.finishBroadcast();}}}}

实现Service的地方:

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;import androidx.annotation.Nullable;import com.android.kvservice.Callback;
import com.android.kvservice.KvInterface;
import com.android.server.kvservice.KVService;public class KService extends Service {private KVService kvService = null;@Overridepublic void onCreate() {super.onCreate();if (kvService == null) {kvService = new KVService(this);}}@Nullable@Overridepublic IBinder onBind(Intent intent) {return binder;}private final KvInterface.Stub binder = new KvInterface.Stub() {@Overridepublic void registerCallback(Callback callback) throws RemoteException {Log.e("KService", " registerCallback ============ ");kvService.registerCallback(callback);Log.e("KService", " registerCallback ============ end");}@Overridepublic void unregisterCallback(Callback callback) throws RemoteException {Log.e("KService", " unregisterCallback ============ ");kvService.unregisterCallback(callback);Log.e("KService", " unregisterCallback ============ end");}@Overridepublic void sendMessage(int type, String value) throws RemoteException {kvService.sendMessage(type, value);Log.e("KService", " sendMessage ============ end");}};
}

本地调用Service的代码。


import android.app.Activity;
import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.widget.Button;import androidx.annotation.Nullable;import com.android.kvservice.Callback;
import com.android.kvservice.KvInterface;
import com.android.server.kvservice.KVService;public class MainActivity extends Activity {//    private MainBinding binding;private KvInterface kvInterface;private Button btnStart;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);
//        binding = MainBinding.inflate(getLayoutInflater());
//        setContentView(binding.getRoot());setContentView(R.layout.main);btnStart = findViewById(R.id.btnStart);btnStart.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {if (kvInterface == null) {bindToService();}}});Button btnSend = findViewById(R.id.btnSend);btnSend.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Log.e("TAG", "send 111");registerCallback();if (kvInterface != null) {try {Log.e("TAG", "send 222");kvInterface.sendMessage(1, "");} catch (RemoteException e) {e.printStackTrace();}}}});}private boolean register = false;private Callback.Stub callback = new Callback.Stub() {@Overridepublic void onMessageReceived(int type, String value) throws RemoteException {Log.e("MainActivity", "rev the type:: ==== " + type);Log.e("MainActivity", "rev the value:: ==== " + value);}};private void registerCallback() {if (!register) {register = true;try {if (kvInterface != null) {kvInterface.registerCallback(callback);}} catch (Exception e) {e.printStackTrace();}}}@Overrideprotected void onDestroy() {super.onDestroy();try {if (kvInterface != null) {kvInterface.unregisterCallback(callback);}} catch (RemoteException e) {throw new RuntimeException(e);}}private ServiceConnection serviceConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {kvInterface = KvInterface.Stub.asInterface(service);}@Overridepublic void onServiceDisconnected(ComponentName name) {kvInterface = null;}};private void bindToService() {
//        bindService(new Intent("com.kingview.qti.service.KService"), serviceConnection, Service.BIND_AUTO_CREATE);bindService(new Intent(this, KService.class), serviceConnection, Service.BIND_AUTO_CREATE);Log.e("TAG","bindToService ======================");}
}

为了第三方App调用,需要再配置文件中做以下声明:

  <serviceandroid:name="com.kingview.qti.service.KService"android:enabled="true"android:exported="true"android:process="com.kingview.qti.service.kservice"><intent-filter><action android:name="com.kingview.service.kservice" /></intent-filter></service>

第三方App调用AIDL服务的代码,发现调用Action并没有什么用,特别注意要输入完整的包名:

  private fun bindToService() {val intent = Intent()intent.setComponent(ComponentName("com.kingview.qti.service","com.kingview.qti.service.KService"))bindService(intent,serviceConnection,BIND_AUTO_CREATE)
//        Log.e("StorageTestActivity", " bindToService ====================== ")}

注册远程回调

 private fun registerCallback() {if (!register) {register = truetry {if (kvInterface != null) {kvInterface!!.registerCallback(callback)}kvInterface?.sendMessage(1, "")} catch (e: Exception) {e.printStackTrace()}}}

反注册callback和 解绑Service。

override fun onDestroy() {super.onDestroy()kvInterface?.unregisterCallback(callback)unbindService(serviceConnection)}

监听实现的操作是这样实现的:

 @Volatileprivate var register = falseprivate val callback: Callback = object : Callback.Stub() {@Throws(RemoteException::class)override fun onMessageReceived(type: Int, value: String) {Log.e("StorageTestActivity", "rev the type:: ==== $type")Log.e("StorageTestActivity", "rev the value:: ==== $value")//解析数据,并显示到界面上if (testKey == KVApplication.TEST_STORAGE) {val strs = value.split("#").filter { v ->v.trim() != ""}for (v in strs) {val bean = Gson().fromJson<StorageBean>(v, StorageBean::class.java)if (bean.description.contains("internal_storage")) {val total = bean.total.toLong() / 1000f / 1000f / 1000f;val used = bean.used.toLong() / 1000f / 1000f / 1000f;val remain = total - usedbinding.txtStatus.post {binding.txtSize.text = " Size: ${total} GB ; Remain ${remain} GB. \n "}}}}}}

通过以上操作你可能会发现,依然无法调用远程的Service。你需要再AndroidManifest里声明一个权限。

 <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"tools:ignore="QueryAllPackagesPermission" />

将App放置在源码工程的的 package/apps/里创建一个文件夹如:XYZService

src/ res/ Androidmanifast.xml 都要从标准App工程里copy出来。

 Android.bp这样写,包含了AIDL文件 :

//
// Copyright (C) 2013 Google Inc.
//package {default_applicable_licenses: ["packages_apps_kvservice_license"],
}// Added automatically by a large-scale-change
// See: http://go/android-license-faq
license {name: "packages_apps_kvservice_license",visibility: [":__subpackages__"],license_kinds: ["SPDX-license-identifier-BSD",],// large-scale-change unable to identify any license_text files
}android_app {name: "XYZServiceTest",defaults: ["platform_app_defaults"],platform_apis: true,certificate: "platform",system_ext_specific: true,privileged: true,srcs: ["src/**/*.java","src/**/*.aidl",],aidl: {local_include_dirs: ["src/aidl"],},manifest: "AndroidManifest.xml",resource_dirs: ["res",],// privileged: true,// sdk_version: "33",// sdk_version: "current",// certificate: "platform",// platform_apis: true,// product_specific: true,static_libs: ["androidx.core_core",// "androidx.annotation:annotation:1.3.0","guava",],
}

 通过阅读Setting源码移植的代码如下:

    public static List<StorageEntry> getAllStorageEntries(Context context,StorageManager storageManager) {final List<StorageEntry> storageEntries = new ArrayList<>();storageEntries.addAll(storageManager.getVolumes().stream().filter(volumeInfo -> isStorageSettingsInterestedVolume(volumeInfo)).map(volumeInfo -> new StorageEntry(context, volumeInfo)).collect(Collectors.toList()));storageEntries.addAll(storageManager.getDisks().stream().filter(disk -> isDiskUnsupported(disk)).map(disk -> new StorageEntry(disk)).collect(Collectors.toList()));storageEntries.addAll(storageManager.getVolumeRecords().stream().filter(volumeRecord -> isVolumeRecordMissed(storageManager, volumeRecord)).map(volumeRecord -> new StorageEntry(volumeRecord)).collect(Collectors.toList()));return storageEntries;}

public class StorageEntry implements Comparable<StorageEntry>, Parcelable  {private final VolumeInfo mVolumeInfo;private final DiskInfo mUnsupportedDiskInfo;private final VolumeRecord mMissingVolumeRecord;private final String mVolumeInfoDescription;public StorageEntry(@NonNull Context context, @NonNull VolumeInfo volumeInfo) {mVolumeInfo = volumeInfo;mUnsupportedDiskInfo = null;mMissingVolumeRecord = null;if (isDefaultInternalStorage()) {// Shows "This device" for default internal storage.mVolumeInfoDescription = "storage_default_internal_storage";} else {mVolumeInfoDescription = context.getSystemService(StorageManager.class).getBestVolumeDescription(mVolumeInfo);}}public StorageEntry(@NonNull DiskInfo diskInfo) {mVolumeInfo = null;mUnsupportedDiskInfo = diskInfo;mMissingVolumeRecord = null;mVolumeInfoDescription = null;}public StorageEntry(@NonNull VolumeRecord volumeRecord) {mVolumeInfo = null;mUnsupportedDiskInfo = null;mMissingVolumeRecord = volumeRecord;mVolumeInfoDescription = null;}private StorageEntry(Parcel in) {mVolumeInfo = in.readParcelable(VolumeInfo.class.getClassLoader());mUnsupportedDiskInfo = in.readParcelable(DiskInfo.class.getClassLoader());mMissingVolumeRecord = in.readParcelable(VolumeRecord.class.getClassLoader());mVolumeInfoDescription = in.readString();}@Overridepublic int describeContents() {return 0;}@Overridepublic void writeToParcel(Parcel out, int flags) {out.writeParcelable(mVolumeInfo, 0 /* parcelableFlags */);out.writeParcelable(mUnsupportedDiskInfo, 0 /* parcelableFlags */);out.writeParcelable(mMissingVolumeRecord, 0 /* parcelableFlags */);out.writeString(mVolumeInfoDescription);}public static final Parcelable.Creator<StorageEntry> CREATOR =new Parcelable.Creator<StorageEntry>() {public StorageEntry createFromParcel(Parcel in) {return new StorageEntry(in);}public StorageEntry[] newArray(int size) {return new StorageEntry[size];}};@Overridepublic boolean equals(Object o) {if (o == this) {return true;}if (!(o instanceof StorageEntry)) {return false;}final StorageEntry StorageEntry = (StorageEntry) o;if (isVolumeInfo()) {return mVolumeInfo.equals(StorageEntry.mVolumeInfo);}if (isDiskInfoUnsupported()) {return mUnsupportedDiskInfo.equals(StorageEntry.mUnsupportedDiskInfo);}return mMissingVolumeRecord.equals(StorageEntry.mMissingVolumeRecord);}@Overridepublic int hashCode() {if (isVolumeInfo()) {return mVolumeInfo.hashCode();}if (isDiskInfoUnsupported()) {return mUnsupportedDiskInfo.hashCode();}return mMissingVolumeRecord.hashCode();}@Overridepublic String toString() {if (isVolumeInfo()) {return mVolumeInfo.toString();}if (isDiskInfoUnsupported()) {return mUnsupportedDiskInfo.toString();}return mMissingVolumeRecord.toString();}@Overridepublic int compareTo(StorageEntry other) {if (isDefaultInternalStorage() && !other.isDefaultInternalStorage()) {return -1;}if (!isDefaultInternalStorage() && other.isDefaultInternalStorage()) {return 1;}if (isVolumeInfo() && !other.isVolumeInfo()) {return -1;}if (!isVolumeInfo() && other.isVolumeInfo()) {return 1;}if (isPrivate() && !other.isPrivate()) {return -1;}if (!isPrivate() && other.isPrivate()) {return 1;}if (isMounted() && !other.isMounted()) {return -1;}if (!isMounted() && other.isMounted()) {return 1;}if (!isVolumeRecordMissed() && other.isVolumeRecordMissed()) {return -1;}if (isVolumeRecordMissed() && !other.isVolumeRecordMissed()) {return 1;}if (getDescription() == null) {return 1;}if (other.getDescription() == null) {return -1;}return getDescription().compareTo(other.getDescription());}/*** Returns default internal storage.*/public static StorageEntry getDefaultInternalStorageEntry(Context context) {return new StorageEntry(context, context.getSystemService(StorageManager.class).findVolumeById(VolumeInfo.ID_PRIVATE_INTERNAL));}/*** If it's a VolumeInfo.*/public boolean isVolumeInfo() {return mVolumeInfo != null;}/*** If it's an unsupported DiskInfo.*/public boolean isDiskInfoUnsupported() {return mUnsupportedDiskInfo != null;}/*** If it's a missing VolumeRecord.*/public boolean isVolumeRecordMissed() {return mMissingVolumeRecord != null;}/*** If it's a default internal storage.*/public boolean isDefaultInternalStorage() {if (isVolumeInfo()) {return mVolumeInfo.getType() == VolumeInfo.TYPE_PRIVATE&& TextUtils.equals(mVolumeInfo.getId(), VolumeInfo.ID_PRIVATE_INTERNAL);}return false;}/*** If it's a mounted storage.*/public boolean isMounted() {return mVolumeInfo == null ? false : (mVolumeInfo.getState() == VolumeInfo.STATE_MOUNTED|| mVolumeInfo.getState() == VolumeInfo.STATE_MOUNTED_READ_ONLY);}/*** If it's an unmounted storage.*/public boolean isUnmounted() {return mVolumeInfo == null ? false : (mVolumeInfo.getState() == VolumeInfo.STATE_UNMOUNTED);}/*** If it's an unmountable storage.*/public boolean isUnmountable() {return mVolumeInfo == null ? false : mVolumeInfo.getState() == VolumeInfo.STATE_UNMOUNTABLE;}/*** If it's a private storage.*/public boolean isPrivate() {return mVolumeInfo == null ? false : mVolumeInfo.getType() == VolumeInfo.TYPE_PRIVATE;}/*** If it's a public storage.*/public boolean isPublic() {return mVolumeInfo == null ? false : mVolumeInfo.getType() == VolumeInfo.TYPE_PUBLIC;}/*** Returns description.*/public String getDescription() {if (isVolumeInfo()) {return mVolumeInfoDescription;}if (isDiskInfoUnsupported()) {return mUnsupportedDiskInfo.getDescription();}return mMissingVolumeRecord.getNickname();}/*** Returns ID.*/public String getId() {if (isVolumeInfo()) {return mVolumeInfo.getId();}if (isDiskInfoUnsupported()) {return mUnsupportedDiskInfo.getId();}return mMissingVolumeRecord.getFsUuid();}/*** Returns disk ID.*/public String getDiskId() {if (isVolumeInfo()) {return mVolumeInfo.getDiskId();}if (isDiskInfoUnsupported()) {return mUnsupportedDiskInfo.getId();}return null;}/*** Returns fsUuid.*/public String getFsUuid() {if (isVolumeInfo()) {return mVolumeInfo.getFsUuid();}if (isDiskInfoUnsupported()) {return null;}return mMissingVolumeRecord.getFsUuid();}/*** Returns root file if it's a VolumeInfo.*/public File getPath() {return mVolumeInfo == null ? null : mVolumeInfo.getPath();}/*** Returns VolumeInfo of the StorageEntry.*/public VolumeInfo getVolumeInfo() {return mVolumeInfo;}
}

Setting关于计算存储卡的代码在

StorageUsageProgressBarPreferenceController 

StorageDashboardFragment文件

这篇关于Android13系统源码内置App并通过AIDL调用获取内置存储卡的真实大小的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1112938

相关文章

Python版本信息获取方法详解与实战

《Python版本信息获取方法详解与实战》在Python开发中,获取Python版本号是调试、兼容性检查和版本控制的重要基础操作,本文详细介绍了如何使用sys和platform模块获取Python的主... 目录1. python版本号获取基础2. 使用sys模块获取版本信息2.1 sys模块概述2.1.1

linux系统中java的cacerts的优先级详解

《linux系统中java的cacerts的优先级详解》文章讲解了Java信任库(cacerts)的优先级与管理方式,指出JDK自带的cacerts默认优先级更高,系统级cacerts需手动同步或显式... 目录Java 默认使用哪个?如何检查当前使用的信任库?简要了解Java的信任库总结了解 Java 信

Java发送SNMP至交换机获取交换机状态实现方式

《Java发送SNMP至交换机获取交换机状态实现方式》文章介绍使用SNMP4J库(2.7.0)通过RCF1213-MIB协议获取交换机单/多路状态,需开启SNMP支持,重点对比SNMPv1、v2c、v... 目录交换机协议SNMP库获取交换机单路状态获取交换机多路状态总结交换机协议这里使用的交换机协议为常

uni-app小程序项目中实现前端图片压缩实现方式(附详细代码)

《uni-app小程序项目中实现前端图片压缩实现方式(附详细代码)》在uni-app开发中,文件上传和图片处理是很常见的需求,但也经常会遇到各种问题,下面:本文主要介绍uni-app小程序项目中实... 目录方式一:使用<canvas>实现图片压缩(推荐,兼容性好)示例代码(小程序平台):方式二:使用uni

MyBatis/MyBatis-Plus同事务循环调用存储过程获取主键重复问题分析及解决

《MyBatis/MyBatis-Plus同事务循环调用存储过程获取主键重复问题分析及解决》MyBatis默认开启一级缓存,同一事务中循环调用查询方法时会重复使用缓存数据,导致获取的序列主键值均为1,... 目录问题原因解决办法如果是存储过程总结问题myBATis有如下代码获取序列作为主键IdMappe

C#使用iText获取PDF的trailer数据的代码示例

《C#使用iText获取PDF的trailer数据的代码示例》开发程序debug的时候,看到了PDF有个trailer数据,挺有意思,于是考虑用代码把它读出来,那么就用到我们常用的iText框架了,所... 目录引言iText 核心概念C# 代码示例步骤 1: 确保已安装 iText步骤 2: C# 代码程

Oracle数据库在windows系统上重启步骤

《Oracle数据库在windows系统上重启步骤》有时候在服务中重启了oracle之后,数据库并不能正常访问,下面:本文主要介绍Oracle数据库在windows系统上重启的相关资料,文中通过代... oracle数据库在Windows上重启的方法我这里是使用oracle自带的sqlplus工具实现的方

Spring Boot中获取IOC容器的多种方式

《SpringBoot中获取IOC容器的多种方式》本文主要介绍了SpringBoot中获取IOC容器的多种方式,包括直接注入、实现ApplicationContextAware接口、通过Spring... 目录1. 直接注入ApplicationContext2. 实现ApplicationContextA

使用Go调用第三方API的方法详解

《使用Go调用第三方API的方法详解》在现代应用开发中,调用第三方API是非常常见的场景,比如获取天气预报、翻译文本、发送短信等,Go作为一门高效并发的编程语言,拥有强大的标准库和丰富的第三方库,可以... 目录引言一、准备工作二、案例1:调用天气查询 API1. 注册并获取 API Key2. 代码实现3

java 恺撒加密/解密实现原理(附带源码)

《java恺撒加密/解密实现原理(附带源码)》本文介绍Java实现恺撒加密与解密,通过固定位移量对字母进行循环替换,保留大小写及非字母字符,由于其实现简单、易于理解,恺撒加密常被用作学习加密算法的入... 目录Java 恺撒加密/解密实现1. 项目背景与介绍2. 相关知识2.1 恺撒加密算法原理2.2 Ja