Android 9 (P)非SDK API限制调用开发指南

2023-10-27

    Android 9 (P)非SDK API限制调用开发指南


Android 9 (P)开发适配指南系列博客目录:

Adnroid 9 (P) recovery升级Map of '@/cache/recovery/block.map’failed问题分析指南
Android 9 (P)版本解决VNDK library: XXX’s ABI has EXTENDING CHANGES
Android 9 (P)非SDK API限制调用开发指南
Android 9 (P)适配以太网功能开发指南
Android 9 (P)在user模式下无法使用fastboot烧录怎么破
Android 9 (P)静默安装/卸载App适配终极指南



引言

  有过Android开发经验的童鞋应该知道,每一次Android大版本的升级,往往会有大量的APP出现兼容性问题,导致这个情况的主要原因是由于APP的热修复以及依赖Android internal API(内部API),也就是非SDK API。这些API是指标记@hide的类、方法以及字段,它们不属于官方Android SDK的字段与函数(当然这其中也包括一些废旧SDK的使用,这个不是本篇讨论的重点)。

  • 非SDK接口指不在官方Android SDK涵盖范围内的 Java 字段和方法。此类接口是 SDK 的内部实现细节,可能随时会被修改,且不对开发者另行通知。

Google希望未来Android大版本升级,APP都能正常运行,而很多APP对内部API的调用通过反射或JNI间接调用的方法来调用,破坏兼容性。 为此Google从Android P开始限制对内部API的使用,继续使用则抛出如下异常。
在这里插入图片描述
Google在Android P版本上对隐藏的Java API进行了一定的限制,后续版本会逐步的完善限制。App侧通过反射等方式调用的Java API将会有很多限制,对于Android应用想再搞一些插件化之类的黑科技便是带着脚手铐跳舞,即便能跳但舞姿已不太优雅了。这也是为了Android 生态在未来更好的发展。真是为难了谷歌妈咪为了Android的和谐发展所做的努力。

Android 7.0对Native的NDK的调用限制是手铐,而Android 9.0对Java层SDK的调用限制就是脚铐。脚铐手铐同时上,为了Android碎片化的整理真是操作了心啊。

注意:本文是以Android 9源码为基础来说明Android P对非SDK API调用开发指南。

libcore/ojluni/src/main/java/java/lang/Class.java
art/runtime/native/java_lang_Class.cc
art/runtime/hidden_api.h
art/runtime/runtime.h
art/libdexfile/dex/hidden_api_access_flags.h
art/runtime/hidden_api.cc
art/runtime/art_method-inl.h
frameworks/base/core/java/android/content/pm/ApplicationInfo.java
libcore/libart/src/main/java/dalvik/system/VMRuntime.java



一.实例演示

说一千道一万,首先让我们来一个实例演示一番,这样给大伙有一个直观的感受不是。


1.1 实例代码

这里我们以VMRuntime类来说明,其中有一个本地方法如下:

//VMRuntime.java
    /** 
     * Sets the list of exemptions from hidden API access enforcement.
     *
     * @param signaturePrefixes
     *         A list of signature prefixes. Each item in the list is a prefix match on the type
     *         signature of a blacklisted API. All matching APIs are treated as if they were on
     *         the whitelist: access permitted, and no logging..
     */
    public native void setHiddenApiExemptions(String[] signaturePrefixes);

