java.io.IOException:无法创建目录:/storage/emulated/0/tokens
java.io.IOException: unable to create directory: /storage/emulated/0/tokens
我正在尝试将我的 Android 应用程序连接到 Google 日历。我遵循了 quick start 教程,已请求用户允许写入外部存储,但我无法在不抛出 java.io.IOException 的情况下将代码获取到 运行。
我的代码如下所示:
GoogleCalendarModule.kt
class GoogleCalendarModule {
var APPLICATION_NAME: String = "Conglobo"
var JSON_FACTORY: JacksonFactory = JacksonFactory.getDefaultInstance()
var TOKENS_DIRECTORY_PATH: String = "./tokens"
var SCOPES: List<String> = Collections.singletonList(CalendarScopes.CALENDAR_READONLY)
private var CREDENTIALS_FILE_PATH: String = "/credentials.json"
fun getCredentials(HTTP_TRANSPORT: NetHttpTransport): com.google.api.client.auth.oauth2.Credential? {
val inputStream: InputStream = this.javaClass.getResourceAsStream(CREDENTIALS_FILE_PATH)
?: throw FileNotFoundException("Resource Not found: $CREDENTIALS_FILE_PATH")
val clientSecrets: GoogleClientSecrets = GoogleClientSecrets.load(JSON_FACTORY, InputStreamReader(inputStream))
val tokenFolder = File(getExternalStorageDirectory(), File.separator.toString() + TOKENS_DIRECTORY_PATH)
if (!tokenFolder.exists()) {
tokenFolder.mkdirs()
}
val flow: GoogleAuthorizationCodeFlow = GoogleAuthorizationCodeFlow.Builder(
HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
.setDataStoreFactory(FileDataStoreFactory(tokenFolder))
.setAccessType("offline")
.build();
val receiver: LocalServerReceiver = LocalServerReceiver.Builder().setPort(8888).build()
return AuthorizationCodeInstalledApp(flow, receiver).authorize("user")
}
fun doSomething() {
val HTTP_TRANSPORT = NetHttpTransport()
val service: Calendar = Calendar.Builder(HTTP_TRANSPORT, JSON_FACTORY, getCredentials(HTTP_TRANSPORT))
.setApplicationName(APPLICATION_NAME)
.build()
val now = DateTime(System.currentTimeMillis())
val events: Events = service.events().list("primary")
.setMaxResults(10)
.setTimeMin(now)
.setOrderBy("startTime")
.setSingleEvents(true)
.execute()
val items: List<Event> = events.items
if (items.isEmpty()) {
println("No events")
} else {
println("Upcoming events")
for(event: Event in items) {
var start: DateTime = event.start.dateTime
if(start == null) {
start = event.start.date
}
print(event.summary)
}
}
}
}
然后我检查 MainActivity 中的当前权限
MainActivity.kt
class MainActivity: AppCompatActivity(), ITeamFragmentDelegate {
@Inject
lateinit var teamInfoModule: TeamInfoModule;
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED) {
print("Permission is granted");
} else {
print("Permission is revoked");
ActivityCompat.requestPermissions(this, Array<String>(1) { Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
}
}
GoogleCalendarModule().doSomething()
setContentView(R.layout.activity_main)
DaggerServiceModuleComponent.create().inject(this)
val teamArrayList: ArrayList<Team> = this.teamInfoModule.getAllTeamData()
for(team: Team in teamArrayList) {
val bundle = Bundle()
val teamFragment = TeamFragment()
bundle.putParcelable("teamData", team)
teamFragment.arguments = bundle
supportFragmentManager.beginTransaction()
.add(R.id.root_container, teamFragment)
.commitAllowingStateLoss()
}
}
override fun onTeamClicked(fragment: TeamFragment, team: Team) {
val intent = Intent(this, ViewTeamBacklogActivity::class.java)
intent.putExtra("teamId", team.id)
startActivity(intent)
}
}
我什至拥有 AndroidManifest.xml
中声明的权限
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.bluelightlite">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".ViewTeamBacklogActivity">
</activity>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
我搜索了 S.O,这似乎是一个常见问题,但我发现的解决方案中有 none 行之有效。有人可以帮忙吗?
不知道有没有用,你可以试试
设置你的 targetSdkVersion 小于 23 ?
找到问题了。
从 Android SDK 23 开始(我相信)。将 GoogleCalendarModule.kt
重构为:
package com.example.bluelightlite.modules
import android.content.Context
import com.example.bluelightlite.constants.APPLICATION_NAME
import com.example.bluelightlite.constants.JSON_FACTORY
import com.google.api.client.http.javanet.NetHttpTransport
import com.google.api.client.util.DateTime
import com.google.api.services.calendar.Calendar
import com.google.api.services.calendar.model.Event
import com.google.api.services.calendar.model.Events
class GoogleCalendarsServiceModule constructor(context: Context) {
private val googleCredentialsUtilityModule = GoogleCredentialsUtilityModule(context)
fun getEvents(): List<Event> {
val httpTransport = NetHttpTransport()
val service: Calendar = Calendar.Builder(httpTransport, JSON_FACTORY, this.googleCredentialsUtilityModule.getCredentials(httpTransport))
.setApplicationName(APPLICATION_NAME)
.build()
val now = DateTime(System.currentTimeMillis())
val events: Events = service.events().list("primary")
.setMaxResults(10)
.setTimeMin(now)
.setOrderBy("startTime")
.setSingleEvents(true)
.execute()
return events.items;
}
}
并创建一个如下所示的 GoogleCredentialsUtilityModule.kt
文件:
package com.example.bluelightlite.modules
import android.content.Context
import com.example.bluelightlite.constants.CREDENTIALS_FILE_PATH
import com.example.bluelightlite.constants.JSON_FACTORY
import com.example.bluelightlite.constants.SCOPES
import com.example.bluelightlite.constants.TOKENS_DIRECTORY_PATH
import com.google.api.client.auth.oauth2.Credential
import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp
import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets
import com.google.api.client.http.javanet.NetHttpTransport
import com.google.api.client.util.store.FileDataStoreFactory
import java.io.File
import java.io.FileNotFoundException
import java.io.InputStream
import java.io.InputStreamReader
class GoogleCredentialsUtilityModule constructor(private val context: Context) {
/**
* Creates an authorized Credential object.
* @param HTTP_TRANSPORT The network HTTP Transport.
* @return An authorized Credential object.
* @throws java.io.IOException If the credentials.json file cannot be found.
*/
fun getCredentials(HTTP_TRANSPORT: NetHttpTransport): Credential {
val inputStream: InputStream = getCredentialsAsInputStream()
val clientSecrets: GoogleClientSecrets = GoogleClientSecrets.load(JSON_FACTORY, InputStreamReader(inputStream))
createTokenFolderIfMissing()
val authorisationFlow: GoogleAuthorizationCodeFlow = getAuthorisationFlow(HTTP_TRANSPORT, clientSecrets)
val receiver: LocalServerReceiver = LocalServerReceiver.Builder().setPort(8888).build()
return AuthorizationCodeInstalledApp(authorisationFlow, receiver).authorize("freeman.marc880@gmail.com")
}
private fun getCredentialsAsInputStream(): InputStream {
return this.javaClass.getResourceAsStream(CREDENTIALS_FILE_PATH)
?: throw FileNotFoundException("Resource Not found: $CREDENTIALS_FILE_PATH")
}
private fun createTokenFolderIfMissing() {
val tokenFolder = getTokenFolder()
if (!tokenFolder.exists()) {
tokenFolder.mkdir()
}
}
private fun getTokenFolder(): File {
return File(this.context.getExternalFilesDir("")?.absolutePath + TOKENS_DIRECTORY_PATH)
}
private fun getAuthorisationFlow(HTTP_TRANSPORT: NetHttpTransport, clientSecrets: GoogleClientSecrets): GoogleAuthorizationCodeFlow {
return GoogleAuthorizationCodeFlow.Builder(
HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
.setDataStoreFactory(FileDataStoreFactory(getTokenFolder()))
.setAccessType("offline")
.build()
}
}
让它发挥作用。
我所做的是将上下文向下传递到 Google 凭据模块,并要求它从这行代码的当前上下文中获取外部存储目录:
private fun getTokenFolder(): File {
return File(this.context.getExternalFilesDir("")?.absolutePath + TOKENS_DIRECTORY_PATH)
}
添加 AndroidManifest.xml
进入Android设备后:设置 -> 应用程序 -> 高级 -> 权限管理器 -> 文件和媒体 -> YourApp -> 允许管理所有文件
再次测试您的代码
我正在尝试将我的 Android 应用程序连接到 Google 日历。我遵循了 quick start 教程,已请求用户允许写入外部存储,但我无法在不抛出 java.io.IOException 的情况下将代码获取到 运行。
我的代码如下所示:
GoogleCalendarModule.kt
class GoogleCalendarModule {
var APPLICATION_NAME: String = "Conglobo"
var JSON_FACTORY: JacksonFactory = JacksonFactory.getDefaultInstance()
var TOKENS_DIRECTORY_PATH: String = "./tokens"
var SCOPES: List<String> = Collections.singletonList(CalendarScopes.CALENDAR_READONLY)
private var CREDENTIALS_FILE_PATH: String = "/credentials.json"
fun getCredentials(HTTP_TRANSPORT: NetHttpTransport): com.google.api.client.auth.oauth2.Credential? {
val inputStream: InputStream = this.javaClass.getResourceAsStream(CREDENTIALS_FILE_PATH)
?: throw FileNotFoundException("Resource Not found: $CREDENTIALS_FILE_PATH")
val clientSecrets: GoogleClientSecrets = GoogleClientSecrets.load(JSON_FACTORY, InputStreamReader(inputStream))
val tokenFolder = File(getExternalStorageDirectory(), File.separator.toString() + TOKENS_DIRECTORY_PATH)
if (!tokenFolder.exists()) {
tokenFolder.mkdirs()
}
val flow: GoogleAuthorizationCodeFlow = GoogleAuthorizationCodeFlow.Builder(
HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
.setDataStoreFactory(FileDataStoreFactory(tokenFolder))
.setAccessType("offline")
.build();
val receiver: LocalServerReceiver = LocalServerReceiver.Builder().setPort(8888).build()
return AuthorizationCodeInstalledApp(flow, receiver).authorize("user")
}
fun doSomething() {
val HTTP_TRANSPORT = NetHttpTransport()
val service: Calendar = Calendar.Builder(HTTP_TRANSPORT, JSON_FACTORY, getCredentials(HTTP_TRANSPORT))
.setApplicationName(APPLICATION_NAME)
.build()
val now = DateTime(System.currentTimeMillis())
val events: Events = service.events().list("primary")
.setMaxResults(10)
.setTimeMin(now)
.setOrderBy("startTime")
.setSingleEvents(true)
.execute()
val items: List<Event> = events.items
if (items.isEmpty()) {
println("No events")
} else {
println("Upcoming events")
for(event: Event in items) {
var start: DateTime = event.start.dateTime
if(start == null) {
start = event.start.date
}
print(event.summary)
}
}
}
}
然后我检查 MainActivity 中的当前权限
MainActivity.kt
class MainActivity: AppCompatActivity(), ITeamFragmentDelegate {
@Inject
lateinit var teamInfoModule: TeamInfoModule;
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED) {
print("Permission is granted");
} else {
print("Permission is revoked");
ActivityCompat.requestPermissions(this, Array<String>(1) { Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
}
}
GoogleCalendarModule().doSomething()
setContentView(R.layout.activity_main)
DaggerServiceModuleComponent.create().inject(this)
val teamArrayList: ArrayList<Team> = this.teamInfoModule.getAllTeamData()
for(team: Team in teamArrayList) {
val bundle = Bundle()
val teamFragment = TeamFragment()
bundle.putParcelable("teamData", team)
teamFragment.arguments = bundle
supportFragmentManager.beginTransaction()
.add(R.id.root_container, teamFragment)
.commitAllowingStateLoss()
}
}
override fun onTeamClicked(fragment: TeamFragment, team: Team) {
val intent = Intent(this, ViewTeamBacklogActivity::class.java)
intent.putExtra("teamId", team.id)
startActivity(intent)
}
}
我什至拥有 AndroidManifest.xml
中声明的权限<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.bluelightlite">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".ViewTeamBacklogActivity">
</activity>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
我搜索了 S.O,这似乎是一个常见问题,但我发现的解决方案中有 none 行之有效。有人可以帮忙吗?
不知道有没有用,你可以试试 设置你的 targetSdkVersion 小于 23 ?
找到问题了。
从 Android SDK 23 开始(我相信)。将 GoogleCalendarModule.kt
重构为:
package com.example.bluelightlite.modules
import android.content.Context
import com.example.bluelightlite.constants.APPLICATION_NAME
import com.example.bluelightlite.constants.JSON_FACTORY
import com.google.api.client.http.javanet.NetHttpTransport
import com.google.api.client.util.DateTime
import com.google.api.services.calendar.Calendar
import com.google.api.services.calendar.model.Event
import com.google.api.services.calendar.model.Events
class GoogleCalendarsServiceModule constructor(context: Context) {
private val googleCredentialsUtilityModule = GoogleCredentialsUtilityModule(context)
fun getEvents(): List<Event> {
val httpTransport = NetHttpTransport()
val service: Calendar = Calendar.Builder(httpTransport, JSON_FACTORY, this.googleCredentialsUtilityModule.getCredentials(httpTransport))
.setApplicationName(APPLICATION_NAME)
.build()
val now = DateTime(System.currentTimeMillis())
val events: Events = service.events().list("primary")
.setMaxResults(10)
.setTimeMin(now)
.setOrderBy("startTime")
.setSingleEvents(true)
.execute()
return events.items;
}
}
并创建一个如下所示的 GoogleCredentialsUtilityModule.kt
文件:
package com.example.bluelightlite.modules
import android.content.Context
import com.example.bluelightlite.constants.CREDENTIALS_FILE_PATH
import com.example.bluelightlite.constants.JSON_FACTORY
import com.example.bluelightlite.constants.SCOPES
import com.example.bluelightlite.constants.TOKENS_DIRECTORY_PATH
import com.google.api.client.auth.oauth2.Credential
import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp
import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets
import com.google.api.client.http.javanet.NetHttpTransport
import com.google.api.client.util.store.FileDataStoreFactory
import java.io.File
import java.io.FileNotFoundException
import java.io.InputStream
import java.io.InputStreamReader
class GoogleCredentialsUtilityModule constructor(private val context: Context) {
/**
* Creates an authorized Credential object.
* @param HTTP_TRANSPORT The network HTTP Transport.
* @return An authorized Credential object.
* @throws java.io.IOException If the credentials.json file cannot be found.
*/
fun getCredentials(HTTP_TRANSPORT: NetHttpTransport): Credential {
val inputStream: InputStream = getCredentialsAsInputStream()
val clientSecrets: GoogleClientSecrets = GoogleClientSecrets.load(JSON_FACTORY, InputStreamReader(inputStream))
createTokenFolderIfMissing()
val authorisationFlow: GoogleAuthorizationCodeFlow = getAuthorisationFlow(HTTP_TRANSPORT, clientSecrets)
val receiver: LocalServerReceiver = LocalServerReceiver.Builder().setPort(8888).build()
return AuthorizationCodeInstalledApp(authorisationFlow, receiver).authorize("freeman.marc880@gmail.com")
}
private fun getCredentialsAsInputStream(): InputStream {
return this.javaClass.getResourceAsStream(CREDENTIALS_FILE_PATH)
?: throw FileNotFoundException("Resource Not found: $CREDENTIALS_FILE_PATH")
}
private fun createTokenFolderIfMissing() {
val tokenFolder = getTokenFolder()
if (!tokenFolder.exists()) {
tokenFolder.mkdir()
}
}
private fun getTokenFolder(): File {
return File(this.context.getExternalFilesDir("")?.absolutePath + TOKENS_DIRECTORY_PATH)
}
private fun getAuthorisationFlow(HTTP_TRANSPORT: NetHttpTransport, clientSecrets: GoogleClientSecrets): GoogleAuthorizationCodeFlow {
return GoogleAuthorizationCodeFlow.Builder(
HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
.setDataStoreFactory(FileDataStoreFactory(getTokenFolder()))
.setAccessType("offline")
.build()
}
}
让它发挥作用。
我所做的是将上下文向下传递到 Google 凭据模块,并要求它从这行代码的当前上下文中获取外部存储目录:
private fun getTokenFolder(): File {
return File(this.context.getExternalFilesDir("")?.absolutePath + TOKENS_DIRECTORY_PATH)
}
添加 AndroidManifest.xml
进入Android设备后:设置 -> 应用程序 -> 高级 -> 权限管理器 -> 文件和媒体 -> YourApp -> 允许管理所有文件
再次测试您的代码