【疑难杂症】解决了这些问题,你就迈进了安卓高级工程师门槛

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zhonglunshun/article/details/88988153

导入

或许由于经历不同,很多的开发者并不再怎么关心性能优化和代码质量这一块,而在一个真正用心做产品的公司,在产品交付前进行的集中测试会暴露出来非常多平时难以解决的问题;主要原因是这些原因在一开始会被认为太难搞定而且在当时来说并不是那么重要,最后要到发布了,由于时间紧凑而且自身积累不够,导致很多疑难杂症得不到解决;

一般这个时候公司会着急招聘一个看起来很资深的程序员来解决这个问题,不过在我看来这种选择代价并不低,排除这种靠谱的人不太好找外,这个人的开发经历和项目的契合度也有很大关系,更可怕的是,在这位资深开发接管了这个项目后,他可能会觉得很难办,因为把一个快要上线的项目源码读懂就不是一件易事,少说也得花个半年时间吧;

最稳妥的办法当然是防范于未然,当然这并不容易,假如你所在的公司对软件质量的重视程度远没有功能迭代的重视速度,那么你可能整天写代码写到完全没有空去学习并应用,或者说你需要挤出额外的时间来学习时间,这样的效率其实并不高;当然,聊胜于无,这比起那些看完了这篇文章不去做任何实践的人来说要靠谱多了;

以下的内容全部来自我对自己公司产品优化过程中遇到的一些疑难杂症,既然是疑难杂症,自然也是花了不少精力去研究整理的,当然由于可能受限于自身的经验,有些地方描述不准确或者有些地方没有提到的,一方面我自己会在后续时间慢慢更新,另一方面也欢迎大家指正互相学习,我也会把大家建议的内容更新到这个博客上;

因此建议转载这篇文章的表明转载出处,本着开源的伟大精神,让更多的开发者受益;

目录

我猜到这篇文章后面会越来越长,因此先把架构搭好,然后再慢慢完善,其实本文结构和任何一本技术类的书籍结构一致,但是并不说明这篇文章会写的那么详细,因为这篇文章是给中级以上的安卓开发人员准备的;

1. 并发与多线程
2. 集合
3. UI交互
4. 编码习惯
5. 认知误区
6. 编程心态

类型1:并发与多线程

这一类问题是我们遇到最多的问题,这些问题对于新手来说也是最头疼的问题,如果处理不当会导致更多的问题;我们现在从原理把这些东西好好梳理一下;

定义: 并发是指在一段时间内同时做多个事情,多线程并发的本质问题源于多个线程使用同一个资源,这些资源包括不限于内存,cpu,对象等;

故事
以搬砖上楼为例,如果是一个人搬50块砖,正常来说他每次可以搬10块,那么他需要5次才能搬完。
如果有5个人搬,那么只需要一次就能搬完,大大节省了搬运时间,程序运行也是一样;
但是如果有50个人同时搬运,可能因为人太多拥挤楼道导致效率反而很低下,甚至堵死楼道;导致任务协调人员没办法解决问题;

问题

  1. 性能问题(资源过度消耗);
  2. 数据安全问题(读脏数据);
  3. 死锁问题(线程无法退出,UI卡死);
  4. 线程失控问题(sleep导致线程无法及时退出,阻塞读写无法关闭);
  5. 乱加锁问题(不该加锁加锁,该用不同锁的用同一把锁);

1. 资源过度消耗

本因

  1. Cpu占用过高,从而导致手机发热严重,同时导致系统卡顿;
  2. 内存消耗太大:在线程中既存在堆内存消耗(new 对象,局部变量),又存在栈内存消耗(方法运行都需要在栈中开辟内存空间存放需要运行的代码);

案例1

在请求Ftp或者http上传下载的时候,为了测试极限速度,采用了很多条线程同时进行,但是由于采用的线程数过多,导致手机发热严重,同时资源消耗也大,经常被一些手机强制关闭应用;

