Android Q (API:29)以上でgetExternalStorageDirectoryに保存ができない

Googleさんのポリシーの変更によりファイルの書き込みや読み込みの権限が厳しくなった

一応暫定処理でAndroidManifest.xmlに

android:requestLegacyExternalStorage="true"

を追加することで使うことはできたのだが、とうとうAndroid API 30以上では完全に書き込みが出来なくなってしまった。

もしAndroid API 30以上で今までのコードのままで使う場合はAndroidManifest.xmlに

<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

を追加し、全てのファイルのアクセス権限をもらえば、getExternalStorageDirectoryに書き込める。でもこれだとGoogle Playにアップロードする場合MANAGE_EXTERNAL_STORAGEの権限は基本的にもらえないのでAPKの更新が出来なくってしまう(申請したが弾かれた)

非常に困った。

とりあえず今のところ、MediaStore経由で保存するしかなさそう

ということでJavaコードで対応させてみた

@RequiresApi(Build.VERSION_CODES.Q)
private String saveFileUsingMediaStore(Context context , String url , String fileName, String mimeType) {
    //https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
    ContentValues contentValues = new ContentValues();
    contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
    contentValues.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);
    contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS);

    ContentResolver resolver = context.getContentResolver();
    Uri uri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues);
    if (uri != null) {
        InputStream is = null;
        OutputStream os = null;
        try {
            is = new FileInputStream(url);
            os = resolver.openOutputStream(uri);
            inputCopy(is, os);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    // ignore
                }
            }
            if(os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    // ignore
                }
            }
            new File(url).delete();//delete original file
        }
        return getPathURI(context,uri);
    }
    return null;
}
public static String getPathURI(Context context, Uri uri) {
    ContentResolver contentResolver = context.getContentResolver();
    String[] columns = { MediaStore.Downloads.DATA };
    Cursor cursor = contentResolver.query(uri, columns, null, null, null);
    cursor.moveToFirst();
    String path = cursor.getString(0);
    cursor.close();
    return path;
}
void inputCopy(InputStream source, OutputStream target) throws IOException {
    byte[] buf = new byte[8192];
    int length;
    while ((length = source.read(buf)) > 0) {
        target.write(buf, 0, length);
    }
}

Androidでjava.lang.NoClassDefFoundError: Failed resolution of: Landroid/icu/text/Transliterator;

半角全角の文字の切り替えをAndroidでしたいと思って

https://qiita.com/makimaki913/items/df745b85b802099a6e32

を見ながらICU4Jを使おうとした。

Transliterator fullToHalf = Transliterator.getInstance(“Fullwidth-Halfwidth”);
Transliterator halftoFull = Transliterator.getInstance(“Halfwidth-Fullwidth”);

でもどうもAndroidで公式にICU4JをサポートしているのはAndorid7からのようでAndroid5.0で動かしたい場合はGradleに

implementation 'com.ibm.icu:icu4j:53.1'

※参考

https://developer.android.com/guide/topics/resources/internationalization

のように記述する必要がある。

これでAndroid5.0で動くぞーと思ったら、なぜか動かない

 

FATAL EXCEPTION: main
java.lang.NoClassDefFoundError: Failed resolution of: Landroid/icu/text/Transliterator;

とでる。

原因は公式のサジェストでライブラリをインポートしたことでした。


import android.icu.text.Transliterator;

から

import com.ibm.icu.text.Transliterator;

にしましょう

Javaでメソッドを引数みたいな感じで別クラスに渡して別クラスでメソッドを実行させる方法

何を書いているか良くわからない。というか表現があっているかもよくわかってない。

Pythonでいう関数渡し的なのをJavaでやりたい

 

やりたいこと

private void AAA(){

class InnerClass{
InnerClass(){

//InnerClassの中身

}

}

new InnerClass();//InnerClass実行

BBB();//こいつに new InnerClass()を渡して実行させたい

}

 

class void BBB(){

//BBBの中身

//InnerClassを実行させたい場所

}

 

これ、実はThreadで解決できる。

じつは常識なのかな?私は知らなかった。

解決法:

private void AAA(){

class NewInnerThread extends Thread {
public void run() {
//InnerClassの中身

}
}

new NewInnerThread ().run();

BBB(new NewInnerThread ());//こいつに new NewInnerThread ()渡す

}

 

