タブレット用プログラムの書き止め

android OS & iPadOS の記録。

RecyclerView【基礎】ImageView の列挙。まとめ。

2022-11-16 15:29:37 | Android studio 日記

【1】リソース画像を列挙。(一般サンプル通りでOK)
【2】画像ファイルを縮小(サムネイル化)して列挙。


【2】のポイント。
 [1]処理速度。
 [2]容量。


 [1-1]サムネイル画像ファイルを作成し、2回目以降はそれを読み込む。
 [1-2]サムネイル画像ファイルの作成はワーカースレッドで実行する。
 [1-3]サムネイル画像ファイルが未作成の時はリソース画像を設定。作成後入れ替える(更新)。

 [2-1]サムネイル画像ファイルの保存場所。
 [2-2]サムネイルのサイズと保存容量。
 [2-3]サムネイル画像ファイルの総数。

 

 [1-1-1]サムネイル画像ファイルの保存場所のファイル有無で処理を分岐する。
 [1-2-1]ExecutorService().submit(new Runnable())
 [1-3-1]RecyclerView.getAdapter().notifyItemChanged(viewID)

 [2-1-1]getCacheDir() + absoluteFilePath 
 [2-2-1]160x160ドット、10Kbyte程度。JPG
 [2-3-1]CacheDirならシステムで一括消去が有る。


【注意点】
・RecyclerViewは任意の時点で更新される。(可能性がある)
・RecyclerView.setAdaptr()でアイテムが更新される。(可能性がある)
・RecyclerViewが更新されてもワーカースレッドは独立進行する。

なので、現在のRecyclerViewの内容がワーカースレッドへ処理発注した時点のRecyclerViewの内容と同じならばImageViewを更新させる。
RecyclerViewの内容が変更されているのに発注した時点の条件で更新すると例外で停止する。

・現在のRecyclerView.getAdapter()オブジェクトとワーカースレッド引数のRecyclerView.getAdapter()オブジェクトを調べる。
・ワーカースレッドへ処理発注時にMyRecyclerViewでRecyclerView_idを渡す。そのIDが現在のRecyclerView_idと同じかを調べる。
 IDはViewModelで管理してRecyclerViewの変更、更新があったらIDを進める。

 

今、RecyclerViewアダプターのオブジェクト比較で同じならnotifyItemChanged(viewID)で更新している。
ViewModelで一元管理するのであれば、RecyclerViewに関する更新が行われた時にnotifyItemChanged(viewID)が実行されない工夫が必要。

・継承MyRecyclerViewにRecyclerView_idを設置。
・ViewModelのLiveDataにRecyclerViewを使う。そして、RecyclerView_idを設置。


継承クラスかViewModelか、試して使い勝手の良い方を選択する。
やっぱり、一元管理かな。

 


  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

代替え AsyncTask 【基礎】 ワーカースレッド。その弐。

2022-11-14 00:43:46 | Android studio 日記

ExecutorService のルールが良く分からない。迷走中。

スレッドプールを利用するときの宣言は1つのアプリで1回が望ましいらしい。
今使うのはシングルワーカースレッドなので使用場所で宣言でも良いのかもしれない。
実際に数十のワーカーを作っても1スレッドで順番に処理をしている。
特に不具合は感じられない。

ただ、これからスレッドプールを使ってワーカーの複数並列処理をしようとした時に癖で利用する場所で宣言してしまうかもしれない。もの凄く悪影響が出るので注意とデベロッパーサイトに記載されていた。
なので、Executor も一ヵ所で管理させる癖を付ける事にする。

複数のフラグメントで呼び出せるのでViewModelで宣言させる。(良いのかな?)

 

mViewModel.getValueExecutorService() 初回に呼んだ時にExecutorServiceを取得する。

public class MyViewModel extends ViewModel {

    private MutableLiveData< ExecutorService> executorService = null;
    public MutableLiveData< ExecutorService> getExecutorService() {
        if ( executorService == null ) {
            executorService = new MutableLiveData< ExecutorService>(Executors.newSingleThreadExecutor());
        }
        return executorService;
    }
    public ExecutorService getValueExecutorService() {
        return getExecutorService().getValue();
    }

    //省略
}


MainActivityで実体を作成する。

public class MainActivity extends AppCompatActivity {

    private MyViewModel mViewModel = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mViewModel = new ViewModelProvider(this).get(MyViewModel.class);
    }

    //省略
}


フラグメントでViewModelを呼び出しワーカースレッドへ渡す。

public class MainFragment extends Fragment {

    private MyViewModel mViewModel;


    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        mViewModel = new ViewModelProvider((ViewModelStoreOwner) requireContext()).get(MyViewModel.class);

    }

    private void test() {

        //省略

        MyAsyncTask2 mAsyncTask = new MyAsyncTask2( mViewModel, null, index );
        mAsyncTask.execute( /* 引数 */ );

    }
    //省略

    @Override
    public void onDestroy() {
        super.onDestroy();

        mViewModel = null;
        //省略
    }
}