代码

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.testBtn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startUploadTest();
            }
        });

    }


    private void startDownloadTest() {
        Timer timer = new Timer();
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                // 1. get st ate
                int arg1 = 100;
                double arg2 = 12.00;
                String arg3 = getArg3();
                SomeState state = new SomeState(arg1, arg2, arg3);

                // 2. do something else
                for (int i = 0; i < state.someCount; i++) {
                    doSomeReport(state);
                }

                //3. save Result
                saveResult2File();
                
                //4. show result
                showTestResult();

            }
        }, 0, 200);
    }
}

解决
1. 禁止使用new Thread的方式产生线程,使用RXJava的小伙伴在每次创建子线程后要保存该线程的引用以便管理生命周期;
2. 通过线程池统一管理线程生命周期,在一次任务完成后记得销毁对应的线程;
3. 命名每一条线程以便于跟踪线程生命周期;
4. 线程中如果有循环特别是死循环如While,do,for,注意出一个可以由外部线程控制运行的接口,否则会导致线程无法停止
5. 控制每一次并发数量,并发数量超过一定值不仅不能提升效率还会导致程序拥堵以至于手机卡顿;

2. 读脏数据

本因

多个线程同时操作同一个数据(对象,内存,文件),并且这些线程不全是读线程;

案例1 ConcurrentModificationException

多发于操作ListView,当适配器还没刷新完上个数据的显示下一个数据就过来了,会抛出异常同时修改异常;

代码

// TODO 邀请大家在这里补齐示例代码

解决
在这里填写解决方案;

案例2 变量被非法修改

循环代码的状态控制标志异常;一个变量控制着线程运行,本来被关闭了,但是被另一个线程打开了;

代码

private boolean isRun = false;

    private void startTest(){
        new Thread(){
            @Override
            public void run() {
                super.run();
                isRun = true;
                doStartTest();
            }
        }.start();
    }

    private void testClean() throws InterruptedException {
        while (!isRun){
            isRun = doSomeClean();
            Thread.sleep(200);
        }
    }

解决
使用原子锁,在这里填写解决方案;

死锁

本因

代码在执行的时候发现需要请求的锁不能被释放,导致线程阻塞死锁;

案例1 锁嵌套

相同锁的锁嵌套,只要其中一条线程运行在主线程就会导致UI卡死;

代码

// TODO 邀请大家在这里补齐示例代码

解决
synchronized尽量缩小锁的范围,锁的时间尽可能短,能锁对象就不要锁类,能锁代码块就不要锁方法;
线程池管理线程,掌握每一个线程的生命周期,线程命名便于跟踪;

案例2 锁被耗时线程持有

请求的锁所在的线程进入了死循环,进入了休眠,在执行耗时操作,导致锁不能及时释放;

代码

// TODO 邀请大家在这里补齐示例代码

解决

线程失控

本因
线程运行在加锁的代码块中,外部线程无法操作该加锁线程,导致线程必须在同步代码块执行完毕后才能被外部线程访问,如果锁住的代码块是耗时操作,且控制运行的变量也处于同步代码块中,就会出现线程失控;

案例1 阻塞线程无法关闭

阻塞读写线程无法关闭,只能等待阻塞线程自己释放,如socket阻塞,文件读写阻塞等;

代码

// TODO 邀请大家在这里补齐示例代码

解决

案例2 JNI中阻塞导致anr

在主线程中调用的JNI方法阻塞,java无法中断JNI调用,必须等到jni解除阻塞状态这个过程中就会导致anr异常以致界面卡死;

代码2

// TODO 邀请大家在这里补齐示例代码

解决2

案例3
控制循环的开关在同步代码块中;

代码3

// TODO 邀请大家在这里补齐示例代码

解决3

案例4
同步代码块中有睡眠,等待操作;

代码4

// TODO 邀请大家在这里补齐示例代码

