「泡菜」为什么套路别人的问题玩得好好的,我一玩就各种问题

花了六百多,我在传说中让人「哭晕」的上海迪士尼玩一天
上海迪士尼乐园的旅游体验如何?
知乎用户,景观设计狗/ACG患者/迷妹
5 月 10 日刚去过。这个问题我来回答一下吧。
5/16 更新,有很多人问什么不能带什么能带,这个我把官方游园指南里面的说明贴在最后了,大家可以看一下。
交通以及 FP 的使用方法也做了图片补充。
大家拿到乐园指南后一定要仔细阅读,里面写的很清楚啦。
前言,迪士尼乐园可能不适合以下这些人:
1. 对迪士尼人物、文化、故事没有兴趣,纯粹去凑热闹的人。
2. 觉得卡通动画就是给小朋友看的人。
3. 为了玩刺激游乐项目的人。
4. 觉得人工、服务、游园环境都应该免费的人。
5. 觉得主题乐园应该是公益项目的人。
6. 觉得你们去了的就是人傻钱多,答主说了那么多我觉得不合理就是不合理!我觉得不好玩,你们觉得好玩就是你们有问题!(哇哦,那我给你鼓鼓掌啊:)
预防针:你们如果要说我是软文我可不接受喔。唯一收到的好处是因为试运营期间,所以是我同学刷脸带我们进去的=w=,除此之外别的开销全是自己来的。证明不是软文要照片?喏。
我之前去过:
香港迪士尼乐园一次
香港海洋公园一次
东京迪士尼乐园一次
东京迪士尼海洋两次
大阪环球影城一次
新加坡环球影城一次
上海欢乐谷年卡
常州恐龙园一次
广州长隆一次
同行有个朋友之前去过:
加州迪士尼(此处更正,之前我搞错了 orz)
加上这次算是亚洲的迪士尼和大型游乐园基本都去过了吧,应该还是能有比较客观的感受的。
收入水平:毕业一年,苦逼画图员,到手月薪 4000 不到。(是不是很少?消费水平肯定比不上知乎大神们吧?只不过因为是魔都土著所以省去了房租水电一块,个人零花用用还是阔以的。)
试运营期间,很多设施都还没有准备好,包括园区内的植物绿化也还没有全部更新好,游园体验肯定有所缺失,但是并不影响。图片都是 iPhone 6S 拍的所以,有的我发社交软件的所以加过滤镜,有的就是原片啦,手机拍照光线比较暗大家将就看。
出发:5 月 10 号,阴天 + 有时有小雨,天空灰蒙蒙的。早九点和另外一个朋友一起去的,因为身体原因从静安区叫的车直接开到园区(112 元,11 号线上班高峰期一路站过去她有点吃不消,大家平摊一下车费还是可以接受的毕竟省时省力)车子是停在园区内的停车场,收费是 100 / 天。经同学介绍外围还有几个停车场但是距离较远,价格不清楚。园区停车场通往景区方向有游客中心,和很宽敞很干净的厕所,外面也有移动式的临时厕所。电梯上去就是一条直通乐园正门的大路。
园区外面是迪士尼小镇,去过的人不少我就不赘述了。
入园:从里至外顺序是 检票&&安检&&铁马限流。不知道是不是试运营的关系,可能这块还没协调好。铁马那里有些混乱,不过因为人流不是特别大所以影响不大。安检(此处修改)是每个札口一个人工开包检查,一个个拉链打开检查。食物带进去没什么问题。但是自拍杆好像是不能带的。问题在于人工检查效率低,且闸口没有封死就是一个桌子两边排了两排队伍,有些人就偷偷从空挡溜了进去。大概安检设备还没跟上吧。接下来就是刷票入园,这里我是刷脸的没什么好说 233。
整个入园过程还算顺利。不过,主入口真是太小啦!!!怎么可以那么小&&无力吐槽&&
过了检票以后就进入园区了,场景和香港的差不多 花坛加门楼
然后傍晚出园时候也拍了一张,开灯了还是挺好看的,拍的不好只体现了 30% 的效果吧= =
进去以后是米奇大街 旁边都是商店啊什么的。 正前方是城堡。(第一张调过色)
我觉得天气不好的时候亮灯之后最美。
入园后我们是先去了左手边的明日世界。
嗯,还是蛮有未来感的。
这里玩了两个项目,一个是&&(原谅我名字不记得了)
坐上去能看到园区风景
好玩指数☆☆☆
排队时间 20 分钟
然后是创 balabala 光轮(原谅我还是记不住名字)总之酷炫的不得了,不管看上去还是坐着。是摩托型的过山车,固定住腰部和腿部,整个人趴在摩托上头一段会冲出室外的特别爽。刺激程度其实也就那样吧(我是欢乐谷绝顶雄风连坐两次的人 - -)
这是预备发车道的实景照片 是不是特别酷炫!!!
好玩指数☆☆☆☆☆
排队时间 40 分钟
因为试运营开园时间就要 11 点了,包括排队等候时间,走路时间,玩好两个已经将近一点了。我们继续顺时针走(园区是个大环路)到北面老树藤食栈吃了饭,准备休息一会等会两点赶去看花车巡游。下面说说大家诟病最多的饮食。
套餐价主食 + 饮料 80RMB (主食和饮料都有四五种)
烤鸡 + 地瓜条,牛肉盖饭。
我觉得味道还不错,量嘛反正我胃口挺大的能吃饱。
炸鸡 + 薯条,炸鱼 + 薯条。
炸鱼的味道难以形容&&
炸鸡还不错~
总体价格,较之于正常水平肯定是偏高的,在旅游区普遍价里也不算便宜。味道我吃的这家还可以的,据说别的什么米奇披萨就比较坑爹了&&
点评:偏贵,随便吃吃,不要认真,拍个照就行了。
建议:自备干粮,自备水果,垃圾不要乱扔。
对了,园区垃圾桶是那种像快餐店一样要推的&&盖子有点重不太方便,建议改进。
吃完饭,差不多一点三刻赶到小飞象那里等待两点的花车巡游。
园区内所有表演时间,还有项目开放时间,在检票的地方都有游园手册可以拿的。写的很详细。有不清楚的也可以问我~毕竟去了很多迪士尼乐园,大同小异这点经验还是有哒~
花车迅游超级值得一看,气氛很好。来迪士尼就是要看这种表演啊,回味作品,值得一提园区内所有演职人员都非常卖力,态度特别特别好,路过都会亲切的和你打招呼。【虽然我朋友吐槽表演表情用力过猛哈哈哈哈哈哈哈哈,不过我觉得不戏剧性就不好看啦。】
音效很不错,美中不足是时间有些短,参与表演的人有点少。而且园区主路其实挺窄的&&
观众秩序维持的还不错,除了表演开始前有些人还乱穿,真的很危险啊!!
大部分都是小青年还有很多美国人(试运营受邀来的),互动很好。我看到有个老婆婆虽然看不懂是啥,但是布鲁托去跟她击掌的时候她笑的超开心。绝大部分人都被气氛感染了,我觉得这正是迪士尼乐园的魅力所在。找回童心,找回最纯粹的快乐的地方。
看完花车,趁人少玩了小飞象。
这个适合一家子坐,座椅有点向外倾斜注意不要让小孩坐在外面~
好玩程度☆☆
排队时间 20 分钟
之后是七个小矮人矿山车
惊险刺激程度和创极速光轮差不多吧,典型的半室内过山车,迪士尼乐园排队的地方都很有趣可以拍拍照什么的,同行人多的话大家可以四人斗地主 hhhh
好玩程度☆☆☆☆☆
排队时间 大于一小时
出来去爱丽丝花园遛了一圈,很弱智的迷宫= =主要是给大家拍照的,栏杆什么的小细节很有趣。手机电不多了就没拍~
出来正好是中央城堡舞台即将开始的金色童话盛典 的演出,下午每过一段时间有一场吧&&大概 20 分钟,提前进到前面的花园广场里。其实我觉得站的远一点看效果更好,近一点的话,嗯,都在看演员飞起的裙摆下的超长打底裤&&表演内容是经典公主们啦~ 我最喜欢爱丽儿!
远远的拍了一张,看到下面舞台上的人了吗~
美中不足是下面观众的掌声和欢呼声实在有点小,希望大家能够更活跃一些。
之后是小飞侠 balabala(全名忘了&&)
挺惊喜的!以为只是坐着船进去看看动画体验下剧情,结果表现方式非常特别,效果特别梦幻!!因为室内很暗拍不了照片所以果咩啦。真心推荐!超有童话氛围!!
强烈安利!
好玩指数☆☆☆☆☆
结束之后是游乐园必玩的旋转木马
旋转木马很大 很美!!!
晚上开了灯更好看啦!!(这图我调过滤镜
反正我很喜欢!!!
好玩的我都动感模糊了=w=
好玩指数☆☆☆☆☆
非常遗憾的是因为晚上还有事,宝藏湾那边没有去到。只能下次正式开园再去拉~
接下来就和小伙伴回到米奇大街那里买买买&&因为之前去过挺多迪士尼了,陆家嘴的迪士尼商店也去了很多次,要买的东西不是那么多。所以就买了上海独家的十二生肖的 duffy!
兔子的超级可爱!!似乎是可以拆卸的。139RMB。我觉得不贵啦&&
下图这个中间很大的裸体 DUFFY 只要 199! 我记得霓虹卖好像也要 3800yen(税拔)吧。我觉得周边价格还是可以的。
我朋友买了两个很可爱的钥匙圈,不锈钢金属的 做工也不错,59 和 99,我觉得都可以接受。
到此为止我这次的迪士尼乐园之旅就结束啦。
送上一张城堡远景~
下面有一些小总结和小提示~
关于排队:
5/10 当天入园人数应该在 1w-2w 左右。园区很大所以相对来说还是比较空旷的。而且还有一些区域没有开放。但是园路不是特别宽敞,所以正式营业后应该会挺挤的。
大项目平均排队时间在 1h 时以上,小项目在 20&&30 分钟不等,也有许多不用排队的。
因为人少,所以我这个时间不具备什么参考性【摊手
大家做好大型项目排队 2 小时以上的准备吧。
关于 fastpass(快速通行证):
这个是节约时间,多玩项目的利器,请大家妥善利用!
网上关于快速通行证(以下简称 FP)的攻略很多,我在这里就简单介绍一下。FP 是提前预约游戏项目,然后在预约时间段内去玩,可以从特别通道走,基本省去排队时间(人多的时候也需要等待一段时间)。
例如:我十点入园,先去领 A 项目的 FP,FP 上预约游玩时间是中午 12:40-1:40,那我只要在这个时间段之内赶到 A 项目,出示 FP 进去就行了。注意过时作废!!!接下来在 10:00 - 预约时间前的这段时间里我就能去玩 BCD 项目。
注意:FP 每隔两小时才能领一次,所以掐点拿。有时候拿 FP 的人太多,可能上午预约下午才能玩。每张门票每次可以领一张 FP。【评论区提示,FP 每天每票预约次数有限制的~】
在园区的若干个服务站里,都有领取 FP 的地方,详情看地图。因为项目的位置关系,一个服务站可能不会有所有项目的 FP。
FP 领取需要门票,为了限制人流保持秩序,同行的人每次派一名代表进去取就行了,根据机器指示来,刷门票上的二维码即可。
补充一张服务指南的官方说明:
关于厕所:对没错,洁癖患者如我非常注重厕所的清洁与否= =卫生间环境宽敞明亮,大部分是蹲位的,位置还是很宽敞的。硬件没的说,所以在此还是呼吁一下大家文明如厕,这个真的是要靠大家,人流大起来,不靠大家自己自觉真的是清洁工也忙不过来。这点东京迪士尼就做得很好(当然日本都很干净当我没说)
关于体验:
1.在一开始我也说了,毕竟没有全部开放,重头戏的烟火表演也没有,所以游玩体验是有一定的缺憾的。(虽然我觉得 6/16 开园也有点匆忙)但是打个分的话,满分十分,我给八分。
2.乐园内的游客素质还是可以的,小镇我没去所以不做评价。地上基本没见到过辣鸡,也没有随地吐痰的行为啊什么的,大概跟年龄层次较为年轻还有老外比较多有关系。但随着人流增多整洁度肯定会总比下降。
3.服务态度。满分 10 分的话我给 200 分。形象点说,就是比海底捞的态度还要好个几倍吧。路边的演职人员全程在招手,面带微笑,非常热情。
4.面积的确是比东京迪士尼要大一圈的,环境目前看来还不错啦,希望之后保持~
5.你问我好不好玩?当然好玩啊!!作为从小看迪士尼动画长大,中二时期受霓虹 ACG 毒害的正规青年,简直喜欢的不得了好吗。我会说在迪士尼海洋看灯光秀的时候都感动哭了吗=.= 我觉得小孩子就算从小看喜羊羊和巴拉拉的应该都对这种没有抵抗力吧。
补充,关于花费:
1.过去因为叫的专车比较奢侈,人均 50RMB。(其实我觉得三四个人从市区叫专车过去完全可行啊,省时省力)回程坐的 11 号线,6RMB,终点站有座位。就是要注意一群身强力壮抢座位的阿姨妈妈们= =
其他交通可以参考下图
2.门票工作日 370RMB
3.午饭套餐 80RMB
(真的建议大家带一点干粮去,评论里有人提出可以带未开封的食物和水进去哦)
4.纪念品 139RMB+1 元购物袋
因为我自己该买的周边也已经买得差不多了 23333,所以就买了个 duffy 熊。提醒大家理性购物,钥匙圈小物件质量什么还是可以的。塑料购物袋 1 元一大个。
5.总计 56(交通)+370(工作日门票)+80(午饭)+140(纪念品)=646RMB
我这里的交通费大家可以酌情减少,留下来吃晚餐的话,餐饮费用肯定也会提高。要说一个毛绒玩具 139 贵不贵,我觉得并不,因为我喜欢。你买了一个心仪已久的游戏花了两三百贵吗?炉石卡包觉得贵吗?驴牌人造革觉得贵吗?
关于爆米花和气球,爆米花是连筒卖的 USJ 的大罐爆米花也差不多这个价。还有 60 块的气球,我嫌贵我就不买呗,爆米花哪里都买得到,气球也是。你花这点钱还不如去纪念品商店里买两个米妮头箍米奇头饰带着开心,以后来玩还能重复利用,你说是吧?
很多人觉得价格不能接受也很正常嘛,消费观不一样,辨不清的话题。商业性质的游乐园,不是社区儿童活动广场,所以爱买买,不买也别管别人买不买,咸吃萝卜淡操心。(●'&'●)丿
能想到的差不多就这么多啦,下次玩好有新的再补充~祝大家玩得开心~
哎嘛,第一次在知乎正儿八经的回答那么多,如果你觉得我说的有用的话就点个赞呗~
弄刚嘚伐~(&ゝ&&)☆ kira
就评论区的一些问题,我来补充一下。
第一,门票是通票,里面项目随意玩,吃喝和纪念品要另外花钱的。狮子王剧场表演是在乐园外的迪士尼小镇,详情讯官网。
第二,能不能带食物入园,还有什么是不能带的,请看下图。
请特别注意我划出的地方。
划重点:1.未开封的,原包装的,密封的食品可以携带,玻璃瓶不行。
2.尺寸超标的行李不能入园。
3.不能 cosplay,不要扮成迪士尼人物,也不要穿任何角色扮演的衣服。
4.不要投喂动物,不要放风筝,操作遥控机。
5.折叠椅、凳子、大型三脚架不允许带。
图中基本能解答各位的问题了,评论区还有类似的问题恕我不能一一解答了。谢谢大家。
客官,这篇文章有意思吗?产生65535问题的原因
LinearAlloc问题的原因
提出的MultiDex方案
MultiDex实现原理
美团的多Dex分包异步加载方案
异步加载方案
目前来说,对于使用 Studio的来说,MultiDex应该不陌生,就是Google为了解决『65535天花板』问题而给出的官方,但是这个方案并不完美,所以美团又给出了异步加载Dex文件的方案。今天这篇是我最近研究MultiDex方案的一点收获,最后还留了一个没有解决的问题,如果你有思路的话,欢迎交流!
产生65535问题的原因
单个Dex文件中,method个数采用使用原生类型short来索引,即4个字节最多65536个method,field、class的个数也均有此限制,关于如何解决由于引用过多基础依赖,造成field超过65535问题,请参考@寒江不钓的这篇文章『当Field邂逅65535』。
对于Dex文件,则是将工程所需全部class文件合并且压缩到一个DEX文件期间,也就是使用Dex工具将class文件转化为Dex文件的过程中, 单个Dex文件可被引用的方法总数(自己开发的代码以及所引用的Android框架、类库的代码)被限制为65536。
这就是65535问题的根本来源。
LinearAlloc问题的原因
这个问题多发生在2.x版本的设备上,安装时会提示INSTALL_FAILED_DEXOPT,这个问题发生在安装期间,在使用Dalvik的设备上安装APK时,会通过DexOpt工具将Dex文件优化为ODex文件,即Optimised Dex,这样可以提高执行效率。
在Android版本不同分别经历了4M/5M/8M/16M限制,目前主流4.2.x系统上可能都已到16M, 在Gingerbread或以下系统LinearAllocHdr分配只有5M大小的, 高于Gingerbread的系统提升到了8M。Dalvik linearAlloc是一个固定大小的缓冲区。dexopt使用LinearAlloc来应用的方法信息。Android 2.2和2.3的缓冲区只有5MB,Android 4.x提高到了8MB或16MB。当应用的方法信息过多导致超出缓冲区大小时,会造成dexopt崩溃,造成INSTALL_FAILED_DEXOPT错误。
Google提出的MultiDex方案
当App不断迭代的时候,总有一天会遇到这个问题,为此Google也给出了解决方案,具体的操作步骤我就不多说了,无非就是配置Application和Gradle文件,下面我们简单看一下这个方案的实现原理。
MultiDex实现原理
实际起作用的是下面这个jar包
~/sdk/extras/android/support/multidex/library/libs/android-support-multidex.jar
不管是继承自MultiDexApplication还是重写attachBaseContext(),实际都是调用下面的方法
public class MultiDexApplication extends Application {
protected void attachBaseContext(final Context base) {
super.attachBaseContext(base);
MultiDex.install((Context)this);
下面重点看下MutiDex.install(Context)的实现,代码很容易理解,重点的地方都有注释
//第二个Dex文件的名,实际地址是/date/date/&_name&/code_cache/secondary-dexes
SECONDARY_FOLDER_NAME = "code_cache" + File.separator + "secondary-dexes";
installedApk = new HashSet&String&();
IS_VM_MULTIDEX_CAPABLE = isVMMultidexCapable(.getProperty(".vm."));
public static void install(final Context context) {
//在使用ART虚拟机的设备上(部分4.4设备,5.0+以上都默认ART环境),已经原生支持多Dex,因此就不需要手动支持了
if (MultiDex.IS_VM_MULTIDEX_CAPABLE) {
Log.i("MultiDex", "VM has multidex support, MultiDex support library is disabled.");
if (Build.VERSION.SDK_INT & 4) {
throw new RuntimeException("Multi dex installation failed. SDK " + Build.VERSION.SDK_INT + " is unsupported. Min SDK version is " + 4 + ".");
final ApplicationInfo applicationInfo = getApplicationInfo(context);
if (applicationInfo == null) {
synchronized (MultiDex.installedApk) {
//如果apk文件已经被加载过了,就返回
final String apkPath = applicationInfo.sourceD
if (MultiDex.installedApk.contains(apkPath)) {
MultiDex.installedApk.add(apkPath);
if (Build.VERSION.SDK_INT & 20) {
Log.w("MultiDex", "MultiDex is not guaranteed to work in SDK version " + Build.VERSION.SDK_INT + ": SDK version higher than " + 20 + " should be backed by " + "runtime with built-in multidex capabilty but it's not the " + "case here: java.vm.version=\"" + System.getProperty("java.vm.version") + "\"");
loader = context.getClassLoader();
catch (RuntimeException e) {
Log.w("MultiDex", "Failure while trying to obtain Context class loader. Must be running in test mode. Skip patching.", (Throwable)e);
if (loader == null) {
Log.e("MultiDex", "Context class loader is null. Must be running in test mode. Skip patching.");
//清楚之前的Dex文件夹,之前的Dex放置在这个文件夹
//final File dexDir = new File(context.getFilesDir(), "secondary-dexes");
clearOldDexDir(context);
catch (Throwable t) {
Log.w("MultiDex", "Something went wrong when trying to clear old MultiDex extraction, continuing without cleaning.", t);
final File dexDir = new File(applicationInfo.dataDir, MultiDex.SECONDARY_FOLDER_NAME);
//将Dex文件加载为File对象
List&File& files = MultiDexExtractor.load(context, applicationInfo, dexDir, false);
//检测是否是zip文件
if (checkValidZipFiles(files)) {
//正式安装其他Dex文件
installSecondaryDexes(loader, dexDir, files);
Log.w("MultiDex", "Files were not valid zip files.
Forcing a reload.");
files = MultiDexExtractor.load(context, applicationInfo, dexDir, true);
if (!checkValidZipFiles(files)) {
throw new RuntimeException("Zip files were not valid.");
installSecondaryDexes(loader, dexDir, files);
catch (Exception e2) {
Log.e("MultiDex", "Multidex installation failure", (Throwable)e2);
throw new RuntimeException("Multi dex installation failed (" + e2.get() + ").");
Log.i("MultiDex", "install done");
从上面的过程来看,只是完成了加载包含着Dex文件的zip文件,具体的加载操作都在下面的方法中
installSecondaryDexes(loader, dexDir, files);
下面重点看下
private static void installSecondaryDexes(final ClassLoader loader, final File dexDir, final List&File& files) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IOException {
if (!files.isEmpty()) {
if (Build.VERSION.SDK_INT &= 19) {
install(loader, files, dexDir);
else if (Build.VERSION.SDK_INT &= 14) {
install(loader, files, dexDir);
install(loader, files);
到这里为了完成不同版本的兼容,实际调用了不同类的方法,我们仅看一下&=14的版本,其他的类似
private static final class V14
private static void install(final ClassLoader loader, final List&File& additionalClassPathEntries, final File optimizedDirectory) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
//通过反射获取loader的pathList字段,loader是由Application.getClassLoader()获取的,实际获取到的是PathClassLoader对象的pathList字段
final Field pathListField = findField(loader, "pathList");
final Object dexPathList = pathListField.get(loader);
//dexPathList是PathClassLoader的私有字段,里面保存的是Main Dex中的class
//dexElements是一个数组,里面的每一个item就是一个Dex文件
//makeDexElements()返回的是其他Dex文件中获取到的Elements[]对象,内部通过反射makeDexElements()获取
//expandFieldArray是为了把makeDexElements()返回的Elements[]对象添加到dexPathList字段的成员变量dexElements中
expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, new ArrayList&File&(additionalClassPathEntries), optimizedDirectory));
private static Object[] makeDexElements(final Object dexPathList, final ArrayList&File& files, final File optimizedDirectory) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
final Method makeDexElements = findMethod(dexPathList, "makeDexElements", (Class&?&[])new Class[] { ArrayList.class, File.class });
return (Object[])makeDexElements.invoke(dexPathList, files, optimizedDirectory);
PathClassLoader.java
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
public PathClassLoader(String dexPath, String libraryPath,
ClassLoader parent) {
super(dexPath, null, libraryPath, parent);
BaseDexClassLoader的代码如下,实际上寻找class时,会调用findClass(),会在pathList中寻找,因此通过反射手动添加其他Dex文件中的class到pathList字段中,就可以实现类的动态加载,这也是MutiDex方案的基本原理。
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathL
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
protected Class&?& findClass(String name) throws ClassNotFoundException {
List&Throwable& suppressedExceptions = new ArrayList&Throwable&();
Class c = pathList.findClass(name, suppressedExceptions);
if (c == null) {
ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t);
通过查看MultiDex的源码,可以发现MultiDex在冷启动时,因为会同步的反射安装Dex文件,进行IO操作,容易导致ANR
在冷启动时因为需要安装Dex文件,如果Dex文件过大时,处理时间过长,很容易引发ANR
采用MultiDex方案的应用因为linearAlloc的BUG,可能不能在2.x设备上启动
美团的多Dex分包、动态异步加载方案
首先我们要明白,美团的这个动态异步加载方案,和插件化的动态加载方案要解决的问题不一样,我们这里讨论的只是单纯的为了解决65535问题,并且想办法解决Google的MutiDex方案的弊端。
首先,采用Google的方案我们不需要关心Dex分包,开发工具会自动的分析依赖关系,把需要的class文件及其依赖class文件放在Main Dex中,因此如果产生了多个Dex文件,那么classes.dex内的方法数一般都接近65535这个极限,剩下的class才会被放到Other Dex中。如果我们可以减小Main Dex中的class数量,是可以加快冷启动速度的。
美团给出了Gradle的配置,但是由于没有具体的实现,所以这块还需要研究。
tasks.whenTaskAdded { task -&
if (task.name.startsWith('proguard') && (task.name.endsWith('Debug') || task.name.endsWith('Release'))) {
task.doLast {
makeDexFileAfterProguardJar();
task.doFirst {
delete "${project.buildDir}/intermediates/classes-proguard";
String flavor = task.name.substring('proguard'.length(), task.name.lastIndexOf(task.name.endsWith('Debug') ? "Debug" : "Release"));
generateMainIndexKeepList(flavor.toLowerCase());
} else if (task.name.startsWith('zipalign') && (task.name.endsWith('Debug') || task.name.endsWith('Release'))) {
task.doFirst {
ensureMultiDexInApk();
实现Dex自定义分包的关键是分析出class之间的依赖关系,并且干涉Dex文件的生成过程。
Dex也是一个工具,通过设置参数可以实现哪一些class文件在Main Dex中。
afterEvaluate {
tasks.matching {
it.name.startsWith('dex')
}.each { dx -&
if (dx.additionalParameters == null) {
dx.additionalParameters = []
dx.additionalParameters += '--multi-dex'
dx.additionalParameters += '--set-max-idx-number=30000'
println("dx param = "+dx.additionalParameters)
dx.additionalParameters += "--main-dex-list=$projectDir/multidex.keep".toString()
–multi-dex 代表采用多Dex分包
–set-max-idx-number=30000 代表每个Dex文件中的最大id数,默认是65535,通过修改这个值可以减少Main Dex文件的大小和个数。比如一个App混淆后方法数为48000,即使开启MultiDex,也不会产生多个Dex,如果设置为30000,则就产生两个Dex文件
–main-dex-list= 代表在Main Dex中的class文件
需要注意的是,上面我给出的gredle task,只在1.4以下管用,在1.4+版本的gradle中,app:dexXXX task 被隐藏了(更多信息请参考Gradle plugin的更新信息),jacoco, progard, multi-dex三个task被合并了。
The Dex task is not available through the variant API anymore….
The goal of this API is to simplify injecting custom class manipulations without having to deal with tasks, and to offer more flexibility on what is manipulated. The internal code processing (jacoco, progard, multi-dex) have all moved to this new mechanism already in 1.5.0-beta1.
所以通过上面的方法无法对Dex过程进行劫持。这也是我现在还没有解决的问题,有解决方案的朋友可以指点一下!
异步加载方案
其实前面的操作都是为了这一步操作的,无论将Dex分成什么样,如果不能异步加载,就解决不了ANR和加载白屏的问题,所以异步加载是一个重点。
异步加载主要问题就是:如何避免在其他Dex文件未加载完成时,造成的ClassNotFoundException问题?
美团给出的解决方案是替换Instrumentation,但是中未给出具体实现,我对这个点进行了简单的实现,Demo在这里MultiDexAsyncLoad,对ActivityThread的反射用的是携程的解决方案。
首先继承自Instrumentation,因为这一块需要涉及到Activity的启动过程,所以对这个过程不了解的朋友请看我的这篇文章【凯子哥带你学Framework】Activity启动过程全解析。
* Created by zhaokaiqiang on 15/12/18.
public class MeituanInstrumentation extends Instrumentation {
private List&String& mByPassActivityClassNameL
public MeituanInstrumentation() {
mByPassActivityClassNameList = new ArrayList&&();
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
if (intent.getComponent() != null) {
className = intent.getComponent().getClassName();
boolean shouldInterrupted = !MeituanApplication.isDexAvailable();
if (mByPassActivityClassNameList.contains(className)) {
shouldInterrupted = false;
if (shouldInterrupted) {
className = WaitingActivity.class.getName();
mByPassActivityClassNameList.add(className);
return super.newActivity(cl, className, intent);
至于为什么重写了newActivity(),是因为在启动Activity的时候,会经过这个方法,所以我们在这里可以进行劫持,如果其他Dex文件还未异步加载完,就跳转到Main Dex中的一个等待Activity——WaitingActivity。
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
ActivityInfo aInfo = r.activityInfo;
if (r.packageInfo == null) {
r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
Context.CONTEXT_INCLUDE_CODE);
Activity activity = null;
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
} catch (Exception e) {
在WaitingActivity中可以一直轮训,等待异步加载完成,然后跳转至目标Activity。
public class WaitingActivity extends BaseActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_wait);
waitForDexAvailable();
private void waitForDexAvailable() {
final Intent intent = getIntent();
final String className = intent.getStringExtra(TAG_TARGET);
timer = new Timer();
timer.schedule(new TimerTask() {
public void run() {
while (!MeituanApplication.isDexAvailable()) {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
Log.d("TAG", "waiting");
intent.setClassName(getPackageName(), className);
startActivity(intent);
protected void onDestroy() {
super.onDestroy();
if (timer != null) {
timer.cancel();
异步加载Dex文件放在什么时候合适呢?
我放在了Application.onCreate()中
public class MeituanApplication extends Application {
private static final String TAG = "MeituanApplication";
private static boolean isDexAvailable = false;
public void onCreate() {
super.onCreate();
loadOtherDexFile();
private void loadOtherDexFile() {
new Thread(new Runnable() {
public void run() {
MultiDex.install(MeituanApplication.this);
isDexAvailable = true;
}).start();
public static boolean isDexAvailable() {
return isDexA
那么替换系统默认的Instrumentation在什么时候呢?
当SplashActivity跳转到MainActivity之后,再进行替换比较合适,于是
public class MainActivity extends BaseActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MeituanApplication.attachInstrumentation();
MeituanApplication.attachInstrumentation()实际就是通过反射替换默认的Instrumentation。
public class MeituanApplication extends Application {
public static void attachInstrumentation() {
SysHacks.defineAndVerify();
MeituanInstrumentation meiTuanInstrumentation = new MeituanInstrumentation();
Object activityThread = AndroidHack.getActivityThread();
Field mInstrumentation = activityThread.getClass().getDeclaredField("mInstrumentation");
mInstrumentation.setAccessible(true);
mInstrumentation.set(activityThread, meiTuanInstrumentation);
} catch (Exception e) {
e.printStackTrace();
至此,异步加载Dex方案的一个基本思路就通了,剩下的就是完善和版本兼容了。
Android 使用android-support-multidex解决Dex超出方法数的限制问题,让你的应用不再爆棚
dex分包变形记
Android dex分包方案
美团Android DEX自动拆包及动态加载简介
手动分割Dex文件的build.gradle配置
Multi-dex to rescue from the infamous 65536 methods limit
secondary-dex-gradle
Using Gradle to split external libraries in separated dex files to solve Android Dalvik 64k methods limit
江湖人称『凯子哥』,Android,喜欢技术分享,热爱开源。
我的CSDN博客:http://blog.csdn.net/zhaokaiqiang1992
我的:裸奔的凯子哥,每天会不定时分享高质量博客,欢迎关注
公众账号:kaizige1992}

我要回帖

更多关于 问别人问题问什么好玩 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信