class void BBB(Thread thread1){

//BBBの中身

thread1.run();

}

 

これでとりあえず解決できた。

 

 

AAPT2 error: check logs for details

Caused by: org.gradle.process.internal.ExecException: Process ‘command ‘C:\Users\user\AppData\Local\Android\Sdk\build-tools\28.0.3\aapt.exe” finished with non-zero exit value 1 at org.gradle.process.internal.DefaultExecHandle$ExecResultImpl.assertNormalExitValue(DefaultExecHandle.java:395) at com.android.build.gradle.internal.process.GradleProcessResult.assertNormalExitValue(GradleProcessResult.java:46)

 

という謎のエラーがでて、コンパイルできなくなった。

これ、悩んだけど、もしかしたら記述したXMLがおかしい可能性があります!

自分は

android:layout_weight=”0dp”

ってなぞのdpを入力してこのエラーでました。

正しくは

android:layout_weight=”0″

です

 

でもログにも何も出てこないからすごく悩んだ。

 

どこにログが出てるかというと、


> Task :processDebugManifest
> Task :splitsDiscoveryTaskDebug
AGPBI: {“kind”:”error”,”text”:”Dimension types not allowed (at \u0027layout_weight\u0027 with value \u00270dp\u0027).”,”sources”:[{“file”:”C:\\Users\\user\\yourApp\\res\\layout\\some.xml”,”position”:{“startLine”:6,”startColumn”:27,”startOffset”:225,”endColumn”:30,”endOffset”:228}}],”original”:””,”tool”:”AAPT”}

って自分はでてた。

本当に分かりづらいけど、あまり情報がなかったからメモです。

 

多量リクエスト(insert)時に10.1.21-MariaDBでmysql-connector-java-5.1.42-bin.jarでJDBCしたときに全然insertが追いつかない(更新されない)問題

なんかMariaDBでmysql-connector-java-5.1.42-bin.jarでJDBCするときにコネクションを貼りまくって、executeUpdateでいっぱい更新しようとしたとき、なんかMariaDBがもういいっぱいだよ~(dirty page だよ~)みたいな感じでぜんぜんSQLのInsertができなくなった

最初はMariaDBの問題かと思って色々チューニングしたりしたけど、解決せず

原因はJDBCのexecuteUpdateでした

これ1秒に100リクエストとかめっちゃ送るときにいちいちコネクション貼り直したりする感じで効率が悪いっぽい

めっちゃいっぱい更新したいときは

executeBatch

を使いましょう

そうしたら恐ろしいほどクエリが改善した。

ついでにいうとBulk insertにするとより早いとおもう!

 

public void doSQL(String sql) {
start();
try {
stmt.executeUpdate(sql);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

 

から

 

}
public void doSQLBig(String sql) {
start();
try {
//conn.setAutoCommit(false);
stmt.addBatch(sql);
int result[]=stmt.executeBatch();

for(int i=0;i<result.length;i++){
System.out.println(“result”+i+”: “+result[i]);
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}

に変更した

出てたエラー↓

at com.mysql.jdbc.StatementImpl.executeUpdateInternal(StatementImpl.java:1552)
at com.mysql.jdbc.StatementImpl.executeLargeUpdate(StatementImpl.java:2607)
at com.mysql.jdbc.StatementImpl.executeUpdate(StatementImpl.java:1480)
at com.coin.java.web.SQL.doSQL(SQL.java:198)
at com.coin.java.web.CommonApi.insertBalanceSQL(CommonApi.java:3256)
at com.coin.java.web.RateCallback.getBalance(RateCallback.java:479)
… 1 more
Caused by: java.net.SocketException: Unrecognized Windows Sockets error: 0: recv failed
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.socketRead(Unknown Source)
at java.net.SocketInputStream.read(Unknown Source)
at java.net.SocketInputStream.read(Unknown Source)
at com.mysql.jdbc.util.ReadAheadInputStream.fill(ReadAheadInputStream.java:101)
at com.mysql.jdbc.util.ReadAheadInputStream.readFromUnderlyingStreamIfNecessary(ReadAheadInputStream.java:144)
at com.mysql.jdbc.util.ReadAheadInputStream.read(ReadAheadInputStream.java:174)
at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:3008)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3469)
… 12 more
com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: No operations allowed after connection closed.
at sun.reflect.GeneratedConstructorAccessor61.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
at java.lang.reflect.Constructor.newInstance(Unknown Source)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:425)
at com.mysql.jdbc.Util.getInstance(Util.java:408)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:918)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:897)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:886)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:860)
at com.mysql.jdbc.ConnectionImpl.throwConnectionClosedException(ConnectionImpl.java:1187)
at com.mysql.jdbc.ConnectionImpl.checkClosed(ConnectionImpl.java:1182)
at com.mysql.jdbc.ConnectionImpl.createStatement(ConnectionImpl.java:2377)
at com.mysql.jdbc.ConnectionImpl.createStatement(ConnectionImpl.java:2361)
at com.coin.java.web.SQL.start(SQL.java:81)
at com.coin.java.web.SQL.doSQL(SQL.java:196)
at com.coin.java.web.CommonApi.insertBalanceSQL(CommonApi.java:3256)
at com.coin.java.web.RateCallback.getBalance(RateCallback.java:482)
at com.coin.java.web.RateCallback$4.run(RateCallback.java:166)
Caused by: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure

The last packet successfully received from the server was 22,566 milliseconds ago. The last packet sent successfully to the server was 22,566 milliseconds ago.
at sun.reflect.GeneratedConstructorAccessor60.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
at java.lang.reflect.Constructor.newInstance(Unknown Source)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:425)
at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:989)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3559)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3459)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3900)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2527)
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2680)
at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2486)
at com.mysql.jdbc.StatementImpl.executeUpdateInternal(StatementImpl.java:1552)
at com.mysql.jdbc.StatementImpl.executeLargeUpdate(StatementImpl.java:2607)
at com.mysql.jdbc.StatementImpl.executeUpdate(StatementImpl.java:1480)
at com.coin.java.web.SQL.doSQL(SQL.java:198)
at com.coin.java.web.CommonApi.insertBalanceSQL(CommonApi.java:3256)
at com.coin.java.web.RateCallback.getBalance(RateCallback.java:479)
… 1 more

 

 

 

java.lang.OutOfMemoryErrorの対処法

java.lang.OutOfMemoryErrorは出ないけど、GCが頻発しまくるのでメモリの調整をしてみた

Tomcat の起動パラメータ設定
Eclipse のTomcat プラグイン設定
ウインドウ > 設定 > Tomcat > JVM 設定 > JVM パラメータへ追加

以下のパラメータを設定する。

-XX:MaxPermSize=256m
-Xmx1024m
-Xms256m
RAMの容量を考慮して、適宜メモリを割り当てる。

追記:また足りなくなった
-XX:MaxPermSize=512m
-Xmx2048m
-Xms512m

なんか根本的に解決探さないといけないかな

PubNub java でPNBadRequestCategory

pubnub-gson-4.19.0-all.jarでWebsocket的な高速のデータ取得(subscription)をやろうとした

subscriptionだけならPublishKeyはなくても良いとのこと

テストコード

PNConfiguration pnConfiguration = new PNConfiguration();
//pnConfiguration.setLogVerbosity(PNLogVerbosity.BODY);

pnConfiguration.setSubscribeKey(“sub-c-e12e9174-dd60-11e6-806b-02ee2ddab7fe”);
//pnConfiguration.setPublishKey(“demo”);

pnConfiguration.setReconnectionPolicy(PNReconnectionPolicy.LINEAR);
//pnConfiguration.setSecure(false);

PubNub pubnub = new PubNub(pnConfiguration);

 

これでpubnubにchannelをexcuteしたらできる

だけどなぜかError=PNConnectedCategoryで動かない

