Selaa lähdekoodia

优化播放组件

yanqiliang 3 kuukautta sitten
vanhempi
commit
30d5cfcbf1

+ 1 - 1
.idea/deploymentTargetDropDown.xml

@@ -12,6 +12,6 @@
         </deviceKey>
       </Target>
     </targetSelectedWithDropDown>
-    <timeTargetWasSelectedWithDropDown value="2025-06-03T07:29:20.553024100Z" />
+    <timeTargetWasSelectedWithDropDown value="2025-12-28T07:29:21.986098Z" />
   </component>
 </project>

+ 1 - 0
.idea/misc.xml

@@ -6,6 +6,7 @@
         <entry key="..\:/ProjectSpace/AndroidProjects/PrAndroidWebView/app/src/main/res/layout/activity_ad.xml" value="0.3976449275362319" />
         <entry key="..\:/ProjectSpace/AndroidProjects/PrAndroidWebView/app/src/main/res/layout/activity_main.xml" value="0.10469314079422383" />
         <entry key="..\:/ProjectSpace/AndroidProjects/PrAndroidWebViewJHP/app/src/main/res/layout/activity_main.xml" value="0.1685185185185185" />
+        <entry key="..\:/ProjectSpace/AndroidProjects/android_PR_JHP/app/src/main/res/layout/activity_main.xml" value="0.36614583333333334" />
       </map>
     </option>
   </component>

+ 2 - 2
app/build.gradle