通过反射调用该方法:

    public void useHideFun() {

        try {
            Log.e("HIDE", "START");
            Class<?> VMRuntimeClass = null;
            VMRuntimeClass = Class.forName("dalvik.system.VMRuntime");
            String[] test = null;
            Method setHiddenApiExemptionsMethod = VMRuntimeClass
                    .getDeclaredMethod("setHiddenApiExemptions", String[].class);
            setHiddenApiExemptionsMethod.setAccessible(true);
            Log.e("HIDE", "SUCCESS");
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

1.2 运行实例

我们运行一把,其中明显实体不能访问hiden method,这就是Android P对非SDK API的调用的限制。
在这里插入图片描述

04-27 14:27:03.468  5933  5933 W om.example.tes: Accessing hidden method Ldalvik/system/VMRuntime;->setHiddenApiExemptions([Ljava/lang/String;)V (blacklist, reflection)
04-27 14:27:03.468   683   683 I ConfigStore: android::hardware::configstore::V1_0::ISurfaceFlingerConfigs::hasHDRDisplay retrieved: 0
04-27 14:27:03.469   683   683 I ConfigStore: android::hardware::configstore::V1_0::ISurfaceFlingerConfigs::hasHDRDisplay retrieved: 0
04-27 14:27:03.469  5933  5933 W System.err: java.lang.NoSuchMethodException: setHiddenApiExemptions [class [Ljava.lang.String;]
04-27 14:27:03.469   683   683 I ConfigStore: android::hardware::configstore::V1_0::ISurfaceFlingerConfigs::hasHDRDisplay retrieved: 0
04-27 14:27:03.469  5933  5933 W System.err:    at java.lang.Class.getMethod(Class.java:2068)
04-27 14:27:03.470  5933  5933 W System.err:    at java.lang.Class.getDeclaredMethod(Class.java:2047)
04-27 14:27:03.470  5933  5933 W System.err:    at com.example.test.MainActivity.getProperty(MainActivity.java:68)
04-27 14:27:03.470  5933  5933 W System.err:    at com.example.test.MainActivity.onCreate(MainActivity.java:27)
04-27 14:27:03.471  5933  5933 W System.err:    at android.app.Activity.performCreate(Activity.java:7144)
04-27 14:27:03.471  5933  5933 W System.err:    at android.app.Activity.performCreate(Activity.java:7135)
04-27 14:27:03.471  5933  5933 W System.err:    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1272)
04-27 14:27:03.472  5933  5933 W System.err:    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2932)
04-27 14:27:03.472  5933  5933 W System.err:    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3087)
04-27 14:27:03.473  5933  5933 W System.err:    at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
04-27 14:27:03.473  5933  5933 W System.err:    at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
04-27 14:27:03.475  5933  5933 W System.err:    at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
04-27 14:27:03.475  5933  5933 W System.err:    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1817)
04-27 14:27:03.476  5933  5933 W System.err:    at android.os.Handler.dispatchMessage(Handler.java:106)
04-27 14:27:03.476  5933  5933 W System.err:    at android.os.Looper.loop(Looper.java:193)
04-27 14:27:03.476  5933  5933 W System.err:    at android.app.ActivityThread.main(ActivityThread.java:6746)
04-27 14:27:03.476  5933  5933 W System.err:    at java.lang.reflect.Method.invoke(Native Method)
04-27 14:27:03.477  5933  5933 W System.err:    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
04-27 14:27:03.477  5933  5933 W System.err:    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)



二.源码分析

在正式开发分析前,先奉上源码分析流程图,这样有一个整体的轮廓认识。
在这里插入图片描述


2.1 getDeclaredMethod

通过前面的实例我们可以看到,我们是在调用getDeclaredMethod的过程中失败了,所以我们先大胆的猜测一定是Android P在getDeclaredMethod的调用过程中做了限制,然后导致反射失败了。让我们大胆验证,小心求证。先从Class.java开始分析。

//Class.java
    @CallerSensitive
    public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
        throws NoSuchMethodException, SecurityException {
        return getMethod(name, parameterTypes, false);//见2.2
    }

2.2 getMethod

//Class.java
    private Method getMethod(String name, Class<?>[] parameterTypes, boolean recursivePublicMethods)
            throws NoSuchMethodException {
        if (name == null) {//判断方法名是否为空
            throw new NullPointerException("name == null");
        }
        if (parameterTypes == null) {//判断参数是否为空
            parameterTypes = EmptyArray.CLASS;
        }
        for (Class<?> c : parameterTypes) {
            if (c == null) {
                throw new NoSuchMethodException("parameter type is null");
            }
        }
        //一切条件满足且是非public方法,则执行getDeclaredMethodInternal方法,具体见2.3
        Method result = recursivePublicMethods ? getPublicMethodRecursive(name, parameterTypes)
                                               : getDeclaredMethodInternal(name, parameterTypes);
        // Fail if we didn't find the method or it was expected to be public.
        //如果没有找到,则抛出异常
        if (result == null ||
            (recursivePublicMethods && !Modifier.isPublic(result.getAccessFlags()))) {
            throw new NoSuchMethodException(name + " " + Arrays.toString(parameterTypes));
        }
        return result;
    }

