expo在安卓端设置徽标是可以的但是清除、获取当前徽标数均有问题。下面通过注册插件方式加载原生代码解决
import { ConfigPlugin, withAndroidManifest, AndroidConfig } from '@expo/config-plugins';
import { ExpoConfig } from '@expo/config-types';
const { addMetaDataItemToMainApplication, getMainApplicationOrThrow } = AndroidConfig.Manifest;
const withAndroidBadge: ConfigPlugin = (config: ExpoConfig) => {
  return withAndroidManifest(config, async (config) => {
    const mainApplication = getMainApplicationOrThrow(config.modResults);
    // 添加 me.leolin.shortcutbadger
    addMetaDataItemToMainApplication(
      mainApplication,
      'me.leolin.shortcutbadger.impl.xiaomi.XiaomiHomeBadger',
      'me.leolin.shortcutbadger.impl.xiaomi.XiaomiHomeBadger'
    );
    // 添加其他厂商的实现...
    addMetaDataItemToMainApplication(
      mainApplication,
      'me.leolin.shortcutbadger.impl.huawei.HuaweiHomeBadger',
      'me.leolin.shortcutbadger.impl.huawei.HuaweiHomeBadger'
    );
    addMetaDataItemToMainApplication(
      mainApplication,
      'me.leolin.shortcutbadger.impl.oppo.OPPOHomeBader',
      'me.leolin.shortcutbadger.impl.oppo.OPPOHomeBader'
    );
    return config;
  });
};
export default withAndroidBadge; import { ConfigPlugin, withDangerousMod, AndroidConfig } from '@expo/config-plugins';
import { ExpoConfig } from '@expo/config-types';
import * as fs from 'fs';
import * as path from 'path';
const createBadgeModuleContents = (packageName: string) => `
package ${packageName};
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Promise;
import me.leolin.shortcutbadger.ShortcutBadger;
import android.content.SharedPreferences;
import android.content.Context;
public class BadgeModule extends ReactContextBaseJavaModule {
    private static final String BADGE_COUNT_KEY = "badge_count";
    private final SharedPreferences preferences;
    public BadgeModule(ReactApplicationContext reactContext) {
        super(reactContext);
        preferences = reactContext.getSharedPreferences("badge_preferences", Context.MODE_PRIVATE);
    }
    @Override
    public String getName() {
        return "BadgeModule";
    }
    @ReactMethod
    public void getBadgeCount(Promise promise) {
        try {
            int count = preferences.getInt(BADGE_COUNT_KEY, 0);
            promise.resolve(count);
        } catch (Exception e) {
            promise.reject("ERROR", e.getMessage());
        }
    }
    @ReactMethod
    public void setBadgeCount(int count, Promise promise) {
        try {
            preferences.edit().putInt(BADGE_COUNT_KEY, count).apply();
            boolean success = ShortcutBadger.applyCount(getReactApplicationContext(), count);
            promise.resolve(success);
        } catch (Exception e) {
            promise.reject("ERROR", e.getMessage());
        }
    }
    @ReactMethod
    public void clearBadge(Promise promise) {
        try {
            preferences.edit().putInt(BADGE_COUNT_KEY, 0).apply();
            boolean success = ShortcutBadger.removeCount(getReactApplicationContext());
            promise.resolve(success);
        } catch (Exception e) {
            promise.reject("ERROR", e.getMessage());
        }
    }
}
`;
const createBadgePackageContents = (packageName: string) => `
package ${packageName};
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class BadgePackage implements ReactPackage {
    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        modules.add(new BadgeModule(reactContext));
        return modules;
    }
}
`;
const withAndroidBadgeModule: ConfigPlugin = (config: ExpoConfig) => {
  return withDangerousMod(config, [
    'android',
    async (config) => {
      const packageName = config.android?.package || 'club.rubicone.dev';
      const packagePath = packageName.replace(/\./g, '/');
      
      const javaPath = path.join(
        config.modRequest.platformProjectRoot,
        'app',
        'src',
        'main',
        'java',
        packagePath
      );
      await fs.promises.mkdir(javaPath, { recursive: true });
      await fs.promises.writeFile(
        path.join(javaPath, 'BadgeModule.java'),
        createBadgeModuleContents(packageName)
      );
      await fs.promises.writeFile(
        path.join(javaPath, 'BadgePackage.java'),
        createBadgePackageContents(packageName)
      );
      // 修改 MainApplication.kt 添加 BadgePackage
      const mainApplicationPath = path.join(javaPath, 'MainApplication.kt');
      let mainApplicationContent = await fs.promises.readFile(mainApplicationPath, 'utf8');
      if (!mainApplicationContent.includes('BadgePackage')) {
        // 匹配 getPackages 函数
        const packagePattern = /override fun getPackages\(\): List<ReactPackage> \{[\s\S]*?val packages = PackageList\(this\)\.packages[\s\S]*?return packages/;
        
        mainApplicationContent = mainApplicationContent.replace(
          packagePattern,
          (match) => {
            const insertPoint = match.indexOf('return packages');
            return match.slice(0, insertPoint) +
              '            packages.add(BadgePackage())\n            ' +
              match.slice(insertPoint);
          }
        );
        // 添加导入语句
        if (!mainApplicationContent.includes('import ' + packageName + '.BadgePackage')) {
          const lastImportIndex = mainApplicationContent.lastIndexOf('import ');
          const insertIndex = mainApplicationContent.indexOf('\n', lastImportIndex) + 1;
          mainApplicationContent = 
            mainApplicationContent.slice(0, insertIndex) +
            `import ${packageName}.BadgePackage\n` +
            mainApplicationContent.slice(insertIndex);
        }
        await fs.promises.writeFile(mainApplicationPath, mainApplicationContent);
      }
      // 修改 build.gradle 添加依赖
      const buildGradlePath = path.join(config.modRequest.platformProjectRoot, 'app', 'build.gradle');
      let buildGradleContent = await fs.promises.readFile(buildGradlePath, 'utf8');
      if (!buildGradleContent.includes('me.leolin:ShortcutBadger')) {
        buildGradleContent = buildGradleContent.replace(
          /dependencies\s*{/,
          `dependencies {\n    implementation "me.leolin:ShortcutBadger:1.1.22@aar"`
        );
        await fs.promises.writeFile(buildGradlePath, buildGradleContent);
      }
      return config;
    },
  ]);
};
export default withAndroidBadgeModule; import { ConfigPlugin, withGradleProperties } from '@expo/config-plugins';
const withAndroidGradleProperties: ConfigPlugin = (config) => {
  return withGradleProperties(config, (config) => {
    config.modResults = config.modResults.filter(
      (item) => !(item.type === 'property') || item.key !== 'shortcutBadgerDependency'
    );
    config.modResults.push({
      type: 'property',
      key: 'shortcutBadgerDependency',
      value: 'me.leolin:ShortcutBadger:1.1.22@aar',
    });
    return config;
  });
};
export default withAndroidGradleProperties; 然后统一封装一下在IOS使用expo-notifications原有的,在安卓端使用我们自己注册的原生插件
```
// 角标管理
export const BadgeManager = {
    // 设置角标数
    setBadgeCount: async (count: number): Promise<boolean> => {
        try {
            if (Platform.OS === 'android') {
                if (NativeModules.BadgeModule) {
                    await NativeModules.BadgeModule.setBadgeCount(count);
                }
            } else {
                await Notifications.setBadgeCountAsync(count);
            }
            return true;
        } catch (error) {
            Logger.getInstance().error("Failed to set badge count", "角标设置", error);
            return false;
        }
    },
    // 清除角标
    clearBadge: async (): Promise<boolean> => {
        try {
            console.log("Clearing badge count")
            if (Platform.OS === 'android') {
                if (NativeModules.BadgeModule) {
                    await NativeModules.BadgeModule.clearBadge();
                }
            } else {
                await Notifications.setBadgeCountAsync(0);
            }
            return true;
        } catch (error) {
            Logger.getInstance().error("Failed to clear badge count", "角标清除", error);
            return false;
        }
    },
    // 增加角标数
    incrementBadge: async (): Promise<boolean> => {
        try {
            let currentBadge=0;
            if (Platform.OS === 'android') {
                if (NativeModules.BadgeModule) {
                    currentBadge = await NativeModules.BadgeModule.getBadgeCount();
                }
            } else {
                currentBadge = await Notifications.getBadgeCountAsync();
            }
            return await BadgeManager.setBadgeCount(currentBadge + 1);
        } catch (error) {
            Logger.getInstance().error("Failed to increment badge count", "角标增加", error);
            return false;
        }
    }
};自动加载注册插件目录下插件
import { ExpoConfig, ConfigContext } from 'expo/config';
import { ConfigPlugin } from '@expo/config-plugins';
import ts from 'typescript';
import fs from 'fs';
import path from 'path';
// 添加缓存机制
const pluginCache = new Map<string, ConfigPlugin<any>>();
const compiledFiles = new Set<string>();
// 修改 compilePlugin 函数,添加错误处理
const compilePlugin = (filePath: string): ConfigPlugin<any> => {
  try {
    // 检查缓存
    const cacheKey = `${filePath}:${fs.statSync(filePath).mtimeMs}`;
    if (pluginCache.has(cacheKey)) {
      return pluginCache.get(cacheKey)!;
    }
    const source = fs.readFileSync(filePath, 'utf8');
    const result = ts.transpileModule(source, {
      compilerOptions: {
        module: ts.ModuleKind.CommonJS,
        target: ts.ScriptTarget.ES2016,
        esModuleInterop: true,
      },
    });
    const compiledDir = path.join(process.cwd(), 'compiled-plugins');
    if (!fs.existsSync(compiledDir)) {
      fs.mkdirSync(compiledDir, { recursive: true });
    }
    const fileName = path.basename(filePath, '.ts');
    const tempFile = path.join(compiledDir, `${fileName}.js`);
    
    console.log('Compiling plugin:', fileName);
    fs.writeFileSync(tempFile, result.outputText);
    
    // 记录已编译的文件
    compiledFiles.add(tempFile);
    const plugin = require(tempFile);
    // 存入缓存
    pluginCache.set(cacheKey, plugin.default);
    return plugin.default;
  } catch (error) {
    console.error(`Error loading plugin ${path.basename(filePath)}:`, error);
    throw error;
  }
};
// 添加清理函数
const cleanupCompiledPlugins = () => {
  if (process.env.EAS_BUILD) {
    // 在 EAS 构建过程中不清理文件
    return;
  }
  
  compiledFiles.forEach(file => {
    try {
      if (fs.existsSync(file)) {
        fs.unlinkSync(file);
      }
    } catch (error) {
      console.warn(`Failed to cleanup file ${file}:`, error);
    }
  });
  compiledFiles.clear();
};
// 修改 loadPlugins 函数
const loadPlugins = (): ConfigPlugin<any>[] => {
  const pluginsDir = path.join(process.cwd(), 'plugins');
  const files = fs.readdirSync(pluginsDir);
  
  // 在加载新插件前清理旧的编译文件
  cleanupCompiledPlugins();
  
  const plugins = files
    .filter(file => file.startsWith('with') && file.endsWith('.ts'))
    .map(file => compilePlugin(path.join(pluginsDir, file)));
  // 注册进程退出时的清理函数
  if (!process.env.EAS_BUILD) {
    process.on('exit', cleanupCompiledPlugins);
    process.on('SIGINT', () => {
      cleanupCompiledPlugins();
      process.exit();
    });
  }
  return plugins;
};
// 加载所有插件
const customPlugins = loadPlugins();
// 定义插件类型
type ExpoPlugin = string | [string, any] | [string] | [] | ConfigPlugin<any>;
// 配置类型
type AppConfig = Omit<ExpoConfig, 'plugins'> & {
  plugins: ExpoPlugin[];
};
 
           
             
          
评论区