package用于指定自动生成的文件的包名,而applicationId用于指定应用的进程名称,即应用的唯一标识符,package可以和applicationId不一样,但一般情况都是一样的。
当我们要改变一个应用的进程名称时,直接修改applicationId即可,其他任何都方都不需要改动。而如果你改package的话,就需要小心了,因为很有可能会出问题,示例如下:
1、我们创建一个全新的Android项目,创建时指定包名为:a.a.aaa,然后再创建一个工具类,如下:
import a.a.aaa.R
import android.content.Context
import android.widget.Toast
object Utils {
fun showText(context: Context) {
Toast.makeText(context, R.string.app_name, Toast.LENGTH_SHORT).show()
}
fun reflect(): String {
val appNameField = Class.forName("a.a.aaa.R.string").getField("app_name")
return appNameField.get(null) as String
}
}
这里我们使用到了反射,通过反射获取一个R.string类中的app_name变量。
接下来,我们直接手动修改AndroidManifest.xml中的package的值为:a.a.bbb,然后就会发现有3个地方报错了,如下:
这是因为,.MainActivity前面的那个点,代表以package中声明的包名作为前缀的,现在我们改在了a.a.bbb,所以就报错了,因为a.a.bbb.MainActivity并不存在,所以这里只能使用全包名形式了:a.a.aaa.MainActivity
第二个报错,MainActivity:
前面我们说了package中的包名用于指定自动生成的类所在的包,没修改之前,a.a.aaa.R.layout.activity_main和a.a.aaa.MainActivity在同一个包下,所以不用导包,修改之后,自动生成的activity_main为a.a.bbb.R.layout.activity_main和a.a.aaa.MainActivity不在同一个包下了,所以需要导包:import a.a.bbb.R
第三个报错,Utils:
容易理解,R文件已经不在a.a.aaa包下,所以这里会报错,而且这里的反射我们也需要手动修改包名,如果是一个真实的项目,类很多,那要这样手动改的话,真是要累死人的,所以要使用重构的方式,我们把代码还原一下,然后在aaa包名上点击一下,然后按下Shift + F6,此时会弹出一个警告对话框,如下:
这个汉化的内容我感觉不太对,于是关闭汉化插件再看一下对话框,如下:
根据我试验的结果,此对话框提示a.a.aaa目录在3个地方都有,一个是我们的android主项目src\main\java\a\a\aaa,一个是对应的Android测试目录src\androidTest\java\a\a\aaa,一个是对应的java测试目录src\java\a\a\aaa,对话框问我们是想要重命名整个包还是只重命名src\main\java\a\a\aaa这一个目录?重构整个包名会重构到上面说的3个目录。
我们先使用“重命名目录”按钮,出现如下对话框:
我们输入bbb,然后点击“预览”按钮,此时会在AndroidStudio的底部出现“重构预览”即在发生重构前,先让你知道重构会改变哪些地方,如下:
可以看到,有两个地方会被修改,第一个是告诉你MainActivity.kt文件会被移动到a.a.bbb目录,第二个是告诉你MainActivity.kt中的package a.a.aaa也会改成package a.a.bbb,这也很容易理解,因为MainActivity已经放到a.a.bbb目录下了,当然package也要改成对应的包。感觉上因为就真的只有这两个地方会被修改,但是ide会做的更多,我们点击“执行重构”,然后提交代码(我提前把代码放到SVN上了),然后就能看到有哪些改动了,如下:
我们发现,activity_main.xml和AndroidManifest.xml中也有改动,如下:
activity_main.xml:
AndroidManifest.xml:
还挺智能的,它知道MainActivity的位置变了,不在相对于a.a.aaa的位置了,所以需要使用全包名的形式。
很明显,这种方式不是我们想要的,它并没有为我们重命名清单文件中的package属性。我们把代码还原,然后再执行重构,这次我们选择“重命名包”按钮,然后出现对话框,如下:
我们把aaa改成bbb,然后点击预览按钮,如下:
双击对应的条目即可快速跳转到相应的位置,比如双击最前面的aaa,我们发现它被定位到主项目的aaa目录,如下:
这里工具有点问题,有两个aaa都指向了同一个地方,另外两个aaa分别指向对应的Android测试目录和Java测试目录,如下:
再往下,这3个目录中对应了3个类,所以类里面的package语句中的包肯定也是要修改的,再往下是清单文件中package的值也会被修改,最后是Utils中使用到了import a.a.aaa.R也会被修改,因为package的值修改了,则自动生成的R对应的包肯定也是需要修改的,但是我们代码中反射使用到了a.a.aaa啊,它不给我重构一下吗?我们取消这次的重构预览,重新执行重构,如下:
这次我们勾选了“在注释和字符中搜索”,再次预览,结果如下:
这还不错啊,找到了我们反射的字符串了,但是同时问题也来了,还会找到了更多我们不想要重构的地方,如下:
这里的项目名称和应用的进程名,我们并不想改,甚至在jdk和sdk中的一些注释或字符串它也改搜索出来了,如下:
对于搜索到jdk和sdk的问题还好解决,在重构对话框中,我们可以在“范围”的地方选择“项目文件”即可,但是它仍然会重构到settings.gradle中的项目名,还有build.gradle中的applicationId,这很简单,我们可以重构完成之后,提交代码,在提交修改对话框中找到这些重构错误的地方还原好可。当然,如果你发现重构错误的地方很多,而你又没有在代码中使用反射,那你可以在重构的时候不要勾选“在注释和字符串中搜索”,或者你有使用反射,但是很少,则可以勾上“在注释和字符串中搜索”然后预览,预览找到使用反射的地方,记下来,取消预览,再次重构时不要勾“在注释和字符串中搜索”,直接重构,然后手动去改反射的那个地方即可,这样的话工作量也非常少。
虽然说有重构预览,但是好习惯是在重构完成之后马上提交代码,以检查哪些地方发生了改动,改动是否是正确的改动,通过提交代码,我发现这个重构有一个小遗憾,就是清单文件中的4大组的声明被重构为全包名形式了,如下:
对于强迫证来说也不难解决,按Ctrl + R,执行替换操作即可,把a.a.bbb. 替换为 . 即可。当然要注意,最好在替换时不要点Replace all按钮,而是点Replace按钮,一个个的替换,预防有些地方就是需要使用全包名的情况。另一个更简单的方式是直接还原这个文件,然后手动修改package的值即可。
到这里,我们完成了包名的重构,但是只是重构了包名的最后部分,比如,我要把a.a.aaa重构为c.d.eee呢?也是一样的原理,只能一部分一部分的进行重构,也就是要重复进行3次重构操作,分别重构a为c,重构a为d,重构aaa为eee,至于先重构那一个部分后重构哪一个部分没有差别。在AndroidStudio上按如下操作可显示包的每一个部分,在需要的部分上单击选择,然后再按重构快捷键进行重构即可,显示包的各个部分的操作如下:
这里,最主要的是拼合包、压缩空的中间包,这两项不要勾,这样就能显示包的每一个部分了,然后就可以单击选择需要重构的部分了,比如选择a.a.aaa中最前面的a,如下:
文章写到最后,运行了一下代码,发现有个地方有错误,即R.string,这个string类,它的完整类名为:a.a.bbb.R$string,而且它的app_name变量是一个int类型的资源引用,修正如下:
fun reflect(context: Context): String {
val appNameField = Class.forName("a.a.bbb.R${'$'}string").getField("app_name")
return context.getString(appNameField.get(null) as Int)
}
注:kotlin中的$符号是一个特殊的符号,它不能使用反斜杠来进行转义,可以这样转义:${’$’}