Android杂谈

ActivityThread和AMS之间简单通信流程图

这里写图片描述

PMS简单通信流程图

这里写图片描述

在application标签内获取meta元素的值获取mata标签
  • 当value是字符串时,使用getString获取值
<meta-data 
    android:name="UMENG_CHANNEL" 
    android:value="china"/> 
try {
            ApplicationInfo appInfo = getPackageManager()
                    .getApplicationInfo(getPackageName(),
                            PackageManager.GET_META_DATA);
            String UMENG_CHANNEL = appInfo.metaData.getString("UMENG_CHANNEL");
            Log.d("meta:", "UMENG_CHANNEL=" + UMENG_CHANNEL);
        } catch (Exception e) {
            Log.d("meta:", "exception");
        }
  • 当value是字符串数字时,需要使用getInt获取值,否则获取值为null
<meta-data
    android:name="width"
    android:value="1080"/>
try {
        ApplicationInfo appInfo = getPackageManager()
                .getApplicationInfo(getPackageName(),
                        PackageManager.GET_META_DATA);
        int width=appInfo.metaData.getInt("design_width",0);
        Log.d("meta:", "width=" + width);
    } catch (Exception e) {
        Log.d("meta:", "exception");
    }
设置横竖屏
  • xml中设置

强制为竖屏

<activity
    android:name="com.cn.MainActivity"
    android:screenOrientation="portrait"/>

强制为横屏

<activity
    android:name="com.cn.MainActivity"
    android:screenOrientation="landscape"/> 
  • 代码中设置
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);  //强制为竖屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); //强制为横屏

注意
1:Activity在不设置configChanges属性时
竖屏切换横屏时,生命周期重新走一遍(只走一遍)
onPause->onSaveInstanceState->onStop->onDestroy->onCreate->onStart->onResume

横屏切换竖屏时,生命周期重新走一遍(也只走一遍)
onPause->onSaveInstanceState->onStop->onDestroy->onCreate->onStart->onResume

2:Activity设置了android:configChanges=”orientation”属性时(和不设置没什么区别)
竖屏切换横屏时,生命周期重新走一遍(只走一遍)
onPause->onSaveInstanceState->onStop->onDestroy->onCreate->onStart->onResume

横屏切换竖屏时,生命周期重新走一遍(也只走一遍)
onPause->onSaveInstanceState->onStop->onDestroy->onCreate->onStart->onResume

3:Activity设置了android:configChanges=”orientation|keyboardHidden|screenSize”属性时

竖屏切换横屏时,只调用:onConfigurationChanged方法

横屏切换竖屏时,只调用:onConfigurationChanged方法

4:按Home键返回桌面再回应用时
onPause->onSaveInstanceState->onStop-onRestart>onStart->onResume

5:从一Activity跳转到二Activity时
onPause->onCreate->onStart->onResume->onSaveInstanceState->onStop

6:从二Activity返回一Activity时
onPause->onRestart->onStart->onResume->onStop->onDestroy

7:各个生命周期中不能执行耗时操作,若onCreate中执行耗时操作,必须等到执行完才能继续执行onStart->onResume等方法

获取屏幕宽高、密度比
WindowManager wm = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE);
int width = wm.getDefaultDisplay().getWidth();
int height = wm.getDefaultDisplay().getHeight();
WindowManager wm1 = this.getWindowManager();
int width1 = wm1.getDefaultDisplay().getWidth();
int height1 = wm1.getDefaultDisplay().getHeight();

以上过时

推荐

WindowManager manager = this.getWindowManager();
DisplayMetrics metric = new DisplayMetrics();
manager.getDefaultDisplay().getMetrics(metric);
int width = metric.widthPixels;     // 屏幕宽度(像素)
int height = metric.heightPixels;   // 屏幕高度(像素)
float density = metric.density;      // 屏幕密度(0.75 / 1.0 / 1.5)
int densityDpi = metric.densityDpi;  // 屏幕密度DPI(120 / 160 / 240)
Resources resources = this.getResources();
DisplayMetrics dm = resources.getDisplayMetrics();
int width = dm.widthPixels;
int height = dm.heightPixels;
float density = dm.density;
分辨率和hdpi对照表

