首页 文章 精选 留言 我的

精选列表

搜索[Vue3组件库],共10000篇文章
优秀的个人博客,低调大师

解开Android应用程序组件Activity的"singleTask"之谜(3)

回到前面的startActivityUncheckedLocked函数中,这里的变量top就为null了,于是执行下面的else语句: if(top!=null){ ...... }else{ //Aspecialcase:weneedto //starttheactivitybecauseitisnotcurrently //running,andthecallerhasaskedtoclearthe //currenttasktohavethisactivityatthetop. addingToTask=true; //Nowpretendlikethisactivityisbeingstarted //bythetopofitstask,soitisputinthe //rightplace. sourceRecord=taskTop; } 于是,变量addingToTask值就为true了,同时将变量sourceRecord的值设置为taskTop,即前面调用findTaskLocked函数的返回值,这里,它就是表示MainActivity了。 继续往下看,下面这个if语句: if(r.packageName!=null){ //Iftheactivitybeinglaunchedisthesameastheonecurrently //atthetop,thenweneedtocheckifitshouldonlybelaunched //once. ActivityRecordtop=topRunningNonDelayedActivityLocked(notTop); if(top!=null&&r.resultTo==null){ if(top.realActivity.equals(r.realActivity)){ if(top.app!=null&&top.app.thread!=null){ ...... } } } }else{ ...... } 它是例行性地检查当前任务顶端的Activity,是否是即将启动的Activity的实例,如果是否的话,在某些情况下,它什么也不做,就结束这个函数调用了。这里,当前任务顶端的Activity为MainActivity,它不是SubActivity实例,于是继续往下执行: booleannewTask=false; //Shouldthisbeconsideredanewtask? if(r.resultTo==null&&!addingToTask &&(launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK)!=0){ ...... }elseif(sourceRecord!=null){ if(!addingToTask&& (launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP)!=0){ ...... }elseif(!addingToTask&& (launchFlags&Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)!=0){ ...... } //Anexistingactivityisstartingthisnewactivity,sowewant //tokeepthenewoneinthesametaskastheonethatisstarting //it. r.task=sourceRecord.task; ...... }else{ ...... } 这里首先将newTask变量初始化为false,表示不要在新的任务中启动这个SubActivity。由于前面的已经把addingToTask设置为true,因此,这里会执行中间的else if语句,即这里会把r.task设置为sourceRecord.task,即把SubActivity放在MainActivity所在的任务中启动。 最后,就是调用startActivityLocked函数继续进行启动Activity的操作了。后面的操作这里就不跟下去了,有兴趣的读者可以参考两篇文章Android应用程序启动过程源代码分析和Android应用程序内部启动Activity过程(startActivity)的源代码分析。 到这里,思路就理清了,虽然SubActivity的launchMode被设置为"singleTask"模式,但是它并不像官方文档描述的一样:The system creates a new task and instantiates the activity at the root of the new task,而是在跟它有相同taskAffinity的任务中启动,并且位于这个任务的堆栈顶端,于是,前面那个图中,就会出现一个带着"singleTask"标签的箭头指向一个任务堆栈顶端的Activity Y了。 那么,我们有没有办法让一个"singleTask"的Activity在新的任务中启动呢?答案是肯定的。从上面的代码分析中,只要我们能够进入函数startActivityUncheckedLocked的这个if语句中: if(r.resultTo==null&&!addingToTask &&(launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK)!=0){ //todo:shoulddobettermanagementofintegers. mService.mCurTask++; if(mService.mCurTask<=0){ mService.mCurTask=1; } r.task=newTaskRecord(mService.mCurTask,r.info,intent, (r.info.flags&ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH)!=0); if(DEBUG_TASKS)Slog.v(TAG,"Startingnewactivity"+r +"innewtask"+r.task); newTask=true; if(mMainStack){ mService.addRecentTaskLocked(r.task); } } 那么,这个即将要启动的Activity就会在新的任务中启动了。进入这个if语句需要满足三个条件,r.resultTo为null,launchFlags的Intent.FLAG_ACTIVITY_NEW_TASK位为1,并且addingToTask值为false。从上面的分析中可以看到,当即将要启动的Activity的launchMode为"singleTask",并且调用startActivity时不要求返回要启动的Activity的执行结果时,前面两个条件可以满足,要满足第三个条件,只要当前系统不存在affinity属性值等于即将要启动的Activity的taskAffinity属性值的任务就可以了。 我们可以稍微修改一下上面的AndroidManifest.xml配置文件来做一下这个实验: <?xmlversion="1.0"encoding="utf-8"?> <manifestxmlns:android="http://schemas.android.com/apk/res/android" package="shy.luo.task" android:versionCode="1" android:versionName="1.0"> <applicationandroid:icon="@drawable/icon"android:label="@string/app_name"> <activityandroid:name=".MainActivity" android:label="@string/app_name" android:taskAffinity="shy.luo.task.main.activity"> <intent-filter> <actionandroid:name="android.intent.action.MAIN"/> <categoryandroid:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <activityandroid:name=".SubActivity" android:label="@string/sub_activity" android:launchMode="singleTask" android:taskAffinity="shy.luo.task.sub.activity"> <intent-filter> <actionandroid:name="shy.luo.task.subactivity"/> <categoryandroid:name="android.intent.category.DEFAULT"/> </intent-filter> </activity> </application> </manifest> 注意,这里我们设置MainActivity的taskAffinity属性值为"shy.luo.task.main.activity",设置SubActivity的taskAffinity属性值为"shy.luo.task.sub.activity"。重新编译一下程序,在模拟器上把这个应用程序再次跑起来,用“adb shell dumpsys activity”命令再来查看一下系统运行的的任务,就会看到: Runningactivities(mostrecentfirst): TaskRecord{4069c020#4Ashy.luo.task.sub.activity} Run#2:HistoryRecord{40725040shy.luo.task/.SubActivity} TaskRecord{40695220#3Ashy.luo.task.main.activity} Run#1:HistoryRecord{406b26b8shy.luo.task/.MainActivity} TaskRecord{40599c90#2Acom.android.launcher} Run#0:HistoryRecord{40646628com.android.launcher/com.android.launcher2.Launcher} 这里就可以看到,SubActivity和MainActivity就分别运行在不同的任务中了。 至此,我们总结一下,设置了"singleTask"启动模式的Activity的特点: 1. 设置了"singleTask"启动模式的Activity,它在启动的时候,会先在系统中查找属性值affinity等于它的属性值taskAffinity的任务存在;如果存在这样的任务,它就会在这个任务中启动,否则就会在新任务中启动。因此,如果我们想要设置了"singleTask"启动模式的Activity在新的任务中启动,就要为它设置一个独立的taskAffinity属性值。 2. 如果设置了"singleTask"启动模式的Activity不是在新的任务中启动时,它会在已有的任务中查看是否已经存在相应的Activity实例,如果存在,就会把位于这个Activity实例上面的Activity全部结束掉,即最终这个Activity实例会位于任务的堆栈顶端中。 看来,要解开Activity的"singleTask"之谜,还是要自力更生啊,不过,如果我们仔细阅读官方文档,在http://developer.android.com/guide/topics/manifest/activity-element.html中,有这样的描述: As shown in the table above, standard is the default mode and is appropriate for most types of activities. SingleTop is also a common and useful launch mode for many types of activities. The other modes — singleTask and singleInstance —are not appropriate for most applications, since they result in an interaction model that is likely to be unfamiliar to users and is very different from most other applications. Regardless of the launch mode that you choose, make sure to test the usability of the activity during launch and when navigating back to it from other activities and tasks using the BACK key. 这样看,官方文档也没有坑我们呢,它告诫我们:make sure to test the usability of the activity during launch。 本文转自 Luoshengyang 51CTO博客,原文链接:http://blog.51cto.com/shyluo/966140,如需转载请自行联系原作者

优秀的个人博客,低调大师

解开Android应用程序组件Activity的"singleTask"之谜(2)

再接下来,就是运行模拟器来运行我们的例子了。关于如何在Android源代码工程中运行模拟器,请参考 在Ubuntu上下载、编译和安装Android最新源代码 一文。 执行以下命令启动模拟器: USER-NAME@MACHINE-NAME:~/Android$emulator 模拟器启动起,就可以App Launcher中找到Task应用程序图标,接着把它启动起来: 点击中间的按钮,就会以"singleTask"的方式来启动SubActivity: 现在,我们如何来确认SubActivity是不是在新的任务中启动并且位于这个新任务的堆栈底部呢?Android源代码工程为我们准备了adb工具,可以查看模拟器上系统运行的状况,执行下面的命令查看; USER-NAME@MACHINE-NAME:~/Android$adbshelldumpsysactivity 这个命令输出的内容比较多,这里我们只关心TaskRecord部分: Runningactivities(mostrecentfirst): TaskRecord{4070d8f8#3Ashy.luo.task} Run#2:HistoryRecord{406a13f8shy.luo.task/.SubActivity} Run#1:HistoryRecord{406a0e00shy.luo.task/.MainActivity} TaskRecord{4067a510#2Acom.android.launcher} Run#0:HistoryRecord{40677518com.android.launcher/com.android.launcher2.Launcher} 果然,SubActivity和MainActivity都是运行在TaskRecord#3中,并且SubActivity在MainActivity的上面。这是怎么回事呢?碰到这种情况,Linus Torvalds告诫我们:Read the fucking source code;去年张麻子又说:枪在手,跟我走;我们没有枪,但是有source code,因此,我要说:跟着代码走。 前面我们在两篇文章Android应用程序启动过程源代码分析和Android应用程序内部启动Activity过程(startActivity)的源代码分析时,分别在Step 9和Step 8中分析了Activity在启动过程中与任务相关的函数ActivityStack.startActivityUncheckedLocked函数中,它定义在frameworks/base/services/java/com/android/server/am/ActivityStack.java文件中: publicclassActivityStack{ ...... finalintstartActivityUncheckedLocked(ActivityRecordr, ActivityRecordsourceRecord,Uri[]grantedUriPermissions, intgrantedMode,booleanonlyIfNeeded,booleandoResume){ finalIntentintent=r.intent; finalintcallingUid=r.launchedFromUid; intlaunchFlags=intent.getFlags(); ...... ActivityRecordnotTop=(launchFlags&Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP) !=0?r:null; ...... if(sourceRecord==null){ ...... }elseif(sourceRecord.launchMode==ActivityInfo.LAUNCH_SINGLE_INSTANCE){ ...... }elseif(r.launchMode==ActivityInfo.LAUNCH_SINGLE_INSTANCE ||r.launchMode==ActivityInfo.LAUNCH_SINGLE_TASK){ //Theactivitybeingstartedisasingleinstance...italways //getslaunchedintoitsowntask. launchFlags|=Intent.FLAG_ACTIVITY_NEW_TASK; } ...... booleanaddingToTask=false; if(((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK)!=0&& (launchFlags&Intent.FLAG_ACTIVITY_MULTIPLE_TASK)==0) ||r.launchMode==ActivityInfo.LAUNCH_SINGLE_TASK ||r.launchMode==ActivityInfo.LAUNCH_SINGLE_INSTANCE){ //Ifbringtofrontisrequested,andnoresultisrequested,and //wecanfindataskthatwasstartedwiththissame //component,theninsteadoflaunchingbringthatonetothefront. if(r.resultTo==null){ //Seeifthereisatasktobringtothefront.Ifthisis //aSINGLE_INSTANCEactivity,therecanbeoneandonlyone //instanceofitinthehistory,anditisalwaysinitsown //uniquetask,sowedoaspecialsearch. ActivityRecordtaskTop=r.launchMode!=ActivityInfo.LAUNCH_SINGLE_INSTANCE ?findTaskLocked(intent,r.info) :findActivityLocked(intent,r.info); if(taskTop!=null){ ...... if((launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP)!=0 ||r.launchMode==ActivityInfo.LAUNCH_SINGLE_TASK ||r.launchMode==ActivityInfo.LAUNCH_SINGLE_INSTANCE){ //Inthissituationwewanttoremoveallactivities //fromthetaskuptotheonebeingstarted.Inmost //casesthismeansweareresettingthetasktoits //initialstate. ActivityRecordtop=performClearTaskLocked( taskTop.task.taskId,r,launchFlags,true); if(top!=null){ ...... }else{ //Aspecialcase:weneedto //starttheactivitybecauseitisnotcurrently //running,andthecallerhasaskedtoclearthe //currenttasktohavethisactivityatthetop. addingToTask=true; //Nowpretendlikethisactivityisbeingstarted //bythetopofitstask,soitisputinthe //rightplace. sourceRecord=taskTop; } }elseif(r.realActivity.equals(taskTop.task.realActivity)){ ...... }elseif((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)==0){ ...... }elseif(!taskTop.task.rootWasReset){ ...... } ...... } } } ...... if(r.packageName!=null){ //Iftheactivitybeinglaunchedisthesameastheonecurrently //atthetop,thenweneedtocheckifitshouldonlybelaunched //once. ActivityRecordtop=topRunningNonDelayedActivityLocked(notTop); if(top!=null&&r.resultTo==null){ if(top.realActivity.equals(r.realActivity)){ if(top.app!=null&&top.app.thread!=null){ ...... } } } }else{ ...... } booleannewTask=false; //Shouldthisbeconsideredanewtask? if(r.resultTo==null&&!addingToTask &&(launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK)!=0){ //todo:shoulddobettermanagementofintegers. mService.mCurTask++; if(mService.mCurTask<=0){ mService.mCurTask=1; } r.task=newTaskRecord(mService.mCurTask,r.info,intent, (r.info.flags&ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH)!=0); if(DEBUG_TASKS)Slog.v(TAG,"Startingnewactivity"+r +"innewtask"+r.task); newTask=true; if(mMainStack){ mService.addRecentTaskLocked(r.task); } }elseif(sourceRecord!=null){ if(!addingToTask&& (launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP)!=0){ ...... }elseif(!addingToTask&& (launchFlags&Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)!=0){ ...... } //Anexistingactivityisstartingthisnewactivity,sowewant //tokeepthenewoneinthesametaskastheonethatisstarting //it. r.task=sourceRecord.task; ...... }else{ ...... } ...... startActivityLocked(r,newTask,doResume); returnSTART_SUCCESS; } ...... } 首先是获得用来启动Activity的Intent的Flags,并且保存在launchFlags变量中,这里,launcFlags的Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP位没有置位,因此,notTop为null。 接下来的这个if语句: if(sourceRecord==null){ ...... }elseif(sourceRecord.launchMode==ActivityInfo.LAUNCH_SINGLE_INSTANCE){ ...... }elseif(r.launchMode==ActivityInfo.LAUNCH_SINGLE_INSTANCE ||r.launchMode==ActivityInfo.LAUNCH_SINGLE_TASK){ //Theactivitybeingstartedisasingleinstance...italways //getslaunchedintoitsowntask. launchFlags|=Intent.FLAG_ACTIVITY_NEW_TASK; } 这里变量r的类型为ActivityRecord,它表示即将在启动的Activity,在这个例子中,即为SubActivity,因此,这里的r.launchMode等于ActivityInfo.LAUNCH_SINGLE_TASK,于是,无条件将launchFlags的Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP位置为1,表示这个SubActivity要在新的任务中启动,但是别急,还要看看其它条件是否满足,如果条件都满足,才可以在新的任务中启动这个SubActivity。 接下将addingToTask变量初始化为false,这个变量也将决定是否要将SubActivity在新的任务中启动,从名字我们就可以看出,默认不增加到原有的任务中启动,即要在新的任务中启动。这里的r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK条成立,条件r.resultTo == null也成立,它表这个Activity不需要将结果返回给启动它的Activity。于是会进入接下来的if语句中,执行: ActivityRecordtaskTop=r.launchMode!=ActivityInfo.LAUNCH_SINGLE_INSTANCE ?findTaskLocked(intent,r.info) :findActivityLocked(intent,r.info) 这里的条件r.launchMode !=ActivityInfo.LAUNCH_SINGLE_INSTANCE成立,于是执行findTaskLocked函数,这个函数也是定义在frameworks/base/services/java/com/android/server/am/ActivityStack.java文件中: publicclassActivityStack{ ...... /** *Returnsthetopactivityinanyexistingtaskmatchingthegiven *Intent.Returnsnullifnosuchtaskisfound. */ privateActivityRecordfindTaskLocked(Intentintent,ActivityInfoinfo){ ComponentNamecls=intent.getComponent(); if(info.targetActivity!=null){ cls=newComponentName(info.packageName,info.targetActivity); } TaskRecordcp=null; finalintN=mHistory.size(); for(inti=(N-1);i>=0;i--){ ActivityRecordr=(ActivityRecord)mHistory.get(i); if(!r.finishing&&r.task!=cp &&r.launchMode!=ActivityInfo.LAUNCH_SINGLE_INSTANCE){ cp=r.task; //Slog.i(TAG,"Comparingexistingcls="+r.task.intent.getComponent().flattenToShortString() //+"/aff="+r.task.affinity+"tonewcls=" //+intent.getComponent().flattenToShortString()+"/aff="+taskAffinity); if(r.task.affinity!=null){ if(r.task.affinity.equals(info.taskAffinity)){ //Slog.i(TAG,"Foundmatchingaffinity!"); returnr; } }elseif(r.task.intent!=null &&r.task.intent.getComponent().equals(cls)){ //Slog.i(TAG,"Foundmatchingclass!"); //dump(); //Slog.i(TAG,"ForIntent"+intent+"bringingtotop:"+r.intent); returnr; }elseif(r.task.affinityIntent!=null &&r.task.affinityIntent.getComponent().equals(cls)){ //Slog.i(TAG,"Foundmatchingclass!"); //dump(); //Slog.i(TAG,"ForIntent"+intent+"bringingtotop:"+r.intent); returnr; } } } returnnull; } ...... } 这个函数无非就是根据即将要启动的SubActivity的taskAffinity属性值在系统中查找这样的一个Task:Task的affinity属性值与即将要启动的Activity的taskAffinity属性值一致。如果存在,就返回这个Task堆栈顶端的Activity回去。在上面的AndroidManifest.xml文件中,没有配置MainActivity和SubActivity的taskAffinity属性,于是它们的taskAffinity属性值就默认为父标签application的taskAffinity属性值,这里,标签application的taskAffinity也没有配置,于是它们就默认为包名,即"shy.luo.task"。由于在启动SubActivity之前,MainActivity已经启动,MainActivity启动的时候,会在一个新的任务里面启动,而这个新的任务的affinity属性就等于它的第一个Activity的taskAffinity属性值。于是,这个函数会动回表示MainActivity的ActivityRecord回去. 回到前面的startActivityUncheckedLocked函数中,这里的taskTop就表示MainActivity,它不为null,于是继续往前执行。由于条件r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK成立,于是执行下面语句: ActivityRecordtop=performClearTaskLocked( kTop.task.taskId,r,launchFlags,true); 函数performClearTaskLocked也是定义在frameworks/base/services/java/com/android/server/am/ActivityStack.java文件中: publicclassActivityStack{ ...... /** *Performclearoperationasrequestedby *{@linkIntent#FLAG_ACTIVITY_CLEAR_TOP}:searchfromthetopofthe *stacktothegiventask,thenlookfor *aninstanceofthatactivityinthestackand,iffound,finishall *activitiesontopofitandreturntheinstance. * *@paramnewRDescriptionofthenewactivitybeingstarted. *@returnReturnstheoldactivitythatshouldbecontinuetobeused, *ornullifnonewasfound. */ privatefinalActivityRecordperformClearTaskLocked(inttaskId, ActivityRecordnewR,intlaunchFlags,booleandoClear){ inti=mHistory.size(); //Firstfindtherequestedtask. while(i>0){ i--; ActivityRecordr=(ActivityRecord)mHistory.get(i); if(r.task.taskId==taskId){ i++; break; } } //Nowclearit. while(i>0){ i--; ActivityRecordr=(ActivityRecord)mHistory.get(i); if(r.finishing){ continue; } if(r.task.taskId!=taskId){ returnnull; } if(r.realActivity.equals(newR.realActivity)){ //Hereitis!Nowfinisheverythinginfront... ActivityRecordret=r; if(doClear){ while(i<(mHistory.size()-1)){ i++; r=(ActivityRecord)mHistory.get(i); if(r.finishing){ continue; } if(finishActivityLocked(r,i,Activity.RESULT_CANCELED, null,"clear")){ i--; } } } //Finally,ifthisisanormallaunchmode(thatis,not //expectingonNewIntent()),thenwewillfinishthecurrent //instanceoftheactivitysoanewfreshonecanbestarted. if(ret.launchMode==ActivityInfo.LAUNCH_MULTIPLE &&(launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP)==0){ if(!ret.finishing){ intindex=indexOfTokenLocked(ret); if(index>=0){ finishActivityLocked(ret,index,Activity.RESULT_CANCELED, null,"clear"); } returnnull; } } returnret; } } returnnull; } ...... } 这个函数中作用无非就是找到ID等于参数taskId的任务,然后在这个任务中查找是否已经存在即将要启动的Activity的实例,如果存在,就会把这个Actvity实例上面直到任务堆栈顶端的Activity通过调用finishActivityLocked函数将它们结束掉。在这个例子中,就是要在属性值affinity等于"shy.luo.task"的任务中看看是否存在SubActivity类型的实例,如果有,就把它上面的Activity都结束掉。这里,属性值affinity等于"shy.luo.task"的任务只有一个MainActivity,而且它不是SubActivity的实例,所以这个函数就返回null了。 本文转自 Luoshengyang 51CTO博客,原文链接:http://blog.51cto.com/shyluo/966135,如需转载请自行联系原作者

优秀的个人博客,低调大师

android 打包自己的自定义组件成JAR包

在项目开发过程中,我们难免会用到自己去制作自定义的VIEW控件,之后我们别的项目如果需要的话就直接将其复制到对应的项目中使用,虽说这么做是一个解决问题的方法,但毕竟不是很好。 原因是,当我们项目积累越来越多,会发现自定义的控件越来越多,而且这些自定义的控件都是可以重复利用的,这时我们可以想想,如果把这些自定义控件都封装成一个JAR包,然后用一个项目积累起来,之后我们以后开发项目只要在原来JAR包的基础上做扩展或者直接使用,可以大大减少自己的工作重复性。 首先Android 工程的基本面貌是这样的: 当然对应的Activity 文件被我删除了,因为当编译成jar 包我们并不需要Activity 文件。 以上面这个工程为例,我们将它打包成JAR包步骤为: 右键工程选择导出: 选择导出目标为:java->JAR文件: 把一些不必要的文件勾选掉,如下图: 到了这一步,己经基本完成,浏览选择jar 文件导出路径即可。 导出完成后,我们就可以像使用其他JAR文件一样使用我们自己的自定义控件包了。下面给出一个小DEMO介绍如何使用这个JAR包。 步骤一: 新建文件夹lib,将jar 包放入。 步骤二: 关联JAR包,如下图: 步骤三,使用JAR包: 如下代码: <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android" xmlns:gif ="http://schemas.android.com/apk/res/com.terry.jarTest" android:id ="@+id/layout" android:orientation ="vertical" android:layout_width ="fill_parent" android:layout_height ="fill_parent" > < Button android:text ="停止" android:id ="@+id/Button01" android:layout_width ="wrap_content" android:layout_height ="wrap_content" ></ Button > < Button android:text ="开始" android:id ="@+id/Button02" android:layout_width ="wrap_content" android:layout_height ="wrap_content" ></ Button > < com.terry.gif.TypegifView android:layout_width ="fill_parent" android:id ="@+id/gifView1" gif:stop ="true" android:layout_height ="wrap_content" gif:delay ="1" ></ com.terry.gif.TypegifView > </ LinearLayout > 有一个不好的就是如果你项目中存在使用属性,必须也把属性一起复制到你要使用的项目中,要不然会识别不了。 本文转自 terry_龙 51CTO博客,原文链接:http://blog.51cto.com/terryblog/564558 ,如需转载请自行联系原作者

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册