解决4

案例5
线程中有睡眠操作又不能直接把线程停掉(有其他业务参与);
比如在执行Autotest任务过程中,有一个变量IsTestRun控制了任务执行是否继续,如果在任务中执行睡眠的睡眠间隔太长,会导致无法及时读到IsTestRun的改变从而停止任务;
线程在sleep或者处于join状态下,由于处于暂停状态,所以线程没办法继续往下执行完线程代码;

代码5

 private boolean waitForAnswer(int callTimeout) {
        boolean isTimeout = false;
        long startTime = SystemClock.elapsedRealtime();
        
        while (!mStopped) {
            isTimeout = SystemClock.elapsedRealtime() - startTime < callTimeout;
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                Log.e(e);
                Thread.currentThread().interrupt();
            }
        }
        return isTimeout;
    }

解决5
将休眠时间设置短一点;
可以通过调用interrupt终端线程,使线程归位;

问题

通过一些问题可以巩固我们对知识的理解:

  1. 多个线程同时读写,读线程的数量远远大于写线程,你认为应该如何解决并发的问题?你会选择加什么样的锁?
  2. 除了synchronized关键字之外,你是怎么来保障线程安全的?
  3. 线程池内部工作原理可以说一下么?
  4. 死锁是什么意思,形成条件是什么?出现死锁是可以通过什么方式去排查。
  5. 讲一下怎么使用分布式锁。
  6. 线程池创建有几种,为什么创建定长的线程池个数最好是5,10,15这样的数字。
  7. Java的设计模式,单例有什么模式,懒汉为什么加volotile,volotile的内存屏障,如何避免死锁。

类型2:集合

由于集合是数据集,因此虽然集合本身指向的内存区域不变(比如使用final修饰了集合),但是其内部的数据还是可以发生改变,可以被任何线程随意读写;这就导致了数据的不安全和不可控;
线程安全和线程不安全集合
迭代器遍历导致异常
list浅拷贝深拷贝问题
值传递引用传递问题:基本数据类型(值)+对象类型(引用),集合属于复杂对象
基本数据类型需要加锁吗?
hashMap线程不安全,多线程操作容易发生cpu超高占用;使用ConcurrentHashMap替代;

问题

  1. HashMap和Hashtable的区别。
  2. 实现一个保证迭代顺序的HashMap。
  3. java常用的数据结构有哪些?哪些是线程安全的?是怎么保证线程安全的?
  4. 说说HashMap的原理, 以及HashMap如何扩充bucket的大小。
  5. HashMap在jdk1.7和1.8的区别,为什么引入这个概念?hash碰撞怎么解决,为什么1.8要比1.7更好,好在哪?
  6. hashmap、hashcode一样,不equals怎么处理 ;hashcode实现原理,currentHashMap原理,实现细节,怎么实现同步的;类为什么要有hascode方法,是不是主要在集合类中都要实现hashcode方法;equals方法怎么实现;两个不同的对象可能有相同的hashcode值吗;常用集合有哪些。
  7. 为什么 Map 接口不继承 Collection 接口。
  8. ThreadLocal 用途是什么,原理是什么,用的时候要注意什么?
  9. ThreadPool用法与优势可以说一下么?
  10. synchronized 的原理是什么?synchronized 和 ReentrantLock 有什么不同?
  11. 有T1,T2,T3三个线程,怎么确保它们按顺序执行?怎样保证T2在T1执行完后执行,T3在T2执行完后执行同步块内的线程抛出异常会发生什么?
  12. 什么是乐观锁(Optimistic Locking)?如何实现乐观锁?如何避免ABA问题。
  13. Java中活锁和死锁有什么区别?
  14. Executors类是什么? Executor和Executors的区别?