“HVGA mdpi”
“WVGA hdpi “
“FWVGA hdpi “
“QHD hdpi “
“720P xhdpi”
“1080P xxhdpi “

图片色彩模式

这里写图片描述

Android图标尺寸

这里写图片描述

px dp dpi ppi sp 是什么

px:像素,电子屏幕上组成图片的基本单元
dp:安卓开发的长度单位,根据不同的屏幕分辨率,与px有不同的对应关系。
ppi:每英寸像素数,该值越高,则屏幕越细腻
dpi:每英寸多少点,也称为像素密度(单位长度屏幕显示的像素)值越高,图片越细腻
sp:安卓开发的字体大小单位(一般情况下sp=dp,若设置文字尺寸是大或超大,则1sp>1dp)

ppi和dpi
dpi最初用于衡量打印物上每英寸的点数密度。DPI值越小图片越不精细。当DPI的概念用在计算机屏幕上时,就应称之为ppi。同理: PPI就是计算机屏幕上每英寸可以显示的像素点的数量。因此,在电子屏幕显示中提到的ppi和dpi是一样的,可认为:dpi=ppi

ppi是指屏幕上的像素密度,其计算公式为:
ppi= 屏幕对角线上的像素点数/对角线长度 = √ (屏幕横向像素点^2 + 屏幕纵向像素点^2)/ 对角线长度

以小米2s为例,该屏幕分辨率为720px*1280px,4.3英寸。则像素密度为 √ (720^2 +1280^2) /4.3 = 342ppi(后期计算比例关系时都以342dpi来计算)

在mdpi时,1dp = 1px。 以mdpi为标准,这些屏幕的密度值比为:ldpi : mdpi : hdpi : xhdpi : xxhdpi = 0.75 : 1 : 1.5 : 2 : 3;即,在xhdpi的密度下,1dp=2px;在hdpi情况下,1dp=1.5px。

不同分辨率下图片的实际大小

测试手机:480dp
测试图片:100px*100px
测试控件:ImageView分别设置background和src

分别放在drawable、drawable-mdpi、drawable-hdpi、drawable-xhdpi、drawable-xxhdpi目录下,图片的显示大小分别为:
drawable:300*300px
drawable-mdpi:300*300px
drawable-hdpi:200*200px
drawable-xhdpi:150*150px
drawable-xxhdpi:100*100px

实验结论
一张图片的实际显示大小与机型DPI和Drawable文件夹DPI均相关

计算公式为
手机显示像素 = 图片实际像素尺寸*机型DPI / 所在资源目录的DPI

如:在480dpi的机型上,把100*100像素的图片放在drawable-hdpi目录内,该图片实际显示像素=100*480/240=200像素
在240dpi的机型上,把100*100像素的图片放在drawable-xxhdpi目录内,该图片实际显示像素=100*240/480=50像素

一张图片占多大内存

实际显示图片宽(像素) x 实际显示图片高(像素) x 字节数

drawable和mipmap文件夹存放图片的区别

首先图片放在drawable-xhdpi和mipmap-xhdpi下,两者占用的内存是一样的
把图片放到mipmaps可以提高系统渲染图片的速度,提高图片质量,减少GPU压力。其他和drawable没有什么区别

Glide简单缓存介绍

Glide的使用自定义的LruResourceCache对象继承LruCahce,使用方法和LruCahce策略一样。
Glide自定义了一个LruBitmapPool 池,用于缓存Bitmap,采用的策略是近期最少使用算法。
分配给磁盘缓存的大小是:250M(DiskCache类中设置)

int DEFAULT_DISK_CACHE_SIZE = 250 * 1024 * 1024;
存储目录是image_manager_disk_cache
绝对目录是:new File(context.getCacheDir(), “image_manager_disk_cache”)

Glide保存图片到内存的过程:
通过EngineResource的acquired变量用来记录图片被引用的次数,调用acquire()方法会让变量加1,调用release()方法会让变量减1,当变量大于0说明图片正在使用, 也就应该放到activeResources弱引用缓存中, 当变量等于0,说明图片不再使用,那么就会从弱引用中删除并放到LruCache中也就是说正在使用中的图片使用弱引用来进行缓存,不在使用中的图片使用LruCache来进行缓存的功能。

