hatena blogに移行しました。
hatena dialy → hatena blog
重い腰あげました。
コンバート機能よくできてました。
android4アプリを6(marshmallow)対応に
いろいろ調べたことのメモ書きです。
runtime permissionを対応する。
Android Marshmallow対応!Permissions事始め
http://qiita.com/chihiro/items/314c1a69d2f2e122aad6
googleのサンプルソース
https://github.com/googlesamples/android-RuntimePermissionsBasic/blob/master/Application/src/main/java/com/example/android/basicpermissions/MainActivity.java
snackbar等のandroid5で導入された部品を使うにはいろいろ最新化が必要
snackbar等のandroid5で導入された使うには
com.android.support:designの導入が必要。
- Android Design Support Library を少しだけ触ってみました
関連していろいろ最新化しないといけない。
play-serviceを最新化
com.google.android.gms:play-serviceを最新にしたら
Unable to execute dex: method ID not in [0, 0xffff]: 65536
とでたのでググったら下記のサイトが見つかったけど、これに従っても解決できずでした。
- Androidのメソッド数が65k(65536)を超えた場合にビルドができなくなる
この場合は下記のサイトが正解。
- Google Playサービスを必要なだけAndroidStudioプロジェクトに追加する
app compat対応
app compatの対応が必要なのでandroid support library v7対応が必要。
- http://qiita.com/KeithYokoma/items/33f5d3e0724751941391
- http://googledevjp.blogspot.jp/2014/11/appcompat-v21-lollipop.html
- http://wada811.blogspot.com/2013/08/action-bar-activity-in-support-library-v7-appcompat.html
v7はv4を拡張してるっぽいので。あまり詳しい文献は見つけられずだけどv4(1.6)サポートは必要ないだろうから気にしない。
といっても一部はv4のままのクラスを使うっぽい。両方使っていくスタイルっぽい。
app compat関連で修正した箇所は下記
- FragmentActivity → AppCompatActivityにした。
- ActionBarActivityへの移行と書いてあるがこれも古くて今はAppCompatActivityを使う。
- ThemeをTheme.AppCompatにした。
- ActionBarDrawerToggle を android.support.v7.app のものに変える
- getActionBar → getSupportActionBarにする。getActionBarのままだとnull返してエラーになる。
- ロゴがでない。
actionBar.setLogo(R.drawable.ic_launcher)
をやめて
actionBar.setDisplayShowHomeEnabled(true);
actionBar.setIcon(R.drawable.ic_launcher);
を呼ぶ。
最後に
時代が変わっていくのついてくの大変。
androidアプリをビルドしてバージョン別フォルダでサーバーへアップロードするスクリプト
aaptコマンドはパスを通す必要あり。
[androidSDKのパス]/build-tools/[version]/aapt
# ビルド ./gradlew assembleStaging ./gradlew assembleRelease # バージョン名取得 versionName=`aapt dump badging app/build/outputs/apk/app-*-staging.apk | sed -n 's/.*versionName='\''\(.*\)'\'' .*/\1/gp'` # リモートにバージョンフォルダ作成。2回目ディレクトリ作成済みでエラーになるけどきにしない。-pは怖いのでやめた。 ssh 転送先ホスト mkdir 設置先パス/$versionName/ # リモートにアップロード scp -r app/build/outputs/apk/app-*-staging.apk 転送先ホスト:設置先パス/$versionName/ scp -r app/build/outputs/apk/app-*-release.apk 転送先ホスト:設置先パス/$versionName/
sed memo
シングルクォートのエスケープは'\''
http://d.hatena.ne.jp/tanakaBox/20070729/1185709167
最長一致になるのでスペースいれたりでマッチしないように工夫必要
http://techtipshoge.blogspot.jp/2011/10/sed.html
sedコマンド
http://bi.biopapyrus.net/linux/sed.html
https://hydrocul.github.io/wiki/commands/sed.html
実行環境
android studio 1.4.1
buildToolsVersion '23.0.1'
com.android.tools.build:gradle:1.3.1
android google analyticsでbuild flavor対応。
トラッキングIDである設定xmlファイル中のga_trackingIdの値をbuild flavorごとに切り替えたい。
当たり前にできると思ったが標準でできなそうで結局hack地味たことした。
android開発って罠多すぎるんだけど大丈夫かねgoogleさん。。
対処方法
Tracker t = analytics.newTracker(R.xml.app_tracker);
t.set("&tid", getString(R.string.ga_trackingId));
試みてだめだったこと
便利メモ
xmlに
っていれると詳細なログでるのでわかりやすい。
参考文献
- 今回のhack方法のってた記事
実行環境
google analytics v4
android 4.3
android studio 0.8.9
sqlでlastIndexOf, rindexを行う
下記でできた。
LENGTH(col1) - INSTR(REVERSE(col1), '#')
指定文字列の前か後の文字列を抜き出す場合
対象文字列 : hoge#hoge2#end
endを抜く (最後の#より後を抜くSQL) (簡単)
SELECT SUBSTRING_INDEX(col1, '#', -1) FROM (SELECT 'hoge#hoge2#end' col1) t
hoge#hoge2を抜く (最後の#より後を抜くSQL) (めんどいが下記でできる)
SELECT SUBSTRING(col1, 1, LENGTH(col1) - INSTR(REVERSE(col1), '#')) FROM (SELECT 'hoge#hoge2#end' col1) t
実行環境
mysql4.1
rubyでmemcachedのキーの一覧を表示する。また特定データを削除する等。
なんで作ろうと思ったか
C#のmemcachedセッションライブラリがバグってて無期限データ入れやがったー。
そのまま放置して130万件にもなりやがった。
くそー 消したい。flush_allすると全データ消えて既存ユーザーが強制timeout扱いなるからそれは避けたい。
なので無期限データのみ消したいんだー 絶対ー!!
アイテム一覧表示 実行結果
$ util.print_items stats {"pid"=>"16348", "uptime"=>"585047", "time"=>"1361858174", "version"=>"1.4.7", "libevent"=>"1.4.13-stable", "pointer_size"=>"64", "rusage_user"=>"1.210815", "rusage_system"=>"5.016237", "curr_connections"=>"24", "total_connections"=>"1554", "connection_structures"=>"34", "cmd_get"=>"16466", "cmd_set"=>"25559", "cmd_flush"=>"0", "get_hits"=>"16424", "get_misses"=>"42", "delete_misses"=>"0", "delete_hits"=>"4", "incr_misses"=>"0", "incr_hits"=>"0", "decr_misses"=>"0", "decr_hits"=>"0", "cas_misses"=>"0", "cas_hits"=>"0", "cas_badval"=>"0", "auth_cmds"=>"0", "auth_errors"=>"0", "bytes_read"=>"783051598", "bytes_written"=>"396680001", "limit_maxbytes"=>"67108864", "accepting_conns"=>"1", "listen_disabled_num"=>"0", "threads"=>"4", "conn_yields"=>"0", "bytes"=>"111905", "curr_items"=>"7", "total_items"=>"25559", "evictions"=>"0", "reclaimed"=>"27"} stats items {5=> {"number"=>"2", "age"=>"583060", "evicted"=>"0", "evicted_nonzero"=>"0", "evicted_time"=>"0", "outofmemory"=>"0", "tailrepairs"=>"0", "reclaimed"=>"11"}, 6=> {"number"=>"1", "age"=>"582577", "evicted"=>"0", "evicted_nonzero"=>"0", "evicted_time"=>"0", "outofmemory"=>"0", "tailrepairs"=>"0", "reclaimed"=>"14"}, 7=> {"number"=>"1", "age"=>"502636", "evicted"=>"0", "evicted_nonzero"=>"0", "evicted_time"=>"0", "outofmemory"=>"0", "tailrepairs"=>"0", "reclaimed"=>"0"}, 28=> {"number"=>"3", "age"=>"505667", "evicted"=>"0", "evicted_nonzero"=>"0", "evicted_time"=>"0", "outofmemory"=>"0", "tailrepairs"=>"0", "reclaimed"=>"1"}} server_start_time:2013-02-19 20:25:27 +0900 item_number:5 key:foo_development:_session_id:f0df468194448c505e26b99143e5d65c bytes:107 time:2013-02-26 20:55:22 +0900 item_number:5 key:foo_development:_session_id:f7450243f80ca59fbb9ffc18049fedc0 bytes:107 time:2013-02-26 20:23:07 +0900 item_number:6 key:foo_development:_session_id:e2674611e09369f8b5bc62ba95c59b92 bytes:165 time:2013-02-26 20:15:04 +0900 item_number:7 key:hoge_e15atqhotfpm4sbnbm3dwa3g bytes:269 time:2013-02-26 16:02:41 +0900 item_number:28 key:hoge_qmeysujdltvz22s2ahtpmrye bytes:36546 time:2013-02-27 14:55:44 +0900 item_number:28 key:hoge_3nsu2trne4j0vyrfqg202lqe bytes:37387 time:2013-02-26 21:04:37 +0900 item_number:28 key:hoge_lwbr2fcdzx21wgbroczjtghq bytes:36536 time:2013-02-26 16:53:29 +0900 2013-02-26 14:54:50 +0900 infinity_count:0 normal_count:7 expire_count:0
ソース
# coding: utf-8 STDOUT.sync = true require 'rubygems' require 'pp' gem 'memcache_do', '>=0.1.1' gem 'dalli', '>=2.6.2' require 'memcache_do' require 'dalli' class MemcacheStats def initialize(host='localhost', port='11211') @host = host @port = port @dc = Dalli::Client.new("#{host}:#{port}") end def list stats_items_hash.each do |no, item| stats_cachedump(no).lines do |line| next if line.chomp == 'END' hoge, key, bytes, second = line.match(/^ITEM ([^ ]*) \[(\d*) b; (\d*)/).to_a second = second.to_i time = Time.at(second) data = {:item_number => no, :key => key, :bytes => bytes, :time => time} yield(data) end end end def exec(cmd) MemcacheDo.exec(cmd, @host, @port) end def stats exec 'stats' end def stats_hash raw = stats hash = {} raw.lines do |line| # p line line.chomp! res = line.match(/^STAT (\w+) ([^ ]+)/) if res hash[res[1]] = res[2] end end hash end def stats_items exec 'stats items' end def stats_cachedump(item_number) exec "stats cachedump #{item_number} 0" end def stats_items_hash raw = stats_items hash = {} raw.lines do |line| # p line line.chomp! res = line.match(/^STAT items:(\d+):(\w+) ([^ ]+)/) if res key_num = res[1].to_i hash[key_num] = {} unless hash.has_key? key_num hash[key_num][res[2]] = res[3] end end hash end def delete(key) @dc.delete key end def dalli(key) @dc end end if $0 == __FILE__ class MemcacheUtil def initialize(host='localhost') @m = MemcacheStats.new(host) end # アイテム一覧表示 def print_items m = @m infinity_count = 0 normal_count = 0 expire_count = 0 # stats取得 # puts m.stats stats = m.stats_hash puts "stats" pp stats # stats_items取得 #puts m.stats_items stats_items = m.stats_items_hash puts "stats items" pp stats_items # サーバー開始時間を計算 server_start_time = Time.at(stats["time"].to_i - stats["uptime"].to_i) puts "server_start_time:#{server_start_time}" # データ一覧取得 m.list do |data| item_number = data[:item_number] key = data[:key] bytes = data[:bytes] time = data[:time] puts "item_number:#{item_number} key:#{key} bytes:#{bytes} time:#{time}" if time == server_start_time # 無期限キャッシュの有効期限はサーバー開始時間と一緒 infinity_count += 1 elsif time < Time.now # 期限切れデータ。どうもstats cachedumpに期限切れてるのがでてくる expire_count += 1 else normal_count += 1 end if key == "hogehoge" # 簡易データ表示(先頭にヘッダーぽいのついてるのを除去してないです) p m.exec("get #{key}") # rubyのデータなら下記のようにm.dalliでDalliClientのインスタンス返すのでご自由にどぞ。 #m.dalli.get(key) end end puts "#{Time.now} infinity_count:#{infinity_count} normal_count:#{normal_count} expire_count:#{expire_count}" end # 無期限データ削除&有効期限切れデータ削除 def delete_infinity_and_expire_data m = @m puts "#{Time.now} start process" loop do infinity_count = 0 normal_count = 0 expire_count = 0 # stats取得 # puts m.stats stats = m.stats_hash puts "stats" pp stats # stats_items取得 #puts m.stats_items stats_items = m.stats_items_hash puts "stats items" pp stats_items # サーバー開始時間を計算 server_start_time = Time.at(stats["time"].to_i - stats["uptime"].to_i) puts "server_start_time:#{server_start_time}" # データ一覧取得 m.list do |data| item_number = data[:item_number] key = data[:key] bytes = data[:bytes] time = data[:time] puts "item_number:#{item_number} key:#{key} bytes:#{bytes} time:#{time}" if (time == server_start_time) # 無期限キャッシュの有効期限はサーバー開始時間と一緒 infinity_count += 1 m.delete key elsif time < Time.now # 期限切れデータ。どうもstats cachedumpに期限切れてるのがでてくる expire_count += 1 m.delete key else normal_count += 1 end end puts "#{Time.now} infinity_count:#{infinity_count} normal_count:#{normal_count} expire_count:#{expire_count}" # 削除対象が無くなるまで繰り返す。 # stats cachedumpは1MBしか返さない制限あるのでゴミデータは削除しないと消したいデータが出てこない場合がある。なので期限切れデータを消した後もループで再実行している。 if infinity_count == 0 && expire_count == 0 break end end puts "#{Time.now} end process" end end util = MemcacheUtil.new('192.168.xxx.xxx') util.print_items # コメント外して使ってください。 # 無期限データ削除&有効期限切れデータ削除 #util.delete_infinity_and_expire_data end
注意点
stats cachedumpだとキー一覧等のデータが1MBまでしか返ってこないとか。もしアクティブなデータがいっぱい入っている場合は無理そう。
また有効期限切れデータも返ってくるので、今回は有効期限切れデータがあった場合は削除するような処理にした。
参考
http://blog.elijaa.org/index.php?post/2010/12/24/Understanding-Memcached-stats-cachedump-command
http://lzone.de/dump+memcache+keys
そのうちコマンドstats cachedumpが消えるかもとかもいってるみたい。まー微妙な仕様なので新しいのができると期待。
rubyメモ書き
rails3.1で大きいデータ(csv等)をストリーミングで出力する方法
はじめに
管理画面等で膨大なデータをcsvで落としたい時等に
そのままDBから文字列を作って返すと反応がないままタイムアウトになると思います。(なんせ全部メモリに落とすのだから)
こういう場合の回避方法(2パターン)
- db等にリクエストをキューイングしておきバッチで処理してzipに圧縮しておく。
- サーバーにはお行儀がいいお作法。ただし工数がかかる。
- 1000件等の細かい単位にしてsqlを発行してストリーミングで出力する。常にブラウザにデータが流れるためタイムアウトしない。
- 工数少なめ。ただしサーバーに通信しっぱなしになるのでサーバーの同時リクエスト数を食いつぶす。管理画面等の一部利用者のみなら問題ないでしょう。
今回2の方法をrails3.1で行う文献が見つからなかったので調べました。
サンプル
1秒おきにtest1,test2..test5のように出力されます。
class TestController < ApplicationController def index headers["Cache-Control"] ||= "no-cache" headers["Transfer-Encoding"] = "chunked" # 上記のheadersを書くかもしくは下記のrenderでもOK。headersのほうが余計な処理が入らないのでお勧めかな?? #render :text=>"", :stream => true # DLにする場合はここのコメントを外せばOK.ブラウザで順次出力されるのを確認できるようにあえて外しておきます。 # headers.merge!( # 'Content-Type' => "text/csv; charset=Shift_JIS", # 'Content-Disposition' => "attachment; filename=\"test.csv\"" # ) self.response_body = Rack::Chunked::Body.new(Enumerator.new do |y| 5.times do |i| y << "test#{i} \n" sleep 1 end end) end end
大抵はDBから1000件ずつ等ちょこちょこ取得して出力するでしょう。その際はfind_eachを使うと便利です。
http://wiki.usagee.co.jp/ruby/rails/RailsGuides%E3%82%92%E3%82%86%E3%81%A3%E3%81%8F%E3%82%8A%E5%92%8C%E8%A8%B3%E3%81%97%E3%81%A6%E3%81%BF%E3%81%9F%E3%82%88/Active%20Record%20Query%20Interface#i803e697
注意点
webrickではストリーミングになりません。
unicornを使ってください。
また古いバージョンのunicornだと設定に下記を指定しないといけないそうです。(現在はデフォでこの設定になってる)
# unicorn.config.rb listen 3000, :tcp_nopush => false
参考
rails3.1の新機能のhttpストリーミング部分のソースを読みました。(実際はrailsのbody部分の出力の流れまで追ってしまいました。。unicornのc部分まで行きそうな所で力尽きましたw)
actionpack-3.1.3/lib/action_controller/metal/streaming.rb
- httpストリーミングとは
- httpストリーミングの使い方
- railsオフィシャルドキュメント(英語)