类型3:UI

  1. 过度绘制问题(大量UI绘制时的频繁刷新使用定时器控制)
  2. 布局复杂嵌套问题
  3. 标题重复问题(每个页面都有各自的标题)
  4. 适配器刷新没有统一的入口;
  5. 适配器没做增量更新导致过度消耗;
  6. 使用dip而不是dp;
  7. 过多使用权重实现固定布局(有些时候需要使用固定dp);
  8. 定时器泛滥问题;

编码风格

权限检查问题:
1.缺乏统一的权限检查入口;

代码风格问题

  1. 不需要增加空格对齐变量;
    2.大括号结束后必须换行;
    3.意思相近的代码之间不需要空行,相反逻辑不相似的代码使用空行隔开;
  2. 在方法体编写中,局部变量尽量定义靠近逻辑代码,不要一股脑定义在最上面,这回增加查找代码的负担;
  3. 独立的功能放在独立的方法,不应该把逻辑内聚度很低的代码写在同一个方法中,更有甚者既在一个方法中处理功能代码又处理逻辑代码;
    7.代码嵌套不能超过3层,超过了看着眼花;
  4. 尽量精简注释,使注释更简洁的描述代码逻辑,同时禁止尾行注释,应该写在代码之上;

过多的静态变量
过多的全局变量
过多的单例使用
过多的EventBus使用

数据没有统一管理接口不知道在哪里修改删除了
经验值缺少统一管理

注释过少问题

功能耦合问题(A功能的代码写在B功能)

类名定义问题

  1. Activity定义成Dialog;
  2. 工具类/功能类做成Sercvice
  3. 适配器写在Activity中;

配置混乱问题
4. 配置key不统一问题:读写Sp有时候不是统一的字符串作为key,导致写入读取不匹配;
5. activity中读写配置问题;
6. 常量配置中写实现问题;
7. 常量和类关系不大问题;

强行耦合问题:
8. 适配器中编写大量的功能代码;
9. 一个方法中实现了过多功能,导致方法臃肿;

资源管理问题
10. 线程无名称问题;
11. 对象不释放问题;

配置混乱问题
12. 配置key不统一问题:读写Sp有时候不是统一的字符串作为key,导致写入读取不匹配;
13. activity中读写配置问题;
14. 常量配置中写实现问题;
15. 常量和类关系不大问题;

强行耦合问题:
16. 适配器中编写大量的功能代码;
17. 一个方法中实现了过多功能,导致方法臃肿;

资源管理问题
18. 线程无名称问题;
19. 对象不释放问题;
20.

逻辑混乱问题:
9. if else嵌套过多;
10. 方法名和参数名随意书写;
11. for循环中嵌套复杂逻辑;
4.单句不加大括号;
5.工匠精神;
6.敢于表达,要有自大精神,并以开放心态迎接批评;

需求规范问题
1.把Answer命令做进了Call命令;

发布版本的测试应该写入周报,并预留时间自己测试;

安全问题

时间问题:用户修改系统时间则获取到错误时间;
场景:System.currentTimemillion 如果使用了doubleClick快速点击判断,或者任何需要和上一个时间进行比对的功能,时间调整后,之前的时间在用来判断就会出错,这会导致很大的问题;
由于System.currentTimemillion获取的时间精度会依赖于操作系统的实现机制因此有时会不太准确;
而且System.currentTimemillion是jni调用效率比非JNI调用比如SystemClock.now()慢很多;
因此在计算这种时间差的场景下,建议使用SystemClock.elapsedRealtime()替代System.currentTimemillion,因为前一个记录的是系统开机到现在的时间(包含了休眠时间);

内存泄漏问题,方法中Activity作为参数容易导致内存泄漏;
类型转换问题(特别是数字转换问题)
字符串截取中正则表达式的规范问题:
广播注册未销毁导致异常;

问题跟踪

日志频繁打印问题,频繁打印的日志建议取消打印;禁止在发布版本中频繁打印堆栈信息;
频繁调用出错的异常应立即处理,如果无法处理,使用能明确出错位置的提示信息替代堆栈打印(太消耗资源);