Glide获取内存缓存的过程:
Glide的图片加载过程中会调用两个方法来获取内存缓存,loadFromCache()和
loadFromActiveResources()。这两个方法中前者使用的就是LruCache算法,后者使用的就是弱引用先通过LruCache从内存中拿图片,如果有则把图片返回并把图片从LruCache中删除,并把图片添加到弱引用中(由HashMap存储key和图片资源的弱引用),如果没有则从弱引用中拿,如果也没有则开启线程请求网络。使用弱引用缓存正在使用中的图片,可以保护这些图片不会被LruCache算法回收掉。也就是说正在使用的图片缓存在弱引用中, 不正在使用的图片保存在LruCache中。

App的简单启动流程

在Android系统中,应用程序是由Launcher启动起来的,其实Launcher本身也是一个应用程序,其他应用程序安装后会在Launcher的界面添加一个快捷图标,点击我们屏幕,触摸屏两层电极会连接在一起,产生一个电压,通过对应的驱动获取点击的x y坐标,操作系统通过对x y坐标范围进行判断,如果按压的是App图标的范围,则开始启动对应监听。
1:Launcher类继承Activity,当点击图标时会调用startActivity,通过层层调用,最终采用Binder的方式向AMS发出startActivity请求,
2:AMS接收到请求后,通过Socket向孵化进行发送创建进程的请求,从而孵化出一个子进程(App进程)
3:AMS再经过一系列调用最终会调用到ActivityThread#AplicationThread的
scheduleLaunchActivity方法(ApplicationThread是IApplicationThread的子类,是一个Binder对象,ApplicationThread是我们应用和系统AMS沟通的桥梁)
4:通过Handler发送消息到ActivityThread的内部类H中(H继承Handler),并调用
handleLaunchActivity方法
5:在此方法内部通过performLaunchActivity方法反射创建了Activity、Application、
Context对象,并开始回调Activity的attach、onCreat方法

栈和堆的区别

有一个很重要的特殊性,就是存在栈中的数据可以共享
缺点是存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。
比如:int a=3; int b=3;

编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找有没有字面值为3的地址,没找到,就开辟一个存放3这个字面值的地址,然后将a指向3的地址。接着处理int b = 3;在创建完b这个引用变量后,由于在栈中已经有3这个字面值,便将b直接指向3的地址。这样,就出现了a与b同时均指向3的情况

堆的优势是可以动态分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的。
缺点就是要在运行时动态分配内存,存取速度较慢;栈的优势是,存取速度比堆要快,仅次、于直接位于CPU中的寄存器。

JVM

是运行所有Java程序的抽象计算机
一个Java虚拟机实例在运行过程中有三个子系统来保障它的正常运行,分别是类加载器子系统, 执行引擎子系统和垃圾收集子系统。类加载器可以加载类但是不能卸载类
运行时数据区,主要分为方法区、Java堆、虚拟机栈、本地方法栈、程序计数器。其中方法区和Java堆一样,是各个线程共享的内存区域,而虚拟机栈、本地方法栈、程序计数器是线程私有的内存区。
线程共享指的就是可以允许被所有线程共享访问的一块内存,包括堆区,方法区和运行时常量池。

ART与DVM的区别

DVM中的应用每次运行时,字节码都需要通过即时编译器(JIT,just in time)转换为机器码,这会使得应用的运行效率降低。而在ART中,系统在安装应用时会进行一次预编译(AOT,ahead of time),将字节码预先编译成机器码并存储在本地,这样应用每次运行时就不需要执行编译了,运行效率也大大提升。

dvm是基于寄存器的虚拟机 而jvm执行是基于虚拟栈的虚拟机

RxJava2.0操作符说明

zip:合并两个Observable,还能够对数据进行处理再发射出去
使用场景:两个网络请求同时发送请求,等待全部请求成功后再处理数据并展示,当有一个请求或者都请求失败时,会走到错误方法里

