I have successfully extracted the class files from an android emulator and replaced them into my android.jar file residing inside my \\platforms\\android-21\\android.jar and
dependencies {
provided files('systemlibs/android.jar')
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.4.0'
compile 'com.android.support:design:23.4.0'
}
Here's an alternative that does not require building the entire android SDK JAR or modifying your android SDK at all. If you have a JAR with some of the @hide
android SDK classes and/or methods in it you can add this hunk to your standard android project build.gradle file:
allprojects {
gradle.projectsEvaluated {
tasks.withType(JavaCompile) {
options.compilerArgs << "-Xbootclasspath/p:<FULL_PATH_TO_CUSTOM_ANDROID_API>.jar"
}
}
}
This will prepend your JAR to the bootclasspath which has the android.jar from the selected SDK in it. The java compiler will prefer to use classes in the prepended bootclasspath when compiling thus overriding the android.jar from the SDK.
See http://docs.oracle.com/javase/7/docs/technotes/tools/windows/javac.html
The drawback is that IntelliJ/Android Studio doesn't seem to understand this at the higher level so methods appear red in the editor but the build works fine. You can actually combine this with the provided
solution to at least get additional classes to show up as valid in the editor but exposed @hide
methods are still unknown to the IDE.
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'], 'exclude': ['<PATH_TO_CUSTOM_ANDROID_API>.jar'])
provided files('libs/<PATH_TO_CUSTOM_ANDROID_API>.jar')
}
I haven't played with it long enough to find how to reference the relative path in the compilerArgs but it should be easy enough.
File > Invalidate Caches and Restart > Invalidate and Restart
I knew something was weird. I knew the methods were there in the jar, I knew it was something with Studio. Here it caches the methods :)
Doing what I posted in the first line fixes it! So happy! Hope others benefit!
For a while I used the Xbootclasspath
trick but Android Studio doesn't really like it (highlighting was broken) and it doesn't work at all if you are trying to access hidden APIs that have changed across SDK levels (these APIs are hidden so they are unstable).
Eventually I found a solution that involves creating pure java libraries that are used in my Android project. I call this trick "platform-unhidden" aka P-U. The main annoyance is there is more boilerplate code and the library structure is complex.
If you need to support two levels (lets say API level 25 and lower, and API level 26 and higher) then you will need 5 java libraries. The "stubs" libraries contain Android platform code copied from AOSP but with everything stubbed out like the android.jar in the SDK usually is but with the hidden methods you want exposed.
The "wrap" libraries use the stub libraries and expose methods/classes with unique names. The P-U library selects the appropriate wrapping library based on the detected SDK level with Build.SDK_INT
.
Visually this looks like:
+---------+ +----------+
+--api-->+ wrap-25 +--compile-only-->+ stubs-25 +--+
+-------+ | +---------+ +----------+ | +---------+
| +---+ +-->+ |
| P-U | | android |
| +---+ +-->+ |
+-------+ | +---------+ +----------+ | +---------+
+--api-->+ wrap-26 +--compile-only-->+ stubs-26 +--+
+---------+ +----------+
Setting up the java libraries required creating a shared gradle file like so:
platform-shared.gradle:
// All the platform-* projects apply this gradle file
apply plugin: 'java'
apply plugin: 'java-library'
sourceCompatibility = 1.8
apply from: file("${androidBuild}/versions.gradle")
// ANDROID_HOME can override ANDROID_SDK_ROOT
// see https://developer.android.com/studio/command-line/variables
def androidHome = System.getenv('ANDROID_HOME')
def androidSdkRoot = System.getenv('ANDROID_SDK_ROOT')
if (androidHome?.trim() && new File(androidHome).canRead()) {
androidSdkRoot = androidHome
}
if (!androidSdkRoot?.trim() || !new File(androidSdkRoot).canRead()) {
def props = new Properties()
def file = new File("${rootDir}/local.properties")
if (file.exists()) {
file.withInputStream { props.load(it) }
if (props.getProperty("sdk.dir")) {
androidSdkRoot = props.getProperty("sdk.dir")
}
}
else {
throw new GradleException('Android SDK root not usable')
}
}
repositories {
// Need an Android SDK jar to compile against even though this is a pure java project
repositories {
flatDir {
dirs "${androidSdkRoot}/platforms/android-${COMPILE_SDK_VERSION}"
}
}
mavenCentral()
}
// Make CI happy by implementing these tasks
task assembleRelease {
dependsOn 'jar'
}
task assembleDebug {
dependsOn 'jar'
}
task assembleAndroidTest {
// Ignored
}
task lintRelease {
// Ignored
}
task lintDebug {
// Ignored
}
task testReleaseUnitTest {
// Ignored
}
task testDebugUnitTest {
// Ignored
}
Then you'll need gradle files for each of the libraries like so (not showing 26, it looks the same as 25). Add more levels as needed.
stubs-25.gradle:
apply from: file("../platform-shared.gradle")
dependencies {
api name: "android"
}
wrap-25.gradle:
apply from: file("../platform-shared.gradle")
dependencies {
compileOnly project(':platform-stubs-25')
}
platform-unhidden.gradle:
apply from: file("../platform-shared.gradle")
dependencies {
compileOnly project(':platform-stubs-25')
compileOnly name: "android"
api project(':platform-wrap-25')
api project(':platform-wrap-26')
}
You don`t have to replace \platforms\android-21\android.jar.
It destroy the SDK and you may not submit those changes onto git.
Below way may be more easier:
See How do I build the Android SDK with hidden and internal APIs available? and get framework_all.jar
Put framework_all.jar in system_libraries besides app in your projects.
change app/build.gradle like
dependencies {
compile fileTree(dir: 'libs', include: ['.jar'])
provided fileTree(dir: '../system_libraries', include: ['.jar'])
}
AS will gives a tips like "Gradle files have changed since .... Sync Now", click Sync Now.
Goto Project view and you will see platform_all.jar in External Libraries.
Each time you changed any .jar under system_libraries, you need to do some change in app/build.gradle and re-sync it.