2.3 getDeclaredMethodInternal

//Class.java
    /**
     * Returns the method if it is defined by this class; {@code null} otherwise. This may return a
     * non-public member.
     *      
     * @param name the method name
     * @param args the method's parameter types
     */     
    @FastNative 
    private native Method getDeclaredMethodInternal(String name, Class<?>[] args);

这是一个Java本地方法,最终通过JNI调用到art/runtime/native/java_lang_Class.cc里面,如下:

//native/java_lang_Class.cc
static jobject Class_getDeclaredMethodInternal(JNIEnv* env, jobject javaThis,
                                               jstring name, jobjectArray args) {
  ScopedFastNativeObjectAccess soa(env);
  StackHandleScope<1> hs(soa.Self());
  DCHECK_EQ(Runtime::Current()->GetClassLinker()->GetImagePointerSize(), kRuntimePointerSize);
  DCHECK(!Runtime::Current()->IsActiveTransaction());
  Handle<mirror::Method> result = hs.NewHandle(
      mirror::Class::GetDeclaredMethodInternal<kRuntimePointerSize, false>(
          soa.Self(),
          DecodeClass(soa, javaThis),
          soa.Decode<mirror::String>(name),
          soa.Decode<mirror::ObjectArray<mirror::Class>>(args)));
  检测该方法是否允许访问
  if (result == nullptr || ShouldBlockAccessToMember(result->GetArtMethod(), soa.Self())) {
    return nullptr;
  }
  return soa.AddLocalReference<jobject>(result.Get());
}

这个地方是关键,我们来看一下Android 9和Android 8相比做了哪些修改。下图中左边是Android 9,右边是Android 8:
在这里插入图片描述
是不是一下就看出来了,Android 的代码在反射时候会增加一个ShouldBlockAccessToMember()判断,如果返回true,那么你在getDeclaredMethod()时候就会得到null。如果你的Rom不想限制客户的API反射调用的话,就可以屏蔽这个地方即可。


2.4 ShouldBlockAccessToMember

template<typename T>
ALWAYS_INLINE static bool ShouldBlockAccessToMember(T* member, Thread* self)
    REQUIRES_SHARED(Locks::mutator_lock_) {
    
  hiddenapi::Action action = hiddenapi::GetMemberAction( // 获取的action的类型是重点
      member, self, IsCallerTrusted, hiddenapi::kReflection);  // kReflection : 反射方式调用
  if (action != hiddenapi::kAllow) {
    hiddenapi::NotifyHiddenApiListener(member); // 当不是kAllow时,则需要警告或者弹窗,则通过此方法通知
  }

  return action == hiddenapi::kDeny; // 返回true则被限制,也就是当action是kAllow/kAllowButWarn/kAllowButWarnAndToast返回false,都是允许accessmember的
}

让我们来重点分析一下这个函数,这个地方就是反射接口限制判断的地方,此处的入参参数AccessMethod access_method等于hiddenapi::kReflection,除此之外还有其他几种模式,如下:

2.4.1 AccessMethod

hidden_api.h

// Hidden API enforcement policy
// This must be kept in sync with ApplicationInfo.ApiEnforcementPolicy in
// frameworks/base/core/java/android/content/pm/ApplicationInfo.java
enum class EnforcementPolicy {
  kNoChecks             = 0,
  kJustWarn             = 1,  // keep checks enabled, but allow everything (enables logging)
  kDarkGreyAndBlackList = 2,  // ban dark grey & blacklist
  kBlacklistOnly        = 3,  // ban blacklist violations only
  kMax = kBlacklistOnly,
};

enum Action {
  kAllow,    //允许
  kAllowButWarn,   //允许 + 警告
  kAllowButWarnAndToast,  //允许+警告+弹窗
  kDeny		//阻止
};

enum AccessMethod {
  kNone,  			//测试模式,不会出现在实际场景访问权限
  kReflection,		//Java发射调用
  kJNI,				//JNI调用过程
  kLinking,			//动态链接过程
};
2.4.1 各种限制场景分析

通过前面的篇章我们知道ShouldBlockAccessToMember是限制内部API访问的最核心代码,让我们对其限制的几种模式一一分析:

  • kReflection反射过程
    Class_newInstance:对象实例化
    Class_getDeclaredConstructorInternal:构造方法
    Class_getDeclaredMethodInternal:获取方法
    Class_getDeclaredField:获取字段
    Class_getPublicFieldRecursive:获取字段

  • kJNI的JNI调用过程
    FindMethodID:查找方法
    FindFieldID:查找字段

  • kJNI的JNI调用过程
    UnstartedClassNewInstance
    UnstartedClassGetDeclaredConstructor
    UnstartedClassGetDeclaredMethod
    UnstartedClassGetDeclaredField


2.5 GetMemberAction

hidden_api.h

template<typename T>
这里的Action就是允许,警告,弹窗,阻止其中的一种
inline Action GetMemberAction(T* member,
                              Thread* self,
                              std::function<bool(Thread*)> fn_caller_is_trusted,
                              AccessMethod access_method)
    REQUIRES_SHARED(Locks::mutator_lock_) {
  //获取hidenn API的可访问标识
  // HiddenApiAccessFlags 定义在 hidden_api_access_flags.h 白名单 浅灰名单 深灰名单 黑名单
  HiddenApiAccessFlags::ApiList api_list = member->GetHiddenApiAccessFlags();

  //获取相应的访问行为【小节2.6】
  Action action = GetActionFromAccessFlags(member->GetHiddenApiAccessFlags());
  if (action == kAllow) {  //允许则直接返回
    return action;
  }

   //检测是否平台调用,通过函数指针调用到IsCallerTrusted函数【小节2.7】
  if (fn_caller_is_trusted(self)) {
    return kAllow;
  }

  //对于hidden接口,且非平台调用【小节2.8】
  return detail::GetMemberActionImpl(member, api_list, action, access_method);
}

此方法里有三处要分析,分别是:

  • 当Action=kAllow,则直接返回;否则执行如下:
  • 通过fn_caller_is_trusted看当前是否是系统调用的,如果是系统调用则返回,否则执行如下:
  • 通过GetMemberActionImpl()做进一步判断

在分析上面三个方法之前先来看看HiddenApiAccessFlags 的定义如下:

. art/libdexfile/dex/hidden_api_access_flags.h

class HiddenApiAccessFlags {
 public:
  enum ApiList {
    kWhitelist = 0,  //白名单
    kLightGreylist, // 浅灰名单
    kDarkGreylist,  // 深灰名单
    kBlacklist,     // 黑名单
  };  

这几个名单的定义如下:

  • 白名单:SDK 本身

  • 浅灰名单:仍允许调用的非 SDK 方法和字段

  • 深灰名单

    – 若应用的 target SDK 低于 Android P (即 targetSdkVersion <28):允许调用深灰名单中的接

    – 若应用的 target SDK 为 Android P 或更高 (即 targetSdkVersion >= 28):深灰名单与黑名单的限制相同