debounce:多少秒后才走后面的逻辑
使用场景,搜索,避免EditText一更改就搜索,但这也有可能会第二次搜索先出来,紧接着第一次搜索再出来,导致数据错误,这时可以结合 switchMap操作符使用

switchMap:只会发射最近的Observables
使用场景:当多少毫秒后,发出第一个搜索请求,当这个请求的过程中,用户又去搜索了,发出第二个请求,不管怎样,switchMap操作符只会发射第二次请求的Observable。

filter:过滤
使用场景,搜索可先过滤前后的空格,网络请求可先查看是否是json格式,是否状态码正确

Map:一对一转换,输入一个数据类型,转出另一个数据类型
使用场景:1:查询书籍目录数据库,传入context拿到数据我们创建的数据库对象,然后查询书籍目录或者其他图片列表
2:上传头像,传入路径,转换成Bitmap,旋转、压缩后保存到本地,从本地拿到压缩、裁剪后的图片上传
3:请求成功,拿到String,经过filter过滤后,再让map转换成需要的对象

flatMap:一对多,多对多转换,传入的顺序可能跟出来的顺序不一样
使用场景:两个网络请求:必须先请求1,再请求2,可以正常请求1,在flatMap中请求2,然后合并后发送事件
注册手机号:第一步先验证手机号是否注册(返回一个bean)-》根据bean中返回码,如果正确则在flatMap中再次请求发送验证码
搜索功能:根据输入,在flatMap中请求网络,再返回一个Observable对象连续请求两个接口

concatMap:和flatMap功能一样,唯一区别是传入顺序和出来顺序一样最后合并Observables->flatMap采用的merge,而concatMap采用的是连接(concat)

doOnNext:每次发送前的准备

combineLatest:用于处理表单是否完全填写完成,全部填写完成才会让登录按钮点亮,

combineLatest和zip区别,前者是每次修改(如EditText添加一个字)都会调用,可多次;后者用于两次网络请求,全部有返回结果才会调用,而且仅一次

Android中Button按钮设置英文全部变成大写问题

添加属性android:textAllCaps=”false”
源码默认设置的是true,全部变成大写

Android三种动画

Frame动画(帧动画)
一张一张图片快速播放,像播放幻灯片一样

Tween动画(补间动画)
可以在一个视图容器内执行一系列简单变换(位置、大小、旋转、透明度)
注意:补间动画执行后并未改变View的真实布局属性值

Property Animation(属性动画)
属性动画实现原理就是修改控件的属性值实现的动画

Android5.0 WebView加载http与https混合内容
webView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
Android WebView加载http或https请求时网页打不开(白屏)
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.setWebViewClient(new webviewClient());

class webviewClient extends WebViewClient {

    @Override
    public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
        //super.onReceivedSslError(view, handler, error);
        //handler.cancel(); 默认的处理方式,WebView变成空白页
        handler.proceed();//接受证书
    }
}

在混淆文件proguard-rules.pro中加入以下代码,否则方法被混淆后依然是打不开网页:

-keep public class android.net.http.SslError
-dontwarn android.webkit.WebView
-dontwarn android.net.http.SslError
-dontwarn Android.webkit.WebViewClient
仿微信打开网页定位到上次看到的位置
mWebView.setWebViewClient(new webviewClient());
class webviewClient extends WebViewClient {

    @Override
    public void onPageFinished(WebView view, String url) {
        super.onPageFinished(view, url);
        //获取保存上次的位置lastPosition
        int last_position = Constant .LAST_POSITION;
        view.scrollTo(0, last_position);//webview加载完成后直接定位到上次访问的位置
    }
}

@Override
protected void onPause() {
    super.onPause();
    if (mWebView != null) {
        Constant .LAST_POSITION = mWebView.getScrollY();//保存上次webView滑到的位置
    }
}
如何保证Service不被杀死?如何保证进程不被杀死

