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获取指定名字的程序的文件路径的两种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要... 最近在做项目,需要用到给定一个程序名字就可以自动获取到这个程序在Windows系统下的绝对路径,以下

SpringBoot 获取请求参数的常用注解及用法

《SpringBoot获取请求参数的常用注解及用法》SpringBoot通过@RequestParam、@PathVariable等注解支持从HTTP请求中获取参数,涵盖查询、路径、请求体、头、C... 目录SpringBoot 提供了多种注解来方便地从 HTTP 请求中获取参数以下是主要的注解及其用法:1

JWT + 拦截器实现无状态登录系统

《JWT+拦截器实现无状态登录系统》JWT(JSONWebToken)提供了一种无状态的解决方案:用户登录后,服务器返回一个Token,后续请求携带该Token即可完成身份验证,无需服务器存储会话... 目录✅ 引言 一、JWT 是什么? 二、技术选型 三、项目结构 四、核心代码实现4.1 添加依赖(pom

基于Python实现自动化邮件发送系统的完整指南

《基于Python实现自动化邮件发送系统的完整指南》在现代软件开发和自动化流程中,邮件通知是一个常见且实用的功能,无论是用于发送报告、告警信息还是用户提醒,通过Python实现自动化的邮件发送功能都能... 目录一、前言:二、项目概述三、配置文件 `.env` 解析四、代码结构解析1. 导入模块2. 加载环

linux系统上安装JDK8全过程

《linux系统上安装JDK8全过程》文章介绍安装JDK的必要性及Linux下JDK8的安装步骤,包括卸载旧版本、下载解压、配置环境变量等,强调开发需JDK,运行可选JRE,现JDK已集成JRE... 目录为什么要安装jdk?1.查看linux系统是否有自带的jdk:2.下载jdk压缩包2.解压3.配置环境

Java调用Python脚本实现HelloWorld的示例详解

《Java调用Python脚本实现HelloWorld的示例详解》作为程序员,我们经常会遇到需要在Java项目中调用Python脚本的场景,下面我们来看看如何从基础到进阶,一步步实现Java与Pyth... 目录一、环境准备二、基础调用:使用 Runtime.exec()2.1 实现步骤2.2 代码解析三、

SQL Server 查询数据库及数据文件大小的方法

《SQLServer查询数据库及数据文件大小的方法》文章介绍了查询数据库大小的SQL方法及存储过程实现,涵盖当前数据库、所有数据库的总大小及文件明细,本文结合实例代码给大家介绍的非常详细,感兴趣的... 目录1. 直接使用SQL1.1 查询当前数据库大小1.2 查询所有数据库的大小1.3 查询每个数据库的详

Nginx添加内置模块过程

《Nginx添加内置模块过程》文章指导如何检查并添加Nginx的with-http_gzip_static模块:确认该模块未默认安装后,需下载同版本源码重新编译,备份替换原有二进制文件,最后重启服务验... 目录1、查看Nginx已编辑的模块2、Nginx官网查看内置模块3、停止Nginx服务4、Nginx

Python如何调用另一个类的方法和属性

《Python如何调用另一个类的方法和属性》在Python面向对象编程中,类与类之间的交互是非常常见的场景,本文将详细介绍在Python中一个类如何调用另一个类的方法和属性,大家可以根据需要进行选择... 目录一、前言二、基本调用方式通过实例化调用通过类继承调用三、高级调用方式通过组合方式调用通过类方法/静

C#控制台程序同步调用WebApi实现方式

《C#控制台程序同步调用WebApi实现方式》控制台程序作为Job时,需同步调用WebApi以确保获取返回结果后执行后续操作,否则会引发TaskCanceledException异常,同步处理可避免异... 目录同步调用WebApi方法Cls001类里面的写法总结控制台程序一般当作Job使用,有时候需要控制