在功能代码中不要使用trycatch,而应该将异常抛出让业务代码来处理;在业务代码中,不应该把所有的异常一股脑用Exception捕获,而应该区别对待;

JVM

问题

  1. 说一说GC。
  2. JVM如何加载一个类的过程,双亲委派模型中有哪些方法?
  3. 你知道哪些或者你们线上使用什么GC策略? 它有什么优势,适用于什么场景?
  4. JAVA类加载器包括几种?它们之间的父子关系是怎么样的?双亲委派机制是什么意思?有什么好处?
  5. 如何自定义一个类加载器?你使用过哪些或者你在什么场景下需要一个自定义的类加载器吗?
  6. 堆内存设置的参数是什么?
  7. 方法区里什么样的对象有可能被回收。
  8. 线上cpu飙升100%你怎么处理。
  9. 频繁FullGC怎么处理。

认知错误

Jni不是耗时操作,最典型的System.currentTimeMillion();
Jni具备和Native相同的能力和权限;
JNI层规范日志打印。因为jvm崩溃最多只能打印是哪个jni方法调用出错;
java对象四种引用。
JRE、JDK、JVM 及 JIT 之间有什么不同。

心态

如果一个开发技术很牛逼,但是没有闭环思维,那么那就不是一个合格的高级开发,至少在我看来不是;
闭环思维是互联网用语,实质上就是,上级交代你一件事情,不管有没有完成,你都需要给上级一个反馈;
举个例子,你朋友要求你帮忙找个东西,你两天没找到,然后就不回复他了,这个时候他就会一直等你的消息,当等待超过他预期,他就认为你失信了,这就是不靠谱的表现;

我们再开发的时候经常有以下心态,导致产出质量不高,返工率高,维护困难,据我观察,初级程序员可能会花掉1/4时间写代码,剩下的时间基本和编写无关;这种情况下可能是由于上头要的急而自己又不懂得拒绝或者急于求成,反正最终结果就是自己交出去的产品自己都不太满意,同时让自己特别的忙,没有效率;
这种错误的心态有但不限于以下:

1.急于提交心态
2.绝对正确心态
3.直面错误心态;
4.逻辑错误心态;
5.着急完成心态;

展开阅读全文

这些分了,只为解决这一个问题!!!

05-03

我租赁的虚拟机有这样约束:rn1、在www文件夹下面创建servlet文件夹,将所有的网站程序放置在servlet文件夹下。rn2、在www文件夹下面创建servlet文件夹,将WEB-INF文件夹放置在servlet文件夹下,同时修改源程序中的相应路径。rn===============================rnrn文件上传时的存储目录问题rn本地目录结构为 rn/myweb/WEB-INFrn远程服务器的目录结构为 rn/www/myweb/WEB-INFrn-------------- rn本地上传代码为: rnmyFile.saveAs("/" + myFile.getFileName()); rn filepath = request.getContextPath() +rn "/" + myFile.getFileName(); rn本地正常 rn--------------------------rn远程服务器(linux。tomcat) rn则显示上传路径不对 rnrn后来改成以下几种还是同样的错误rn代码是这样的:rn1:rnmyFile.saveAs("www/"+myFile.getFileName());rn filepath = request.getContextPath() +rn "/" + myFile.getFileName(); rn2:rnmyFile.saveAs("www\\"+myFile.getFileName());rn filepath = request.getContextPath() +rn "/" + myFile.getFileName(); rnrn3:-->("www\\"+myFile.getFileName());rn4:-->("\\"+myFile.getFileName());rn5:-->(request.getContextPath() + "\\"+myFile.getFileName());rn6:-->("../"+myFile.getFileName())rn7:-->rnString picPath = "upload/";rn String aa = getServletContext().getRealPath("/") + picPath;rn aa = aa.replace('\\', '/');rnSystem.out.println("----------------" + aa);rn//这里的输出结果 linux下 /home/user/www/myweb/upload/rn//windows 下 F:/mywebpro/myweb/upload/rnrnmyFile.saveAs(aa + fileName);rnrnwindos 正常rn上传后 报同样的错rnrnrnrn远程库里的filepath字段值为空rn上传文件时保存文件的路径到底应该怎么写? rn如过在本地tomcat设置成和远程的tomcat的目录结构相同怎么设置? rn郁闷了好长时间了rn再解决不了真的要疯 了!!!!rn谢谢啊 rn 论坛