(1):onStartCommand方法,返回START_STICKY
public int onStartCommand(Intent intent, int flags, int startId) {
flags = START_STICKY;
return super.onStartCommand(intent, flags, startId);
}
手动返回START_STICKY,亲测当service因内存不足被kill,当内存又有的时候,service又被重新创建,比较不错,但是不能保证任何情况下都被重建,比如进程被干掉了….
(2):提升service进程优先级
使用startForeground 将service放到前台状态。这样在低内存时被kill的几率会低一些。在onDestroy里还需要stopForeground(true),这时状态栏就不会显示了。

(3):onDestroy方法里重启service
service +broadcast 方式,就是当service走ondestory的时候,发送一个自定义的广播,当收到广播的时候,重新启动service

Android Activity 、 Window 、 View之间的关系

Activity在onCreate之前调用attach方法,在attach方法中会创建window对象。window对象创建时并木有创建Decor对象对象。用户在Activity中调用setContentView,然后调用window的setContentView,这时会检查DecorView是否存在,如果不存在则创建DecorView对象,然后把用户自己的View 添加到DecorView中

总结起来说就是 Activity会调用PhoneWindow的setContentView()将layout布局添加到DecorView上,而此时的DecorView就是那个最底层的View。然后通过LayoutInflater.infalte()方法加载布局生成View对象并通过addView()方法添加到Window上,(一层一层的叠加到Window上)所以,Activity其实不是显示视图,Window才是真正的显示视图。

注:一个Activity构造的时候只能初始化一个Window(PhoneWindow),另外这个PhoneWindow有一个View容器 mContentParent,这个View容器是一个ViewGroup,是最初始的跟视图,然后通过addView方法将View一个个层叠到
mContentParent上,这些层叠的View最终放在Window这个载体上面。

多进程说明

给process指定多进程有两种不同的形式
:remote
进程名以 “:”的含义是指要在进程名前面附加上当前的包名,这个进程属于当前应用的私有进程,其他应用不可以和他跑在同一个进程。
com.xxx.xxx
这种属于全局进程,其他应用可以通过ShareUID方式可以和它跑在同一个进程,我们都知道系统会为每个应用分配一个唯一的UID,具有相同UID的应用才能共享数据。两个应用通过ShareUID跑在同一个进程,是需要相同的ShareUID并且签名相同才可以。不管它们是不是跑在同一个进程中,具有相同ShareUID的它们可以访问对方的私有数据,如:data目录、组件信息等。当然如果是在同一个进程中,除了data目录、组件信息还能共享内存数据。

说明:
通过android:process=”:processService”设置的进程需要注意:
1:Static静态变量缓存失效以及单例:1进程把变量更改后,2进程并不会修改,因为是两个隔离的内存空间,一个内存空间里值的修改并不会影响到另外一个内存空间。,解决方式:通过AIDL方式,让获取单例对象的方法放到服务中统一获取
2:Application会多次初始化,解决方式:通过当前进程UID获取到所属的进程包名并与主进程包名比较,不一致则不初始化
3:SharePreferenc通信多线程下不再安全,当不是多线程操作时可使用。 可以用ContentProvider,但很麻烦,就用AIDL吧
4:EvnetBus通信失效,注解失效
5:通过Debug调试时,只能调试当前进程,通过process设置的进程无法进行调试,所以当调试时可以先把process去掉
6:Intent传值可用
7:文件共享问题,解决办法就是多进程的时候不并发访问同一个文件,比如子进程涉及到操作数据库,就可以考虑调用主进程进行数据的操作。

ScaleType类型说明

android:scaleType=”center”
显示在ImageView中心,当原图的size和ImageView的size不同时,以原图中心进行裁剪。(原图小,直接显示,原图大,裁剪超出的)

android:scaleType=”centerCrop”
以填满整个ImageView为目的,将原图的中心对准ImageView的中心,等比例放大原图(不会缩小),直到填满IamgeView为止(ImageView的宽高都要填满),原图超过ImageView的部分裁剪

android:scaleType=”centerInside”
以原图完全显示为目的,将图片的内容完整居中显示,通过按比例缩小原图宽高到等于或小于ImageView的宽高并居中显示,如果原图的宽高本身就小于ImageView的宽高,则原图不作任何处理居中显示即可

android:scaleType=”fitCenter”
以原图显示为目的,将图片完整居中显示,即把原图按比例扩大或缩小到ImageView能容纳的大小。

centerInside和fitCenter区别
相同点:都是原图显示并居中显示
不同点:
centerInside:图片大,ImageView小,则缩小图片;图片小,ImageView大,直接显示
fitCenter:图片大,ImageView小,则缩小图片;图片小,ImageView大,则放大图片

android:scaleType=”fitEnd”
以原图显示为目的,将图片完整显示于右下边,即把原图按比例扩大或缩小到ImageView能容纳的大小。

android:scaleType=”fitStart”
以原图显示为目的,将图片完整显示于左上边,即把原图按比例扩大或缩小到ImageView能容纳的大小。

android:scaleType=”fitXY”
把原图按照View指定的大小在View中显示,拉伸显示图片,不保持原比例,填满ImageView.

android:scaleType=”matrix”
不改变原图的大小,从ImageView的左上角开始绘制原图,原图超过ImageView的部分作裁剪处理。

SharedPreference.Editor的apply和commit方法异同
  1. apply没有返回值而commit返回boolean表明修改是否提交成功
  2. apply是将修改数据原子提交到内存, 然后异步提交到硬件磁盘, 而commit是同步的提交到硬件磁盘,因此,在多个并发的提交commit的时候,他们会等待正在处理的commit保存到磁盘后在操作,从而降低了效率。而apply只是原子的提交到内存,后面有调用apply的函数的将会直接覆盖前面的内存数据,这样从一定程度上提高了很多效率。
  3. apply方法不会提示任何失败的提示。 由于在一个进程中,sharedPreference是单实例,一般不会出现并发冲突,如果对提交的结果不关心的话,建议使用apply,当然需要确保提交成功且有后续操作的话,还是需要用commit的。
自定义View的canvas的save和restore方法

save方法:用于保存Canvas的状态,save方法之后的代码,可以调用Canvas的平移、缩放、旋转、裁剪等操作。
restore方法:用于恢复Canvas之前保存的状态,防止save方法代码之后对Canvas执行的操作,影响后续绘制。

也就是说save和restore方法之间我们可以对画布做一些操作
比如要绘制一个向右的箭头和右下角的一个圆圈,我们绘制箭头时可以把画布逆向旋转90(旋转之前调用save方法),这样比较好理解绘制,绘制完成后调用restore,把画布恢复到正常状态(即顺向旋转90度),然后接着画圆,如果不调用restore方法,而直接画圆,这是当直接运行到手机时,相当于是在onDraw方法最后系统帮我们调用的restore方法,恢复到正常状态,而我们画的圆就不是我们想要的位置了。

自定义View时,如果在初始化中设置当前自定义View为Gone,则不会调用onMeasure onLayout onDraw方法,如果设置Visiable则会调用,onMeasure什么情况下会被调用:View的大小发生变化,View被删除,添加,或者添加其他view导致该view的位置发生变化的时候都会调用该view的onMeasure方法。
简单RecyclerView使用
private void initRecyclerViewData() {
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < 30; i++) {
            list.add("你好");
        }
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        MyAdapter adapter = new MyAdapter(this, list);
        mRecyclerView.setAdapter(adapter);
    }

class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {

        private Context mContext;
        private ArrayList<String> mList;
        private LayoutInflater mInflater;

        public MyAdapter(Context context, ArrayList<String> list) {
            this.mContext = context;
            this.mList = list;
            mInflater = LayoutInflater.from(context);
        }

        @Override
        public MyAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View view = mInflater.inflate(R.layout.item_test, parent, false);
            return new MyViewHolder(view);
        }

        @Override
        public void onBindViewHolder(MyAdapter.MyViewHolder holder, int position) {
            holder.tv.setText(mList.get(position));
        }

        @Override
        public int getItemCount() {
            return mList.size();
        }

        public class MyViewHolder extends RecyclerView.ViewHolder {

            private TextView tv;

            public MyViewHolder(View itemView) {
                super(itemView);
                tv = itemView.findViewById(R.id.tv);
            }
        }
    }
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值