ワーカースレッド。一部分整理して簡潔に書き直し。

public class MyAsyncTask2 {

    private MyViewModel mViewModel;
    private RecyclerView mRecyclerView;
    private final int vID;
    //省略

    public MyAsyncTask2(MyViewModel data, RecyclerView rv, int id ) {
        super();

        mViewModel = data;
        mRecyclerView = rv ;
        vID = id;

        //省略

    }

    private class TaskRunnable implements Runnable {

        private String targetPath = "";
        public TaskRunnable(String param) { targetPath = param; }

        @Override
        public void run() {
            //省略。処理内容。

            new Handler(Looper.getMainLooper())
                    .post(() -> onPostExecute(retult));
        }
    }

    public void execute(String param) {
        mViewModel.getValueExecutorService().submit(new TaskRunnable(param));
    }

    void onPostExecute(int result) {

        if ( mRecyclerView == null ||
                mRecyclerView.getAdapter() == null ||
                mViewModel.getValueRecyclerView() == null ||
                mViewModel.getValueRecyclerView().getAdapter() == null ||
                !mRecyclerView.getAdapter().equals(mViewModel.getValueRecyclerView().getAdapter())//整理した
        ) {
            finish();
            return;
        }

        switch ( result ) {
            case 0:
                if ( vID < mRecyclerView.getAdapter().getItemCount() ) {
                        mRecyclerView.getAdapter().notifyItemChanged(vID);
                }
                break;
            //省略
        }

        finish();
    }
    private void finish() {
        mRecyclerView = null;
        mViewModel = null;
    }
}


【旧クラス】
public class MyAsyncTask2 {

    ExecutorService executorService;
    //省略

    public MyAsyncTask2(MyViewModel data, RecyclerView rv, int id ) {
        super();

        executorService  = Executors.newSingleThreadExecutor();

        //省略

    }

    private class TaskRunnable implements Runnable {

        @Override
        public void run() {
            //省略

            new Handler(Looper.getMainLooper())
                    .post(() -> onPostExecute(retult == 0));
        }
    }

    public void execute(String param) {
        targetPath = param;
        executorService.submit(new TaskRunnable());
    }

    void onPostExecute(boolean result) {

        //省略
    }
}

 


  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

メソッド【基礎】アプリ名とバージョンを文字列で返す。

2022-11-12 15:46:38 | Android studio 日記

/*
 * スタート画面にアプリ名とバージョンを表記したい場合はシステムの情報から抜き出す。
 * 下のメソッドはそれを文字列にして返すもの。TextView.setText()で表示する。
 */

  public String getNameAndVersion(Activity activity) {
    try {
      String packageName = activity.getPackageName();
      PackageManager pm = activity.getPackageManager();
      PackageInfo info = pm.getPackageInfo(packageName, PackageManager.GET_META_DATA);

      ApplicationInfo ai = pm.getApplicationInfo(packageName, 0);
      String appName = ai.loadLabel(pm).toString();

      return ( appName + " " + info.versionName );

    } catch (PackageManager.NameNotFoundException e) {
      e.printStackTrace();
      return null;
    }
  }

 


  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

Fragment【基礎】ジェスチャーの組み込み。

2022-11-12 15:41:50 | Android studio 日記

OnTouchListener()の動作が良く分かっていなかった。
なのでActivityのLayoutビューにOnTouchListener()を設置してGestureDetectorCompatへ横流しをしていた。

何となくMotionEventの流れが末端のビューからActivity(親)まで遡って来るのをイメージできた。
OnTouchListener()の設置場所はFragmentのビューで良いらしい。

fragment_main.xml に View を書いて findViewById().setOnTouchListener()でMotionEventをフック。
return true; でイベント消費すれば、不本意な動作は抑制できそう。

 

public class MainFragment extends Fragment {

        // 省略
    private GestureDetectorCompat mDetector;
    private class MyGestureListener extends GestureDetector.SimpleOnGestureListener {

        // 省略
    }


    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        // 省略

        mDetector = new GestureDetectorCompat( requireContext(), new MyGestureListener() );

        // fragment_main.xml の id にOnTouchListener を設定して mDetector.onTouchEvent()を呼ぶ。
        requireActivity().findViewById(R.id.ui_mainFragment).setOnTouchListener( 
                new View.OnTouchListener() {
                    public boolean onTouch(View v, MotionEvent event) {
                        return mDetector.onTouchEvent(event);
                    }
                }
        );
        // 省略

    }


}

 


  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

代替え AsyncTask 【基礎】 ワーカースレッド。

2022-11-11 16:48:15 | Android studio 日記


私は理解度&知識がやや低いので他力本願である。

【 nyan のアプリ開発 [Android] 非同期処理 Executorの使い方 】
【 Android 11でdeprecatedになったAsyncTask対応Java編 】

2つの解説サイトを参考に
元[MyAsyncTask] を 代替え[MyAsyncTask2]クラスで置き換え。

