前言
在寫 Android 應用時,當你新建一個 Activity
,Service
,ContentProvider
,Broadcast
(著名的四大組件)時,你是不是經常性的寫完就直接運行,然后程序就崩潰了,通過查看日志,你才發覺原來忘記在 AndroidManifest.xml
中進行注冊。甚至于,當我們代碼運行需要某些權限時,你也要跳轉到 AndroidManifest.xml
中進行權限聲明,然后代碼才能正確運行,而這些操作,我們往往都會忘記。
筆者個人認為,導致我們經常性忘記在 AndroidManifest.xml
中進行注冊的一個主要的原因就在于編寫代碼和進行注冊是發生在兩個文件中的,也就是我們需要進行文件切換,這種切換操作對于我們正在編寫程序的思路起到了切斷作用,所以我們往往在專注于寫代碼的時候,就會忘記進行組件注冊。
基于以上原因,筆者開發了一套開源框架:InjectManifest,這套框架致力于解決上面我們提到的編寫代碼和進行組件注冊需要進行文件切換的不便,框架提供注解進行注冊,讓我們在編寫相關需要進行注冊的代碼的同時,可以很方便地直接使用注解進行相關內容的注冊,再也無需切換到 AndroidManifest.xml
去做這些事。
優點
- 采用編譯期注解與自定義
Gradle
插件完成注冊過程,對程序運行無任何影響; - 支持注解和原生
AndroidManifest.xml
協同工作,最終會將兩者結合起來,保留不一致的元素,相同的元素只保留一份; - 對支持的標簽的所有屬性配置均支持;
缺點
- 在每次使用注解注冊后,需要
rebuild
一下才能生成新的AndroidManifest.xml
文件,如果采用注解注冊后,直接運行程序,可以看到新的AndroidManifest.xml
也生成了,但是程序此時使用的是舊的(也就是原生的)AndroidManifest.xml
配置。這個地方的原因我猜測應該是processDebugManifest/processReleaseManifest
運行在 新的AndroidManifest.xml
生成前,所以這個問題我猜測是不是可以有什么辦法把processDebugManifest/processReleaseManifest
放到文件生成后再執行····這個地方我暫時也沒找出什么辦法進行解決,如果有誰知道怎么解決這個問題的,麻煩跟我講下,謝謝。 - 目前只支持
manifest
,application
,activity
,service
,receiver
,provider
,uses-permission
標簽的解析,對于其他標簽,無法進行融合,在新生成的AndroidManifest.xml
中這些元素不會被保留;
示例
-
manifest
標簽注冊:
@InjectManifest(
pkName = "com.yn.injectmanifest",
installLocation = INTERNAL_ONLY,
sharedUserId = "android.uid.system"
)
public class App extends Application {
}
rebuild
一下,你就可以看到 AndroidManifest.xml
變成這樣:
manifest
標簽的其他屬性 @InjectManifest
均支持。
-
application
標簽注冊:
@InjectApp(
name = ".App", //you can full class name or just simply using a .classSimpleName
label = "i am app",
debuggable = TRUE,
metaData = @InjectMetaData(name = "app/meta-data")
)
public class App extends Application {
}
rebuild
一下,你就可以看到 AndroidManifest.xml
變成這樣:
application
標簽的其他屬性 @InjectApp
均支持。
-
activity
標簽注冊:
@InjectActivity(
name = ".MainActivity",
intentFilter = @InjectIntentFilter(
action = {"android.intent.action.MAIN", "android.intent.action_whyn_test"},
category = {"android.intent.category.LAUNCHER", "android.intent.category.whyn"},
data = @InjectData(mimeType = "image/*")
))
public class MainActivity extends AppCompatActivity {}
rebuild
一下,你就可以看到 AndroidManifest.xml
變成這樣:
activity
標簽的其他屬性 @InjectActivity
均支持。
-
service
標簽注冊:
@InjectService(
enabled = TRUE,
name = ".FirstService",
label = "Inject Service test",
intentFilter = @InjectIntentFilter(
action = "com.yn.action.FirstService",
category = "com.yn.category.serviceTest",
data = @InjectData(
host = "sdcard",
mimeType = "video/mp4",
path = "/sdcard/1.MP4",
pathPattern = ".*\\.mp4",
pathPrefix = "/sdcard/",
port = "-2",
scheme = "file"
)
),
metaData = @InjectMetaData(name = "com.yn.meta-data.service")
)
public class FirstService extends Service {···}
rebuild
一下,你就可以看到 AndroidManifest.xml
變成這樣:
service
標簽的其他屬性 @InjectService
均支持。
-
receiver
標簽注冊:
@InjectReceiver(
name = ".FirstReceiver",
label = "hi i am first receiver",
process = ".remote",
enabled = TRUE
)
public class FirstReceiver extends BroadcastReceiver {···}
rebuild
一下,你就可以看到 AndroidManifest.xml
變成這樣:
receiver
標簽的其他屬性 @InjectReceiver
均支持。
-
provider
標簽注冊:
@InjectProvider(
authorities = "com.yn.authorities",
name = ".FirstProvider",
label = "I am ContentProvider"
)
public class FirstProvider extends android.content.ContentProvider {···}
provider
標簽的其他屬性 @InjectProvider
均支持。
-
uses-permission
標簽注冊:
@InjectUsesPermission({
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.BLUETOOTH,
Manifest.permission.ACCESS_WIFI_STATE,
})
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
rebuild
一下,你就可以看到 AndroidManifest.xml
變成這樣:
uses-permission
標簽的其他屬性 @InjectUsesPermission
均支持。
目前暫時就只支持以上所講的標簽,后續我有時間就會不定時更新下。
下載
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.whyn:injectmanifest-plugin:1.1.0'
}
}
然后,apply
到你的 module
:
apply plugin: 'com.android.application'
apply plugin: 'com.whyn.plugin.injectmanifest'
注意事項:
-
InjectManifest 默認會將生成的
AndroidManifest.xml
替換掉原來的AndroidManifest.xml
,但在替換前,會將原來的AndroidManifest.xml
保存為AndroidManifest_old.xml
,所以,對于暫時未支持的xml
標簽,新生成的文件無法保留,那么你就可以從AndroidManifest_old.xml
中找回。
如果想更換上面的默認行為,那就需要在module
的build.gradle
中增加下面的擴展屬性:
manifestConfig {
//the defautl AndroidManifest.xml path
originManifestPath android.sourceSets.main.manifest.srcFile.absolutePath
//the AndroidManifest.xml path generated by annotation processor
genManifestPath "$project.buildDir/generated/source/apt/debug/Collections.xml"
//to save the original AndroidManifest: true -- save,false -- not save
saveOrigin false
}
- 如果你在開發過程中,要為注解處理器傳遞參數,請記住加上
+
號,代表追加,否則,會導致gradle
插件里面默認設置的注解參數失效,這樣就不會合并原生AndroidManifest.xml
了。
android {
defaultConfig{
···
···
javaCompileOptions {
annotationProcessorOptions {
arguments += [xxxxx: 'yyyyy']
}
}
}
}
- 如果你對
AndroidManifest.xml
的默認路徑進行了修改,如果你還希望能合并AndroidManifest.xml
,那你需要手動傳遞最新路徑給annotation processor
:
android {
defaultConfig{
···
···
javaCompileOptions {
annotationProcessorOptions {
arguments = [AndroidManifestPath: android.sourceSets.main.manifest.srcFile.absolutePath]
}
}
}
}
附錄
源碼傳送門:InjectManifest
AndroidManifest.xml
應用清單官方文檔: here