解决这个问题你就XX了(java)

10-19

有这样一个问题。我循环打印信息,知道输入N的时候,程序结束,打印所有信息,但是,我这个程序只能打印最后输入的信息,前面的信息打不出来;。。这是为什么呢?rnrnrn如下。。rnimport java.util.Scanner;rnrnpublic class CustomerApp rnrn /**rn * @param argsrn */rn public static void main(String[] args) rn // TODO Auto-generated method stubrn rn while(true)rn Scanner input=new Scanner(System.in);rn System.out.println("name:");rn String name1=input.next();rn rn System.out.println("email:");rn String email=input.next(); rn rn System.out.println("age:");rn int age1=input.nextInt();rn rn rn Customer cm1=new Customer(name1,age1,email);rn CustomerService cs=new CustomerService();rn cs.addCustomer(cm1);rn System.out.println("是否要继续输入?y/n");rn String s=input.next(); rn if(s.equals("n")) rn cs.showCustomer();rn break;rn rn rn rn rnrn rn rnrnrn——————————————————————————————————rn——————————————————————————————————rnpublic class Customer rn public String name;rn public String email;rn public int age;rn public Customer(String name,int age,String email)rn this.name=name;rn this.email=email;rn this.age=age;rn rnrnrn——————————————————————————————————rn——————————————————————————————————rnpublic class CustomerService rn rn public Customer[] customer=new Customer[100];rn rn rn public void addCustomer(Customer cm)rn //最终向数据库存储数据rn int point=0;rn for(int i=0;i 论坛

AI应用火爆之前,你需要解决这些问题

09-05