@@ -8,8 +8,8 @@ android {
         applicationId "com.example.jhpapp"
         minSdk 22
         targetSdk 31
-        versionCode 4
-        versionName "2.8"
+        versionCode 5
+        versionName "2.9"
 
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
         ndk {  abiFilters "armeabi","armeabi-v7a","x86"}

+ 1 - 0
app/src/main/AndroidManifest.xml

@@ -16,6 +16,7 @@
     <uses-feature android:name="android.hardware.wifi" />
     <uses-permission android:name="android.permission.BLUETOOTH" />
     <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+    <uses-permission android:name="android.permission.INTERNET" />
     <!--悬浮窗-->
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
     <!-- 声明开机完成的权限 -->

+ 168 - 82
app/src/main/java/com/example/jhpapp/MainActivity.java

@@ -1,5 +1,7 @@
 package com.example.jhpapp;
 import androidx.annotation.RequiresApi;
+import androidx.lifecycle.LifecycleEventObserver;
+
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.SharedPreferences;
@@ -35,15 +37,64 @@ import pl.droidsonroids.gif.GifImageView;
 
 public class MainActivity extends BaseActivity {
 
-    private static Context context;
-    static WebView webIDs;
+    private WebView webIDs;
+    private Context context;
     Button call_Js,but1,but02,but2,but3;
     TextView js_info;
-    static TextView textValue;
+    TextView textValue;
     GifImageView webViewLoading;
 
-    //播放提示音模块使用
-    private SimpleExoPlayer player;
+    // ❷ 播放模块:全局监听器(避免重复创建)+ volatile保障线程可见性
+    private volatile SimpleExoPlayer player;
+    private final Player.Listener globalPlayerListener = new Player.Listener() {
+        @Override
+        public void onPlaybackStateChanged(int playbackState) {
+            switch (playbackState) {
+                case Player.STATE_IDLE:
+                    Log.i("mediaplay","状态: 空闲");
+                    break;
+                case Player.STATE_BUFFERING:
+                    Log.i("mediaplay","状态: 缓冲中");
+                    break;
+                case Player.STATE_READY:
+                    Log.i("mediaplay","状态: 就绪");
+                    // 就绪后自动播放(仅全局监听器处理,避免重复)
+                    if (player != null && !player.isPlaying()) {
+                        player.play();
+                    }
+                    break;
+                case Player.STATE_ENDED:
+                    Log.i("mediaplay","状态: 播放完成");
+                    // ❸ 封装通知JS方法,增加Activity状态检查
+                    notifyJs("mediaPlayOver", "success");
+                    // 播放完成后重置播放器,避免下次播放异常
+                    if (player != null) {
+                        player.stop();
+                    }
+                    break;
+            }
+        }
+
+        @Override
+        public void onPlayerError(PlaybackException error) {
+            Log.e("mediaplay", "播放错误: " + error.getMessage());
+            switch (error.errorCode) {
+                case PlaybackException.ERROR_CODE_DECODING_FAILED:
+                    Log.e("mediaplay", "解码失败");
+                    break;
+                case PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED:
+                    Log.e("mediaplay", "网络连接失败");
+                    break;
+            }
+            // ❹ 播放错误通知前端JS
+            notifyJs("mediaPlayOver", "error");
+        }
+
+        @Override
+        public void onIsPlayingChanged(boolean isPlaying) {
+            Log.i("mediaplay","播放状态: " + isPlaying);
+        }
+    };
 
     //项目安装包下载链接
     public static final String APP_APKDOWN = "https://**?type=apk&target=selfprint";
@@ -53,7 +104,33 @@ public class MainActivity extends BaseActivity {
     SharedPreferences.Editor mEditor;
 
 
-    private Handler handlerWebErr = new Handler();
+    // ❺ 绑定生命周期的Handler(避免泄漏)
+    private Handler handlerWebErr;
+
+    // ❻ 生命周期观察者(管理播放器暂停/恢复)
+    private final LifecycleEventObserver lifecycleObserver = (source, event) -> {
+        if (player == null) return;
+        switch (event) {
+            case ON_PAUSE:
+                // 后台暂停播放
+                if (player.isPlaying()) {
+                    player.pause();
+                }
+                break;
+            case ON_RESUME:
+                // 前台恢复播放(仅就绪状态)
+                if (player.getPlaybackState() == Player.STATE_READY && !player.isPlaying()) {
+                    player.play();
+                }
+                break;
+            case ON_DESTROY:
+                // 销毁时清理Handler
+                if (handlerWebErr != null) {
+                    handlerWebErr.removeCallbacksAndMessages(null);
+                }
+                break;
+        }
+    };
 
     @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
     @SuppressLint({"SetJavaScriptEnabled", "JavascriptInterface"})
@@ -63,6 +140,12 @@ public class MainActivity extends BaseActivity {
 
         context = this;
 
+        // ❶ 绑定生命周期观察者
+        getLifecycle().addObserver(lifecycleObserver);
+
+        // 初始化Handler(与主线程绑定,避免泄漏)
+        handlerWebErr = new Handler(Looper.getMainLooper());
+
         //初始化
         init();
 
@@ -128,7 +211,7 @@ public class MainActivity extends BaseActivity {
         webIDs.addJavascriptInterface(this,"JavaClientCall");
         webIDs.setWebViewClient(webViewClient);
         webIDs.loadUrl("http://10.1.34.29:8090/callapp/");
-//        webIDs.loadUrl("https://172.16.1.6/callapp/");
+//        webIDs.loadUrl("http://10.1.19.20:8100");
 //        webIDs.loadUrl("https://np.h03.p0551.com/callapp/");
 
 
@@ -157,7 +240,6 @@ public class MainActivity extends BaseActivity {
 //                case R.id.but2:
 //                    break;
                 case R.id.but3:
-
 //                    mediaFilePlay("http://10.1.34.29:8090/images29/wav/ffd029b9983de18e2017042a3d7c393b.wav");
 //                    mediaFilePlay("https://np.h03.p0551.com/images/wav/eba7ea72748b33c07d55ca7c443d6146.wav");
                     break;
@@ -230,52 +312,28 @@ public class MainActivity extends BaseActivity {
         }
     };
 
+
     public void initMedAudio(){
-        // 创建ExoPlayer实例
-        player = new SimpleExoPlayer.Builder(this).build();
+        if (player == null) {
+            player = new SimpleExoPlayer.Builder(this).build();
+            // 仅添加一次全局监听器
+            player.addListener(globalPlayerListener);
+        }
+    }
 
-        // 监听播放状态
-        player.addListener(new Player.Listener() {
-            @Override
-            public void onPlaybackStateChanged(int playbackState) {
-                switch (playbackState) {
-                    case Player.STATE_IDLE:
-                        Log.i("mediaplay","状态: 空闲");
-                        break;
-                    case Player.STATE_BUFFERING:
-                        Log.i("mediaplay","状态: 缓冲中");
-                        break;
-                    case Player.STATE_READY:
-                        Log.i("mediaplay","状态: 就绪");
-                        break;
-                    case Player.STATE_ENDED:
-                        Log.i("mediaplay","状态: 播放完成");
-                        String status = "success";
-                        runOnUiThread(new Runnable() {
-                            @Override
-                            public void run() {
-                                webIDs.loadUrl("javascript:mediaPlayOver('"+ status +"')");
-                            }
-                        });
-                        break;
-                }
-            }
-            @Override
-            public void onPlayerError(PlaybackException error) {
-                Log.e("mediaplay", "播放错误: " + error.getMessage());
-                switch (error.errorCode) {
-                    case PlaybackException.ERROR_CODE_DECODING_FAILED:
-                        Log.e("mediaplay", "解码失败");
-                        break;
-                    case PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED:
-                        Log.e("mediaplay", "网络连接失败");
-                        break;
-                }
-            }
-            @Override
-            public void onIsPlayingChanged(boolean isPlaying) {
-                // 播放状态处理代码保持不变
-                Log.i("mediaplay","播放状态: " +isPlaying);
+    // ❷ 封装通知JS方法(统一处理线程+状态检查)
+    private void notifyJs(String method, String param) {
+        // 检查Activity状态,避免销毁后调用
+        if (isFinishing() || isDestroyed() || webIDs == null) {
+            Log.w("mediaplay", "Activity已销毁,跳过JS通知");
+            return;
+        }
+        // 确保在主线程执行
+        runOnUiThread(() -> {
+            try {
+                webIDs.loadUrl("javascript:" + method + "('" + param + "')");
+            } catch (Exception e) {
+                Log.e("mediaplay", "通知JS失败: " + e.getMessage());
             }
         });
     }
@@ -300,41 +358,51 @@ public class MainActivity extends BaseActivity {
     // 播放音频文件
     @JavascriptInterface
     public void mediaFilePlay(String audioInfo){
-        // 设置本地媒体源
-//        int resId = R.raw.dwgm;
-//        Uri uri = Uri.parse("android.resource://" + context.getPackageName() + "/" + resId);
-//        MediaItem mediaItem = MediaItem.fromUri(uri);
 
-        MediaItem mediaItem = MediaItem.fromUri(audioInfo);
-        // 检查播放器是否初始化
-        if (player == null) {
-            initMedAudio();
+        if (audioInfo == null || audioInfo.trim().isEmpty()) {
+            Log.e("mediaplay", "音频Uri为空");
+            notifyJs("mediaPlayOver", "error");
+            return;
+        }
+
+        // ❸ 切换到主线程操作播放器(使用ExoPlayer工具类判断主线程)
+        // 改用原生API判断主线程(无依赖风险)
+        if (Looper.getMainLooper() == Looper.myLooper()) {
+            doPlayAudio(audioInfo);
+        } else {
+            new Handler(Looper.getMainLooper()).post(() -> doPlayAudio(audioInfo));
         }
-        // 使用Handler确保在主线程调用
-        Handler mainHandler = new Handler(Looper.getMainLooper());
-        mainHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                player.setMediaItem(mediaItem);
-                // 准备播放器
-                player.prepare();
-                // 不立即调用play(),而是等待STATE_READY状态
-                player.addListener(new Player.Listener() {
-                    @Override
-                    public void onPlaybackStateChanged(int state) {
-                        if (state == Player.STATE_READY) {
-                            // 播放器准备就绪,此时可以安全播放
-                            player.play();
-                        }
-                    }
-                });
-            }
-        });
     }
     /**
      * 以上方法提供给webview中的JS调用————————————————————————————————————————————————————————————————————end
      */
 
+    // ❹ 实际播放逻辑(主线程执行)
+    private void doPlayAudio(String audioInfo) {
+        // 双重检查播放器是否初始化
+        if (player == null) {
+            initMedAudio();
+        }
+        try {
+            // 重置播放器:清空旧媒体源,避免冲突
+            player.stop();
+            player.clearMediaItems();
+
+            // 设置本地媒体源
+//          int resId = R.raw.dwgm;
+//          Uri uri = Uri.parse("android.resource://" + context.getPackageName() + "/" + resId);
+//          MediaItem mediaItem = MediaItem.fromUri(uri);
+
+            // 设置新媒体源并准备
+            MediaItem mediaItem = MediaItem.fromUri(audioInfo);
+            player.setMediaItem(mediaItem);
+            player.prepare();
+        } catch (Exception e) {
+            Log.e("mediaplay", "播放准备失败: " + e.getMessage());
+            notifyJs("mediaPlayOver", "error");
+        }
+    }
+
 
     /**
      * 重写按键操作处理方法
@@ -353,9 +421,27 @@ public class MainActivity extends BaseActivity {
     protected void onDestroy() {
         // TODO Auto-generated method stub
         super.onDestroy();
+        // 1. 移除生命周期观察者
+        getLifecycle().removeObserver(lifecycleObserver);
+
+        // 2. 释放播放器(核心:移除监听器+释放+置空)
         if (player != null) {
-            player.release();  // 释放所有资源
-            player = null;     // 置空引用,避免误操作
+            player.removeListener(globalPlayerListener); // 移除监听器,切断引用
+            player.release();
+            player = null;
+        }
+
+        // 3. 清理Handler,避免延迟任务执行
+        if (handlerWebErr != null) {
+            handlerWebErr.removeCallbacksAndMessages(null);
+            handlerWebErr = null;
+        }
+
+        // 4. 释放WebView(避免泄漏)
+        if (webIDs != null) {
+            webIDs.removeAllViews();
+            webIDs.destroy();
+            webIDs = null;
         }
     }
 }