  • 黑名单:不论 target SDK 版本为多少,所有应用均不允许调用黑名单接口。对开发者来说,相当于系统里不存在这些接口。比如,当应用试图调用此类接口时,系统会抛出 NoSuchMethodError / NoSuchFieldException 异常,并且在应用获取特定类的字段和方法列表时,不在返回列表中包含此类接口。


2.6 GetActionFromAccessFlags

先看GetActionFromAccessFlags(member->GetHiddenApiAccessFlags()),要先分析member->GetHiddenApiAccessFlags.

art_method-inl.h

inline HiddenApiAccessFlags::ApiList ArtMethod::GetHiddenApiAccessFlags()
    REQUIRES_SHARED(Locks::mutator_lock_) {
  if (UNLIKELY(IsIntrinsic())) {
    switch (static_cast<Intrinsics>(GetIntrinsic())) {
      case Intrinsics::kSystemArrayCopyChar:
      case Intrinsics::kStringGetCharsNoCheck:
      case Intrinsics::kReferenceGetReferent:
        // These intrinsics are on the light greylist and will fail a DCHECK in
        // SetIntrinsic() if their flags change on the respective dex methods.
        // Note that the DCHECK currently won't fail if the dex methods are
        // whitelisted, e.g. in the core image (b/77733081). As a result, we
        // might print warnings but we won't change the semantics.
        return HiddenApiAccessFlags::kLightGreylist;
      case Intrinsics::kVarHandleFullFence:
      case Intrinsics::kVarHandleAcquireFence:
      case Intrinsics::kVarHandleReleaseFence:
      case Intrinsics::kVarHandleLoadLoadFence:
      case Intrinsics::kVarHandleStoreStoreFence:
      case Intrinsics::kVarHandleCompareAndExchange:
      case Intrinsics::kVarHandleCompareAndExchangeAcquire:
      case Intrinsics::kVarHandleCompareAndExchangeRelease:
      case Intrinsics::kVarHandleCompareAndSet:
      case Intrinsics::kVarHandleGet:
      case Intrinsics::kVarHandleGetAcquire:
      case Intrinsics::kVarHandleGetAndAdd:
      case Intrinsics::kVarHandleGetAndAddAcquire:
      case Intrinsics::kVarHandleGetAndAddRelease:
      case Intrinsics::kVarHandleGetAndBitwiseAnd:
      case Intrinsics::kVarHandleGetAndBitwiseAndAcquire:
      case Intrinsics::kVarHandleGetAndBitwiseAndRelease:
      case Intrinsics::kVarHandleGetAndBitwiseOr:
      case Intrinsics::kVarHandleGetAndBitwiseOrAcquire:
      case Intrinsics::kVarHandleGetAndBitwiseOrRelease:
      case Intrinsics::kVarHandleGetAndBitwiseXor:
      case Intrinsics::kVarHandleGetAndBitwiseXorAcquire:
      case Intrinsics::kVarHandleGetAndBitwiseXorRelease:
      case Intrinsics::kVarHandleGetAndSet:
      case Intrinsics::kVarHandleGetAndSetAcquire:
      case Intrinsics::kVarHandleGetAndSetRelease:
      case Intrinsics::kVarHandleGetOpaque:
      case Intrinsics::kVarHandleGetVolatile:
      case Intrinsics::kVarHandleSet:
      case Intrinsics::kVarHandleSetOpaque:
      case Intrinsics::kVarHandleSetRelease:
      case Intrinsics::kVarHandleSetVolatile:
      case Intrinsics::kVarHandleWeakCompareAndSet:
      case Intrinsics::kVarHandleWeakCompareAndSetAcquire:
      case Intrinsics::kVarHandleWeakCompareAndSetPlain:
      case Intrinsics::kVarHandleWeakCompareAndSetRelease:
        // These intrinsics are on the blacklist and will fail a DCHECK in
        // SetIntrinsic() if their flags change on the respective dex methods.
        // Note that the DCHECK currently won't fail if the dex methods are
        // whitelisted, e.g. in the core image (b/77733081). Given that they are
        // exclusively VarHandle intrinsics, they should not be used outside
        // tests that do not enable hidden API checks.
        return HiddenApiAccessFlags::kBlacklist;
      default:
        // Remaining intrinsics are public API. We DCHECK that in SetIntrinsic().
        return HiddenApiAccessFlags::kWhitelist;
    }
  } else {
    return HiddenApiAccessFlags::DecodeFromRuntime(GetAccessFlags());
  }
}

GetHiddenApiAccessFlags()获取相应的flag,接着看GetActionFromAccessFlags:

hidden_api.h

inline Action GetActionFromAccessFlags(HiddenApiAccessFlags::ApiList api_list) {
  if (api_list == HiddenApiAccessFlags::kWhitelist) {
    return kAllow;   //位于白名单,则允许访问
  }

  EnforcementPolicy policy = Runtime::Current()->GetHiddenApiEnforcementPolicy();
  if (policy == EnforcementPolicy::kNoChecks) {
    return kAllow;  //非强制执行策略,则允许访问
  }

  if (policy == EnforcementPolicy::kJustWarn) {
    return kAllowButWarn;
  }
  
  //执行到这,policy>=kDarkGreyAndBlackList
  if (static_cast<int>(policy) > static_cast<int>(api_list)) {
    return api_list == HiddenApiAccessFlags::kDarkGreylist
        ? kAllowButWarnAndToast : kAllowButWarn;
  } else {
    return kDeny;
  }
}

以上逻辑用图来展示EnforcementPolicy和HiddenApiAccessFlags在不同取值的情况下,所对应的Action值。 纵轴代表强制策略级别横轴代表隐藏API的标识,表中数据代表Action,如下所示:

纵轴/横轴 kWhitelist kLightGreylist kDarkGreylist kBlacklist
kNoChecks kAllow kAllow kAllow kAllow
kJustWarn kAllow kAllowButWarn kAllowButWarn kAllowButWarn
kDarkGreyAndBlackList kAllow kAllowButWarn kDeny kDeny
kBlacklistOnly kAllow kAllowButWarn kAllowButWarnAndToast kDeny

策略图解如下:

  • 当HiddenApiAccessFlags等于kWhitelist,则Action=kAllow,否则如下
  • 当EnforcementPolicy等于kNoChecks,则Action=kAllow,否则如下
  • 当EnforcementPolicy等于kJustWarn,则Action=kAllowButWarn,否则如下
  • 当HiddenApiAccessFlags等于kLightGreylist,则Action=kAllowButWarn,否则如下
  • 当HiddenApiAccessFlags等于kDarkGreylist,且等于EnforcementPolicy=kBlacklistOnly,则- kAllowButWarnAndToast,否则如下
  • 否则Action=kDeny
2.6.1 EnforcementPolicy

EnforcementPolicy的级别如下:

// Hidden API enforcement policy
// This must be kept in sync with ApplicationInfo.ApiEnforcementPolicy in
// frameworks/base/core/java/android/content/pm/ApplicationInfo.java
enum class EnforcementPolicy {
  kNoChecks             = 0,
  kJustWarn             = 1,  // keep checks enabled, but allow everything (enables logging)
  kDarkGreyAndBlackList = 2,  // ban dark grey & blacklist
  kBlacklistOnly        = 3,  // ban blacklist violations only
  kMax = kBlacklistOnly,
};
  • kNoChecks:允许调用所有API,不做任何检测
  • kJustWarn:允许调用所有API,但是对于私有API的调用会打印警告log
  • kDarkGreyAndBlackList:会阻止调用dark grey或black list中的API
  • kBlacklistOnly:会阻止调用black list中的API

可通过SetHiddenApiEnforcementPolicy()来修改Runtime中的成员变量hidden_api_policy_,如下所示:

void SetHiddenApiEnforcementPolicy(hiddenapi::EnforcementPolicy policy) {
  hidden_api_policy_ = policy;
}

hiddenapi::EnforcementPolicy GetHiddenApiEnforcementPolicy() const {
  return hidden_api_policy_;
}

2.7 IsCallerTrusted

fn_caller_is_trusted 其实是通过函数指针调用到IsCallerTrusted函数

java_lang_Class.cc

static bool IsCallerTrusted(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) {
    ...
    FirstExternalCallerVisitor visitor(self);
    visitor.WalkStack();
    //【见小节2.7.1】
    return visitor.caller != nullptr &&
           hiddenapi::IsCallerTrusted(visitor.caller->GetDeclaringClass())
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android 9 (P)非SDK API限制调用开发指南 的相关文章

  • Spring Cloud Alibaba实战(八) - Dubbo + Nacos

    目录 一 Nacos动态配置 二 Nacos注册中心 三 Sentinel之限流 四 Sentinel之熔断 五 Gateway之路由 限流 六 Gateway之鉴权 日志 七 Gateway搭配Nacos实现动态路由 八 Dubbo Na
  • libcareplus生成热补丁文件

    libcareplus生成热补丁文件 kpatch gensrc汇编文件生成 使用kpatch strip的 strip裁剪不需要关注的节 使用kpatch strip的 rel fixup修正重定位信息 通过strip strip unn
  • Arduino制作温湿度计

    之前买的arduino套装 里面有一个LCD显示屏 就想用它加上手头的一些传感器做点实用的东西 顺便验证一下显示屏是否可用 于是想到了可以做一个温湿度计 实验目的 将温湿度传感器采集的温湿度显示在LCD显示屏上 首先准备工作 1 ardui
  • css区别margin、padding、width、height值为百分比

    margin padding设置为百分比 是相对父元素宽来说的 width设置百分比是相对父元素宽来说的 height设置百分比是相对父元素高来说的 使用padding占位的好处就是布局不会因为图片没有加载而改变
  • 4步教你打造好莱坞科幻特效

    大家一定有看过好莱坞电影 电影里的一幕大家一定印象深刻 男主角在电脑前熟练地敲着键盘 电脑屏幕飞快地闪动 字符也在快速跳动 很有科技感 这样的效果 在 Linux 下也可以实现 甚至连不懂任何 IT 技术的小白跟着本教程也可以轻松装13 我
  • 【数据结构】两栈共享空间(双端栈)

    1 定义 两栈共享空间 使用一个数组来存储两个栈 让一个栈的栈底为该数组的始端 另一个栈的栈底为该数组的末端 两个栈从各自的端点向中间延伸 栈1的底固定在下标为0的一端 栈2的底固定在下标为StackSize 1的一端 top1和top2分
  • 学习软件测试真的三个月可以找到工作吗?

    最近我看到有很多同学私信我 都在问我学了三个月的测试 现在工作怎么样 薪资如何 学的东西能不能在公司里面用到 今天看到这些信息 我就刚好写一篇文章 给大家科普一下 本人之前是做销售行业的 之前写的文章有提到过 因为销售行业竞争太大 并且每个
  • macOS虚拟机安装全过程(VMware)

    作为一名忠实果粉 我最大的愿望就是能够拥有一台Macbook 体验macOS 但是作为学生党 这价钱 贵到离谱啊 不过 VMware这个神器 可以解决一切问题 既然macOS可以在Macbook上运行 为什么就不能在VMware虚拟机上运行

随机推荐

  • python x 0b1010_下面代码的输出结果是

    单选题 下面代码的执行结果是 x 2 x 3 5 2 单选题 下面代码的输出结果是 x 0o1010 print x 单选题 下面代码的执行结果是 s 11 5in eval s 1 2 单选题 关于CSV文件的描述 以下选项中错误的是 单
  • 前端开发--CSS基础

    快速生成css样式 采用简写的方式即可 w200 tab键 width 200px lh200 tab键line height 200px web服务器 免费的远程服务 免费空间 http free 3v do css的书写顺序 布局定位属
  • mac外接显示屏开启hidpi的方法

    之前一直用着switchResX 但是手贱升级到macos10 14之后就失效了 只好另寻别的方法了 准备工作 RDM 开源的更改分辨率插件 快捷下载 PlistEdit PRO plist文件修改器 在线16进制和10进制互转工具 1 关
  • 加州伯克利计算机科学录取,2020加州大学伯克利分校统计学录取案例。

    一 学生背景 本科院校类型 美国本科 本科专业 Applied Mathematics GPA 3 4 4 0 GRE 321 录取项目 MA in Statistics 二 录取学校加州大学伯克利分校介绍 加州大学伯克利分校 Univer
  • HDU 2888 Check Corners

    题目链接 http acm hdu edu cn showproblem php pid 2888 include
  • Vue3 readonly

    readonly 接收一个 ref 或者 reactive 包装对象 返回一个只读的响应式对象 实例
  • ubuntu20.04防火墙相关命令整理

    1 查看防火墙状态 sudo ufw status 2 开启防火墙 sudo ufw enable 3 关闭防火墙 sudo ufw disable 4 重启防火墙 sudo ufw reload 4 开启指定端口 sudo ufw all
  • pytorch踩坑日记

    昨天使用pytorch写一个程序 程序写完之后却一直不能正确运行 今天定位到了代码的问题所在 我的代码其中有一处逻辑是这样的 get a 这里的a就是我想反向求导更新的参数 b torch nonzero a 得到a里面所有不为0的下标 f
  • Spring自定义注解定义AOP配置去xml

    原理参考ImportBeanDefinitionRegistrar SPI简化Spring开发 spring中AOP使用非常广泛 引入方式一般分为两种 注解方式或xml方式 直接方式使用 AspectJ这样的注解 其缺点是需要手写切面实现业
  • 机器学习笔记--1.6数据可视化

    1 表与线性结构的可视化 Python提供四种容器结构 list dict set tuple来装载数据 其中线性结构有两种 list和tuple 由于tuple是只读结构 仅用于外部生成器生成的数据 所以最常用的线性结构就是list im
  • Ldap简单介绍(转)

    注 文章内容转载 觉得对ldap初次接触的你我非常的实用 关于LDAP的概念随便网上有很多 我不想重复 这里只是说一下我自己的 理解 都说它是 轻量级目录协议 太专业 我不懂 我只把它想象成 简单 的 目录协议 几个很重要的概念 以后会用到
  • Linux下MySQL安装

    MySQL安装 过程 下载官方包 wget i c http dev mysql com get mysql57 community release el7 10 noarch rpm 成功信息 FINISHED 2023 03 20 09
  • php预览md文件,用HTML+CSS做一个实时预览的markdown编辑器

    这次给大家带来用HTML CSS做一个实时预览的markdown编辑器 用HTML CSS做一个实时预览的markdown编辑器的注意事项有哪些 下面就是实战案例 一起来看一下 第一步 搭建布局 1 构思布局 以下是总体布局 2 项目下新建
  • 在cmd命令下启动软件

    1 配置jdk 1 找到jdk的安装路径 点开到bin目录下 复制这个目录 如下图 2 我的电脑 右键属性 高级系统设置 环境变量 双击 如下图 3 系统变量 path 双击 如下图 4 粘贴上面复制的路径到变量值最前面 末尾以英文的逗号结
  • TypeScript反射机制动态创建类

    前言 在前一篇文章桥接模式与策略模式的区别与刘伟老师的桥接模式中 我们可以明白桥接模式处理得比较好的一个点是在于Java的反射机制 那么 假如我们需要再TypeScript中 来实现桥接模式的处理 需要怎么样来实现这个 反射 呢 注 在策略
  • 【计算机毕业设计】045新闻推荐系统

    一 系统截图 需要演示视频可以私聊 摘要 随着信息互联网购物的飞速发展 国内放开了自媒体的政策 一般企业都开始开发属于自己内容分发平台的网站 本文介绍了新闻推荐系统的开发全过程 通过分析企业对于新闻推荐系统的需求 创建了一个计算机管理新闻推
  • Python3 requests_htm 设置代理

    简介 Python上有一个非常著名的HTTP库 requests 相比大家都听说过 用过的人都说好 现在requests库的作者又发布了一个新库 叫做requests html 看名字也能猜出来 这是一个解析HTML的库 而且用起来和req
  • QT入门Containers之QGroupBox、QDockWidget

    目录 一 QGroupBox界面相关 1 布局介绍 二 QDockWidget的介绍 1 去除标题栏 2 设置垂直属性 3 代码测试下 三 Demo展示 此文为作者原创 创作不易 转载请标明出处 一 QGroupBox界面相关 1 布局介绍
  • 在Tomcat中部署war包,404

    用IDEA中的mevan插件打包后 放在服务器中 访问404 放war包的位置没有问题 端口也开放了 就是访问不到 解决方法为 在启动类上继承 SpringBootServletInitializer 然后重写config方法 再次打包后
  • Android 9 (P)非SDK API限制调用开发指南

    Android 9 P 非SDK API限制调用开发指南 Android 9 P 开发适配指南系列博客目录 Adnroid 9 P recovery升级Map of cache recovery block map failed问题分析指南