如果说这个世界上有一种东西,能让程序员像着了魔一样的,那一定是代码。比代码还让他们无限向往的,那就是用敲出来的代码改变世界。十年之间,从手机到外卖,我们的世界已经被程序员改变得太多!rnrn我是“计院”出身转了销售,身边程序员无数,基友晓伟也是大家俗称的“码农”。在很多人的刻板印象里,他就是无休止的加班单身狗,是黑夜里敲打痛苦与快乐的“代码侠”,他确实是!rnrn但最近,他一改往日忙碌的画风,社交生活多姿多彩,生活似乎全面升级,连我这潇洒惯了的销售都羡慕,他到底发生了什么?rnrn[b]一、升级朋友圈的打开方式[/b]rnrn[align=center][img=https://img-bbs.csdn.net/upload/201809/05/1536133921_850768.jpg][/img][/align]rnrn[b]【曾经的朋友圈】rn[/b]原来,他每天沉浸在无限的基础代码编写中,rn 经常在冗长的代码编写中忘记了编程的灵感,rn而且,稍有不慎就是个bug!rn他幻想着能从敲代码中解放出来,rn好有更多精力进行创意和开发。rnrn[b]【升级后的朋友圈】[/b]rn纳尼?rn曾经不知下班为何物的他,rn竟然有时间参加演唱会!rn那么问题来了,rn代码谁写?rn无限好奇……rnrn[b]二、升级购物车打开方式[/b]rnrn[align=center][img=https://img-bbs.csdn.net/upload/201809/05/1536134039_745176.jpg][/img][/align]rnrn[b]【曾经的购物车】[/b]rnrn记得去年他还四处询问TPU,rn急得像热锅上的蚂蚁,rn视职业如生命的他,rn怎能允许算力不够的问题,rn无奈TPU这种东西没处买...rnrn[b]【更新后的购物车打开方式】[/b]rnrn零食旅行两不误,rn工作生活都兼顾。rn这还是大家眼中只知道苦呵呵敲代码的吗?rn还有了女朋友?rn简直就是完美逆袭啊!rn我不禁惊呼:到底是什么拯救了他?rn不,是整个行业!rnrn[b]三、更新周末的打开方式[/b]rnrn[align=center][img=https://img-bbs.csdn.net/upload/201809/05/1536134793_834705.jpg][/img][/align]rnrnrn[b]【曾经的周末】[/b]rnrn曾经的无数个周末,rn他只能与”调优“为伴,rn开发程序,好不容易建好了模,调完了优,rn一换平台,又要重新干。rn生命不止,调优不停!rnrn[b]【更新后的周末打开方式】[/b]rnrn曾经无限调优的日子一去不复返啦!rn我怀着无比好奇的心理,rn终于忍不住问他,rnrn[b][color=#800000]没想到他给我发来一个网站,说秘密全在里面[/color][/b]rnrn[align=center][img=https://img-bbs.csdn.net/upload/201809/05/1536134183_984507.jpg][/img][/align]rnrn赶紧点开一看,rn原来是华为全联接大会官网,rnAI、大数据、云计算、物联网、5G……听说华为全联接大会全都有!rn简直开启了新世界的大门!rnrn据传华为云要在全联接大会上,rn公布一个[color=#800000][b]跟AI有关的神奇框架[/b][/color],rn让无数码农摇身变成为真正的AI开发者,rn一站式完成从AI模型训练到AI模型部署的全套开发!rn节省了时间和人力,rn哪怕是技术小白,也能轻松上手!rn[color=#800000][b]真正让沉浸在代码中的程序猿,rn升级为专注研发的开发者。[/b][/color]rnrn[b]欢迎进入“新猿”世界![/b]rnrn还等什么?三日全通票早鸟价只需3816元,三日普通票早鸟价只需816元,单日票早就鸟价只需150元,[color=#800000]五人组队团购价更有9折优惠,凭学生证可享超值学生价![/color]rnrn[align=center][img=https://img-bbs.csdn.net/upload/201809/05/1536134402_283501.jpg][/img][/align]rnrn[i](温馨提示早鸟优惠截止: 2018年9月10日)[/i]rnrn[url=https://www.bagevent.com/event/1761752][size=16px]立即购票[/size][/url]rnrn购票可专享CSDN 多种VIP年卡权益!价值千元!(以下三种会员权益可同时共享,为期一年!)rnrnCSDN VIP年卡套餐:rnrn[b]GitChat 会员权益[/b]rnrn* 20 场达人课免费获取:精品课程随心学,和大咖聊不休rnrn* 优质资源任意享:达人课程任你免费下载和阅读rnrn* 领域专家畅快聊:进入专家的读者圈,尽情互动、深度追问rnrn[b]CSDN 下载会员权益[/b]rnrn* 下载会员有效期为 12 个月,免费下载 600 个资源,支持每天下载 20 个。rnrn* 600 个下载次数用完或者 12 个月后会员权益自动过期。rnrn使用说明请点击[url=https://mall.csdn.net/product/1482]https://mall.csdn.net/product/1482[/url]rnrnCSDN学院年度会员rnrn* 会员独享 1000+专区课程免费学rnrn* 购课无忧 畅想全场9折优惠rnrn* 尊享会员 专属标识 rnrn* 红包礼遇 288元大礼包rnrnrnrn以上权益,购票并现场签到后即可获得![url=https://www.bagevent.com/event/1761752][b][color=#800000][size=16px]快来购票吧~[/size][/color][/b][/url] 论坛

没有更多推荐了,返回首页