正しいパターン
statusPNStatus(category=PNConnectedCategory, errorData=null, error=false, statusCode=200, operation=PNSubscribeOperation, tlsEnabled=true, uuid=null, authKey=null, origin=ps.pndsn.com, clientRequest=Request{method=GET, url=https://ps.pndsn.com/v2/subscribe/sub-c-52a9ab50-291b-11e5-baaa-0619f8945a4f/lightning_ticker_BTC_JPY/0?requestid=5fd2956a-3935-4f3d-85f1-da2fb9c9fb0f&heartbeat=300&tt=0&pnsdk=PubNub-Java-Unified/4.19.0&uuid=pn-acc4761e-6587-4195-931b-dff944ddf1cc, tag=null}, affectedChannels=[lightning_ticker_BTC_JPY], affectedChannelGroups=[], executedEndpoint=null)

正しくないパターン
statusPNStatus(category=PNBadRequestCategory, errorData=PNErrorData(information=null, throwable=com.pubnub.api.PubNubException), error=true, statusCode=0, operation=PNSubscribeOperation, tlsEnabled=false, uuid=null, authKey=null, origin=null, clientRequest=null, affectedChannels=[lightning_ticker_BTC_JPY], affectedChannelGroups=[], executedEndpoint=com.pubnub.api.endpoints.pubsub.Subscribe@65a58842)

別環境では動いたからライブラリが干渉してたっぽい

具体的にはgson-2.3.1.jarをはずすと動いた。

If you catch the error “PNBadRequestCategory” in pubnub-gson-4.19.0-all.jar  , just remove gson library (gson-2.3.1.jar etc…)

ってことだ

 

OANDAのFIXprotocolでレートを取得

色々あってドル円とかのレートデータを高速で取得する必要があった。
調べたところThe Financial Information eXchange (FIX) protocolがなんか良い感じでスタンダードらしいからそれで構築することにした。

使用したものはQuickFIX/J

http://www.quickfixj.org/

です

YJFXのFIXプロトコルはそこそこ素直に出来たけどOANDAはかなり悩んだ。

ハマったポイントはレートサーバーは別です!
//settings.setString(“SocketConnectHost”, “fxgame-fix.oanda.com”);
settings.setString(“SocketConnectHost”, “fxtrade-fix.oanda.com”);

だけじゃだめ

if(rateServer)settings.setString(“TargetSubID”, “RATES”);

これがいる

これあるとレートサーバーに繋いでくれます。そうじゃないと

toApp :8=FIX.4.49=12735=V34=249=USERID52=20171104-11:48:06.69256=OANDA262=foo263=1264=1265=1266=Y146=155=USD/JPY267=2269=0269=110=207

fromApp :8=FIX.4.49=10835=j34=349=OANDA52=20171104-11:48:05.33056=pp35260445=258=MsgType <35> = V not supported.372=V380=310=180

って怒られます。

正しいリクエスト↓

toApp :8=FIX.4.49=13635=V34=249=USERID52=20171104-11:54:35.94856=OANDA57=RATES262=foo263=1264=1265=1266=Y146=155=USD/JPY267=2269=0269=110=251

んですべてのリクエストに毎回「57=RATES」を入れないとダメです

@Override
public void toAdmin(Message msg, SessionID sessionID) {

try {
String msgType = msg.getHeader().getString(MsgType.FIELD);
if(settings.isSetting(“TargetSubID”))
try {
msg.setString(quickfix.field.TargetSubID.FIELD,
settings.getString(sessionID, “TargetSubID”));
} catch (ConfigError | FieldConvertError e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
if(MsgType.LOGON.compareTo(msgType) == 0)
{

try {
if(settings.isSetting(“Password”))msg.setString(quickfix.field.Password.FIELD,
settings.getString(sessionID, “Password”));
msg.setString(quickfix.field.ResetSeqNumFlag.FIELD,
“Y”);

} catch (ConfigError e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (FieldConvertError e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} catch (FieldNotFound e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}

System.out.println(“toAdmin :”+msg.toString());
}
public void toApp(Message msg, SessionID sessionId) throws DoNotSend {
if(settings.isSetting(“TargetSubID”))
try {
msg.setString(quickfix.field.TargetSubID.FIELD,
settings.getString(sessionID, “TargetSubID”));
} catch (ConfigError | FieldConvertError e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(“toApp :”+msg.toString());
}

ゴミコードも置いときます。

 

eclipseのコンソール出力をテキストファイルに保存する方法

そのままConsoleのlogをtextで保存したい。

やり方

runの▷矢印の右らへんの▽クリックしてrun configurationでEclipse application選択して、CommonでOutput Fileにチェックして保存自体場所を選んで終わり