不具合も無く、違和感も今のところ感じられない。
これで API 17以降使えるようになった。

 

今までの AsyncTask。
public class MyAsyncTask extends AsyncTask<String,Object,Object> {

    private MainViewModel mViewModel;
    private RecyclerView mRecyclerView;
    private final int vID;
    private final int saveSize;// 保存する画像サイズの目安
    private int ret = 0;

    public MyAsyncTask(MainViewModel data, RecyclerView rv, int id ) {
        super();
        mViewModel = data;
        mRecyclerView = rv ;
        vID = id;

        saveSize = (int)mViewModel.getValueMyMainModel().getContext()
                              .getResources().getDimension( R.dimen.thumbnail_save_size );

    }

    // 別のスレッドで実行
    @Override
    protected Object doInBackground(String... p) { // MyTask.execute(引数);で呼ばれる
        File souFile = new File(p[0]);
        if ( !souFile.exists() || !souFile.isFile() ) return false;
        File cacheDir = mViewModel.getValueMyMainModel().getCacheDir(souFile);
        if ( cacheDir == null || ( !cacheDir.exists() && !cacheDir.mkdirs() ) ) return false;

        File cacheFile = new File( cacheDir, souFile.getName() );
        if ( cacheFile.exists() ) return true; // キャッシュファイルが無いからここに来ているけど..

        ret = new MyMakeThumbnail().toThumbnailFile(souFile, cacheFile, saveSize);
        return ret == 0;

    }

    // 終了後、メインスレッドで実行
    @Override
    protected void onPostExecute( Object result ) {
        if ( result.equals(true)  &&
                mRecyclerView != null &&
                mRecyclerView.getAdapter() != null &&
                vID <  mRecyclerView.getAdapter().getItemCount()
        ) {
            boolean flag = false;
            if (mViewModel.getValueRecyclerView_thumbnail() != null &&
                    mRecyclerView.getAdapter().equals(mViewModel.getValueRecyclerView_thumbnail().getAdapter())
            )
                flag = true;
            else if (mViewModel.getValueRecyclerView_directory() != null &&
                    mRecyclerView.getAdapter().equals(mViewModel.getValueRecyclerView_directory().getAdapter())
            )
                flag = true;

            if (flag)
                mRecyclerView.getAdapter().notifyItemChanged(vID);

        } else if (ret == -1)
            // 省略
        else if (ret == -2) 
            // 省略

        mRecyclerView = null;
        mViewModel = null;

    }
}

 

代替え Executor を使ったワーカースレッド。
public class MyAsyncTask2 {

    ExecutorService executorService;
    private MainViewModel mViewModel;
    private RecyclerView mRecyclerView;
    private final int vID;
    private final int saveSize;
    private int ret = 0;
    private String targetPath = "";

    public MyAsyncTask2(MainViewModel data, RecyclerView rv, int id ) {
        super();

        executorService  = Executors.newSingleThreadExecutor();
        mViewModel = data;
        mRecyclerView = rv ;
        vID = id;

        saveSize = (int)mViewModel.getValueMyMainModel().getContext()
                              .getResources().getDimension( R.dimen.thumbnail_save_size );

    }

    private class TaskRunnable implements Runnable {

        @Override
        public void run() {
            File souFile = new File(targetPath);
            if ( !souFile.exists() || !souFile.isFile() ) {
                return;
            }
            File cacheDir = mViewModel.getValueMyMainModel().getCacheDir(souFile);
            if ( cacheDir == null || ( !cacheDir.exists() && !cacheDir.mkdirs() ) ) {
                return;
            }

            File cacheFile = new File( cacheDir, souFile.getName() );
            if ( cacheFile.exists() ) return; // キャッシュファイルが無いからここに来ているけど..

            ret = new MyMakeThumbnail().toThumbnailFile(souFile, cacheFile, saveSize);

            new Handler(Looper.getMainLooper())
                    .post(() -> onPostExecute(ret == 0));
        }
    }

    public void execute(String param) {
        targetPath = param;
        executorService.submit(new TaskRunnable());
    }

    void onPostExecute(boolean result) {

        if ( result &&
                mRecyclerView != null &&
                mRecyclerView.getAdapter() != null &&
                vID <  mRecyclerView.getAdapter().getItemCount()
        ) {
            boolean flag = false;
            if (mViewModel.getValueRecyclerView_thumbnail() != null &&
                    mRecyclerView.getAdapter().equals(mViewModel.getValueRecyclerView_thumbnail().getAdapter())
            )
                flag = true;
            else if (mViewModel.getValueRecyclerView_directory() != null &&
                    mRecyclerView.getAdapter().equals(mViewModel.getValueRecyclerView_directory().getAdapter())
            ) 
                flag = true;

            if (flag)
                    mRecyclerView.getAdapter().notifyItemChanged(vID);

        } else if (ret == -1)
            // 省略
        else if (ret == -2)
            // 省略
       
        mRecyclerView = null;
        mViewModel = null;
    }

}

 


  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする